Added automatic starting of schedules and better tracking to NEW workflow onboardings

This commit is contained in:
frikky
2022-11-11 14:46:11 +01:00
parent 6a961f3c64
commit 7e0228c69e
+366 -53
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks";
import {
InputAdornment,
@@ -15,7 +16,11 @@ import {
ListItemText,
Fade,
} from "@material-ui/core";
import { FavoriteBorder as FavoriteBorderIcon } from "@material-ui/icons";
import {
FavoriteBorder as FavoriteBorderIcon,
Error as ErrorIcon,
CheckCircleRounded as CheckCircleRoundedIcon,
} from "@mui/icons-material";
import { FixName } from "../views/Apps.jsx";
// Handles workflow updates on first open to highlight the issues of the workflow
@@ -45,6 +50,8 @@ const ConfigureWorkflow = (props) => {
setAuthenticationType,
alert,
showTriggers,
workflowExecutions,
getWorkflowExecution,
} = props;
const [requiredActions, setRequiredActions] = React.useState([]);
const [requiredVariables, setRequiredVariables] = React.useState([]);
@@ -54,6 +61,22 @@ const ConfigureWorkflow = (props) => {
const [firstLoad, setFirstLoad] = React.useState("");
const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false);
const [checkStarted, setCheckStarted] = React.useState(false);
const { start, stop } = useInterval({
duration: 3000,
startImmediate: false,
callback: () => {
if (getWorkflowExecution !== undefined && workflowExecutions !== undefined) {
const paramkey = workflow.id
getWorkflowExecution(paramkey)
} else {
console.log("Executions or getWorkflowExecutions not defined")
}
},
});
// Where is this from?
if (workflow === undefined || workflow === null) {
return null;
}
@@ -118,6 +141,8 @@ const ConfigureWorkflow = (props) => {
action: action,
update_version: action.app_version,
app: {},
steps: [],
show_steps: false,
};
const app = apps.find(
@@ -126,17 +151,21 @@ const ConfigureWorkflow = (props) => {
(app.app_version === action.app_version ||
(app.loop_versions !== null &&
app.loop_versions.includes(action.app_version)))
);
)
//newaction.steps = wazuhSteps
if (app === undefined || app === null) {
//console.log("App not found: ", action.app_name);
const subapp = apps.find(app => app.name === action.app_name)
if (subapp !== undefined && subapp !== null) {
newaction.update_version = "1.1.0"
}
newaction.must_activate = true;
newaction.steps.push({
"title": "Activate app",
"type": "activate",
"required": true,
})
} else {
if (
action.authentication_id === "" &&
@@ -157,6 +186,12 @@ const ConfigureWorkflow = (props) => {
}
}
newaction.steps.push({
"title": "Authenticate app",
"type": "authenticate",
"required": true,
})
if (!filled) {
newaction.must_authenticate = true;
newaction.action_ids.push(action.id);
@@ -244,6 +279,38 @@ const ConfigureWorkflow = (props) => {
var trigger = workflow.triggers[key];
trigger.index = key;
if (trigger.trigger_type === "WEBHOOK") {
console.log("Found webhook: ", trigger)
if (trigger.app_association !== undefined && trigger.app_association.name !== null && trigger.app_association.name !== "") {
console.log("Actions: ", newactions)
const findapp = trigger.app_association.name.toLowerCase()
const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp)
// Adding webhook to start of it
if (foundindex >= 0) {
const tmpsteps = newactions[foundindex].steps
newactions[foundindex].steps = [
{
"title": "Configure Webhook",
"type": "webhook",
"required": true,
}
]
for (var subkey in tmpsteps) {
newactions[foundindex].steps.push(tmpsteps[subkey])
}
newactions[foundindex].show_steps = true
console.log("CHANGED ACTION: ", newactions[foundindex])
//console.log("Index: ", newactions[foundindex])
continue
}
}
}
if (trigger.status === "running") {
continue;
}
@@ -297,7 +364,7 @@ const ConfigureWorkflow = (props) => {
}
const TriggerSection = (props) => {
const { trigger } = props;
const { trigger } = props
return (
<ListItem>
@@ -527,8 +594,8 @@ const ConfigureWorkflow = (props) => {
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : "#ffffff",
color: action.auth_done ? "#686a6c" : "#2f2f2f",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
color: action.auth_done ? "#686a6c" : "#ffffff",
borderRadius: theme.palette.borderRadius,
minWidth: 350,
maxHeight: 50,
@@ -571,46 +638,11 @@ const ConfigureWorkflow = (props) => {
src={action.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
{action.auth_done ? "Authenticated" : `Authenticate ${action.app_name}`}
{action.auth_done ? "Authenticated" : `Authenticate ${action.app_name.replaceAll("_", " ")}`}
</Typography>
</Button>
: null}
{action.must_activate ? (
<Button
fullWidth
variant="contained"
disabled={action.auth_done}
style={{
flex: 1,
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : "#ffffff",
color: action.auth_done ? "#686a6c" : "#2f2f2f",
borderRadius: theme.palette.borderRadius,
minWidth: 350,
maxHeight: 50,
overflow: "hidden",
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
onClick={() => {
console.log("ACTION: ", action)
activateApp(action.action.app_id, action.app_name, action.app_version);
setItemChanged(true);
}}
>
<img
alt={action.app_name}
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
src={action.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
Activate
</Typography>
</Button>
) : null}
{action.update_version !== action.app_version ? (
{action.update_version !== action.app_version ?
<Button
fullWidth
variant="contained"
@@ -620,8 +652,8 @@ const ConfigureWorkflow = (props) => {
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : "#ffffff",
color: action.auth_done ? "#686a6c" : "#2f2f2f",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
color: action.auth_done ? "#686a6c" : "#ffffff",
borderRadius: theme.palette.borderRadius,
minWidth: 350,
maxHeight: 50,
@@ -629,7 +661,8 @@ const ConfigureWorkflow = (props) => {
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
onClick={() => {
onClick={(event) => {
event.preventDefault()
console.log("Set version to: ", action.update_version)
if (workflow.actions !== null) {
@@ -656,10 +689,47 @@ const ConfigureWorkflow = (props) => {
src={action.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
{action.update_version}
Update to version {action.update_version}
</Typography>
</Button>
) : null}
:
action.must_activate ?
<Button
fullWidth
variant="contained"
disabled={action.auth_done}
style={{
flex: 1,
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
color: action.auth_done ? "#686a6c" : "#ffffff",
borderRadius: theme.palette.borderRadius,
minWidth: 350,
maxHeight: 50,
overflow: "hidden",
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
onClick={() => {
console.log("ACTION: ", action)
activateApp(action.action.app_id, action.app_name, action.app_version);
setItemChanged(true);
}}
>
<img
alt={action.app_name}
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
src={action.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
Activate
</Typography>
</Button>
:
null
}
</ListItem>
);
}
@@ -667,10 +737,245 @@ const ConfigureWorkflow = (props) => {
// Based on the color here. Default: #f86a3e
//backgroundColor: selectedUsecaseCategory === usecase.name ? usecase.color : theme.palette.surfaceColor,
const BoxHighlight = (props) => {
const {data, appname, appinfo, index, activeStep, setActiveStep, finished, } = props
const [hovered, setHovered] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const [isLoading, setIsLoading] = useState(false)
// This kind of just works for new workflows..
// What if we try many times?
var webhook = {
"name": "Testhook",
"description": `A Webhook Trigger has been started and is ready to receive events from ${appname}. Click to copy the URL to send events to.`,
"url": "",
}
useEffect(() => {
if (data.type === "webhook" && !finished) {
if (!checkStarted) {
setCheckStarted(true)
start()
}
}
}, [])
// Load webhook docs from the app itself (Wazuh)
// Add a "sample" for what the event is supposed to look like
// Have a listener for when ACTUALLY is received
// INJECT the URL into the documentation when loading it in
// How can we load it in? Should we just use the app name & get docs -> parse?
if (data.type === "webhook" && workflow.triggers !== undefined && workflow.triggers !== null) {
//console.log("Find webhook in the workflow!")
for (var key in workflow.triggers) {
if (workflow.triggers[key].trigger_type !== "WEBHOOK") {
continue
}
for (var subkey in workflow.triggers[key].parameters) {
const param = workflow.triggers[key].parameters[subkey]
if (param.name === "url") {
webhook.url = param.value
if (isLoading === false) {
setIsLoading(true)
}
break
}
}
}
} else if (data.type == "authenticate") {
//console.log("Handle app authentication in the workflow!")
}
return (
<div style={{backgroundColor: hovered ? theme.palette.inputColor : "inherit", padding: "10px 15px 10px 15px", borderTop: "1px solid rgba(255,255,255,0.15)",}}
onClick={() => {
setIsOpen(!isOpen)
setActiveStep(index)
}}
onMouseOver={() => {
setHovered(true);
}}
onMouseOut={() => {
setHovered(false);
}}
>
<div style={{display: "flex"}}>
<Typography variant="h6" style={{flex: 10, }}>{data.title}</Typography>
{finished ?
<CheckCircleRoundedIcon style={{color: "#0f9d58", flex: 1, }} />
:
<ErrorIcon style={{color: "#ffd300", flex: 1, }} />
}
</div>
{activeStep === index ?
<div>
{data.type === "webhook" ?
<div onClick={(event) => {
event.preventDefault()
console.log("Clicked Webhook")
var copyText = document.getElementById("copy_element_shuffle")
if (copyText !== undefined && copyText !== null) {
console.log("NAVIGATOR: ", navigator);
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
alert.error("Can only copy over HTTPS (port 3443)");
return;
}
navigator.clipboard.writeText(webhook.url);
copyText.select();
copyText.setSelectionRange(
0,
99999
); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
alert.success("Copied Webhook URL");
}
}}>
<Typography variant="body2" color="textSecondary">{webhook.description}</Typography>
{/*<Typography variant="body2" color="textSecondary">{webhook.url}</Typography>*/}
{isLoading && finished === false ?
<div style={{margin: "auto", width: 60, height: 60, marginTop: 5, }}>
<CircularProgress />
</div>
:
null
}
</div>
:
<AppSection key={index} action={appinfo} />
}
</div>
: null}
</div>
)
}
const AppWrapper = (props) => {
const {data, parentindex} = props
const [clicked, setClicked] = useState(true)
const [hovered, setHovered] = useState(false)
const [activeStep, setActiveStep] = useState(0)
const [firstRun, setFirstRun] = useState(true)
const [finishCount, setFinishCount] = useState(0)
return (
<div style={{backgroundColor: hovered ? theme.palette.inputColor : "inherit", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, cursor: "pointer", }}
>
<div style={{display: "flex", marginLeft: 15, marginTop: 15, marginBottom: 15, }}
onClick={() => {
//setClicked(!clicked)
}}
onMouseOver={() => {
setHovered(true);
}}
onMouseOut={() => {
setHovered(false);
}}
>
<Avatar variant="rounded" style={{}}>
<img
alt={data.label}
src={data.large_image}
style={{ width: 50, }}
/>
</Avatar>
<Typography variant="h6" style={{marginLeft: 15, }}>
Configure {data.app_name.replaceAll("_", " ")}
</Typography>
</div>
{clicked === true ?
data.steps.map((step, index) => {
var finished = false
if (step.type === "activate") {
if (data.activation_done === true) {
finished = true
if (index === activeStep && firstRun === true) {
setActiveStep(activeStep+1)
}
if (firstRun) {
setFinishCount(finishCount+1)
}
}
}
if (step.type === "authenticate") {
console.log("AUTH STEP: ", step)
if (data.must_authenticate === true ) {
finished = false
} else {
if (data.activation_done === true && data.auth_done === true) {
finished = true
if (firstRun) {
setFinishCount(finishCount+1)
}
if (index === activeStep && firstRun === true) {
setActiveStep(activeStep+1)
}
}
}
}
if (step.type === "webhook") {
for (var key in workflowExecutions) {
const exec = workflowExecutions[key]
if (exec.execution_argument !== undefined && exec.execution_argument !== null && exec.execution_argument.length > 0 && exec.execution_source === "webhook") {
//console.log("Done: ", exec)
finished = true
if (index === activeStep && firstRun === true) {
setActiveStep(activeStep+1)
}
if (firstRun) {
setFinishCount(finishCount+1)
}
// Finished + source = webhook
stop()
//if (isLoading === true) {
// setIsLoading(false)
//}
break
}
}
}
if (firstRun === true && index === data.steps.length-1) {
setFirstRun(false)
}
return (
<BoxHighlight appinfo={data} appname={"Wazuh"} key={index} data={step} index={index} activeStep={activeStep} setActiveStep={setActiveStep} finished={finished} />
)
})
: null}
</div>
)
}
const topColor = "#f86a3e, #fc3922"
return (
<div>
<div style={{height: 125, width: "100%", background: `linear-gradient(to right, ${topColor}`, position: "relative",}}>
<div style={{height: 75, width: "100%", background: `linear-gradient(to right, ${topColor}`, position: "relative",}}>
</div>
<div style={{margin: "25px 50px 50px 50px", maxHeight: 475, }}>
<Typography variant="h6">{workflow.name}</Typography>
@@ -679,13 +984,20 @@ const ConfigureWorkflow = (props) => {
</Typography>
{requiredActions.length > 0 ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
<Typography variant="body1" style={{ marginTop: 10, }}>
Required Actions
</Typography>
<List>
{requiredActions.map((data, index) => {
return (
<AppSection key={index} action={data} />
<div>
{data.steps !== undefined && data.steps !== null && data.show_steps === true ?
<AppWrapper data={data} parentindex={index} />
:
<AppSection key={index} action={data} />
}
</div>
)
})}
</List>
@@ -743,6 +1055,7 @@ const ConfigureWorkflow = (props) => {
variant={"outlined"}
style={{}}
onClick={() => {
stop()
setShowFinalizeAnimation(true)
setTimeout(() => {
if (itemChanged) {