Files
shuffle-cracked/frontend/src/components/ConfigureWorkflow.jsx
T
2023-11-14 15:20:31 +01:00

1354 lines
39 KiB
React
Executable File

import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks";
import { toast } from 'react-toastify';
import theme from "../theme.jsx";
import {
InputAdornment,
Tooltip,
TextField,
CircularProgress,
ButtonGroup,
Button,
Avatar,
ListItemAvatar,
Typography,
List,
ListItem,
ListItemText,
Collapse,
IconButton,
} from "@mui/material";
import {
FavoriteBorder as FavoriteBorderIcon,
Error as ErrorIcon,
CheckCircleRounded as CheckCircleRoundedIcon,
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon,
Check as CheckIcon,
Visibility as VisibilityIcon,
} from "@mui/icons-material";
import { FixName } from "../views/Apps.jsx";
import aa from 'search-insights'
// Handles workflow updates on first open to highlight the issues of the workflow
// Variables
// Action (exists, missing fields)
// Action auth
// Triggers
//
// Specifically used for UNSAVED workflows only?
const ConfigureWorkflow = (props) => {
const {
apps,
isCloud,
workflow,
userdata,
globalUrl,
newWebhook,
referenceUrl,
saveWorkflow,
showTriggers,
submitSchedule,
setSelectedApp,
selectedAction,
appAuthentication,
setSelectedAction,
workflowExecutions,
getWorkflowExecution,
setAuthenticationType,
setAuthenticationModalOpen,
setConfigureWorkflowModalOpen,
} = props;
const [requiredActions, setRequiredActions] = React.useState([]);
const [requiredVariables, setRequiredVariables] = React.useState([]);
const [requiredTriggers, setRequiredTriggers] = React.useState([]);
const [previousAuth, setPreviousAuth] = React.useState(appAuthentication);
const [itemChanged, setItemChanged] = React.useState(false);
const [firstLoad, setFirstLoad] = React.useState("");
const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false);
const [loopRunning, setLoopRunning] = useState(false)
const [checkStarted, setCheckStarted] = React.useState(false);
const stop = () => {
setLoopRunning(false)
}
const start = () => {
setLoopRunning(true)
}
useEffect(() => {
if (loopRunning) {
const intervalId = setInterval(() => {
if (!loopRunning) {
clearInterval(intervalId);
}
if (getWorkflowExecution !== undefined && workflowExecutions !== undefined) {
const paramkey = workflow.id
getWorkflowExecution(paramkey)
} else {
console.log("Executions or getWorkflowExecutions not defined")
}
}, 3000)
return () => clearInterval(intervalId);
}
}, [loopRunning])
/*
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")
}
},
});
*/
// ONLY when component is being unloaded, run stop() function
// This is to prevent the interval from running when the component is not being used
/*
useEffect(() => {
return () => {
stop()
}
}, [])
*/
// Where is this from?
if (workflow === undefined || workflow === null || workflow.id === undefined) {
return null;
}
if (apps === undefined || apps === null) {
console.log("Apps is undefined or null: ", apps)
return null;
}
if (appAuthentication === undefined || appAuthentication === null) {
console.log("App authentication is undefined or null: ", appAuthentication)
return null;
}
const getApp = (actionId, appId) => {
fetch(globalUrl + "/api/v1/apps/" + appId + "/config?openapi=false", {
headers: {
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
//toast("Successfully GOT app "+appId)
} else {
toast("Failed getting app");
}
return response.json();
})
.then((responseJson) => {
console.log("ACTION: ", responseJson);
if (
responseJson.actions !== undefined &&
responseJson.actions !== null
) {
}
})
.catch((error) => {
toast(error.toString());
});
};
if (firstLoad.length === 0 || firstLoad !== workflow.id) {
if (apps === undefined || apps === null || apps.length === 0) {
console.log("No apps loaded: ", apps);
if (setConfigureWorkflowModalOpen !== undefined) {
setConfigureWorkflowModalOpen(false);
}
return null;
}
setFirstLoad(workflow.id)
const newactions = [];
for (let [key, keyval] in Object.entries(workflow.actions)) {
const action = workflow.actions[key];
var newaction = {
large_image: action.large_image,
app_name: action.app_name,
app_version: action.app_version,
activation_done: false,
must_activate: false,
must_authenticate: false,
auth_done: false,
action_ids: [],
action: action,
update_version: action.app_version,
app: {},
steps: [],
show_steps: false,
}
if (action.app_name.toLowerCase().endsWith("_api")) {
action.app_name = action.app_name.slice(0, -4)
}
// ID match OR name match + version match
//const app = apps.find((app) => app.id === action.app_id || (app.name === action.app_name && (app.app_version === action.app_version || (app.loop_versions !== null && app.loop_versions.includes(action.app_version)))))
//
// without version match
const newappname = action.app_name.toLowerCase().replaceAll(" ", "_")
const app = apps.find((app) => app.id === action.app_id || app.name.toLowerCase().replaceAll(" ", "_") === newappname)
if (app === undefined || app === null) {
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 === "" && app.authentication.required === true && action.parameters !== undefined && action.parameters !== null) {
// Check if configuration is filled or not
var filled = true;
for (let [key,keyval] in Object.entries(action.parameters)) {
if (action.parameters[key].configuration) {
//console.log("Found config: ", action.parameters[key])
if (
action.parameters[key].value === null ||
action.parameters[key].value.length === 0
) {
filled = false;
break;
}
}
}
newaction.steps.push({
"title": "Authenticate app",
"type": "authenticate",
"required": true,
})
if (!filled) {
newaction.must_authenticate = true;
newaction.action_ids.push(action.id);
}
} else if (action.authentication_id !== "" && app.authentication.required === true) {
console.log("Should verify authentication ID ", action.authentication_id)
}
newaction.app = app;
}
if (
action.errors !== undefined &&
action.errors !== null &&
action.errors.length > 0
) {
//console.log("Node has errors!: ", action.errors)
}
if (newaction.must_authenticate) {
var authenticationOptions = [];
for (let [key,keyval] in Object.entries(appAuthentication)) {
const auth = appAuthentication[key];
if (auth.app.name === app.name && auth.active) {
//console.log("Found auth: ", auth)
authenticationOptions.push(auth);
newaction.authenticationId = auth.id;
break;
}
}
if (
newaction.authenticationId === null ||
newaction.authenticationId === undefined ||
newaction.authenticationId.length === ""
) {
//console.log("FAILED to authenticate node!")
if (
newactions.find(
(tmpaction) =>
tmpaction.app_id === newaction.app_id &&
tmpaction.app_name === newaction.app_name
) !== undefined
) {
//console.log("Action already found.");
} else {
newactions.push(newaction);
}
} else {
//console.log("Skipping node as it's already authenticated.")
newaction.authentication = authenticationOptions;
workflow.actions[key] = newaction;
}
} else if (newaction.must_activate) {
if (
newactions.find(
(tmpaction) =>
tmpaction.app_id === newaction.app_id &&
tmpaction.app_name === newaction.app_name
) !== undefined
) {
console.log("Action already found.");
} else {
newactions.push(newaction);
}
}
}
if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length !== 0) {
for (let [key,keyval] in Object.entries(workflow.workflow_variables)) {
const variable = workflow.workflow_variables[key];
if (variable.value === undefined || variable.value === undefined || variable.value.length === 0) {
variable.value = "";
requiredVariables.push(variable);
}
variable.index = key;
}
}
if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length !== 0) {
for (let [key,keyval] in Object.entries(workflow.triggers)) {
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 (let [subkey,subkeyval] in Object.entries(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;
}
if (
trigger.trigger_type === "SUBFLOW" ||
trigger.trigger_type === "USERINPUT"
) {
continue;
}
requiredTriggers.push(trigger);
}
}
if (requiredTriggers.length === 0 && requiredVariables.length === 0 && newactions.length === 0 && setConfigureWorkflowModalOpen !== undefined) {
setConfigureWorkflowModalOpen(false);
}
setRequiredTriggers(requiredTriggers);
setRequiredVariables(requiredVariables);
setRequiredActions(newactions);
}
if (appAuthentication !== undefined && previousAuth !== undefined && appAuthentication.length !== previousAuth.length) {
var newactions = []
for (let [actionkey, actionkeyval] in Object.entries(requiredActions)) {
var newaction = requiredActions[actionkey];
const app = newaction.app;
for (let [key,keyval] in Object.entries(appAuthentication)) {
const auth = appAuthentication[key];
// Does this account for all the different ones of the same?
if (auth.app.name === app.name && auth.active === true) {
newaction.auth_done = true;
break;
}
}
newactions.push(newaction);
}
setRequiredActions(newactions);
setPreviousAuth(appAuthentication);
// Set auth done to true
//"auth_done": false
}
const TriggerSection = (props) => {
const { trigger } = props
return (
<ListItem style={{padding: 0, }}>
<ListItemAvatar>
<Avatar variant="rounded">
<img
alt={trigger.label}
src={trigger.large_image}
style={{ width: 50 }}
/>
</Avatar>
</ListItemAvatar>
<ListItemText
primary={trigger.name}
secondary={trigger.description}
style={{}}
/>
{trigger.trigger_type === "WEBHOOK" && trigger.status !== "running" ? (
<Button
disabled={trigger.status === "running"}
color="primary"
variant="contained"
onClick={() => {
workflow.triggers[trigger.index].status = "running";
if (workflow.triggers[trigger.index].parameters === null) {
workflow.triggers[trigger.index].parameters = [
{
name: "url",
value: referenceUrl + "webhook_" + trigger.id,
},
{ name: "tmp", value: "webhook_" + trigger.id },
];
}
newWebhook(workflow.triggers[trigger.index]);
saveWorkflow(workflow);
setItemChanged(true);
}}
>
{trigger.status !== "running" ? "Start" : "Running"}
</Button>
) : trigger.trigger_type === "SCHEDULE" &&
trigger.status !== "running" ? (
<Button
disabled={trigger.status === "running"}
color="primary"
variant="contained"
onClick={() => {
workflow.triggers[trigger.index].status = "running";
if (workflow.triggers[trigger.index].parameters === null) {
workflow.triggers[trigger.index].parameters = [
{ name: "cron", value: isCloud ? "*/15 * * * *" : "120" },
{
name: "execution_argument",
value: '{"example": {"json": "is cool"}}',
},
];
}
submitSchedule(workflow.triggers[trigger.index], trigger.index);
saveWorkflow(workflow);
setItemChanged(true);
}}
>
{trigger.status !== "running" ? "Start" : "Running"}
</Button>
) : null}
</ListItem>
);
};
const VariableSection = (props) => {
const { variable } = props;
//<Typography variant="body2">Name: {variable.name} - {variable.value}. </Typography>
return (
<ListItem style={{padding: 0, }}>
<ListItemAvatar>
<Avatar>
<FavoriteBorderIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={variable.name}
secondary={variable.description}
style={{}}
/>
<ListItemText
primary={
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: 5,
}}
InputProps={{
endAdornment: <InputAdornment position="end"></InputAdornment>,
}}
fullWidth
color="primary"
type={"text"}
placeholder={`New value for ${variable.name}`}
onChange={(event) => {
console.log(
"NEW VALUE ON INDEX",
variable.index,
variable.value
);
}}
onBlur={(event) => {
workflow.workflow_variables[variable.index].value =
event.target.value;
}}
/>
}
style={{}}
/>
</ListItem>
);
};
const activateApp = (app_id, app_name, app_version) => {
if (aa !== undefined) {
aa('init', {
appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240",
})
const timestamp = new Date().getTime()
aa('sendEvents', [
{
eventType: 'conversion',
eventName: 'Public App Activated',
index: 'appsearch',
objectIDs: [app_id],
timestamp: timestamp,
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
}
fetch(
`${globalUrl}/api/v1/apps/${app_id}/activate?app_name=${app_name}&app_version=${app_version}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
//window.location.pathname = "/search"
//toast("Failed to find this app. Is it public?")
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed to activate the app: "+responseJson.reason);
} else {
toast("Failed to activate the app");
}
} else {
toast("App activated for your organization!");
}
})
.catch((error) => {
toast(error.toString());
});
};
const AppSectionSelfcontained = (props) => {
const { action } = props;
const [opened, setOpened] = useState(false);
const [filled, setFilled] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [finalized, setFinalized] = useState(false);
const [authFields, setAuthFields] = useState([])
const [sensitiveFields, setSensitiveFields] = useState([])
if (authFields.length === 0 && opened === true) {
// Loop through fields of the action
var newfields = []
const params = action.action.parameters
var sensitiveIndexes = []
var index = 0
for (let key in params) {
const param = params[key]
if (param.configuration === true) {
if (param.name.toLowerCase().includes("key") || param.name.toLowerCase().includes("token") || param.name.toLowerCase().includes("password")) {
sensitiveIndexes.push(index)
}
newfields.push({
"key": param.name,
"example": param.example === undefined ? "" : param.example,
"value": param.name === "url" ? param.example : "",
})
index += 1
}
}
if (newfields.length > 0) {
setSensitiveFields(sensitiveIndexes)
setAuthFields(newfields)
}
}
const submitLocalAuth = (app, fields) => {
const appAuthData = {
active: true,
app: app,
fields: fields,
label: "Authentication for " + app.name,
usage: [{"workflow_id": workflow.id}],
auto_distribute: true,
}
fetch(globalUrl + "/api/v1/apps/authentication", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(appAuthData),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for setting app auth :O!");
}
setSubmitted(false)
return response.json();
})
.then((responseJson) => {
if (!responseJson.success) {
toast("Failed to set app auth: " + responseJson.reason);
} else {
toast("App auth set for app " + app.name.replace("_", " "));
setFinalized(true)
setOpened(false)
}
})
.catch((error) => {
setSubmitted(false)
//toast(error.toString());
console.log("New auth error: ", error.toString());
});
}
var parsedName = action.app_name.replaceAll("_", " ");
if (action.app_name.toLowerCase().endsWith("_api")) {
parsedName = parsedName.substring(0, parsedName.length - 4);
}
// Remove _basic at the end if it exists
if (parsedName.toLowerCase().endsWith("_basic")) {
parsedName = parsedName.substring(0, parsedName.length - 6);
}
parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " ");
return (
<ListItem
style={{padding: 0, display: "flex", flexDirection: "column", }}
>
<div
style={{
border: filled ? `1px solid ${theme.palette.green}` : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, width: "100%", padding: 12, cursor: "pointer",
}}
id="app-config"
>
<div style={{display: "flex", }}
onClick={() => {
setOpened(!opened);
}}
>
<div style={{display: "flex", flex: 7, }}>
<span style={{marginTop: 10, marginRight: 10, }}>
{!opened ? <ExpandMoreIcon /> : <ExpandLessIcon />}
</span>
<img
alt={parsedName}
style={{ margin: 4, minHeight: 40, maxHeight: 40, borderRadius: 30, }}
src={action.large_image}
/>
<Typography style={{ marginLeft: 10, marginTop: 10, }} variant="body1">
<b>{finalized ? "Authenticated" : `Configure ${parsedName}`}</b>
</Typography>
</div>
{filled ?
<CheckIcon style={{color: theme.palette.green, marginLeft: 10, marginTop: 10, flex: 1, }} />
: null}
</div>
{opened ?
<div style={{padding: 12, }}>
{authFields.map((field, index) => {
var parsedName = field.key
// Remove _basic at the end if it exists
if (parsedName.toLowerCase().endsWith("_basic")) {
parsedName = parsedName.substring(0, parsedName.length - 6);
}
parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " ");
return (
<div key={index} style={{marginBottom: 15, marginLeft: 10, marginRight: 10, }}>
<Typography style={{ }} variant="body1">
{parsedName}
</Typography>
<TextField
style={{
backgroundColor: theme.palette.inputColor,
}}
defaultValue={field.value}
onChange={(event) => {
event.preventDefault();
authFields[index].value = event.target.value;
setAuthFields(authFields);
var allFilled = true;
authFields.forEach((field) => {
if (field.value.length === 0) {
allFilled = false;
} else {
//console.log("Field is not filled: "+field.key)
}
})
if (allFilled) {
console.log("Should test the fields, and submit them")
setFilled(true);
} else {
if (filled) {
setFilled(false);
}
}
}}
endAdornment={
// Show item that can show field value if password
//field.name.toLowerCase().includes("key") || field.name.toLowerCase().includes("token") || field.name.toLowerCase().includes("password") ?
field.key.toLowerCase().includes("key") || field.key.toLowerCase().includes("token") || field.key.toLowerCase().includes("password") ?
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => {
setSensitiveFields(sensitiveFields.filter((item) => item !== index))
}}
onMouseDown={(event) => {
event.preventDefault();
}}
>
<VisibilityIcon />
</IconButton>
</InputAdornment>
: null
}
fullWidth
color="primary"
type={sensitiveFields.includes(index) ? "password" : "text"}
placeholder={field.example ? field.example : `Enter your ${field.key}`}
data-lpignore="true"
dataLPIgnore="true"
autocomplete="off"
/>
</div>
)
})}
<Button
variant="contained"
color="primary"
style={{
marginTop: 15,
width: 150,
marginLeft: 135,
}}
disabled={!filled || submitted}
onClick={() => {
setSubmitted(true);
// const submitLocalAuth = (app, fields) => {
submitLocalAuth({"id": action.app.id, "name": action.app.name, "version": action.app.version, "large_image": action.large_image, }, authFields);
}}
>
{submitted ? <CircularProgress style={{color: theme.palette.primary.main, }} /> : "Submit"}
</Button>
</div>
: null}
</div>
</ListItem>
)
}
const AppSection = (props) => {
const { action } = props;
var parsedName = action.app_name.replaceAll("_", " ");
if (action.app_name.toLowerCase().endsWith("_api")) {
parsedName = parsedName.substring(0, parsedName.length - 4);
}
return (
<ListItem>
{action.must_authenticate ?
<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={() => {
if (setAuthenticationType !== undefined) {
setAuthenticationType(action.app.authentication.type === "oauth2" && action.app.authentication.redirect_uri !== undefined && action.app.authentication.redirect_uri !== null
?
{
type: "oauth2",
redirect_uri: action.app.authentication.redirect_uri,
token_uri: action.app.authentication.token_uri,
scope: action.app.authentication.scope,
}
:
{
type: "",
}
)
}
setItemChanged(true);
if (setSelectedAction !== undefined) {
setSelectedAction(action.action);
}
if (setSelectedApp !== undefined) {
setSelectedApp(action.app);
}
if (setAuthenticationModalOpen !== undefined) {
setAuthenticationModalOpen(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">
{action.auth_done ? "Authenticated" : `Authenticate ${action.app_name.replaceAll("_", " ")}`}
</Typography>
</Button>
: null}
{action.update_version !== action.app_version ?
<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={(event) => {
event.preventDefault()
console.log("Set version to: ", action.update_version)
if (workflow.actions !== null) {
//console.log(workflow.actions)
toast("Setting action to version "+action.update_version)
for (let [key,keyval] in Object.entries(workflow.actions)) {
if (workflow.actions[key].app_name === action.app_name && workflow.actions[key].app_version === action.app_version) {
workflow.actions[key].app_version = action.update_version
if (!itemChanged) {
setItemChanged(true)
}
}
}
action.must_activate = false
action.update_version = action.app_version
}
}}
>
<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">
Update to version {action.update_version}
</Typography>
</Button>
:
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>
);
}
// 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, filled, } = 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" && !filled) {
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>
{filled ?
<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) {
toast("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");
toast("Copied Webhook URL");
}
}}>
<Typography variant="body2" color="textSecondary">{webhook.description}</Typography>
{/*<Typography variant="body2" color="textSecondary">{webhook.url}</Typography>*/}
{isLoading && filled === false ?
<div style={{margin: "auto", width: 60, height: 60, marginTop: 5, }}>
<CircularProgress />
</div>
:
null
}
</div>
:
setConfigureWorkflowModalOpen !== undefined ?
<AppSection key={index} action={appinfo} />
:
<AppSectionSelfcontained 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 filled = false
if (step.type === "activate") {
if (data.activation_done === true) {
filled = 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 ) {
filled = false
} else {
if (data.activation_done === true && data.auth_done === true) {
filled = 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)
filled = 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} filled={filled} />
)
})
: null}
</div>
)
}
return (
<div>
<div style={{margin: setConfigureWorkflowModalOpen !== undefined ? "0px 50px 0px 50px" : "35px 0px 0px", maxHeight: 475, }}>
{setConfigureWorkflowModalOpen !== undefined ?
<Typography variant="h6">{workflow.name}</Typography>
: null
}
<Typography variant="body2" style={{}}>
Please configure the following apps for automatic startup of automation:
</Typography>
{requiredActions.length > 0 ? (
<span>
{setConfigureWorkflowModalOpen !== undefined ?
<Typography variant="body1" style={{ marginTop: 10, }}>
Required Actions
</Typography>
: null}
<List style={{paddingBottom: window.location.pathname.includes("/workflows/") ? 0 : 250, }}>
{requiredActions.map((data, index) => {
return (
<div key={index} style={{marginBottom: 10, }}>
{data.steps !== undefined && data.steps !== null && data.show_steps === true && setConfigureWorkflowModalOpen !== undefined ?
<AppWrapper data={data} parentindex={index} />
:
setConfigureWorkflowModalOpen !== undefined ?
<AppSection key={index} action={data} />
:
<AppSectionSelfcontained key={index} action={data} />
}
</div>
)
})}
</List>
</span>
) : null}
{setConfigureWorkflowModalOpen !== undefined && requiredVariables.length > 0 ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
Variables
</Typography>
<List>
{requiredVariables.map((data, index) => {
return <VariableSection key={index} variable={data} />;
})}
</List>
</span>
) : null}
{setConfigureWorkflowModalOpen !== undefined && requiredTriggers.length > 0 && showTriggers !== false ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
Triggers
</Typography>
<List>
{requiredTriggers.map((data, index) => {
return <TriggerSection key={index} trigger={data} />;
})}
</List>
</span>
) : null}
{setConfigureWorkflowModalOpen !== undefined ?
<div style={{ textAlign: "left", display: "flex", marginTop: 20 }}>
{showFinalizeAnimation ?
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, margin: "auto",}} onLoad={() => {
console.log("Img loaded.")
setTimeout(() => {
console.log("Img closing.")
setConfigureWorkflowModalOpen(false);
}, 1250)
}}/>
:
<ButtonGroup style={{ margin: "auto" }}>
{/*
<Button color="primary" variant={"outlined"} style={{
}} onClick={() => {
setConfigureWorkflowModalOpen(false)
}}>
Skip
</Button>
*/}
<Button
variant={"outlined"}
style={{}}
onClick={() => {
stop()
setShowFinalizeAnimation(true)
setTimeout(() => {
if (itemChanged) {
if (saveWorkflow !== undefined) {
saveWorkflow(workflow);
window.location.reload();
}
} else {
}
}, 1000)
}}
>
Finalize
</Button>
</ButtonGroup>
}
</div>
: null}
</div>
</div>
);
};
export default ConfigureWorkflow;