enabled user input subflows and fixed bugs

This commit is contained in:
frikky
2023-02-27 02:31:30 +01:00
parent f604068619
commit 3ed4d86a3b
12 changed files with 482 additions and 417 deletions
+7 -6
View File
@@ -910,7 +910,9 @@ const AppFramework = (props) => {
}
useEffect(() => {
handleLoadNextSuggestion(frameworkData)
if (!window.location.pathname.includes("usecases")) {
handleLoadNextSuggestion(frameworkData)
}
}, [])
useEffect(() => {
@@ -985,9 +987,8 @@ const AppFramework = (props) => {
}, [newSelectedApp])
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const imgSize = 50;
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
@@ -1906,7 +1907,7 @@ const AppFramework = (props) => {
/>
</div>
{injectedApps.map((apps) => {
{injectedApps.map((apps, appindex) => {
var categoryTop = 100
var categoryLeft = 100
@@ -1934,7 +1935,7 @@ const AppFramework = (props) => {
}
return (
<div style={{display: "flex", position: "absolute", top: categoryTop, left: categoryLeft, zIndex: 10010, }}>
<div key={appindex} style={{display: "flex", position: "absolute", top: categoryTop, left: categoryLeft, zIndex: 10010, }}>
{apps.map((app, appIndex) => {
return (
<Chip
@@ -170,10 +170,7 @@ const ConfigureWorkflow = (props) => {
"required": true,
})
} else {
if (
action.authentication_id === "" &&
app.authentication.required === true
) {
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)) {
+112 -119
View File
@@ -701,10 +701,7 @@ const ParsedAction = (props) => {
}
// bad detection mechanism probably
if (
event.target.value[event.target.value.length - 1] === "." &&
actionlist.length > 0
) {
if (event.target.value[event.target.value.length - 1] === "." && actionlist.length > 0) {
console.log("GET THE LAST ARGUMENT FOR NODE!");
// THIS IS AN EXAMPLE OF SHOWING IT
/*
@@ -2098,6 +2095,8 @@ const ParsedAction = (props) => {
toComplete += values[key].autocomplete;
}
// Handles the fields under OpenAPI body to be parsed.
if (data.name.startsWith("${") && data.name.endsWith("}")) {
console.log("INSIDE VALUE REPLACE: ", data.name, toComplete);
@@ -2131,10 +2130,8 @@ const ParsedAction = (props) => {
}
}
selectedActionParameters[count]["value_replace"] =
paramcheck;
selectedAction.parameters[count]["value_replace"] =
paramcheck;
selectedActionParameters[count]["value_replace"] = paramcheck;
selectedAction.parameters[count]["value_replace"] = paramcheck;
setSelectedAction(selectedAction);
setUpdate(Math.random());
@@ -2144,13 +2141,15 @@ const ParsedAction = (props) => {
}
}
selectedActionParameters[count].value += toComplete;
selectedAction.parameters[count].value =
selectedActionParameters[count].value;
setSelectedAction(selectedAction);
setUpdate(Math.random());
setShowDropdown(false);
console.log("In nestedclick!!")
var newValue = selectedActionParameters[count].value + toComplete
changeActionParameter({target: {value: newValue}}, count, data)
//selectedActionParameters[count].value += toComplete;
//selectedAction.parameters[count].value = selectedActionParameters[count].value;
//setSelectedAction(selectedAction);
//setUpdate(Math.random());
setShowDropdown(false);
setMenuPosition(null);
};
@@ -2604,7 +2603,9 @@ const ParsedAction = (props) => {
setUpdate(Math.random());
}}
onClick={() => setShowAutocomplete(true)}
onClick={() => {
setShowAutocomplete(true)
}}
fullWidth
open={showAutocomplete}
style={{
@@ -2614,22 +2615,12 @@ const ParsedAction = (props) => {
borderRadius: theme.palette.borderRadius,
}}
onChange={(e) => {
if (
selectedActionParameters[count].value[
selectedActionParameters[count].value.length - 1
] === "."
) {
e.target.value.autocomplete =
e.target.value.autocomplete.slice(
1,
e.target.value.autocomplete.length
);
if (selectedActionParameters[count].value[selectedActionParameters[count].value.length - 1] === ".") {
e.target.value.autocomplete = e.target.value.autocomplete.slice(1,e.target.value.autocomplete.length);
}
selectedActionParameters[count].value +=
e.target.value.autocomplete;
selectedAction.parameters[count].value =
selectedActionParameters[count].value;
selectedActionParameters[count].value += e.target.value.autocomplete;
selectedAction.parameters[count].value = selectedActionParameters[count].value;
setSelectedAction(selectedAction);
setUpdate(Math.random());
@@ -2973,108 +2964,110 @@ const ParsedAction = (props) => {
// Should make it a function lol
if (workflow.branches !== undefined && workflow.branches !== null) {
for (let [key,keyval] in Object.entries(workflow.branches)) {
for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) {
const condition = workflow.branches[key].conditions[subkey]
const sourceparam = condition.source
const destinationparam = condition.destination
if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) {
for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) {
const condition = workflow.branches[key].conditions[subkey]
const sourceparam = condition.source
const destinationparam = condition.destination
// Should have a smarter way of discovering node names
// Finding index(es) and replacing at the location
if (sourceparam.value.includes("$")) {
try {
var cnt = -1
var previous = 0
while (true) {
cnt += 1
// Need to make sure e.g. changing the first here doesn't change the 2nd
// $change_me
// $change_me_2
const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
if (foundindex === previous && foundindex !== 0) {
break
}
if (foundindex >= 0) {
previous = foundindex+newname.length
// Need to add diff of length to word
// Check location:
// If it's a-zA-Z_ then don't replace
if (sourceparam.value.length > foundindex+parsedBaseLabel.length) {
const regex = /[a-zA-Z0-9_]/g;
const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex);
if (match !== null) {
continue
}
}
// Should have a smarter way of discovering node names
// Finding index(es) and replacing at the location
if (sourceparam.value.includes("$")) {
try {
var cnt = -1
var previous = 0
while (true) {
cnt += 1
// Need to make sure e.g. changing the first here doesn't change the 2nd
// $change_me
// $change_me_2
console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value)
const extralength = newname.length-parsedBaseLabel.length
sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length)
const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
if (foundindex === previous && foundindex !== 0) {
break
}
if (foundindex >= 0) {
previous = foundindex+newname.length
// Need to add diff of length to word
// Check location:
// If it's a-zA-Z_ then don't replace
if (sourceparam.value.length > foundindex+parsedBaseLabel.length) {
const regex = /[a-zA-Z0-9_]/g;
const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex);
if (match !== null) {
continue
}
}
console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value)
const extralength = newname.length-parsedBaseLabel.length
sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length)
console.log("New: ", workflow.branches[key].conditions[subkey].source.value)
} else {
break
}
console.log("New: ", workflow.branches[key].conditions[subkey].source.value)
} else {
break
}
// Break no matter what after 5 replaces. May need to increase
if (cnt >= 5) {
break
}
// Break no matter what after 5 replaces. May need to increase
if (cnt >= 5) {
break
}
}
} catch (e) {
console.log("Failed value replacement based on index: ", e)
}
} catch (e) {
console.log("Failed value replacement based on index: ", e)
}
}
if (destinationparam.value.includes("$")) {
try {
var cnt = -1
var previous = 0
while (true) {
cnt += 1
// Need to make sure e.g. changing the first here doesn't change the 2nd
// $change_me
// $change_me_2
const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
if (foundindex === previous && foundindex !== 0) {
break
}
if (foundindex >= 0) {
previous = foundindex+newname.length
// Need to add diff of length to word
// Check location:
// If it's a-zA-Z_ then don't replace
if (destinationparam.value.length > foundindex+parsedBaseLabel.length) {
const regex = /[a-zA-Z0-9_]/g;
const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex);
if (match !== null) {
continue
}
}
if (destinationparam.value.includes("$")) {
try {
var cnt = -1
var previous = 0
while (true) {
cnt += 1
// Need to make sure e.g. changing the first here doesn't change the 2nd
// $change_me
// $change_me_2
console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value)
const extralength = newname.length-parsedBaseLabel.length
destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length)
const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
if (foundindex === previous && foundindex !== 0) {
break
}
if (foundindex >= 0) {
previous = foundindex+newname.length
// Need to add diff of length to word
// Check location:
// If it's a-zA-Z_ then don't replace
if (destinationparam.value.length > foundindex+parsedBaseLabel.length) {
const regex = /[a-zA-Z0-9_]/g;
const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex);
if (match !== null) {
continue
}
}
console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value)
const extralength = newname.length-parsedBaseLabel.length
destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length)
console.log("New: ", workflow.branches[key].conditions[subkey].destination.value)
} else {
break
}
console.log("New: ", workflow.branches[key].conditions[subkey].destination.value)
} else {
break
}
// Break no matter what after 5 replaces. May need to increase
if (cnt >= 5) {
break
}
// Break no matter what after 5 replaces. May need to increase
if (cnt >= 5) {
break
}
}
} catch (e) {
console.log("Failed value replacement based on index: ", e)
}
} catch (e) {
console.log("Failed value replacement based on index: ", e)
}
}
}
+30 -9
View File
@@ -54,8 +54,14 @@ import { useNavigate, Link, useParams } from "react-router-dom";
const liquidFilters = [
{"name": "Size", "value": "size", "example": ""},
{"name": "Date", "value": `date: "%Y%m%d"`, "example": `{{ "now" | date: "%s" }}`},
{"name": "Split", "value": `split: ","`, "example": `{{ "this,can,become,a,list" | split: "," }}`},
{"name": "Join", "value": `join: ","`, "example": `{{ ["this","can","become","a","string"] | join: "," }}`},
{"name": "Escape String", "value": `{{ \"\"\"'string with weird'" quotes\"\"\" | escape_string }}`, "example": ``},
{"name": "Flatten", "value": `flatten`, "example": `{{ [1, [1, 2], [2, 3, 4]] | flatten }}`},
{"name": "URL encode", "value": `url_encode`, "example": `{{ "https://www.google.com/search?q=hello world" | url_encode }}`},
{"name": "URL decode ", "value": `url_decode`, "example": `{{ "https://www.google.com/search?q=hello%20world" | url_decode }}`},
{"name": "base64_encode", "value": `base64_encode`, "example": `{{ "https://www.google.com/search?q=hello%20world" | base64_encode }}`},
{"name": "base64_decode", "value": `base64_decode`, "example": `{{ "aGVsbG8K" | base64_encode }}`},
]
const mathFilters = [
@@ -426,10 +432,11 @@ const CodeEditor = (props) => {
if(fixedVariable.slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){
valuefound = true
console.log("Valuefound: ", fixedVariable, actionlist[j].example)
try {
if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
if (typeof actionlist[j].example === "object") {
input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example));
} else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example));
} else {
input = input.replace(fixedVariable, actionlist[j].example)
@@ -438,6 +445,7 @@ const CodeEditor = (props) => {
input = input.replace(fixedVariable, actionlist[j].example)
}
} else {
// Couldn't find the correct example value
}
}
@@ -565,12 +573,14 @@ const CodeEditor = (props) => {
inputdata = JSON.stringify(inputdata)
}
// Shuffle Tools 1.2.0 (in most cases?)
const appid = "3e2bdf9d5069fe3f4746c29d68785a6a"
const actiondata = {"description":"Repeats the call parameter","id":"","name":"repeat_back_to_me","label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id":"3e2bdf9d5069fe3f4746c29d68785a6a","tags":null,"authentication":[],"tested":false,"parameters":[{"description":"The message to repeat","id":"","name":"call","example":"REPEATING: Hello world","value":inputdata,"multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false,"autocompleted":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}}
setExecutionResult({
"valid": false,
"result": baseResult,
"errors": [],
})
setExecuting(true)
@@ -593,21 +603,27 @@ const CodeEditor = (props) => {
})
.then((responseJson) => {
//console.log("RESPONSE: ", responseJson)
var newResult = {}
if (responseJson.success === true && responseJson.result !== null && responseJson.result !== undefined && responseJson.result.length > 0) {
const result = responseJson.result.slice(0, 50)+"..."
//alert.info("SUCCESS: "+result)
const validate = validateJson(responseJson.result)
setExecutionResult(validate)
newResult = validate
} else if (responseJson.success === false && responseJson.reason !== undefined && responseJson.reason !== null) {
alert.error(responseJson.reason)
setExecutionResult({"valid": false, "result": responseJson.reason})
newResult = {"valid": false, "result": responseJson.reason}
} else if (responseJson.success === true) {
setExecutionResult({"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."})
newResult = {"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."}
} else {
setExecutionResult({"valid": false, "result": "Couldn't finish execution (2). Please fill all the required fields, and validate the execution."})
newResult = {"valid": false, "result": "Couldn't finish execution (2). Please fill all the required fields, and validate the execution."}
}
if (responseJson.errors !== undefined && responseJson.errors !== null && responseJson.errors.length > 0) {
newResult.errors = responseJson.errors
}
setExecutionResult(newResult)
setExecuting(false)
})
.catch(error => {
@@ -1262,7 +1278,7 @@ const CodeEditor = (props) => {
<IconButton disabled={executing} color="primary" style={{border: `1px solid ${theme.palette.primary.main}`, marginLeft: 300, padding: 8}} variant="contained" onClick={() => {
executeSingleAction(expOutput)
}}>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' action with what you see in the expected output window." placement="top">
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
{executing ? <CircularProgress style={{height: 18, width: 18, }} /> : <PlayArrowIcon style={{height: 18, width: 18, }} /> }
</Tooltip>
@@ -1340,6 +1356,11 @@ const CodeEditor = (props) => {
Test output: {executionResult.result}
</Typography>
: null}
{executionResult.errors !== undefined && executionResult.errors !== null && executionResult.errors.length > 0 ?
<Typography variant="body2" style={{maxHeight: 100, overflow: "auto", color: "#f85a3e",}}>
Errors ({executionResult.errors.length}): {executionResult.errors.join("\n")}
</Typography>
: null}
</span>
}
</div>
+48 -20
View File
@@ -209,7 +209,7 @@ const Admin = (props) => {
const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false);
const [openFileId, setOpenFileId] = React.useState(false);
const allowedFileTypes = ["txt", "py", "yaml","yml","json"]
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv",]
const runUpdateText = (text) =>{
fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, {
@@ -1017,6 +1017,36 @@ const Admin = (props) => {
});
};
const rerunCloudWorkflows = (environment) => {
alert.info("Starting execution reruns. This can run in the background.")
fetch(
`${globalUrl}/api/v1/environments/${environment.id}/rerun`,
{
method: "GET",
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
return;
} else {
alert.error(response.reason);
//alert.info("Aborted all dangling workflows");
}
return response.json();
})
.then((responseJson) => {
console.log("Got response for execution: ", responseJson);
//console.log("RESPONSE: ", responseJson)
//setFiles(responseJson)
})
.catch((error) => {
//alert.error(error.toString())
});
};
const abortEnvironmentWorkflows = (environment) => {
//console.log("Aborting all workflows started >10 minutes ago, not finished");
@@ -3943,7 +3973,7 @@ const Admin = (props) => {
<List>
<ListItem>
<ListItemText
primary="Created"
primary="Updated"
style={{ maxWidth: 225, minWidth: 225 }}
/>
<ListItemText
@@ -3989,7 +4019,8 @@ const Admin = (props) => {
bgColor = "#1f2023";
}
const isDisabledButton = isCloud || file.filesize < 100000 && file.status === ("active") && allowedFileTypes.includes(file.filename.split(".")[1]) === true
const filenamesplit = file.filename.split(".")
const iseditable = file.filesize < 100000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1])
return (
<ListItem
@@ -4006,7 +4037,7 @@ const Admin = (props) => {
minWidth: 225,
overflow: "hidden",
}}
primary={new Date(file.created_at * 1000).toISOString()}
primary={new Date(file.updated_at * 1000).toISOString()}
/>
<ListItemText
style={{
@@ -4099,13 +4130,13 @@ const Admin = (props) => {
<ListItemText
primary=<span style={{ display:"inline"}}>
<Tooltip
title={"Edit File"}
title={`Edit File (${allowedFileTypes.join(", ")})`}
style={{}}
aria-label={"Edit"}
>
<span>
<IconButton
disabled={isDisabledButton ? false : true}
disabled={!iseditable}
style = {{padding: "6px"}}
onClick={() => {
setOpenEditor(true)
@@ -4114,11 +4145,7 @@ const Admin = (props) => {
}}
>
<EditIcon
style={{
color: isDisabledButton === true
? "white"
: "grey",
}}
style={{color: iseditable ? "white" : "grey",}}
/>
</IconButton>
</span>
@@ -4888,9 +4915,7 @@ const Admin = (props) => {
>
<div style={{ display: "flex" }}>
<Button
variant={
environment.archived ? "contained" : "outlined"
}
variant={environment.archived ? "contained" : "outlined"}
style={{ borderRadius: "0px" }}
onClick={() => deleteEnvironment(environment)}
color="primary"
@@ -4900,16 +4925,19 @@ const Admin = (props) => {
<Button
variant={"outlined"}
style={{ borderRadius: "0px" }}
disabled={isCloud && environment.Name.toLowerCase() !== "cloud"}
onClick={() => {
console.log(
"Should clear executions for: ",
environment
);
abortEnvironmentWorkflows(environment);
console.log("Should clear executions for: ", environment);
if (isCloud && environment.Name.toLowerCase() === "cloud") {
rerunCloudWorkflows(environment);
} else {
abortEnvironmentWorkflows(environment);
}
}}
color="primary"
>
Clear
{isCloud && environment.Name.toLowerCase() === "cloud" ? "Rerun" : "Clear"}
</Button>
</div>
</ListItemText>
+220 -85
View File
@@ -274,6 +274,7 @@ const AngularWorkflow = (defaultprops) => {
const [triggerAuthentication, setTriggerAuthentication] = React.useState({});
const [triggerFolders, setTriggerFolders] = React.useState([]);
const [workflows, setWorkflows] = React.useState([]);
const [parentWorkflows, setParentWorkflows] = React.useState([]);
const [showEnvironment, setShowEnvironment] = React.useState(false);
const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false);
@@ -487,7 +488,6 @@ const AngularWorkflow = (defaultprops) => {
})
.then((responseJson) => {
if (responseJson !== undefined) {
setWorkflows(responseJson);
// Sets up subflow trigger with the right info
if (trigger_index > -1) {
@@ -529,6 +529,37 @@ const AngularWorkflow = (defaultprops) => {
}
}
}
if (workflows.length === 0) {
//console.log("First request. Checking for parent trigger (if this is subflow")
var parentworkflows = []
for (let workflowkey in responseJson) {
const innerworkflow = responseJson[workflowkey]
for (let triggerkey in innerworkflow.triggers) {
const trigger = innerworkflow.triggers[triggerkey]
if (trigger.trigger_type === "SUBFLOW") {
for (let paramkey in trigger.parameters) {
const param = trigger.parameters[paramkey]
if (param.name === "workflow" && param.value === props.match.params.key) {
parentworkflows.push({
id: innerworkflow.id,
name: innerworkflow.name,
image: innerworkflow.image,
})
}
}
}
}
}
if (parentworkflows.length > 0) {
setParentWorkflows(parentworkflows.filter(wf => wf.id !== props.match.params.key))
}
}
setWorkflows(responseJson);
}
})
.catch((error) => {
@@ -2324,54 +2355,85 @@ const AngularWorkflow = (defaultprops) => {
((nodedata.app_name !== "Shuffle Tools" &&
nodedata.app_name !== "Testing" &&
nodedata.app_name !== "Shuffle Workflow" &&
nodedata.app_name !== "User Input" &&
nodedata.app_name !== "Webhook" &&
nodedata.app_name !== "Schedule" &&
nodedata.app_name !== "Email") ||
nodedata.app_name !== "User Input") ||
nodedata.isStartNode)
) {
const allNodes = cy.nodes().jsons();
var found = false;
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === nodedata.id &&
currentNode.data.isDescriptor
) {
found = true;
console.log("FOUND THE NODE!");
break;
}
}
const allNodes = cy.nodes().jsons();
var found = false;
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === nodedata.id &&
currentNode.data.isDescriptor
) {
found = true;
console.log("FOUND THE NODE!");
break;
}
}
// Readding the icon after moving the node
if (!found) {
const iconInfo = GetIconInfo(nodedata);
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
console.log("Found triggers. Add!")
const offset = nodedata.isStartNode ? 36 : 44;
const decoratorNode = {
position: {
x: event.target.position().x + offset,
y: event.target.position().y + offset,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
label: "",
image: svgpin_Url,
imageColor: iconInfo.iconBackgroundColor,
attachedTo: nodedata.id,
},
};
if (!found) {
console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
// Find how many executions it has
var executions = 0
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
console.log("Matches: ", matchingExecutions.length)
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
const decoratorNode = {
position: {
x: event.target.position().x + 44,
y: event.target.position().y + 44,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
isTrigger: true,
label: `${matchingExecutions.length}`,
attachedTo: nodedata.id,
imageColor: color,
hasExecutions: true,
},
};
cy.add(decoratorNode).unselectify();
} else {
console.log("Node already exists - don't add descriptor node");
}
cy.add(decoratorNode)
}
} else {
// Readding the icon after moving the node
if (!found) {
const iconInfo = GetIconInfo(nodedata);
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
const offset = nodedata.isStartNode ? 36 : 44;
const decoratorNode = {
position: {
x: event.target.position().x + offset,
y: event.target.position().y + offset,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
label: "",
image: svgpin_Url,
imageColor: iconInfo.iconBackgroundColor,
attachedTo: nodedata.id,
},
};
cy.add(decoratorNode).unselectify();
} else {
console.log("Node already exists - don't add descriptor node");
}
}
}
originalLocation = {
@@ -2676,6 +2738,11 @@ const AngularWorkflow = (defaultprops) => {
return;
} else if (data.isDescriptor) {
console.log("Can't select descriptor");
if (data.isTrigger) {
console.log("But maybe we can select trigger descriptor? Maybe open execution tab?")
setExecutionModalOpen(true)
}
event.target.unselect();
return;
}
@@ -4211,6 +4278,11 @@ const AngularWorkflow = (defaultprops) => {
});
};
const addRunCountButton = (event) => {
// Count executions?
// Maybe it shouldn't be onclick?
}
const addCopyButton = (event) => {
var parentNode = cy.$("#" + event.target.data("id"));
if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
@@ -4379,7 +4451,52 @@ const AngularWorkflow = (defaultprops) => {
//if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
if (nodedata.app_name !== undefined && !workflow.public === true) {
const allNodes = cy.nodes().jsons();
const allNodes = cy.nodes().jsons();
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
console.log("In this :)")
var found = false;
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === nodedata.id &&
currentNode.data.isDescriptor
) {
found = true;
console.log("FOUND THE NODE!");
break;
}
}
if (!found) {
console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
// Find how many executions it has
var executions = 0
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
console.log("Matches: ", matchingExecutions.length)
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
const decoratorNode = {
position: {
x: event.target.position().x + 44,
y: event.target.position().y + 44,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
isTrigger: true,
label: `${matchingExecutions.length}`,
attachedTo: nodedata.id,
imageColor: color,
hasExecutions: true,
},
};
cy.add(decoratorNode)
}
}
var found = false;
for (var _key in allNodes) {
@@ -4413,7 +4530,10 @@ const AngularWorkflow = (defaultprops) => {
if (nodedata.type === "TRIGGER") {
if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") {
addCopyButton(event);
}
} else {
// Check how many executions from the source
addRunCountButton(event);
}
} else {
addCopyButton(event);
addStartnodeButton(event);
@@ -4467,7 +4587,7 @@ const AngularWorkflow = (defaultprops) => {
// locked: true,
// })
//}
}
}
if (event.target !== undefined && event.target !== null) {
event.target.animate(
@@ -4722,6 +4842,7 @@ const AngularWorkflow = (defaultprops) => {
return decoratorNode;
});
const triggers = workflow.triggers.map((trigger) => {
const node = {};
node.position = trigger.position;
@@ -5017,14 +5138,7 @@ const AngularWorkflow = (defaultprops) => {
}
// App length necessary cus of cy initialization
if (
// First load - gets the workflow
elements.length === 0 &&
workflow.actions !== undefined &&
!graphSetup &&
Object.getOwnPropertyNames(workflow).length > 0
) {
if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
setGraphSetup(true);
setupGraph();
console.log("In graph setup")
@@ -10073,6 +10187,10 @@ const AngularWorkflow = (defaultprops) => {
name: "custom_response_body",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "await_response",
value: "v1",
};
setWorkflow(workflow);
} else {
// Always update
@@ -11065,26 +11183,6 @@ const AngularWorkflow = (defaultprops) => {
style={{ paddingLeft: 10, backgroundColor: inputColor }}
row
>
<FormControlLabel
control={
<Checkbox
checked={
workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("subflow")
}
onChange={() => {
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
disabled
/>
}
label={<div style={{ color: "white" }}>Subflow</div>}
/>
<FormControlLabel
control={
<Checkbox
@@ -11119,12 +11217,23 @@ const AngularWorkflow = (defaultprops) => {
}
label={<div style={{ color: "white" }}>SMS</div>}
/>
<FormControlLabel
control={
<Checkbox
checked={workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow")}
onChange={() => {
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
/>
}
label={<div style={{ color: "white" }}>Subflow</div>}
/>
</FormGroup>
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("email") ? (
workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ? (
<TextField
style={{
backgroundColor: inputColor,
@@ -11187,11 +11296,7 @@ const AngularWorkflow = (defaultprops) => {
}}
/>
) : null}
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("subflow") ? (
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow") ? (
<TextField
style={{
backgroundColor: inputColor,
@@ -11213,8 +11318,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers[selectedTriggerIndex].parameters[5].value
}
onBlur={(event) => {
workflow.triggers[selectedTriggerIndex].parameters[5].value =
event.target.value;
workflow.triggers[selectedTriggerIndex].parameters[5].value = event.target.value;
setWorkflow(workflow);
setUpdate(Math.random());
}}
@@ -11565,6 +11669,36 @@ const AngularWorkflow = (defaultprops) => {
<h2 style={{ margin: 0 }}>{workflow.name}</h2>
</Breadcrumbs>
</div>
<div style={{display: "flex", marginLeft: 10, }}>
{parentWorkflows.slice(0,5).map((wf, index) => {
return (
<a href={`/workflows/${wf.id}`} target="_blank" rel="noopener noreferrer" key={index}>
<Tooltip arrow placement="left" title={
<span style={{}}>
{wf.image !== undefined && wf.image !== null && wf.image.length > 0 ?
<img
src={wf.image}
alt={wf.name}
style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }}
/>
: null}
<Typography>
Parent workflow: '{wf.name}'
</Typography>
</span>
} placement="bottom">
<span onClick={() => {
console.log("Click: ", wf)
}}>
<img src={theme.palette.defaultImage} style={{height: 25, width: 25, cursor: "pointer", border: 15, marginRight: 5, marginTop: 5, filter: "grayscale(90%)", }} />
</span>
</Tooltip>
</a>
)
})}
</div>
</div>
);
};
@@ -14146,6 +14280,7 @@ const AngularWorkflow = (defaultprops) => {
width: imgsize,
height: imgsize,
border: `2px solid ${statusColor}`,
filter: curapp === undefined ? "grayscale(100%)" : null,
}}
/>
)}
+17 -12
View File
@@ -222,22 +222,23 @@ const parseCurl = (s) => {
return out;
};
// Basically CRUD for each category + special
export const appCategories = [
{
"name": "Communication",
"color": "#FFC107",
"icon": "communication",
"action_labels": ["List Messages", "Send Message",],
"action_labels": ["List Messages", "Send Message", "Get Message", "Search messages"],
}, {
"name": "SIEM",
"color": "#FFC107",
"icon": "siem",
"action_labels": ["Get alerts", "Search", "Create detection",],
"action_labels": ["List Alerts", "Search", "Create detection", "Add hash to lookup_list",],
}, {
"name": "Eradication",
"color": "#FFC107",
"icon": "eradication",
"action_labels": ["List tickets", "Update ticket", "Block hash", "Isolate host"],
"action_labels": ["List Alerts", "Update Alert", "Create detection", "Block hash", "Search Hosts", "Isolate host"],
}, {
"name": "Cases",
"color": "#FFC107",
@@ -247,27 +248,27 @@ export const appCategories = [
"name": "Assets",
"color": "#FFC107",
"icon": "assets",
"action_labels": [],
"action_labels": ["List Assets", "Get Asset", "Search Assets", "Search Users", "Search endpoints", "Search vulnerabilities"],
}, {
"name": "Intel",
"color": "#FFC107",
"icon": "intel",
"action_labels": [],
"action_labels": ["Get IOC", "Search IOC", "Create IOC", "Update IOC", "Delete IOC", "Add IOC",],
}, {
"name": "IAM",
"color": "#FFC107",
"icon": "iam",
"action_labels": [],
"action_labels": ["Get Identity", "Get Asset", "Search Identity", "Reset Password", "Disable user", ],
}, {
"name": "Network",
"color": "#FFC107",
"icon": "network",
"action_labels": ["Block IP",],
"action_labels": ["Get Rules", "Allow IP", "Block IP",],
}, {
"name": "Other",
"color": "#FFC107",
"icon": "other",
"action_labels": [],
"action_labels": ["Update Info", "Get Info", "Get Status", "Get Version", "Get Health", "Get Config", "Get Configs", "Get Configs by type", "Get Configs by name", "Run script"],
},
]
@@ -3152,11 +3153,15 @@ const AppCreator = (defaultprops) => {
<Select
fullWidth
onChange={(e) => {
console.log("Should change: ", e.target.value)
console.log("Should change: ", e.target.value, " Index: ", index)
actions[index].action_label = e.target.value
setActions(actions)
setUpdate(Math.random());
const foundIndex = actions.findIndex((action) => action.name === data.name)
console.log("Found index: ", foundIndex)
if (foundIndex !== undefined && foundIndex !== null && foundIndex >= 0) {
actions[foundIndex].action_label = e.target.value
setActions(actions)
setUpdate(Math.random())
}
}}
value={data.action_label}
style={{
+39 -4
View File
@@ -99,6 +99,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
const [description, setDescription] = useState("");
const [video, setVideo] = useState("");
const [blogpost, setBlogpost] = useState("");
const [workflowOutline, setWorkflowOutline] = useState("");
const [selectedWorkflows, setSelectedWorkflows] = useState([])
const [firstLoad, setFirstLoad] = useState(true)
@@ -188,6 +189,11 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
parsedUsecase.mitre = mitreTags
}
if (workflowOutline !== inputUsecase.workflow_outline) {
inputUsecase.workflow_outline = workflowOutline
parsedUsecase.workflow_outline = workflowOutline
}
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "POST",
headers: {
@@ -303,6 +309,10 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
if (inputUsecase.extra_buttons !== undefined && inputUsecase.extra_buttons !== null) {
subcase.extra_buttons = inputUsecase.extra_buttons
}
if (inputUsecase.workflow_outline !== undefined && inputUsecase.workflow_outline !== null) {
subcase.workflow_outline = inputUsecase.workflow_outline
}
}
}
@@ -433,6 +443,12 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
if (subcase.mitre !== undefined && subcase.mitre !== null) {
setMitreTags(subcase.mitre)
}
if (subcase.workflow_outline !== undefined && subcase.workflow_outline !== null) {
setWorkflowOutline(subcase.workflow_outline)
} else {
setWorkflowOutline("")
}
}}
>
<EditIcon style={{ color: usecase.color }} />
@@ -502,9 +518,6 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
<div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, marginRight: 15, }}>
{editing ?
<div style={{flex: 1, marginRight: 50, }}>
<Typography variant="h6">
Editing!
</Typography>
<TextField
style={{
marginTop: 10,
@@ -553,6 +566,26 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
}}
id="videoEditing"
/>
<TextField
style={{
marginTop: 10,
marginRight: 10,
}}
variant="outlined"
fullWidth
color="primary"
label="Workflow Outline"
placeholder={"Workflow Outline"}
value={workflowOutline}
multiline
minRows={3}
onChange={(event) => {
setWorkflowOutline(event.target.value)
}}
id="workflowOutline"
tabIndex="-1"
/>
<div
style={{
display: 'flex',
@@ -573,6 +606,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
setDescription("")
setVideo("")
setBlogpost("")
setWorkflowOutline("")
setEditing(false)
}}
>
@@ -590,10 +624,11 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
cursor: "pointer"
}}
onClick={(event) => {
setEditing(false)
setUsecaseItem(inputUsecase)
setEditing(false)
setDescription("")
setVideo("")
setWorkflowOutline("")
setBlogpost("")
}}
>