From 9dbdbafc6797e243ca7a6305ab9c1ed9fa93382a Mon Sep 17 00:00:00 2001 From: frikky Date: Tue, 6 Dec 2022 19:51:44 +0100 Subject: [PATCH 001/122] Fixed nightly push mechanism --- .github/push_nightly.sh | 91 +++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/.github/push_nightly.sh b/.github/push_nightly.sh index 92d5320b..b3660193 100644 --- a/.github/push_nightly.sh +++ b/.github/push_nightly.sh @@ -2,40 +2,79 @@ # Done manually for now since GHCR isn't being pushed to easily with the current Github action CI. Nightly = Latest IF we run hotfixes on latest ### Pull latest from ghcr CI/CD -#docker pull ghcr.io/shuffle/shuffle-app_sdk:nightly -#docker pull ghcr.io/shuffle/shuffle-worker:nightly -#docker pull ghcr.io/shuffle/shuffle-orborus:nightly -#docker pull ghcr.io/shuffle/shuffle-frontend:nightly +docker pull ghcr.io/shuffle/shuffle-app_sdk:nightly +docker pull ghcr.io/shuffle/shuffle-worker:nightly +docker pull ghcr.io/shuffle/shuffle-orborus:nightly +docker pull ghcr.io/shuffle/shuffle-frontend:nightly #docker pull ghcr.io/shuffle/shuffle-backend:nightly +# +### NIGHTLY releases +docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:nightly +docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:nightly +docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:nightly +docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:nightly +docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:nightly + +docker push ghcr.io/frikky/shuffle-app_sdk:nightly +docker push ghcr.io/frikky/shuffle-worker:nightly +docker push ghcr.io/frikky/shuffle-orborus:nightly +docker push ghcr.io/frikky/shuffle-frontend:nightly +docker push ghcr.io/frikky/shuffle-backend:nightly ### LATEST releases: -#docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:latest -#docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:latest -#docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:latest -#docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:latest -#docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:latest -# -#docker push ghcr.io/shuffle/shuffle-app_sdk:latest -#docker push ghcr.io/shuffle/shuffle-worker:latest -#docker push ghcr.io/shuffle/shuffle-orborus:latest -#docker push ghcr.io/shuffle/shuffle-frontend:latest -#docker push ghcr.io/shuffle/shuffle-backend:latest +## shuffle/shuffle +docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:latest +docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:latest +docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:latest +docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:latest +docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:latest + +docker push ghcr.io/shuffle/shuffle-app_sdk:latest +docker push ghcr.io/shuffle/shuffle-worker:latest +docker push ghcr.io/shuffle/shuffle-orborus:latest +docker push ghcr.io/shuffle/shuffle-frontend:latest +docker push ghcr.io/shuffle/shuffle-backend:latest + +## frikky/shuffle +docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:latest +docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:latest +docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:latest +docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:latest +docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:latest + +docker push ghcr.io/frikky/shuffle-app_sdk:latest +docker push ghcr.io/frikky/shuffle-worker:latest +docker push ghcr.io/frikky/shuffle-orborus:latest +docker push ghcr.io/frikky/shuffle-frontend:latest +docker push ghcr.io/frikky/shuffle-backend:latest ### 1.1.0 releases: -#docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:1.1.0 -#docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:1.1.0 -#docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:1.1.0 -#docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:1.1.0 -#docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:1.1.0 -# -#docker push ghcr.io/shuffle/shuffle-app_sdk:1.1.0 -#docker push ghcr.io/shuffle/shuffle-worker:1.1.0 -#docker push ghcr.io/shuffle/shuffle-orborus:1.1.0 -#docker push ghcr.io/shuffle/shuffle-frontend:1.1.0 -#docker push ghcr.io/shuffle/shuffle-backend:1.1.0 +## shuffle/shuffle +docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:1.1.0 +docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:1.1.0 +docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:1.1.0 +docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:1.1.0 +docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:1.1.0 +docker push ghcr.io/shuffle/shuffle-app_sdk:1.1.0 +docker push ghcr.io/shuffle/shuffle-worker:1.1.0 +docker push ghcr.io/shuffle/shuffle-orborus:1.1.0 +docker push ghcr.io/shuffle/shuffle-frontend:1.1.0 +docker push ghcr.io/shuffle/shuffle-backend:1.1.0 +## frikky/shuffle +docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:1.1.0 +docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:1.1.0 +docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:1.1.0 +docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:1.1.0 +docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:1.1.0 + +docker push ghcr.io/frikky/shuffle-app_sdk:1.1.0 +docker push ghcr.io/frikky/shuffle-worker:1.1.0 +docker push ghcr.io/frikky/shuffle-orborus:1.1.0 +docker push ghcr.io/frikky/shuffle-frontend:1.1.0 +docker push ghcr.io/frikky/shuffle-backend:1.1.0 ### Manage worker-scale upload (Requires auth) # This is supposed to be unavailable, and only be downloadable by customers From e26001c3356b5b8681583a9fd2b5a1d00a5255ea Mon Sep 17 00:00:00 2001 From: Dhaval Dave Date: Wed, 11 Jan 2023 13:53:55 +0000 Subject: [PATCH 002/122] fixed ConfiguredWorkflow.jsx & AngularWorkflow.jsx --- frontend/src/components/ConfigureWorkflow.jsx | 18 +- frontend/src/views/AngularWorkflow.jsx | 166 +++++++++--------- 2 files changed, 92 insertions(+), 92 deletions(-) diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 0e5cc988..128f7503 100644 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -129,7 +129,7 @@ const ConfigureWorkflow = (props) => { setFirstLoad(workflow.id) const newactions = []; - for (var key in workflow.actions) { + for (let [key, keyval] in workflow.actions.entries()) { const action = workflow.actions[key]; var newaction = { large_image: action.large_image, @@ -175,7 +175,7 @@ const ConfigureWorkflow = (props) => { ) { // Check if configuration is filled or not var filled = true; - for (var key in action.parameters) { + for (let [key,keyval] in action.parameters.entries()) { if (action.parameters[key].configuration) { //console.log("Found config: ", action.parameters[key]) if ( @@ -216,7 +216,7 @@ const ConfigureWorkflow = (props) => { if (newaction.must_authenticate) { var authenticationOptions = []; - for (var key in appAuthentication) { + for (let [key,keyval] in appAuthentication.entries()) { const auth = appAuthentication[key]; if (auth.app.name === app.name && auth.active) { //console.log("Found auth: ", auth) @@ -264,7 +264,7 @@ const ConfigureWorkflow = (props) => { } } - for (var key in workflow.workflow_variables) { + for (let [key,keyval] in workflow.workflow_variables.entries()) { const variable = workflow.workflow_variables[key]; if ( variable.value === undefined || @@ -277,7 +277,7 @@ const ConfigureWorkflow = (props) => { } } - for (var key in workflow.triggers) { + for (let [key,keyval] in workflow.triggers.entries()) { var trigger = workflow.triggers[key]; trigger.index = key; @@ -299,7 +299,7 @@ const ConfigureWorkflow = (props) => { } ] - for (var subkey in tmpsteps) { + for (let [subkey,subkeyval] in tmpsteps.entries()) { newactions[foundindex].steps.push(tmpsteps[subkey]) } @@ -342,11 +342,11 @@ const ConfigureWorkflow = (props) => { if (appAuthentication.length !== previousAuth.length) { var newactions = [] - for (var actionkey in requiredActions) { + for (let [actionkey, actionkeyval] in requiredActions.entries()) { var newaction = requiredActions[actionkey]; const app = newaction.app; - for (var key in appAuthentication) { + for (let [key,keyval] in appAuthentication.entries()) { const auth = appAuthentication[key]; // Does this account for all the different ones of the same? @@ -690,7 +690,7 @@ const ConfigureWorkflow = (props) => { if (workflow.actions !== null) { //console.log(workflow.actions) alert.info("Setting action to version "+action.update_version) - for (var key in workflow.actions) { + for (let [key,keyval] in workflow.actions.entries()) { 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 diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 638234d5..027db155 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -201,7 +201,7 @@ export function removeParam(key, sourceURL) { if (queryString !== "") { params_arr = queryString.split("&"); - for (var i = params_arr.length - 1; i >= 0; i -= 1) { + for (let i = params_arr.length - 1; i >= 0; i -= 1) { param = params_arr[i].split("=")[0]; if (param === key) { params_arr.splice(i, 1); @@ -495,7 +495,7 @@ const AngularWorkflow = (defaultprops) => { var baseSubflow = {} const trigger = workflow.triggers[trigger_index]; if (trigger.parameters.length >= 3) { - for (var key in trigger.parameters) { + for (let [key,keyval] in trigger.parameters.entries()) { const param = trigger.parameters[key]; if (param.name === "workflow") { @@ -877,7 +877,7 @@ const AngularWorkflow = (defaultprops) => { } if (responseJson.results !== null && responseJson.results.length > 0) { - for (var key in responseJson.results) { + for (let [key,keyval] in responseJson.results.entries()) { var item = responseJson.results[key]; var currentnode = cy.getElementById(item.action.id); if (currentnode.length === 0) { @@ -952,7 +952,7 @@ const AngularWorkflow = (defaultprops) => { // FIXME - add outgoing nodes to executing //const outgoingNodes = outgoingEdges.find().data().target if (outgoingEdges.length > 0) { - for (var i = 0; i < outgoingEdges.length; i++) { + for (let i = 0; i < outgoingEdges.length; i++) { const edge = outgoingEdges[i]; const targetnode = cy.getElementById(edge.data().target); if ( @@ -1028,7 +1028,7 @@ const AngularWorkflow = (defaultprops) => { } var curelements = cy.elements(); - for (var i = 0; i < curelements.length; i++) { + for (let i = 0; i < curelements.length; i++) { if (curelements[i].classes().includes("executing-highlight")) { curelements[i].removeClass("executing-highlight"); curelements[i].addClass("failure-highlight"); @@ -1103,7 +1103,7 @@ const AngularWorkflow = (defaultprops) => { var newBranches = []; var newVBranches = []; var newComments = []; - for (var key in cyelements) { + for (let [key,keyval] in cyelements.entries()) { if (cyelements[key].data === undefined) { continue; } @@ -1173,7 +1173,7 @@ const AngularWorkflow = (defaultprops) => { // Cleans up OpenAPI items var newparams = []; - for (var key in curworkflowAction.parameters) { + for (let [key,keyval] in curworkflowAction.parameters.entries()) { const thisitem = curworkflowAction.parameters[key]; if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) { continue; @@ -1334,19 +1334,19 @@ const AngularWorkflow = (defaultprops) => { const cyelements = cy.elements(); - for (var i = 0; i < cyelements.length; i++) { + for (let i = 0; i < cyelements.length; i++) { //cyelements[i].removeStyle(); cyelements[i].data().is_valid = true; cyelements[i].data().errors = []; } - for (var key in workflow.actions) { + for (let [key,keyval] in workflow.actions.entries()) { workflow.actions[key].is_valid = true; workflow.actions[key].errors = []; } } - for (var key in workflow.errors) { + for (let [key,keyval] in workflow.errors.entries()) { //alert.info(workflow.errors[key]); } @@ -1372,7 +1372,7 @@ const AngularWorkflow = (defaultprops) => { var firstnode = cy.getElementById(workflow.start); if (firstnode.length === 0) { var found = false; - for (var key in workflow.actions) { + for (let [key,keyval] in workflow.actions.entries()) { if (workflow.actions[key].isStartNode) { console.log("Updating startnode"); workflow.start = workflow.actions[key].id; @@ -1419,7 +1419,7 @@ const AngularWorkflow = (defaultprops) => { stop() var curelements = cy.elements(); - for (var i = 0; i < curelements.length; i++) { + for (let i = 0; i < curelements.length; i++) { curelements[i].addClass("not-executing-highlight"); } @@ -1451,7 +1451,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionRequestStarted(false); stop(); - for (var i = 0; i < curelements.length; i++) { + for (let i = 0; i < curelements.length; i++) { curelements[i].removeClass("not-executing-highlight"); } return; @@ -1472,7 +1472,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionRequestStarted(false); stop(); - for (var i = 0; i < curelements.length; i++) { + for (let i = 0; i < curelements.length; i++) { curelements[i].removeClass("not-executing-highlight"); } return; @@ -1519,7 +1519,7 @@ const AngularWorkflow = (defaultprops) => { .then((responseJson) => { if (responseJson.success) { var newauth = []; - for (var key in responseJson.data) { + for (let [key,keyval] in responseJson.data.entries()) { if (responseJson.data[key].defined === false) { continue; } @@ -1545,11 +1545,11 @@ const AngularWorkflow = (defaultprops) => { var tmpAuth = JSON.parse(JSON.stringify(responseJson.data)); var latest = 0; - for (var key in tmpAuth) { + for (let [key,keyval] in tmpAuth.entries()) { var item = tmpAuth[key]; const newfields = {}; - for (var filterkey in item.fields) { + for (let [key,keyval] in item.fields.entries()) { newfields[item.fields[filterkey].key] = item.fields[filterkey].value; } @@ -1563,7 +1563,7 @@ const AngularWorkflow = (defaultprops) => { latest = item.edited; selectedAction.selectedAuthentication = item; - for (var key in workflow.actions) { + for (let [key,keyval] in workflow.actions.entries()) { if (workflow.actions[key].app_name === selectedApp.name) { workflow.actions[key].selectedAuthentication = item; workflow.actions[key].authentication_id = item.id; @@ -1813,7 +1813,7 @@ const AngularWorkflow = (defaultprops) => { //{appGroup.map((data, index) => { //const [appGroup, setAppGroup] = React.useState([]); var appsFound = [] - for (var key in responseJson.actions) { + for (let [key,keyval] in responseJson.actions.entries()) { const parsedAction = responseJson.actions[key] if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") { continue @@ -1826,7 +1826,7 @@ const AngularWorkflow = (defaultprops) => { setAppGroup(appsFound) appsFound = [] - for (var key in responseJson.triggers) { + for (let [key,keyval] in responseJson.triggers.entries()) { const parsedAction = responseJson.triggers[key] if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){ appsFound.push(parsedAction) @@ -2026,7 +2026,7 @@ const AngularWorkflow = (defaultprops) => { // Wait for new node to possibly be selected //setTimeout(() => { const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { + for (let [key,keyval] in typeIds.entries()) { const item = typeIds[idkey] if (item.data.isButton === true) { //console.log("Reselect old node & return - or just return?") @@ -2234,7 +2234,7 @@ const AngularWorkflow = (defaultprops) => { } const connected = event.target.connectedEdges().jsons() - for (var key in connected) { + for (let [key,keyval] in connected.entries()) { const edge = connected[key] //console.log("EDGE:", edge) @@ -2331,7 +2331,7 @@ const AngularWorkflow = (defaultprops) => { ) { const allNodes = cy.nodes().jsons(); var found = false; - for (var key in allNodes) { + for (let [key,keyval] in allNodes.entries()) { const currentNode = allNodes[key]; if ( currentNode.data.attachedTo === nodedata.id && @@ -2398,7 +2398,7 @@ const AngularWorkflow = (defaultprops) => { if (nodedata.app_name !== undefined) { const allNodes = cy.nodes().jsons(); - for (var key in allNodes) { + for (let [key,keyval] in allNodes.entries()) { const currentNode = allNodes[key]; if (currentNode.data.attachedTo === nodedata.id) { cy.getElementById(currentNode.data.id).remove(); @@ -2481,7 +2481,7 @@ const AngularWorkflow = (defaultprops) => { elementMouseIsOver.style.border = newBorder; console.log("STYLED: ", styledElements); - for (var key in styledElements) { + for (let [key,keyval] in styledElements.entries()) { const curElement = document.getElementById(styledElements[key]); if (curElement !== null && curElement !== undefined) { curElement.style.border = curElement.style.original_border; @@ -2494,7 +2494,7 @@ const AngularWorkflow = (defaultprops) => { elementMouseIsOver.id === "cytoscape_view" || elementMouseIsOver.id === "" ) { - for (var key in styledElements) { + for (let [key,keyval] in styledElements.entries()) { const curElement = document.getElementById(styledElements[key]); if (curElement !== null && curElement !== undefined) { curElement.style.border = curElement.style.original_border; @@ -2636,7 +2636,7 @@ const AngularWorkflow = (defaultprops) => { foundbranch.destination_id === parentNode.data("id") ); - for (var key in sourcebranches) { + for (let [key,keyval] in sourcebranches.entries()) { var newbranch = JSON.parse(JSON.stringify(sourcebranches[key])); newbranch.id = uuidv4(); newbranch.source_id = newNodeData.id; @@ -2650,7 +2650,7 @@ const AngularWorkflow = (defaultprops) => { }); } - for (var key in destinationbranches) { + for (let [key,keyval] in destinationbranches.entries()) { var newbranch = JSON.parse( JSON.stringify(destinationbranches[key]) ); @@ -2729,8 +2729,8 @@ const AngularWorkflow = (defaultprops) => { console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname) curaction.matching_actions = [] - for (var key in newapps) { - for (var subkey in newapps[key].actions) { + for (let [key,keyval] in newapps.entries()) { + for (let [subkey,subkeyval] in newapps[key].actions.entries()) { const tmpaction = newapps[key].actions[subkey] if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) { console.log("MATCH!: ", newapps[key]) @@ -2801,11 +2801,11 @@ const AngularWorkflow = (defaultprops) => { const tmpAuth = JSON.parse(JSON.stringify(newAppAuth)); //var tmpAuth = newAppAuth - for (var key in tmpAuth) { + for (let [key,keyval] in tmpAuth.entries()) { var item = tmpAuth[key]; const newfields = {}; - for (var filterkey in item.fields) { + for (let [filterkey,filterkeyval] in item.fields.entries()) { newfields[item.fields[filterkey].key] = item.fields[filterkey].value; } @@ -2837,7 +2837,7 @@ const AngularWorkflow = (defaultprops) => { curaction.parameters !== null && curaction.parameters.length > 0 ) { - for (var key in curaction.parameters) { + for (let [key,keyval] in curaction.parameters.entries()) { if ( curaction.parameters[key].options !== undefined && curaction.parameters[key].options !== null && @@ -2849,7 +2849,7 @@ const AngularWorkflow = (defaultprops) => { } } else { console.log("Should check APP if it has the same params as ACTION") - for (var key in curapp.actions) { + for (let [key,keyval] in curapp.actions.entries()) { const tmpaction = curapp.actions[key] if (tmpaction.name === curaction.name) { console.log("Found action - needs change?", tmpaction) @@ -2997,7 +2997,7 @@ const AngularWorkflow = (defaultprops) => { var exampledata = item.example === undefined ? "" : item.example; if (workflowExecutions.length > 0) { // Look for the ID - for (var key in workflowExecutions) { + for (let [key,keyval] in workflowExecutions.entries()) { if ( workflowExecutions[key].results === undefined || workflowExecutions[key].results === null @@ -3144,7 +3144,7 @@ const AngularWorkflow = (defaultprops) => { selectedkey = `.${key}`; } - for (var subitem in value) { + for (let [subitem,subitemval] in value.entries()) { toreturn = GetParamMatch( paramname, value[subitem], @@ -3195,7 +3195,7 @@ const AngularWorkflow = (defaultprops) => { // 2. Check parents in order var exampledata = GetExampleResult({ id: "exec", name: "exec" }); var parentlabel = "exec"; - for (var paramkey in dstdata.parameters) { + for (let [paramkey,paramkeyval] in dstdata.parameters.entries()) { const param = dstdata.parameters[paramkey]; // Skip authentication params if (param.configuration) { @@ -3220,7 +3220,7 @@ const AngularWorkflow = (defaultprops) => { var parents = getParents(dstdata); if (parents.length > 1) { - for (var key in parents) { + for (let [key,keyval] in parents.entries()) { const item = parents[key]; if (item.label === "Execution Argument") { continue; @@ -3231,7 +3231,7 @@ const AngularWorkflow = (defaultprops) => { ? "" : item.label.toLowerCase().trim().replaceAll(" ", "_"); exampledata = GetExampleResult(item); - for (var paramkey in dstdata.parameters) { + for (let [paramkey,paramkeyval] in dstdata.parameters.entries()) { const param = dstdata.parameters[paramkey]; // Skip authentication params if (param.configuration) { @@ -3381,7 +3381,7 @@ const AngularWorkflow = (defaultprops) => { // dest == dest && source == source // backend: check all children? to stop recursion var found = false; - for (var key in workflow.branches) { + for (let [key,keyval] in workflow.branches.entries()) { if ( workflow.branches[key].destination_id === edge.source && workflow.branches[key].source_id === edge.target @@ -3516,7 +3516,7 @@ const AngularWorkflow = (defaultprops) => { } // Remove bad startnode - for (var key in workflow.actions) { + for (let [key,keyval] in workflow.actions.entries()) { const action = workflow.actions[key]; if (action.isStartNode && workflow.start !== action.id) { action.isStartNode = false; @@ -3580,7 +3580,7 @@ const AngularWorkflow = (defaultprops) => { ) { var newparameters = []; - for (var subkey in nodedata.parameters) { + for (let [subkey,subkeyval] in nodedata.parameters.entries()) { var newparam = JSON.parse( JSON.stringify(nodedata.parameters[subkey]) ); @@ -3673,7 +3673,7 @@ const AngularWorkflow = (defaultprops) => { // Check if the source is trigger and can start console.log("Removed: ", edge.data()) const allNodes = cy.nodes().jsons() - for (var key in allNodes) { + for (let [key,keyval] in allNodes.entries()) { const curnode = allNodes[key] if (curnode.data.type !== "TRIGGER") { continue @@ -3765,7 +3765,7 @@ const AngularWorkflow = (defaultprops) => { if (data.app_name !== undefined) { const allNodes = cy.nodes().jsons(); - for (var key in allNodes) { + for (let [key,keyval] in allNodes.entries()) { const currentNode = allNodes[key]; if (currentNode.data.attachedTo === data.id) { cy.getElementById(currentNode.data.id).remove(); @@ -3945,7 +3945,7 @@ const AngularWorkflow = (defaultprops) => { const parsedjson = JSON.parse(clipboard); //console.log("Parsed: ", parsedjson) - for (var key in parsedjson) { + for (let [key,keyval] in parsedjson.entries()) { const item = parsedjson[key]; console.log("Adding: ", item); item.data.id = uuidv4() @@ -3993,7 +3993,7 @@ const AngularWorkflow = (defaultprops) => { .then((responseJson) => { var found = false; var showEnvCnt = 0; - for (var key in responseJson) { + for (let [key,keyval] in responseJson.entries()) { if (responseJson[key].default && !found) { setDefaultEnvironmentIndex(key); found = true; @@ -4009,7 +4009,7 @@ const AngularWorkflow = (defaultprops) => { } if (!found) { - for (var key in responseJson) { + for (let [key,keyval] in responseJson.entries()) { if (!responseJson[key].archived) { setDefaultEnvironmentIndex(key); break; @@ -4051,7 +4051,7 @@ const AngularWorkflow = (defaultprops) => { const nodedata = event.target.data(); if (nodedata.app_name !== undefined) { const allNodes = cy.nodes().jsons(); - for (var key in allNodes) { + for (let [key,keyval] in allNodes.entries()) { const currentNode = allNodes[key]; if ( currentNode.data.isButton && @@ -4071,7 +4071,7 @@ const AngularWorkflow = (defaultprops) => { // Skipping node editing if it's the selected one if (cy !== undefined) { const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { + for (let [idkey,idkeyval] in typeIds.entries()) { const item = typeIds[idkey] if (item.data.id === nodedata.id) { return @@ -4329,7 +4329,7 @@ const AngularWorkflow = (defaultprops) => { const allNodes = cy.nodes().jsons(); var found = false; - for (var key in allNodes) { + for (let [key,keyval] in allNodes.entries()) { const currentNode = allNodes[key]; if ( (currentNode.data.isButton || currentNode.data.isSuggestion) && @@ -4380,7 +4380,7 @@ const AngularWorkflow = (defaultprops) => { } const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { + for (let [idkey,idkeyval] in typeIds) { const item = typeIds[idkey] if (item.data.id === nodedata.id) { //console.log("items: ", item.data.id, nodedata.id) @@ -4791,7 +4791,7 @@ const AngularWorkflow = (defaultprops) => { // Verifies if a branch is valid and skips others var newedges = []; - for (var key in edges) { + for (let [key,keyval] in edges.entries()) { var item = edges[key]; if (item.data === undefined) { continue; @@ -5139,7 +5139,7 @@ const AngularWorkflow = (defaultprops) => { var mappedStartnode = "" const alledges = cy.edges().jsons() - for (var key in alledges) { + for (let [key,keyval] in alledges.entries()) { const tmp = alledges[key] console.log("TMP: ", tmp, tmp.data.source) if (tmp.data.source === trigger.id) { @@ -5917,11 +5917,11 @@ const AngularWorkflow = (defaultprops) => { } const tmpAuth = JSON.parse(JSON.stringify(appAuthentication)); - for (var key in tmpAuth) { + for (let [key,keyval] in tmpAuth.entries()) { var item = tmpAuth[key]; const newfields = {}; - for (var filterkey in item.fields) { + for (let [filterkey,filterkeyval] in item.fields.entries()) { newfields[item.fields[filterkey].key] = item.fields[filterkey].value; } @@ -5947,7 +5947,7 @@ const AngularWorkflow = (defaultprops) => { authenticationOptions !== null && authenticationOptions.length > 0 ) { - for (var key in authenticationOptions) { + for (let [key,keyval] in authenticationOptions.entries()) { const option = authenticationOptions[key]; if (option.active && newAppData.authentication_id === "") { @@ -6351,7 +6351,7 @@ const AngularWorkflow = (defaultprops) => { if (newApps.length === 0) { const searchvalue = value.trim().toLowerCase(); newApps = allApps.filter((app) => { - for (var key in app.actions) { + for (let [key,keyval] in app.actions.entries()) { const inneraction = app.actions[key]; if (inneraction.name.toLowerCase().includes(searchvalue)) { return true; @@ -6715,7 +6715,7 @@ const AngularWorkflow = (defaultprops) => { var highest = ""; const allitems = workflow.actions.concat(workflow.triggers); - for (var key in allitems) { + for (let [key,keyval] in allitems.entries()) { const item = allitems[key]; if ( item.app_name === appName && @@ -6776,7 +6776,7 @@ const AngularWorkflow = (defaultprops) => { // Simmple action swap autocompleter if (oldaction.parameters !== undefined && newSelectedAction.parameters !== undefined && oldaction.id === newSelectedAction.id) { var fileid_found = false - for (var paramkey in oldaction.parameters) { + for (let [paramkey,paramkeyval] in oldaction.parameters.entries()) { const param = oldaction.parameters[paramkey]; if (param.name === "file_id") { @@ -6890,7 +6890,7 @@ const AngularWorkflow = (defaultprops) => { // FIXME - should change icon-node (descriptor) as well const allNodes = cy.nodes().jsons(); - for (var key in allNodes) { + for (let [key,keyval] in allNodes.entries()) { const currentNode = allNodes[key]; if ( currentNode.data.attachedTo === oldaction.id && @@ -6982,7 +6982,7 @@ const AngularWorkflow = (defaultprops) => { var iterations = 0; var maxiter = 10; while (true) { - for (var key in allkeys) { + for (let [key,keyval] in allkeys.entries()) { var currentnode = cy.getElementById(allkeys[key]); if (currentnode === undefined || currentnode === null) { continue; @@ -7010,7 +7010,7 @@ const AngularWorkflow = (defaultprops) => { continue; } - for (var i = 0; i < incomingEdges.length; i++) { + for (let i = 0; i < incomingEdges.length; i++) { var tmp = incomingEdges[i]; if (tmp.data("decorator")) { continue @@ -7138,7 +7138,7 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers[selectedTriggerIndex].parameters[2].value.split(","); console.log(splitItems); if (splitItems.includes(value)) { - for (var i = 0; i < splitItems.length; i++) { + for (let i = 0; i < splitItems.length; i++) { if (splitItems[i] === value) { splitItems.splice(i, 1); } @@ -7147,7 +7147,7 @@ const AngularWorkflow = (defaultprops) => { splitItems.push(value); } - for (var i = 0; i < splitItems.length; i++) { + for (let i = 0; i < splitItems.length; i++) { if (splitItems[i] === "") { splitItems.splice(i, 1); } @@ -7229,7 +7229,7 @@ const AngularWorkflow = (defaultprops) => { workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0 ) { - for (var key in workflow.workflow_variables) { + for (let [key,keyval] in workflow.workflow_variables.entries()) { const item = workflow.workflow_variables[key]; actionlist.push({ type: "workflow_variable", @@ -7248,7 +7248,7 @@ const AngularWorkflow = (defaultprops) => { workflow.execution_variables !== undefined && workflow.execution_variables.length > 0 ) { - for (var key in workflow.execution_variables) { + for (let [key,keyval] in workflow.execution_variables.entries()) { const item = workflow.execution_variables[key]; actionlist.push({ type: "execution_variable", @@ -7264,7 +7264,7 @@ const AngularWorkflow = (defaultprops) => { const destAction = cy.getElementById(selectedEdge.target); var parents = getParents(destAction.data()); if (parents.length > 1) { - for (var key in parents) { + for (let [key,keyval] in parents.entries()) { const item = parents[key]; if (item.label === "Execution Argument") { continue; @@ -7440,7 +7440,7 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers !== null && workflow.triggers.length > 0 ) { - for (var key in workflow.triggers) { + for (let [key,keyval] in workflow.triggers.entries()) { const item = workflow.triggers[key]; var node = cy.getElementById(item.id); @@ -8784,7 +8784,7 @@ const AngularWorkflow = (defaultprops) => { workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0 ) { - for (var key in workflow.workflow_variables) { + for (let [key,keyval] in workflow.workflow_variables.entries()) { const item = workflow.workflow_variables[key]; actionlist.push({ type: "workflow_variable", @@ -8803,7 +8803,7 @@ const AngularWorkflow = (defaultprops) => { workflow.execution_variables !== undefined && workflow.execution_variables.length > 0 ) { - for (var key in workflow.execution_variables) { + for (let [key,keyval] in workflow.execution_variables.entries()) { const item = workflow.execution_variables[key]; actionlist.push({ type: "execution_variable", @@ -8818,7 +8818,7 @@ const AngularWorkflow = (defaultprops) => { var parents = getParents(selectedTrigger); if (parents.length > 1) { - for (var key in parents) { + for (let [key,keyval] in parents.entries()) { const item = parents[key]; if (item.label === "Execution Argument") { continue; @@ -8828,7 +8828,7 @@ const AngularWorkflow = (defaultprops) => { // Find previous execution and their variables if (workflowExecutions.length > 0) { // Look for the ID - for (var key in workflowExecutions) { + for (let [key,keyval] in workflowExecutions.entries()) { if ( workflowExecutions[key].results === undefined || workflowExecutions[key].results === null @@ -8957,7 +8957,7 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers !== null && workflow.triggers.length > 0 ) { - for (var key in workflow.triggers) { + for (let [key,keyval] in workflow.triggers.entries()) { const item = workflow.triggers[key]; if (cy !== undefined) { @@ -10542,7 +10542,7 @@ const AngularWorkflow = (defaultprops) => { ) console.log("Starting mail sub: ", workflow.triggers[selectedTriggerIndex].parameters[0].value, splitItem); - for (var key in splitItem) { + for (let [key,keyval] in splitItem.entries()) { const item = splitItem[key]; const curfolder = triggerFolders.find((a) => a.displayName === item); if (curfolder === undefined) { @@ -12680,7 +12680,7 @@ const AngularWorkflow = (defaultprops) => { } to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_"); - for (var key in copy.namespace) { + for (let [key,keyval] in copy.namespace.entries()) { if (copy.namespace[key].includes("Results for")) { continue; } @@ -12955,7 +12955,7 @@ const AngularWorkflow = (defaultprops) => { data.workflow.actions !== null ? data.workflow.actions.length : 0; - for (var key in data.workflow.triggers) { + for (let [key,keyval] in data.workflow.triggers.entries()) { const trigger = data.workflow.triggers[key]; if ( (trigger.app_name === "User Input" && @@ -13008,7 +13008,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionRunning(true); setExecutionRequestStarted(false); } else { - for (var key in data.results) { + for (let [key,keyval] in data.results.entries()) { if (data.results[key].status !== "SUCCESS") { continue } @@ -13491,7 +13491,7 @@ const AngularWorkflow = (defaultprops) => { if (data.similar_actions !== undefined && data.similar_actions !== null) { var minimumMatch = 85 var matching_executions = [] - for (var k in data.similar_actions){ + for (let [k,kval] in data.similar_actions.entries()){ if (data.similar_actions.hasOwnProperty(k)) { if (data.similar_actions[k].similarity > minimumMatch) { matching_executions.push(data.similar_actions[k].execution_id) @@ -13837,7 +13837,7 @@ const AngularWorkflow = (defaultprops) => { }} onClick={(e) => { e.preventDefault(); - for (var key in workflowExecutions) { + for (let [key,keyval] in workflowExecutions.entries()) { const execution = workflowExecutions[key]; const result = execution.results.find( (data) => @@ -13878,7 +13878,7 @@ const AngularWorkflow = (defaultprops) => { }} onClick={(e) => { e.preventDefault(); - for (var key in workflowExecutions) { + for (let [key,keyval] in workflowExecutions.entries()) { const execution = workflowExecutions[key]; const result = execution.results.find( (data) => @@ -14552,7 +14552,7 @@ const AngularWorkflow = (defaultprops) => { authenticationOption.app.actions = []; - for (var key in selectedApp.authentication.parameters) { + for (let [key,keyval] in selectedApp.authentication.parameters.entries()) { if ( authenticationOption.fields[ selectedApp.authentication.parameters[key].name @@ -14571,7 +14571,7 @@ const AngularWorkflow = (defaultprops) => { // Automatically mapping fields that already exist (predefined). // Warning if fields are NOT filled - for (var key in selectedApp.authentication.parameters) { + for (let [key,keyval] in selectedApp.authentication.parameters.entries()) { if ( authenticationOption.fields[ selectedApp.authentication.parameters[key].name @@ -14620,7 +14620,7 @@ const AngularWorkflow = (defaultprops) => { var newAuthOption = JSON.parse(JSON.stringify(authenticationOption)); var newFields = []; - for (const key in newAuthOption.fields) { + for (let [key,keyval] in newAuthOption.fields.entries()) { const value = newAuthOption.fields[key]; newFields.push({ key: key, From 20350178e3b655807fbe0f9d5eb842537a25e3d8 Mon Sep 17 00:00:00 2001 From: Dhaval Dave Date: Thu, 12 Jan 2023 06:46:13 +0000 Subject: [PATCH 003/122] fixed Par/components/ParsedAction.jsx --- frontend/src/components/ParsedAction.jsx | 60 ++++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 455c0e57..2ac8aaed 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -277,7 +277,7 @@ const ParsedAction = (props) => { console.log("FOUNDACTION: ", foundAction); if (foundAction !== null && foundAction !== undefined) { var foundparams = []; - for (var paramkey in foundAction.parameters) { + for (let [paramkey,paramkeyval] in foundAction.parameters.entries()) { const param = foundAction.parameters[paramkey]; const foundParam = selectedAction.parameters.find( @@ -401,7 +401,7 @@ const ParsedAction = (props) => { if (actionlist.length === 0) { // FIXME: Have previous execution values in here if (workflowExecutions.length > 0) { - for (var key in workflowExecutions) { + for (let [key,keyval] in workflowExecutions.entries()) { if ( workflowExecutions[key].execution_argument === undefined || workflowExecutions[key].execution_argument === null || @@ -451,7 +451,7 @@ const ParsedAction = (props) => { workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0 ) { - for (var key in workflow.workflow_variables) { + for (let [key,keyval] in workflow.workflow_variables.entries()) { const item = workflow.workflow_variables[key]; actionlist.push({ type: "workflow_variable", @@ -470,7 +470,7 @@ const ParsedAction = (props) => { workflow.execution_variables !== undefined && workflow.execution_variables.length > 0 ) { - for (var key in workflow.execution_variables) { + for (let [key,keyval] in workflow.execution_variables.entries()) { const item = workflow.execution_variables[key]; actionlist.push({ type: "execution_variable", @@ -488,7 +488,7 @@ const ParsedAction = (props) => { var parents = getParents(selectedAction); if (parents.length > 1) { - for (var key in parents) { + for (let [key,keyval] in parents.entries()) { const item = parents[key]; if (item.label === "Execution Argument") { continue; @@ -500,7 +500,7 @@ const ParsedAction = (props) => { if (workflowExecutions.length > 0) { // Look for the ID const found = false; - for (var key in workflowExecutions) { + for (let [key,keyval] in workflowExecutions.entries()) { if ( workflowExecutions[key].results === undefined || workflowExecutions[key].results === null @@ -566,7 +566,7 @@ const ParsedAction = (props) => { if (found !== null && found !== undefined) { var new_occurences = [] - for (var key in found) { + for (let [key,keyval] in found.entries()) { if (found[key][0] !== "\\") { new_occurences.push(found[key]) } @@ -579,7 +579,7 @@ const ParsedAction = (props) => { if (found !== null) { try { // When the found array is empty. - for (var i = 0; i < found.length; i++) { + for (let i = 0; i < found.length; i++) { const variableSplit = found[i].split(".#") if ((variableSplit.length-1) > 1) { //console.log("Larger than 1: ", variableSplit) @@ -589,7 +589,7 @@ const ParsedAction = (props) => { } var foundSlice = false - for (var j = 0; j < actionlist.length; j++) { + for (let j = 0; j < actionlist.length; j++) { //console.log("ACTION: ", found[i], actionlist[j]) //console.log("ACTION :", found[i].split(".")[0].slice(1,).toLowerCase(), actionlist[j].autocomplete.toLowerCase()) if(found[i].split(".")[0].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){ @@ -730,7 +730,7 @@ const ParsedAction = (props) => { var curstring = ""; var record = false; - for (var key in selectedActionParameters[count].value) { + for (let [key,keyval] in selectedActionParameters[count].value.entries()) { const item = selectedActionParameters[count].value[key]; if (record) { curstring += item; @@ -911,7 +911,7 @@ const ParsedAction = (props) => { var curstring = "" var record = false - for (var key in selectedActionParameters[count].value) { + for (let [key,keyval] in selectedActionParameters[count].value.entries()) { const item = selectedActionParameters[count].value[key] if (record) { curstring += item @@ -1187,7 +1187,7 @@ const ParsedAction = (props) => { renderInput={(params) => { if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { const prefixes = ["Post", "Put", "Patch"] - for (var key in prefixes) { + for (let [key,keyval] in prefixes.entries()) { if (params.inputProps.value.startsWith(prefixes[key])) { params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) if (params.inputProps.value.length > 1) { @@ -1426,7 +1426,7 @@ const ParsedAction = (props) => { setHideBody(!hideBody); - for (var key in selectedActionParameters) { + for (let [key,keyval] in selectedActionParameters.entries()) { var currentItem = selectedActionParameters[key]; if (currentItem.name === "ssl_verify") { @@ -1472,10 +1472,10 @@ const ParsedAction = (props) => { openApiHelperText = "OpenAPI spec: fill the following fields."; //console.log("SHOULD ADD TO selectedActionParameters!: ", found, selectedActionParameters) var changed = false; - for (var specKey in found) { + for (let [speckey,speckeyval] in found.entries()) { const tmpitem = found[specKey]; var skip = false; - for (var innerkey in selectedActionParameters) { + for (let [innerkey,innerkeyval] in selectedActionParameters.entries()) { if (selectedActionParameters[innerkey].name === tmpitem) { skip = true; break; @@ -1707,7 +1707,7 @@ const ParsedAction = (props) => { var foundnewline = false var allValues = [] - for (var key in splitdata) { + for (let [key,keyval] in splitdata.entries()) { const line = splitdata[key] if (line === "") { foundnewline = true @@ -1777,7 +1777,7 @@ const ParsedAction = (props) => { const tmpsplit = selectedActionParameters[count].value.split("\n") var valsplit = [] var add_empty = false - for (var key in tmpsplit) { + for (let [key,keyval] in tmpsplit.entries()) { if (tmpsplit[key] === "") { add_empty = true continue @@ -1792,7 +1792,7 @@ const ParsedAction = (props) => { console.log("Split: ", valsplit) var newarr = [] - for (var key in valsplit) { + for (let [key,keyval] in valsplit.entries()) { var line = valsplit[key] if (key == index) { @@ -1832,7 +1832,7 @@ const ParsedAction = (props) => { var tmpsplit = selectedActionParameters[count].value.split("\n") var valsplit = [] var add_empty = false - for (var key in tmpsplit) { + for (let [key,keyval] in tmpsplit.entries()) { if (tmpsplit[key] === "") { add_empty = true continue @@ -1847,7 +1847,7 @@ const ParsedAction = (props) => { console.log("Split: ", valsplit) var newarr = [] - for (var key in valsplit) { + for (let [key,keyval] in valsplit.entries()) { var line = valsplit[key] if (key == index) { @@ -2094,7 +2094,7 @@ const ParsedAction = (props) => { : "$" + values[0].autocomplete; toComplete = toComplete.toLowerCase().replaceAll(" ", "_"); - for (var key in values) { + for (let [key,keyval] in values.entries()) { if (key == 0 || values[key].autocomplete.length === 0) { continue; } @@ -2205,7 +2205,7 @@ const ParsedAction = (props) => { workflow.triggers !== null && workflow.triggers.length > 0 ) { - for (var key in workflow.triggers) { + for (let [key,keyval] in workflow.triggers.entries()) { const item = workflow.triggers[key]; if (cy !== undefined) { @@ -2727,7 +2727,7 @@ const ParsedAction = (props) => { if (workflowExecutions.length > 0) { // Look for the ID const found = false; - for (var key in workflowExecutions) { + for (let [key,keyval] in workflowExecutions.entries()) { if ( workflowExecutions[key].results === undefined || workflowExecutions[key].results === null @@ -2971,8 +2971,8 @@ const ParsedAction = (props) => { // // Should make it a function lol if (workflow.branches !== undefined && workflow.branches !== null) { - for (var key in workflow.branches) { - for (var subkey in workflow.branches[key].conditions) { + for (let [key,keyval] in workflow.branches.entries()) { + for (let [key,keyval] in workflow.branches[key].conditions.entries()) { const condition = workflow.branches[key].conditions[subkey] const sourceparam = condition.source const destinationparam = condition.destination @@ -3080,12 +3080,12 @@ const ParsedAction = (props) => { } } - for (var key in workflow.actions) { + for (let [key,keyval] in workflow.actions.entries()) { if (workflow.actions[key].id === selectedAction.id) { continue } - for (var subkey in workflow.actions[key].parameters) { + for (let [subkey, subkeyval] in workflow.actions[key].parameters.entries()) { const param = workflow.actions[key].parameters[subkey]; if (!param.value.includes("$")) { continue @@ -3251,7 +3251,7 @@ const ParsedAction = (props) => { selectedAction.selectedAuthentication = {}; selectedAction.authentication_id = ""; - for (var key in selectedAction.parameters) { + for (let [key,keyval] in selectedAction.parameters.entries()) { //console.log(selectedAction.parameters[key]) if (selectedAction.parameters[key].configuration) { selectedAction.parameters[key].value = ""; @@ -3568,7 +3568,7 @@ const ParsedAction = (props) => { if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { var extraUrl = "" const descSplit = data.description.split("\n") - for (var line in descSplit) { + for (let [key,keyval] in descSplit.entries()) { if (descSplit[line].includes("http") && descSplit[line].includes("://")) { const urlsplit = descSplit[line].split("/") try { @@ -3629,7 +3629,7 @@ const ParsedAction = (props) => { renderInput={(params) => { if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { const prefixes = ["Post", "Put", "Patch"] - for (var key in prefixes) { + for (let [key,keyval] in prefixes.entries()) { if (params.inputProps.value.startsWith(prefixes[key])) { params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) if (params.inputProps.value.length > 1) { From 7e1cccf82012188a3f7abd2f69e7c7155825426c Mon Sep 17 00:00:00 2001 From: Dhaval Dave Date: Fri, 13 Jan 2023 05:35:19 +0000 Subject: [PATCH 004/122] fixed PrasedAction.jsx and few more fixes to ConfiguredWorkflow & Angularworkflow.jsx --- frontend/src/components/ParsedAction.jsx | 6 +-- frontend/src/views/AngularWorkflow.jsx | 4 +- frontend/src/views/AppCreator.jsx | 50 ++++++++++++------------ 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 2ac8aaed..7b89288c 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1472,7 +1472,7 @@ const ParsedAction = (props) => { openApiHelperText = "OpenAPI spec: fill the following fields."; //console.log("SHOULD ADD TO selectedActionParameters!: ", found, selectedActionParameters) var changed = false; - for (let [speckey,speckeyval] in found.entries()) { + for (let [specKey,specKeyVal] in found.entries()) { const tmpitem = found[specKey]; var skip = false; for (let [innerkey,innerkeyval] in selectedActionParameters.entries()) { @@ -2972,7 +2972,7 @@ const ParsedAction = (props) => { // Should make it a function lol if (workflow.branches !== undefined && workflow.branches !== null) { for (let [key,keyval] in workflow.branches.entries()) { - for (let [key,keyval] in workflow.branches[key].conditions.entries()) { + for (let [subkey,subkeyval] in workflow.branches[key].conditions.entries()) { const condition = workflow.branches[key].conditions[subkey] const sourceparam = condition.source const destinationparam = condition.destination @@ -3568,7 +3568,7 @@ const ParsedAction = (props) => { if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { var extraUrl = "" const descSplit = data.description.split("\n") - for (let [key,keyval] in descSplit.entries()) { + for (let [line,lineval] in descSplit.entries()) { if (descSplit[line].includes("http") && descSplit[line].includes("://")) { const urlsplit = descSplit[line].split("/") try { diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 027db155..6d45efa4 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1549,7 +1549,7 @@ const AngularWorkflow = (defaultprops) => { var item = tmpAuth[key]; const newfields = {}; - for (let [key,keyval] in item.fields.entries()) { + for (let [filterkey, filterkeyval] in item.fields.entries()) { newfields[item.fields[filterkey].key] = item.fields[filterkey].value; } @@ -2026,7 +2026,7 @@ const AngularWorkflow = (defaultprops) => { // Wait for new node to possibly be selected //setTimeout(() => { const typeIds = cy.elements('node:selected').jsons(); - for (let [key,keyval] in typeIds.entries()) { + for (let [idkey,idkeyval] in typeIds.entries()) { const item = typeIds[idkey] if (item.data.isButton === true) { //console.log("Reselect old node & return - or just return?") diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 81538b8f..a8da7482 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -456,7 +456,7 @@ const AppCreator = (defaultprops) => { } var newitem = data; - for (var key in paramsplit) { + for (let [key,keyval] in paramsplit.entries()) { var tmpparam = paramsplit[key]; if (tmpparam === "#") { continue; @@ -579,7 +579,7 @@ const AppCreator = (defaultprops) => { console.log("Tags: ", data.tags) if (data.tags !== undefined && data.tags.length > 0) { var newtags = []; - for (var key in data.tags) { + for (let [key,keyval] in data.tags.entries()) { if (data.tags[key].name.length > 50) { console.log( "Skipping tag because it's too long: ", @@ -685,7 +685,7 @@ const AppCreator = (defaultprops) => { const pathsplit = path.split("/"); var categoryindex = -1; // Stupid way of finding a category/grouping - for (var key in pathsplit) { + for (let [key,keyval] in pathsplit.entries()) { if (pathsplit[key].includes("_shuffle_replace_")) { const regex = /_shuffle_replace_\d/i; //console.log("NEW: ", @@ -750,9 +750,9 @@ const AppCreator = (defaultprops) => { } //console.log("Data: ", data) - for (var subkey in methodvalue["requestBody"]["content"][ + for (let [subkey,subkeyval] in methodvalue["requestBody"]["content"][ "application/json" - ]["schema"]["required"]) { + ]["schema"]["required"].entries()) { const tmpitem = methodvalue["requestBody"]["content"][ "application/json" @@ -771,7 +771,7 @@ const AppCreator = (defaultprops) => { ); var newbody = {}; // Can handle default, required, description and type - for (var propkey in retRef.properties) { + for (let [propkey,propkeyval] in retRef.properties.entries()) { console.log("replace: ", propkey) const parsedkey = propkey.replaceAll(" ", "_").toLowerCase(); @@ -810,9 +810,9 @@ const AppCreator = (defaultprops) => { tmpobject[prop] = `\$\{${prop}\}`; } - for (var subkey in methodvalue["requestBody"]["content"][ + for (let [subkey,subkeyval] in methodvalue["requestBody"]["content"][ "application/xml" - ]["schema"]["required"]) { + ]["schema"]["required"].entries()) { const tmpitem = methodvalue["requestBody"]["content"][ "application/xml" @@ -917,7 +917,7 @@ const AppCreator = (defaultprops) => { parameter["type"] === "object" ) { var newbody = {}; - for (var propkey in parameter.properties) { + for (let [propkey,propkeyval] in parameter.properties.entries()) { console.log("propkey2: ", propkey) const parsedkey = propkey.replaceAll(" ", "_").toLowerCase(); if (parameter.properties[propkey].type === undefined) { @@ -1046,7 +1046,7 @@ const AppCreator = (defaultprops) => { parameter["type"] === "object" ) { var newbody = {}; - for (var propkey in parameter.properties) { + for (let [propkey,propkeyval] in parameter.properties.entries()) { console.log("propkey3: ", propkey) const parsedkey = propkey.replaceAll(" ", "_").toLowerCase(); if ( @@ -1135,7 +1135,7 @@ const AppCreator = (defaultprops) => { parameter["type"] === "object" ) { var newbody = {}; - for (var propkey in parameter.properties) { + for (let [propkey,propkeyval] in parameter.properties.entries()) { console.log("propkey4: ", propkey) const parsedkey = propkey.replaceAll(" ", "_").toLowerCase(); if ( @@ -1219,7 +1219,7 @@ const AppCreator = (defaultprops) => { parameter["type"] === "object" ) { var newbody = {}; - for (var propkey in parameter.properties) { + for (let [propkey,keyval] in parameter.properties.entries()) { console.log("propkey5: ", propkey) const parsedkey = propkey .replaceAll(" ", "_") @@ -1287,7 +1287,7 @@ const AppCreator = (defaultprops) => { } } - for (var key in methodvalue.parameters) { + for (let [key,keyval] in methodvalue.parameters.entries()) { const parameter = handleGetRef(methodvalue.parameters[key], data); if (parameter.in === "query") { var tmpaction = { @@ -1334,7 +1334,7 @@ const AppCreator = (defaultprops) => { if (Object.getOwnPropertyNames(wordlist).length === 0) { for (let [newpath, pathvalue] of Object.entries(data.paths)) { const newpathsplit = newpath.split("/"); - for (var key in newpathsplit) { + for (let [key,keyval] in newpathsplit.entries()) { const pathitem = newpathsplit[key].toLowerCase(); if (wordlist[pathitem] === undefined) { wordlist[pathitem] = 1; @@ -1351,7 +1351,7 @@ const AppCreator = (defaultprops) => { const urlsplit = path.split("/"); if (urlsplit.length > 0) { var curname = ""; - for (var key in urlsplit) { + for (let [key,keyval] in urlsplit.entries()) { var subpath = urlsplit[key]; if (wordlist[subpath] > 2 || subpath.length < 1) { continue; @@ -1413,7 +1413,7 @@ const AppCreator = (defaultprops) => { const regex = /{\w+}/g; const found = firstUrl.match(regex); if (found !== null) { - for (var key in found) { + for (let [key,keyval] in found.entries()) { const item = found[key].slice(1, found[key].length - 1); const foundVar = data.servers[0].variables[item]; if (foundVar["default"] !== undefined) { @@ -1683,7 +1683,7 @@ const AppCreator = (defaultprops) => { if (newWorkflowTags.length > 0) { var newtags = []; - for (var key in newWorkflowTags) { + for (let [key,keyval] in newWorkflowTags.entries()) { newtags.push({ name: newWorkflowTags[key] }); } @@ -1696,7 +1696,7 @@ const AppCreator = (defaultprops) => { // Handles actions var handledPaths = [] - for (var key in actions) { + for (let [key,keyval] in actions.entries()) { var item = JSON.parse(JSON.stringify(actions[key])) if (item.errors.length > 0) { alert.error("Saving with error in action " + item.name); @@ -1712,7 +1712,7 @@ const AppCreator = (defaultprops) => { if (handledPaths.includes(pathjoin)) { // Max 100 of same lol - for (var i = 0; i < 100; i++) { + for (let i = 0; i < 100; i++) { item.url = item.url+"_shuffle_replace_"+i pathjoin = item.url+"_"+item.method.toLowerCase() @@ -1822,7 +1822,7 @@ const AppCreator = (defaultprops) => { if (item.queries.length > 0) { var skipped = false; var querynames = [] - for (var querykey in item.queries) { + for (let [querykey,querykeyval] in item.queries.entries()) { const queryitem = item.queries[querykey]; if (queryitem === undefined || queryitem === null || queryitem.name === undefined || queryitem.name === null || queryitem.name === "") { @@ -1923,7 +1923,7 @@ const AppCreator = (defaultprops) => { //data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) if (item.paths.length > 0) { - for (querykey in item.paths) { + for (let [querykey,querykeyval] in item.paths.entries()) { const queryitem = item.paths[querykey]; if (queryitem.toLowerCase() == "url") { @@ -1958,7 +1958,7 @@ const AppCreator = (defaultprops) => { const values = getCurrentPaths(item.url); const paths = values[0]; - for (querykey in paths) { + for (let [querykey,querykeyval] in paths.entries()) { const queryitem = paths[querykey]; newitem = { in: "path", @@ -1996,7 +1996,7 @@ const AppCreator = (defaultprops) => { // but it's the only way we can properly support e.g. GraphQL // with good examples var newbody = "" - for (var key in item.body) { + for (let [key,keyval] in item.body.entries()) { if (item.body[key] === "$") { if (key > 0) { //console.log("Found: ", item.body[key-1]) @@ -2118,9 +2118,9 @@ const AppCreator = (defaultprops) => { const required = false; const headersSplit = item.headers.split("\n"); - for (var key in headersSplit) { + for (let [key,keyval] in headersSplit.entries()) { const header = headersSplit[key]; - var key = ""; + // var key = ""; var value = ""; if (header.length > 0 && header.includes("= ")) { const headersplit = header.split("= "); From 78d62789cc3e0fe93421c4915c6c81186b90eb6e Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:30:46 +0100 Subject: [PATCH 005/122] Fixed minor issue with app sdk --- .env | 1 + backend/app_sdk/app_base.py | 65 ++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/.env b/.env index 307c9d35..023a973f 100644 --- a/.env +++ b/.env @@ -63,6 +63,7 @@ SHUFFLE_ELASTIC=true SHUFFLE_LOGS_DISABLED=false SHUFFLE_CHAT_DISABLED=false # Controls support chat SHUFFLE_RERUN_SCHEDULE=300 +SHUFFLE_DISABLE_RERUN_AND_ABORT=false # DATABASE CONFIGURATIONS DATASTORE_EMULATOR_HOST=shuffle-database:8000 diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 2f23755f..8af1f9a1 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -327,7 +327,8 @@ class AppBase: new_input = fixed_return except Exception as e: - self.logger.info(f"[ERROR] Failed to run magic parser (2): {e}") + # Not used anymore + #self.logger.info(f"[ERROR] Failed to run magic parser (2): {e}") return input_data try: @@ -401,15 +402,6 @@ class AppBase: # FIXME: Add cleanup of parameters to not send to frontend here params = {} - #action = action_result["action"] - #try: - # for item in action["authentication"]: - # for action["parameters"] - # self.logger.info("AUTH: ", key, value) - # params[item["key"]] = item["value"] - #except KeyError: - # self.logger.info("No authentication specified!") - # pass # I wonder if this actually works self.logger.info(f"[DEBUG] Before last stream result") @@ -448,7 +440,7 @@ class AppBase: try: ret = requests.post(url, headers=headers, json=action_result, timeout=10) - self.logger.info(f"[DEBUG] Result: {ret.status_code} (break on 200)") + self.logger.info(f"[DEBUG] Result: {ret.status_code} (break on 200 or 201)") if ret.status_code == 200 or ret.status_code == 201: finished = True break @@ -1015,6 +1007,7 @@ class AppBase: # An attempt at decomposing coroutine results + # Backwards compatibility try: if asyncio.iscoroutine(tmp): self.logger.info("[DEBUG] In coroutine (2)") @@ -1028,7 +1021,8 @@ class AppBase: tmp = asyncio.run(parse_value(tmp)) else: - self.logger.info("[DEBUG] Not in coroutine (2)") + #self.logger.info("[DEBUG] Not in coroutine (2)") + pass except Exception as e: self.logger.warning("[ERROR] Failed to parse coroutine value for old app: {e}") @@ -1448,7 +1442,7 @@ class AppBase: failed = False break - elif ret.status_code == 500: + elif ret.status_code == 500 or ret.status_code == 400: self.logger.info("[ERROR] (fails: %d) Error in app with status code %d for results (1). RETRYING because results can't be handled" % (i+1, ret.status_code)) rettext = ret.text @@ -1522,7 +1516,7 @@ class AppBase: except Exception as e: self.logger.info(f"[WARNING] Failed in replace params action parsing: {e}") - self.logger.info("[DEBUG] AFTER FULLEXEC stream result (init)") + self.logger.info(f"[DEBUG] AFTER FULLEXEC stream result (init): {self.current_execution_id}") # Gets the value at the parenthesis level you want def parse_nested_param(string, level): @@ -1708,7 +1702,7 @@ class AppBase: # Because liquid can handle ALL of this now. # Implemented for >0.9.25 - self.logger.info("[DEBUG] Skipping parser because use of its been deprecated >0.9.25 due to Liquid implementation") + #self.logger.info("[DEBUG] Skipping parser because use of its been deprecated >0.9.25 due to Liquid implementation") return data, False wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length", "lenght", "join", "replace"] @@ -2409,9 +2403,11 @@ class AppBase: newvalue[key] = recurse_cleanup_script(value) except json.decoder.JSONDecodeError as e: - print(f"[WARNING] Failed JSON replacement for OpenAPI keys (3) {e}") + # Since here the data isn't at all JSON compatible..? + # Seems to happen with newlines in variables being parsed in as strings? + print(f"[ERROR] Failed JSON replacement for OpenAPI keys (3) {e}. Value: {data}") except Exception as e: - print(f"[WARNING] Failed as an exception (1): {e}") + print(f"[ERROR] Failed as an exception (1): {e}") try: for deletekey in deletekeys: @@ -2476,9 +2472,17 @@ class AppBase: except: self.logger.info("Error in initial replacement of escaped dollar!") - # Basic fix in case variant isn't set + paramname = "" try: - self.logger.info(f"[DEBUG] Parameter variant: {parameter['variant']} of length {len(parameter['value'])}") + paramname = parameter["name"] + except: + pass + + # Basic fix in case variant isn't set + # Variant is ALWAYS STATIC_VALUE from mid 2021~ + try: + self.logger.info(f"[DEBUG] Parameter '{paramname}' of length {len(parameter['value'])}") + parameter["variant"] = parameter["variant"] except: parameter["variant"] = "STATIC_VALUE" @@ -2904,7 +2908,8 @@ class AppBase: if " " in actionname: actionname.replace(" ", "_", -1) - #print(action) + #print("ACTION: ", action) + #print("exec: ", self.full_execution) #if action.generated: # actionname = actionname.lower() @@ -2931,12 +2936,14 @@ class AppBase: # What variables are necessary here tho hmm params = {} + # This replacement should happen in backend as part of params + # error log is useless try: for item in action["authentication"]: - #self.logger.info("AUTH: ", key, value) - params[item["key"]] = item["value"] - except KeyError: - self.logger.info("[DEBUG] No authentication specified!") + self.logger.info("AUTH PARAM: ", key, value) + #params[item["key"]] = item["value"] + except KeyError as e: + self.logger.info(f"[WARNING] No authentication specified! Is this correct? err: {e}") # Fixes OpenAPI body parameters for later. newparams = [] @@ -3007,7 +3014,7 @@ class AppBase: pass - self.logger.info(f"""HANDLING {action["parameters"][counter]["value"]}""") + self.logger.info(f"""HANDLING BODY: {action["parameters"][counter]["value"]}""") action["parameters"][counter]["value"] = recurse_cleanup_script(action["parameters"][counter]["value"]) #self.logger.info(action["parameters"]) @@ -3442,7 +3449,7 @@ class AppBase: errorstring = f"{e}" if "the JSON object must be" in errorstring: - self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (0)?") + self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (0)? the JSON object must be in...") try: e = json.loads(f"{e}") except: @@ -3472,7 +3479,7 @@ class AppBase: }) break except Exception as e: - self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (1)?") + self.logger.info(f"[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (1)? err: {e}") try: e = json.loads(f"{e}") @@ -3499,7 +3506,8 @@ class AppBase: newres = asyncio.run(parse_value(newres)) else: - self.logger.info("[DEBUG] Not in coroutine (1)") + #self.logger.info("[DEBUG] Not in coroutine (1)") + pass except Exception as e: self.logger.warning("[ERROR] Failed to parse coroutine value for old app: {e}") @@ -3712,7 +3720,6 @@ class AppBase: def execute(): if request.method == "POST": #print(request.get_json(force=True)) - #print("DATA: ", request.data) requestdata = {} try: requestdata = json.loads(request.data) From ea72d9f41c006487dbe307330e65ca1aa531c0a7 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:36:40 +0100 Subject: [PATCH 006/122] Fixed searchbox z-index issue --- frontend/src/components/Appsearch.jsx | 58 ++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx index 0573883d..9f219722 100644 --- a/frontend/src/components/Appsearch.jsx +++ b/frontend/src/components/Appsearch.jsx @@ -1,9 +1,8 @@ import React, { useState, useEffect } from 'react'; - import ReactGA from 'react-ga'; import { useTheme } from '@material-ui/core/styles'; import {Link} from 'react-router-dom'; - +import { useAlert } from "react-alert"; import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; //import algoliasearch from 'algoliasearch/lite'; @@ -11,13 +10,12 @@ import algoliasearch from 'algoliasearch'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core'; import aa from 'search-insights' - const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const Appsearch = props => { - const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, } = props + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList} = props const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - + const alert = useAlert(); const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs const theme = useTheme(); @@ -27,13 +25,51 @@ const Appsearch = props => { const [message, setMessage] = React.useState(""); const [formMessage, setFormMessage] = React.useState(""); const [selectedApp, setSelectedApp] = React.useState({}); - const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,} const innerColor = "rgba(255,255,255,0.65)" const borderRadius = 3 window.title = "Shuffle | Apps | Find and integration any app" + const setUserSpecialzedApp = (user, data) => { + // var data = newfields] + console.log("data value", data) + const appData = {"user_id":user,"specialized_apps":[{}]} + console.log("User Check for appdata:", user) + appData["specialized_apps"][0]["name"] = data["name"] + appData["specialized_apps"][0]["image"] = data["image_url"] + appData["specialized_apps"][0]["category"] = data["categories"].toString() + console.log("AppData:",appData) + console.log("setActionImageList",setActionImageList) + console.log("actionImageList",actionImageList) + + const finalData = actionImageList.concat(appData["specialized_apps"]) + appData["specialized_apps"]=finalData + fetch(globalUrl + "/api/v1/users/updateuser", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(appData), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for set creator :O!"); + } + alert.success("Sucessfully updated specialzed app.") + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success && responseJson.reason !== undefined) { + alert.error("Failed updating user: " + responseJson.reason); + } + }) + .catch((error) => { + console.log(error); + }); + }; const submitContact = (email, message) => { const data = { "firstname": "", @@ -170,6 +206,16 @@ const Appsearch = props => { }} onMouseOut={() => { setMouseHoverIndex(-1) }} onClick={() => { + if(isCreatorPage === true){ + console.log("data:",data) + console.log("userdata.id",userdata.id) + console.log("is creator", isCreatorPage) + if (setNewSelectedApp !== undefined) { + // setUserSpecialzedApp = data + setUserSpecialzedApp(userdata.id, data) + //setActionImageList(userdata.id, data) + } + } if (setNewSelectedApp !== undefined) { setNewSelectedApp(data) } From 44ca758230700e739f30895ebf2dab18968082bd Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:36:46 +0100 Subject: [PATCH 007/122] Fixed max creators to get --- frontend/src/components/CreatorGrid.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx index 66d852c5..f09f84a0 100644 --- a/frontend/src/components/CreatorGrid.jsx +++ b/frontend/src/components/CreatorGrid.jsx @@ -239,7 +239,7 @@ const CreatorGrid = props => {
- + {showSuggestion === true ?
From d9d9ee22ef96d084142cebbe7b3dbf6b4e49954f Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:37:06 +0100 Subject: [PATCH 008/122] Fixed autocomplete of more oauth2 apps --- frontend/src/components/Oauth2Auth.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index fcb03f23..dd1870d9 100644 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -69,6 +69,9 @@ const registeredApps = [ "todoist", "microsoft_sentinel", "microsoft_365_defender", + "google_sheets", + "google_drive", + "google_disk", ] const AuthenticationOauth2 = (props) => { @@ -220,6 +223,22 @@ const AuthenticationOauth2 = (props) => { ["SecurityEvents.ReadWrite.All"], admin_consent, ) + } else if (selectedApp.name.toLowerCase().includes("google_sheets")) { + handleOauth2Request( + "253565968129-mppu17aciek8slr3kpgnb37hp86dmvmb.apps.googleusercontent.com", + "", + "https://sheets.googleapis.com", + ["https://www.googleapis.com/auth/spreadsheets"], + admin_consent, + ) + } else if (selectedApp.name.toLowerCase().includes("google_drive")) { + handleOauth2Request( + "253565968129-6pij4g6ojim4gpum0h9m9u3bc357qsq7.apps.googleusercontent.com", + "", + "https://www.googleapis.com/drive/v3", + ["https://www.googleapis.com/auth/drive"], + admin_consent, + ) } } From a89c6bb9e3a6525f6f487a6ef9f8f85faba94388 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:37:43 +0100 Subject: [PATCH 009/122] Fixed bug from loading undefined field --- frontend/src/components/ParsedAction.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 455c0e57..b9ded7f8 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1911,7 +1911,7 @@ const ParsedAction = (props) => { // Basic helpertext - if (data.name.toLowerCase() === "file_category") { + if (files !== undefined && files !== null && data.name.toLowerCase() === "file_category") { //selectedActionParameters[count].options.length > 0 console.log("FileS: ", files) if (files.namespaces !== undefined && files.namespaces !== null && files.namespaces.length > 0) { From 37efb56eee807d39c5da57f78e6b85955171ed38 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:37:56 +0100 Subject: [PATCH 010/122] Fixed searchbar z-index --- frontend/src/components/Searchfield.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Searchfield.js b/frontend/src/components/Searchfield.js index a15ff5f3..43fa7934 100644 --- a/frontend/src/components/Searchfield.js +++ b/frontend/src/components/Searchfield.js @@ -625,7 +625,7 @@ const SearchField = props => { const CustomDocHits = connectHits(DocHits) return ( -
+
{ console.log("CLICKED") }}> From 543fe7f59df4795b09bd4240f70f5bc95d9a339e Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:38:12 +0100 Subject: [PATCH 011/122] Improved workflow suggestions to match emails --- frontend/src/components/SuggestedWorkflows.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/components/SuggestedWorkflows.jsx b/frontend/src/components/SuggestedWorkflows.jsx index 94898915..eac3dc3e 100644 --- a/frontend/src/components/SuggestedWorkflows.jsx +++ b/frontend/src/components/SuggestedWorkflows.jsx @@ -83,6 +83,12 @@ const SuggestedWorkflows = (props) => { dstimage = usecase.items[2].app } + console.log(srcimage, dstimage, usecase.items) + if (srcimage === undefined || dstimage === undefined) { + console.log("Error in src or dst: returning!") + return null + } + const finished = finishedUsecases.includes(usecasename) const selectedIcon = finished ? : From 77d86f0ebaae116575897d567bf68d0240830c05 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:38:29 +0100 Subject: [PATCH 012/122] Improved usecase generator --- frontend/src/components/UsecaseSearch.jsx | 353 ++++++++++++---------- 1 file changed, 185 insertions(+), 168 deletions(-) diff --git a/frontend/src/components/UsecaseSearch.jsx b/frontend/src/components/UsecaseSearch.jsx index e9edb0f4..d5d3acf2 100644 --- a/frontend/src/components/UsecaseSearch.jsx +++ b/frontend/src/components/UsecaseSearch.jsx @@ -27,7 +27,7 @@ import { Divider, } from "@material-ui/core"; -const defaultValue = {"id": "", "name": "", +const defaultValue = {"id": "", "name": "Build your own", "source": {"text": "No trigger selected", "error": ""}, "destination": {"text": "No subflow selected", "error": ""}, "middle": [] @@ -284,7 +284,7 @@ export const subflows = [ ] const UsecaseSearch = (props) => { - const { defaultSearch, appFramework, globalUrl, showTitle, canExpand, apps, setFoundWorkflowId, getFramework, userdata, usecaseSearch, setUsecaseSearch, autotry, setCloseWindow, } = props + const { defaultSearch, appFramework, globalUrl, showTitle, canExpand, apps, setFoundWorkflowId, getFramework, userdata, usecaseSearch, setUsecaseSearch, autotry, setCloseWindow, sourceapp, destinationapp} = props const [allusecases, setAllUsecases] = React.useState([ @@ -371,21 +371,18 @@ const UsecaseSearch = (props) => { setUpdate(Math.random()) } } else { - console.log("NOT FOUND FOR: ", defaultSearch) setAllUsecases([JSON.parse(JSON.stringify(defaultValue))]) setUpdate(Math.random()) } } if (defaultSearch !== undefined && defaultSearch !== null && defaultSearch !== searchType) { - console.log("Setting searchtype to", defaultSearch) - - setSearchType(defaultSearch) setUsecaseIndex(0) rerunWorkflowCheck(defaultSearch, usecaseSearch) } + //if (defaultSearch !== undefined && defaultSearch.length > 0 && allusecases[usecaseIndex].name === "") { // rerunWorkflowCheck() //} @@ -442,10 +439,10 @@ const UsecaseSearch = (props) => { var usecases = JSON.parse(JSON.stringify(allusecases)) const imagestyle = { - height: 50, - width: 50, + height: 40, + width: 40, borderRadius: 25, - border: "1px solid rgba(255,255,255,0.3)", + border: "1px solid rgba(255,255,255,0.7)", cursor: "pointer", } @@ -869,8 +866,8 @@ const UsecaseSearch = (props) => { } const mergeWorkflowUsecases = (usecasedata) => { - //const url = `${globalUrl}/api/v1/workflows/merge`; - const url = `https://shuffler.io/api/v1/workflows/merge`; + // To properly handle multiple-locations + const url = isCloud ? `${globalUrl}/api/v1/workflows/merge` : `https://shuffler.io/api/v1/workflows/merge`; fetch(url, { mode: "cors", method: "POST", @@ -981,7 +978,6 @@ const UsecaseSearch = (props) => { } else if (isCloud) { if (responseJson.workflow_id !== null && responseJson.workflow_id !== undefined) { if (responseJson.added_auth !== undefined && responseJson.added_auth !== null && responseJson.added_auth.length > 0) { - console.log("SHOULD HANDLE AUTH: ", responseJson.added_auth) setConfigureWorkflowAuth(responseJson.added_auth) if (setFoundWorkflowId !== undefined) { @@ -1021,7 +1017,11 @@ const UsecaseSearch = (props) => { }); } - //console.log("Filled in: ", appFramework, usecases) + // Autosearch in framework & set it? + if (sourceapp !== undefined && destinationapp !== undefined) { + console.log("FIND APPS", sourceapp, destinationapp) + } + if (appFramework !== undefined && usecases !== undefined) { for (var key in usecases) { @@ -1180,10 +1180,10 @@ const UsecaseSearch = (props) => { mergeWorkflowUsecases(data) } - if (autotry === true && firstRequest === true) { - console.log("Should autotry the usecase! Make sure data is in order first. Usecase: ", usecases) + if (autotry === true && firstRequest === true && appFramework !== undefined && usecases !== undefined && isUploading === false) { + console.log("Should autotry the usecase! Make sure data is in order first. Usecase: ", usecases) - if (usecases !== undefined && usecases !== null && usecases.length > 0 && usecases[0].name !== "") { + if (usecases !== undefined && usecases !== null && usecases.length > 0 && usecases[0].name !== "Build your own" && usecases[0].name !== "") { setFirstRequest(false) createWorkflowFromTemplate(usecases[0]) } @@ -1332,44 +1332,38 @@ const UsecaseSearch = (props) => { const looplist = type === "source" ? triggerlist : type === "destination" ? subflows : midflows const hasError = data.error !== undefined && data.error !== null && data.error.length > 0 const borderColor = hasError ? theme.palette.primary.main : "rgba(255,255,255,0.3)" + const startText = miditem ? "" : type === "source" ? "When" : "Then" + + if (data.text.includes(startText)) { + data.text = data.text.replace(startText, "") + } return ( -
+
+ + {startText} + +
- {selectionOpen === true ? - - : null} + {selectionOpen === true ? + + : null} -
-
- - {data.app_name} { +
+
+ + {data.app_name} { - if (data.type === undefined || data.type === null || data.type === "") { - setexpanded(true) - console.log("No type. Skipping window open.") - return - } - - changeAppType() - }}/> - -
- - {data.text} - - {hasError ? - { if (data.type === undefined || data.type === null || data.type === "") { setexpanded(true) console.log("No type. Skipping window open.") @@ -1377,140 +1371,161 @@ const UsecaseSearch = (props) => { } changeAppType() - }}> - {data.error} + }}/> + +
+ + {data.text} - : - null} + {hasError ? + { + if (data.type === undefined || data.type === null || data.type === "") { + setexpanded(true) + console.log("No type. Skipping window open.") + return + } + + changeAppType() + }}> + {data.error} + + : + null} +
+
+
+ + + { + }} + > + + + + + + + + + {type === "middle" ? + { + allusecases[index][type] = [] + + setAllUsecases(allusecases) + setUpdate(Math.random()) + }} + style={{marginTop: 0, paddingTop: 8,}} + > + + + : null} + + + { + setexpanded(!expanded) + }} + > + {expanded ? : } + + +
-
- - - { - }} - > - - - - - - - - - {type === "middle" ? - { - allusecases[index][type] = [] + + {expanded ? + looplist.map((subdata, curindex) => { + if (appFramework === undefined) { + subdata.image = theme.palette.defaultImage + } else { + const srctype = getType(subdata.type) + const srcinfo = appFramework[srctype] + + if (srcinfo !== undefined && srcinfo.large_image !== undefined && srcinfo.large_image !== "" && (subdata.app_name === undefined || subdata.app_name === "")) { + subdata.image = srcinfo.large_image + subdata.app_id = srcinfo.id + subdata.app_name = srcinfo.name + } else { + subdata.image = theme.palette.defaultImage + } + } + + return ( +
{ + if (subdata.disabled === true) { + //alert.info("Usecase not available yet.") + return + } + + if (type === "middle") { + allusecases[index][type][subindex] = subdata + } else { + allusecases[index][type] = subdata + } setAllUsecases(allusecases) setUpdate(Math.random()) - }} - > - - - : null} - - - { - setexpanded(!expanded) - }} - > - {expanded ? : } - - - -
+ setexpanded(false) + }}> + {subdata.app_name} { + }}/> + + {subdata.text} + +
+ ) + }) + : + null + }
- - {expanded ? - looplist.map((subdata, curindex) => { - if (appFramework === undefined) { - subdata.image = theme.palette.defaultImage - } else { - const srctype = getType(subdata.type) - const srcinfo = appFramework[srctype] - - if (srcinfo !== undefined && srcinfo.large_image !== undefined && srcinfo.large_image !== "" && (subdata.app_name === undefined || subdata.app_name === "")) { - subdata.image = srcinfo.large_image - subdata.app_id = srcinfo.id - subdata.app_name = srcinfo.name - } else { - subdata.image = theme.palette.defaultImage - } - } - - return ( -
{ - if (subdata.disabled === true) { - //alert.info("Usecase not available yet.") - return - } - - if (type === "middle") { - allusecases[index][type][subindex] = subdata - } else { - allusecases[index][type] = subdata - } - - setAllUsecases(allusecases) - setUpdate(Math.random()) - setexpanded(false) - }}> - {subdata.app_name} { - }}/> - - {subdata.text} - -
- ) - }) - : - null - }
) } //console.log("ALLUSECASES: ", allusecases) + // {defaultSearch}: {allusecases[usecaseIndex].name} + //console.log("UseCase: ", usecases) return ( -
+
{configureWorkflowModal} {authenticationModal} {showTitle !== false && defaultSearch !== undefined ? - {defaultSearch}: {allusecases[usecaseIndex].name} + {allusecases[usecaseIndex].name} : null} {usecases.map((data, index) => { return ( -
+
{data.middle !== undefined && data.middle !== null && data.middle.length > 0 ? @@ -1551,19 +1566,17 @@ const UsecaseSearch = (props) => { {showTitle !== false && workflow.id === undefined ? : null} @@ -1576,9 +1589,13 @@ const UsecaseSearch = (props) => { target="_blank" style={{ textDecoration: "none", }} > - } From b90a489d74958147262123930df164b640fc5272 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 16 Jan 2023 00:38:51 +0100 Subject: [PATCH 013/122] Tiny bugfix in admin panel --- frontend/src/views/Admin.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index b6284821..08798cbb 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -73,7 +73,7 @@ import { import { useAlert } from "react-alert"; import Dropzone from "../components/Dropzone"; -import HandlePayment from "./HandlePayment"; +import HandlePaymentNew from "./HandlePaymentNew"; import OrgHeader from "../components/OrgHeader.jsx"; import { display, style } from "@mui/system"; @@ -3244,7 +3244,7 @@ const Admin = (props) => { )}
- Date: Mon, 16 Jan 2023 00:41:58 +0100 Subject: [PATCH 014/122] Minor fixes to bad app viewing --- frontend/src/views/AngularWorkflow.jsx | 10573 ++++++++++++----------- 1 file changed, 5315 insertions(+), 5258 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 638234d5..43571456 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -13,7 +13,7 @@ import NestedMenuItem from "material-ui-nested-menu-item"; import ReactMarkdown from "react-markdown"; import { useAlert } from "react-alert"; import theme from '../theme'; -import { isMobile } from "react-device-detect" +import { isMobile } from "react-device-detect" import aa from 'search-insights' import Drift from "react-driftjs"; @@ -25,9 +25,9 @@ import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from ' import algoliasearch from 'algoliasearch/lite'; import { - Zoom, - Avatar, - Popover, + Zoom, + Avatar, + Popover, TextField, Drawer, Button, @@ -50,31 +50,31 @@ import { IconButton, Menu, Input, - Fade, + Fade, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, - SwipeableDrawer, + SwipeableDrawer, Switch, - Chip, - Card, - List, - ListItem, - ListItemText, - ListItemAvatar, - Badge, + Chip, + Card, + List, + ListItem, + ListItemText, + ListItemAvatar, + Badge, } from "@material-ui/core"; import { - AvatarGroup, + AvatarGroup, } from "@mui/material" import { - Folder as FolderIcon, - LibraryBooks as LibraryBooksIcon, + Folder as FolderIcon, + LibraryBooks as LibraryBooksIcon, OpenInNew as OpenInNewIcon, Undo as UndoIcon, GetApp as GetAppIcon, @@ -105,12 +105,12 @@ import { ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon, AddComment as AddCommentIcon, - Edit as EditIcon, + Edit as EditIcon, } from "@material-ui/icons"; import { - Preview as PreviewIcon, - ContentCopy as ContentCopyIcon, + Preview as PreviewIcon, + ContentCopy as ContentCopyIcon, } from '@mui/icons-material'; import Autocomplete from "@material-ui/lab/Autocomplete"; @@ -132,8 +132,9 @@ import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; import ParsedAction from "../components/ParsedAction.jsx"; import PaperComponent from "../components/PaperComponent.jsx" -import ExtraApps from "../components/ExtraApps.jsx" -import EditWorkflow from "../components/EditWorkflow.jsx" +import ExtraApps from "../components/ExtraApps.jsx" +import EditWorkflow from "../components/EditWorkflow.jsx" +import AppStats from "../components/AppStats.jsx"; const surfaceColor = "#27292D"; const inputColor = "#383B40"; @@ -250,15 +251,14 @@ const svgSize = 24; const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const AngularWorkflow = (defaultprops) => { - const { globalUrl, isLoggedIn, isLoaded, userdata } = defaultprops; + const { globalUrl, isLoggedIn, isLoaded, userdata, data_id } = defaultprops; const referenceUrl = globalUrl + "/api/v1/hooks/"; const alert = useAlert() - let navigate = useNavigate(); - - const params = useParams(); - var props = JSON.parse(JSON.stringify(defaultprops)) - props.match = {} - props.match.params = params + let navigate = useNavigate(); + const params = useParams(); + var props = JSON.parse(JSON.stringify(defaultprops)) + props.match = {} + props.match.params = params const green = "#86c142"; const yellow = "#FECC00"; @@ -286,10 +286,10 @@ const AngularWorkflow = (defaultprops) => { const [creatorProfile, setCreatorProfile] = React.useState({}); const [usecases, setUsecases] = React.useState([]); const [files, setFiles] = React.useState({ - "namespaces": [ - "default", - ] - }); + "namespaces": [ + "default", + ] + }); const [appGroup, setAppGroup] = React.useState([]); const [triggerGroup, setTriggerGroup] = React.useState([]); const [executionText, setExecutionText] = React.useState(""); @@ -303,7 +303,7 @@ const AngularWorkflow = (defaultprops) => { const [history, setHistory] = React.useState([]); const [historyIndex, setHistoryIndex] = React.useState(history.length); - const [variableInfo, setVariableInfo] = React.useState({}) + const [variableInfo, setVariableInfo] = React.useState({}) const [appAuthentication, setAppAuthentication] = React.useState([]); const [variablesModalOpen, setVariablesModalOpen] = React.useState(false); @@ -379,10 +379,11 @@ const AngularWorkflow = (defaultprops) => { const [executionData, setExecutionData] = React.useState({}); const [appsLoaded, setAppsLoaded] = React.useState(false); const [showVideo, setShowVideo] = React.useState(""); - const [editWorkflowModalOpen, setEditWorkflowModalOpen] = React.useState(false); + const [editWorkflowModalOpen, setEditWorkflowModalOpen] = React.useState(false); const [userediting, setUserediting] = React.useState(false) const [lastSaved, setLastSaved] = React.useState(true); + const [selectionOpen, setSelectionOpen] = React.useState(false); // eslint-disable-next-line no-unused-vars const [_, setUpdate] = useState(""); // Used for rendring, don't remove @@ -394,8 +395,8 @@ const AngularWorkflow = (defaultprops) => { // Use states and don't update lol const cloudSyncEnabled = props.userdata !== undefined && - props.userdata.active_org !== null && - props.userdata.active_org !== undefined + props.userdata.active_org !== null && + props.userdata.active_org !== undefined ? props.userdata.active_org.cloud_sync === true : false; const isCloud = @@ -407,8 +408,8 @@ const AngularWorkflow = (defaultprops) => { const classes = useStyles(); const [bodyWidth, bodyHeight] = useWindowSize() - //console.log("Mobile: ", isMobile, bodyWidth, bodyHeight) - const cytoscapeWidth = isMobile ? bodyWidth - leftBarSize : bodyWidth - leftBarSize - 25 + //console.log("Mobile: ", isMobile, bodyWidth, bodyHeight) + const cytoscapeWidth = isMobile ? bodyWidth - leftBarSize : bodyWidth - leftBarSize - 25 const [elements, setElements] = useState([]); @@ -439,16 +440,16 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .then((responseJson) => { - if (responseJson.success === true) { - if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) { - if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) { - selectedApp.documentation = responseJson.reason - setSelectedApp(selectedApp) - setUpdate(Math.random()) - } - } - } - + if (responseJson.success === true) { + if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) { + if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) { + selectedApp.documentation = responseJson.reason + setSelectedApp(selectedApp) + setUpdate(Math.random()) + } + } + } + }) .catch((error) => { alert.error(error.toString()); @@ -456,19 +457,19 @@ const AngularWorkflow = (defaultprops) => { }; useEffect(() => { - if (authenticationModalOpen === true && selectedAction.app_name !== undefined) { - console.log(`Should get app docs for: ${selectedAction.app_name}`) - //console.log(selectedAction) - //console.log("APP: ", selectedApp) + if (authenticationModalOpen === true && selectedAction.app_name !== undefined) { + console.log(`Should get app docs for: ${selectedAction.app_name}`) + //console.log(selectedAction) + //console.log("APP: ", selectedApp) - if (selectedAction.documentation === undefined || selectedAction.documentation === null || selectedAction.documentation.length === 0) { - // SelectedApp.documentation = Markdown? If so, it works - // - const apptype = selectedApp.generated === false ? "python" : "openapi" - getAppDocs(selectedAction.app_name, apptype, selectedAction.app_version) - } - } - }, [authenticationModalOpen]) + if (selectedAction.documentation === undefined || selectedAction.documentation === null || selectedAction.documentation.length === 0) { + // SelectedApp.documentation = Markdown? If so, it works + // + const apptype = selectedApp.generated === false ? "python" : "openapi" + getAppDocs(selectedAction.app_name, apptype, selectedAction.app_version) + } + } + }, [authenticationModalOpen]) const getAvailableWorkflows = (trigger_index) => { fetch(globalUrl + "/api/v1/workflows", { @@ -492,7 +493,7 @@ const AngularWorkflow = (defaultprops) => { // Sets up subflow trigger with the right info if (trigger_index > -1) { - var baseSubflow = {} + var baseSubflow = {} const trigger = workflow.triggers[trigger_index]; if (trigger.parameters.length >= 3) { for (var key in trigger.parameters) { @@ -501,13 +502,13 @@ const AngularWorkflow = (defaultprops) => { if (param.name === "workflow") { if (param.value === workflow.id) { setSubworkflow(workflow); - baseSubflow = workflow + baseSubflow = workflow } else { const sub = responseJson.find( (data) => data.id === param.value ); if (sub !== undefined && subworkflow.id !== sub.id) { - baseSubflow = sub + baseSubflow = sub setSubworkflow(sub); } } @@ -518,14 +519,14 @@ const AngularWorkflow = (defaultprops) => { param.value !== undefined && param.value !== null ) { - if (Object.getOwnPropertyNames(baseSubflow).length > 0) { - const foundAction = baseSubflow.actions.find(action => action.id === param.value) - if (foundAction !== null && foundAction !== undefined) { - setSubworkflowStartnode(foundAction); - } - } else { - setSubworkflowStartnode(param.value); - } + if (Object.getOwnPropertyNames(baseSubflow).length > 0) { + const foundAction = baseSubflow.actions.find(action => action.id === param.value) + if (foundAction !== null && foundAction !== undefined) { + setSubworkflowStartnode(foundAction); + } + } else { + setSubworkflowStartnode(param.value); + } } } } @@ -644,17 +645,17 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if ( - responseJson.success === true && + responseJson.success === true && (responseJson.apikey === undefined || - responseJson.apikey.length === 0 || - responseJson.apikey === null) + responseJson.apikey.length === 0 || + responseJson.apikey === null) ) { generateApikey(); } - if (responseJson.success === true) { - setUserSettings(responseJson) - } + if (responseJson.success === true) { + setUserSettings(responseJson) + } }) .catch((error) => { console.log("Settings error: ", error); @@ -750,27 +751,27 @@ const AngularWorkflow = (defaultprops) => { }); const newitem = removeParam("execution_id", cursearch); - navigate(curpath + newitem) + navigate(curpath + newitem) //props.history.push(curpath + newitem); } else { - console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView) + console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView) - //setExecutionRequestStarted(true); - const cur_execution = { - execution_id: tmpView, - //authorization: data.authorization, + //setExecutionRequestStarted(true); + const cur_execution = { + execution_id: tmpView, + //authorization: data.authorization, } setExecutionModalView(1); setExecutionRequest(cur_execution); - start(); + start(); const newitem = removeParam("execution_id", cursearch); - navigate(curpath + newitem) + navigate(curpath + newitem) - setTimeout(() => { - stop() - }, 5000); - } + setTimeout(() => { + stop() + }, 5000); + } } } }) @@ -799,7 +800,7 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .then((responseJson) => { - //console.log("RESPONSE: ", responseJson) + //console.log("RESPONSE: ", responseJson) handleUpdateResults(responseJson, executionRequest); }) .catch((error) => { @@ -813,11 +814,11 @@ const AngularWorkflow = (defaultprops) => { fetch( globalUrl + - "/api/v1/workflows/" + - props.match.params.key + - "/executions/" + - executionRequest.execution_id + - "/abort", + "/api/v1/workflows/" + + props.match.params.key + + "/executions/" + + executionRequest.execution_id + + "/abort", { method: "GET", headers: { @@ -847,212 +848,212 @@ const AngularWorkflow = (defaultprops) => { // Loop nodes and find results // Update on every interval? idk - ReactDOM.unstable_batchedUpdates(() => { - if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) { - // FIXME: If another is selected, don't edit.. - // Doesn't work because this is some async garbage - if ( - executionData.execution_id === undefined || - (responseJson.execution_id === executionData.execution_id && - responseJson.results !== undefined && - responseJson.results !== null) - ) { - if ( - executionData.status !== responseJson.status || - executionData.result !== responseJson.result || - executionData.results.length !== responseJson.results.length - ) { - setExecutionData(responseJson); - } else { - //console.log("NOT updating state."); - } - } - } + ReactDOM.unstable_batchedUpdates(() => { + if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) { + // FIXME: If another is selected, don't edit.. + // Doesn't work because this is some async garbage + if ( + executionData.execution_id === undefined || + (responseJson.execution_id === executionData.execution_id && + responseJson.results !== undefined && + responseJson.results !== null) + ) { + if ( + executionData.status !== responseJson.status || + executionData.result !== responseJson.result || + executionData.results.length !== responseJson.results.length + ) { + setExecutionData(responseJson); + } else { + //console.log("NOT updating state."); + } + } + } - if (responseJson.execution_id !== executionRequest.execution_id) { - cy.elements().removeClass( - "success-highlight failure-highlight executing-highlight" - ); - return; - } + if (responseJson.execution_id !== executionRequest.execution_id) { + cy.elements().removeClass( + "success-highlight failure-highlight executing-highlight" + ); + return; + } - if (responseJson.results !== null && responseJson.results.length > 0) { - for (var key in responseJson.results) { - var item = responseJson.results[key]; - var currentnode = cy.getElementById(item.action.id); - if (currentnode.length === 0) { - continue; - } + if (responseJson.results !== null && responseJson.results.length > 0) { + for (var key in responseJson.results) { + var item = responseJson.results[key]; + var currentnode = cy.getElementById(item.action.id); + if (currentnode.length === 0) { + continue; + } - currentnode = currentnode[0]; - const outgoingEdges = currentnode.outgoers("edge"); - const incomingEdges = currentnode.incomers("edge"); + currentnode = currentnode[0]; + const outgoingEdges = currentnode.outgoers("edge"); + const incomingEdges = currentnode.incomers("edge"); - switch (item.status) { - case "EXECUTING": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - incomingEdges.addClass("success-highlight"); - currentnode.addClass("executing-highlight"); - break; - case "SKIPPED": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.addClass("skipped-highlight"); - break; - case "WAITING": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.addClass("executing-highlight"); + switch (item.status) { + case "EXECUTING": + currentnode.removeClass("not-executing-highlight"); + currentnode.removeClass("success-highlight"); + currentnode.removeClass("failure-highlight"); + currentnode.removeClass("shuffle-hover-highlight"); + currentnode.removeClass("awaiting-data-highlight"); + incomingEdges.addClass("success-highlight"); + currentnode.addClass("executing-highlight"); + break; + case "SKIPPED": + currentnode.removeClass("not-executing-highlight"); + currentnode.removeClass("success-highlight"); + currentnode.removeClass("failure-highlight"); + currentnode.removeClass("shuffle-hover-highlight"); + currentnode.removeClass("awaiting-data-highlight"); + currentnode.removeClass("executing-highlight"); + currentnode.addClass("skipped-highlight"); + break; + case "WAITING": + currentnode.removeClass("not-executing-highlight"); + currentnode.removeClass("success-highlight"); + currentnode.removeClass("failure-highlight"); + currentnode.removeClass("shuffle-hover-highlight"); + currentnode.removeClass("awaiting-data-highlight"); + currentnode.addClass("executing-highlight"); - if (!visited.includes(item.action.label)) { - if (executionRunning) { - visited.push(item.action.label); - setVisited(visited); - } - } + if (!visited.includes(item.action.label)) { + if (executionRunning) { + visited.push(item.action.label); + setVisited(visited); + } + } - // FIXME - add outgoing nodes to executing - //const outgoingNodes = outgoingEdges.find().data().target - if (outgoingEdges.length > 0) { - outgoingEdges.addClass("success-highlight"); - } - break; - case "SUCCESS": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.addClass("success-highlight"); - incomingEdges.addClass("success-highlight"); - outgoingEdges.addClass("success-highlight"); + // FIXME - add outgoing nodes to executing + //const outgoingNodes = outgoingEdges.find().data().target + if (outgoingEdges.length > 0) { + outgoingEdges.addClass("success-highlight"); + } + break; + case "SUCCESS": + currentnode.removeClass("not-executing-highlight"); + currentnode.removeClass("executing-highlight"); + currentnode.removeClass("failure-highlight"); + currentnode.removeClass("shuffle-hover-highlight"); + currentnode.removeClass("awaiting-data-highlight"); + currentnode.addClass("success-highlight"); + incomingEdges.addClass("success-highlight"); + outgoingEdges.addClass("success-highlight"); - if ( - visited !== undefined && - visited !== null && - !visited.includes(item.action.label) - ) { - if (executionRunning) { - visited.push(item.action.label); - setVisited(visited); - } - } + if ( + visited !== undefined && + visited !== null && + !visited.includes(item.action.label) + ) { + if (executionRunning) { + visited.push(item.action.label); + setVisited(visited); + } + } - // FIXME - add outgoing nodes to executing - //const outgoingNodes = outgoingEdges.find().data().target - if (outgoingEdges.length > 0) { - for (var i = 0; i < outgoingEdges.length; i++) { - const edge = outgoingEdges[i]; - const targetnode = cy.getElementById(edge.data().target); - if ( - targetnode !== undefined && - !targetnode.classes().includes("success-highlight") && - !targetnode.classes().includes("failure-highlight") - ) { - targetnode.removeClass("not-executing-highlight"); - targetnode.removeClass("success-highlight"); - targetnode.removeClass("shuffle-hover-highlight"); - targetnode.removeClass("failure-highlight"); - targetnode.removeClass("awaiting-data-highlight"); - targetnode.addClass("executing-highlight"); - } - } - } - break; - case "FAILURE": - //When status comes as failure, allow user to start workflow execution - if (executionRunning) { - setExecutionRunning(false); - } + // FIXME - add outgoing nodes to executing + //const outgoingNodes = outgoingEdges.find().data().target + if (outgoingEdges.length > 0) { + for (var i = 0; i < outgoingEdges.length; i++) { + const edge = outgoingEdges[i]; + const targetnode = cy.getElementById(edge.data().target); + if ( + targetnode !== undefined && + !targetnode.classes().includes("success-highlight") && + !targetnode.classes().includes("failure-highlight") + ) { + targetnode.removeClass("not-executing-highlight"); + targetnode.removeClass("success-highlight"); + targetnode.removeClass("shuffle-hover-highlight"); + targetnode.removeClass("failure-highlight"); + targetnode.removeClass("awaiting-data-highlight"); + targetnode.addClass("executing-highlight"); + } + } + } + break; + case "FAILURE": + //When status comes as failure, allow user to start workflow execution + if (executionRunning) { + setExecutionRunning(false); + } - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.addClass("failure-highlight"); + currentnode.removeClass("not-executing-highlight"); + currentnode.removeClass("executing-highlight"); + currentnode.removeClass("success-highlight"); + currentnode.removeClass("awaiting-data-highlight"); + currentnode.removeClass("shuffle-hover-highlight"); + currentnode.addClass("failure-highlight"); - if (!visited.includes(item.action.label)) { - if ( - item.action.result !== undefined && - item.action.result !== null && - !item.action.result.includes("failed condition") - ) { - alert.error( - "Error for " + - item.action.label + - " with result " + - item.result - ); - } - visited.push(item.action.label); - setVisited(visited); - } - break; - case "AWAITING_DATA": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.addClass("awaiting-data-highlight"); - break; - default: - console.log("DEFAULT?"); - break; - } - } - } + if (!visited.includes(item.action.label)) { + if ( + item.action.result !== undefined && + item.action.result !== null && + !item.action.result.includes("failed condition") + ) { + alert.error( + "Error for " + + item.action.label + + " with result " + + item.result + ); + } + visited.push(item.action.label); + setVisited(visited); + } + break; + case "AWAITING_DATA": + currentnode.removeClass("not-executing-highlight"); + currentnode.removeClass("executing-highlight"); + currentnode.removeClass("success-highlight"); + currentnode.removeClass("failure-highlight"); + currentnode.removeClass("shuffle-hover-highlight"); + currentnode.addClass("awaiting-data-highlight"); + break; + default: + console.log("DEFAULT?"); + break; + } + } + } - if ( - responseJson.status === "ABORTED" || - responseJson.status === "STOPPED" || - responseJson.status === "FAILURE" || - responseJson.status === "WAITING" - ) { - stop(); + if ( + responseJson.status === "ABORTED" || + responseJson.status === "STOPPED" || + responseJson.status === "FAILURE" || + responseJson.status === "WAITING" + ) { + stop(); - if (executionRunning) { - setExecutionRunning(false); - } + if (executionRunning) { + setExecutionRunning(false); + } - var curelements = cy.elements(); - for (var i = 0; i < curelements.length; i++) { - if (curelements[i].classes().includes("executing-highlight")) { - curelements[i].removeClass("executing-highlight"); - curelements[i].addClass("failure-highlight"); - } - } + var curelements = cy.elements(); + for (var i = 0; i < curelements.length; i++) { + if (curelements[i].classes().includes("executing-highlight")) { + curelements[i].removeClass("executing-highlight"); + curelements[i].addClass("failure-highlight"); + } + } - getWorkflowExecution(props.match.params.key, ""); - } else if (responseJson.status === "FINISHED") { - setExecutionRunning(false); - stop(); - getWorkflowExecution(props.match.params.key, ""); - setUpdate(Math.random()); - } - }) + getWorkflowExecution(props.match.params.key, ""); + } else if (responseJson.status === "FINISHED") { + setExecutionRunning(false); + stop(); + getWorkflowExecution(props.match.params.key, ""); + setUpdate(Math.random()); + } + }) }; - const sendStreamRequest = (body) => { - console.log("Stream not activated yet.") - return + const sendStreamRequest = (body) => { + console.log("Stream not activated yet.") + return - // Session may be important here huh - body.user_id = userdata.id + // Session may be important here huh + body.user_id = userdata.id - fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key + "/stream", { + fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key + "/stream", { method: "POST", headers: { "Content-Type": "application/json", @@ -1061,22 +1062,22 @@ const AngularWorkflow = (defaultprops) => { body: JSON.stringify(body), credentials: "include", }) - .then((response) => { - setSavingState(0); - if (response.status !== 200) { - console.log("Status not 200 for setting workflows :O!"); - } + .then((response) => { + setSavingState(0); + if (response.status !== 200) { + console.log("Status not 200 for setting workflows :O!"); + } - return response.json(); - }) - .then((responseJson) => { - console.log("RESP: ", responseJson) - }) - .catch((error) => { - console.log("Stream send error: ", error.toString()) - //alert.error(error.toString()); - }) - } + return response.json(); + }) + .then((responseJson) => { + console.log("RESP: ", responseJson) + }) + .catch((error) => { + console.log("Stream send error: ", error.toString()) + //alert.error(error.toString()); + }) + } const saveWorkflow = (curworkflow, executionArgument, startNode) => { var success = false; @@ -1087,283 +1088,283 @@ const AngularWorkflow = (defaultprops) => { return; } - setSavingState(2); + setSavingState(2); - // This might not be the right course of action, but seems logical, as items could be running already - // Makes it possible to update with a version in current render - stop(); - var useworkflow = workflow; - if (curworkflow !== undefined) { - useworkflow = curworkflow; - } + // This might not be the right course of action, but seems logical, as items could be running already + // Makes it possible to update with a version in current render + stop(); + var useworkflow = workflow; + if (curworkflow !== undefined) { + useworkflow = curworkflow; + } - var cyelements = cy.elements(); - var newActions = []; - var newTriggers = []; - var newBranches = []; - var newVBranches = []; - var newComments = []; - for (var key in cyelements) { - if (cyelements[key].data === undefined) { - continue; - } + var cyelements = cy.elements(); + var newActions = []; + var newTriggers = []; + var newBranches = []; + var newVBranches = []; + var newComments = []; + for (var key in cyelements) { + if (cyelements[key].data === undefined) { + continue; + } - var type = cyelements[key].data()["type"]; - if (type === undefined) { - if ( - cyelements[key].data().source === undefined || - cyelements[key].data().target === undefined - ) { - continue; - } + var type = cyelements[key].data()["type"]; + if (type === undefined) { + if ( + cyelements[key].data().source === undefined || + cyelements[key].data().target === undefined + ) { + continue; + } - var parsedElement = { - id: cyelements[key].data().id, - source_id: cyelements[key].data().source, - destination_id: cyelements[key].data().target, - conditions: cyelements[key].data().conditions, - decorator: cyelements[key].data().decorator, - }; + var parsedElement = { + id: cyelements[key].data().id, + source_id: cyelements[key].data().source, + destination_id: cyelements[key].data().target, + conditions: cyelements[key].data().conditions, + decorator: cyelements[key].data().decorator, + }; - if (parsedElement.decorator) { - newVBranches.push(parsedElement); - } else { - newBranches.push(parsedElement); - } - } else { - if (type === "ACTION") { - const cyelement = cyelements[key].data(); - const elementid = - cyelement.id === undefined || cyelement.id === null - ? cyelement["_id"] - : cyelement.id; + if (parsedElement.decorator) { + newVBranches.push(parsedElement); + } else { + newBranches.push(parsedElement); + } + } else { + if (type === "ACTION") { + const cyelement = cyelements[key].data(); + const elementid = + cyelement.id === undefined || cyelement.id === null + ? cyelement["_id"] + : cyelement.id; - var curworkflowAction = useworkflow.actions.find( - (a) => - a !== undefined && - (a["id"] === elementid || a["_id"] === elementid) - ); - if (curworkflowAction === undefined) { - curworkflowAction = cyelements[key].data(); - } + var curworkflowAction = useworkflow.actions.find( + (a) => + a !== undefined && + (a["id"] === elementid || a["_id"] === elementid) + ); + if (curworkflowAction === undefined) { + curworkflowAction = cyelements[key].data(); + } - curworkflowAction.position = cyelements[key].position(); + curworkflowAction.position = cyelements[key].position(); - // workaround to fix some edgecases - if ( - curworkflowAction.parameters === "" || - curworkflowAction.parameters === null - ) { - curworkflowAction.parameters = []; - } + // workaround to fix some edgecases + if ( + curworkflowAction.parameters === "" || + curworkflowAction.parameters === null + ) { + curworkflowAction.parameters = []; + } - if ( - curworkflowAction.example === undefined || - curworkflowAction.example === "" || - curworkflowAction.example === null - ) { - if (cyelements[key].data().example !== undefined) { - curworkflowAction.example = cyelements[key].data().example; - } - } + if ( + curworkflowAction.example === undefined || + curworkflowAction.example === "" || + curworkflowAction.example === null + ) { + if (cyelements[key].data().example !== undefined) { + curworkflowAction.example = cyelements[key].data().example; + } + } - // Override just in this place - curworkflowAction.errors = []; - curworkflowAction.isValid = true; + // Override just in this place + curworkflowAction.errors = []; + curworkflowAction.isValid = true; - // Cleans up OpenAPI items - var newparams = []; - for (var key in curworkflowAction.parameters) { - const thisitem = curworkflowAction.parameters[key]; - if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) { - continue; - } + // Cleans up OpenAPI items + var newparams = []; + for (var key in curworkflowAction.parameters) { + const thisitem = curworkflowAction.parameters[key]; + if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) { + continue; + } - newparams.push(thisitem); - } + newparams.push(thisitem); + } - curworkflowAction.parameters = newparams; - newActions.push(curworkflowAction); - } else if (type === "TRIGGER") { - if (useworkflow.triggers === undefined || useworkflow.triggers === null) { - useworkflow.triggers = []; - } + curworkflowAction.parameters = newparams; + newActions.push(curworkflowAction); + } else if (type === "TRIGGER") { + if (useworkflow.triggers === undefined || useworkflow.triggers === null) { + useworkflow.triggers = []; + } - var curworkflowTrigger = useworkflow.triggers.find( - (a) => a.id === cyelements[key].data()["id"] - ); - if (curworkflowTrigger === undefined) { - curworkflowTrigger = cyelements[key].data(); - } + var curworkflowTrigger = useworkflow.triggers.find( + (a) => a.id === cyelements[key].data()["id"] + ); + if (curworkflowTrigger === undefined) { + curworkflowTrigger = cyelements[key].data(); + } - curworkflowTrigger.position = cyelements[key].position(); - if (curworkflowTrigger.canConnect === false) { - continue - } + curworkflowTrigger.position = cyelements[key].position(); + if (curworkflowTrigger.canConnect === false) { + continue + } - newTriggers.push(curworkflowTrigger); - } else if (type === "COMMENT") { - if (useworkflow.comments === undefined || useworkflow.comments === null) { - useworkflow.comments = []; - } + newTriggers.push(curworkflowTrigger); + } else if (type === "COMMENT") { + if (useworkflow.comments === undefined || useworkflow.comments === null) { + useworkflow.comments = []; + } - var curworkflowComment = useworkflow.comments.find( - (a) => a.id === cyelements[key].data()["id"] - ) + var curworkflowComment = useworkflow.comments.find( + (a) => a.id === cyelements[key].data()["id"] + ) - if (curworkflowComment === undefined) { - curworkflowComment = cyelements[key].data(); - try { - curworkflowComment.position.x = parseInt(curworkflowComment.position.x) - } catch (e) { - console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x) - } + if (curworkflowComment === undefined) { + curworkflowComment = cyelements[key].data(); + try { + curworkflowComment.position.x = parseInt(curworkflowComment.position.x) + } catch (e) { + console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x) + } - try { - curworkflowComment.position.y = parseInt(curworkflowComment.position.y) - } catch (e) { - console.log("Failed to parse position Y of comment: ", curworkflowComment.position.y) - } - } + try { + curworkflowComment.position.y = parseInt(curworkflowComment.position.y) + } catch (e) { + console.log("Failed to parse position Y of comment: ", curworkflowComment.position.y) + } + } - const parsedHeight = parseInt(curworkflowComment["height"]) - if (!isNaN(parsedHeight)) { - curworkflowComment.height = parsedHeight - } else { - curworkflowComment.width = 150 - } + const parsedHeight = parseInt(curworkflowComment["height"]) + if (!isNaN(parsedHeight)) { + curworkflowComment.height = parsedHeight + } else { + curworkflowComment.width = 150 + } - const parsedWidth = parseInt(curworkflowComment["width"]) - if (!isNaN(parsedWidth)) { - curworkflowComment.width = parsedWidth - } else { - curworkflowComment.width = 200 - } + const parsedWidth = parseInt(curworkflowComment["width"]) + if (!isNaN(parsedWidth)) { + curworkflowComment.width = parsedWidth + } else { + curworkflowComment.width = 200 + } - curworkflowComment.position = cyelements[key].position(); - //console.log(curworkflowComment) + curworkflowComment.position = cyelements[key].position(); + //console.log(curworkflowComment) - newComments.push(curworkflowComment); - } else { - alert.info("No handler for type: " + type); - } - } - } + newComments.push(curworkflowComment); + } else { + alert.info("No handler for type: " + type); + } + } + } - if (userediting === true) { - useworkflow.user_editing = true - } + if (userediting === true) { + useworkflow.user_editing = true + } - useworkflow.actions = newActions; - useworkflow.triggers = newTriggers; - useworkflow.branches = newBranches; - useworkflow.comments = newComments; - useworkflow.visual_branches = newVBranches; + useworkflow.actions = newActions; + useworkflow.triggers = newTriggers; + useworkflow.branches = newBranches; + useworkflow.comments = newComments; + useworkflow.visual_branches = newVBranches; - // Errors are backend defined - useworkflow.errors = []; - useworkflow.previously_saved = true; + // Errors are backend defined + useworkflow.errors = []; + useworkflow.previously_saved = true; - if (cy !== undefined) { - // scale: 0.3, - // bg: "#27292d", - const cyImageData = cy.png({ - output: "base64uri", - maxWidth: 480, - maxHeight: 270, - }) + if (cy !== undefined) { + // scale: 0.3, + // bg: "#27292d", + const cyImageData = cy.png({ + output: "base64uri", + maxWidth: 480, + maxHeight: 270, + }) - if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) { - useworkflow.image = cyImageData - } - } + if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) { + useworkflow.image = cyImageData + } + } - setLastSaved(true); - fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(useworkflow), - credentials: "include", - }) - .then((response) => { - setSavingState(0); - if (response.status !== 200) { - console.log("Status not 200 for setting workflows :O!"); - } + setLastSaved(true); + fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(useworkflow), + credentials: "include", + }) + .then((response) => { + setSavingState(0); + if (response.status !== 200) { + console.log("Status not 200 for setting workflows :O!"); + } - return response.json(); - }) - .then((responseJson) => { - if (executionArgument !== undefined && startNode !== undefined) { - //console.log("Running execution AFTER saving"); - executeWorkflow(executionArgument, startNode, true); - return; - } + return response.json(); + }) + .then((responseJson) => { + if (executionArgument !== undefined && startNode !== undefined) { + //console.log("Running execution AFTER saving"); + executeWorkflow(executionArgument, startNode, true); + return; + } - if (!responseJson.success) { - console.log(responseJson); - if (responseJson.reason !== undefined && responseJson.reason !== null) { - alert.error("Failed to save: " + responseJson.reason); - } else { - alert.error("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.") - } - } else { + if (!responseJson.success) { + console.log(responseJson); + if (responseJson.reason !== undefined && responseJson.reason !== null) { + alert.error("Failed to save: " + responseJson.reason); + } else { + alert.error("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.") + } + } else { - sendStreamRequest({ - "item": "workflow", - "type": "save", - "id": workflow.id, - }) + sendStreamRequest({ + "item": "workflow", + "type": "save", + "id": workflow.id, + }) - if ( - responseJson.new_id !== undefined && - responseJson.new_id !== null - ) { - window.location.pathname = "/workflows/" + responseJson.new_id; - } + if ( + responseJson.new_id !== undefined && + responseJson.new_id !== null + ) { + window.location.pathname = "/workflows/" + responseJson.new_id; + } - success = true; - if (responseJson.errors !== undefined) { - workflow.errors = responseJson.errors; - if (responseJson.errors.length === 0) { - workflow.isValid = true; - workflow.is_valid = true; + success = true; + if (responseJson.errors !== undefined) { + workflow.errors = responseJson.errors; + if (responseJson.errors.length === 0) { + workflow.isValid = true; + workflow.is_valid = true; - const cyelements = cy.elements(); - - for (var i = 0; i < cyelements.length; i++) { - //cyelements[i].removeStyle(); - cyelements[i].data().is_valid = true; - cyelements[i].data().errors = []; - } + const cyelements = cy.elements(); - for (var key in workflow.actions) { - workflow.actions[key].is_valid = true; - workflow.actions[key].errors = []; - } - } + for (var i = 0; i < cyelements.length; i++) { + //cyelements[i].removeStyle(); + cyelements[i].data().is_valid = true; + cyelements[i].data().errors = []; + } - for (var key in workflow.errors) { - //alert.info(workflow.errors[key]); - } + for (var key in workflow.actions) { + workflow.actions[key].is_valid = true; + workflow.actions[key].errors = []; + } + } - setWorkflow(workflow); - } + for (var key in workflow.errors) { + //alert.info(workflow.errors[key]); + } - setSavingState(1); - setTimeout(() => { - setSavingState(0); - }, 1500); - } - }) - .catch((error) => { - setSavingState(0); - //alert.error(error.toString()); - console.log("Save workflow error: ", error.toString()); - }); + setWorkflow(workflow); + } + + setSavingState(1); + setTimeout(() => { + setSavingState(0); + }, 1500); + } + }) + .catch((error) => { + setSavingState(0); + //alert.error(error.toString()); + console.log("Save workflow error: ", error.toString()); + }); return success; }; @@ -1396,103 +1397,103 @@ const AngularWorkflow = (defaultprops) => { }; const executeWorkflow = (executionArgument, startNode, hasSaved) => { - ReactDOM.unstable_batchedUpdates(() => { - if (hasSaved === false) { - setExecutionRequestStarted(true); - saveWorkflow(workflow, executionArgument, startNode); - console.log("FIXME: Might have forgotten to save before executing."); - return; - } + ReactDOM.unstable_batchedUpdates(() => { + if (hasSaved === false) { + setExecutionRequestStarted(true); + saveWorkflow(workflow, executionArgument, startNode); + console.log("FIXME: Might have forgotten to save before executing."); + return; + } - if (workflow.public) { - alert.info("Save it to get a new version"); - } + if (workflow.public) { + alert.info("Save it to get a new version"); + } - var returncheck = monitorUpdates(); - if (!returncheck) { - alert.error("No startnode set."); - return; - } + var returncheck = monitorUpdates(); + if (!returncheck) { + alert.error("No startnode set."); + return; + } - setVisited([]) - setExecutionRequest({}) - stop() + setVisited([]) + setExecutionRequest({}) + stop() - var curelements = cy.elements(); - for (var i = 0; i < curelements.length; i++) { - curelements[i].addClass("not-executing-highlight"); - } + var curelements = cy.elements(); + for (var i = 0; i < curelements.length; i++) { + curelements[i].addClass("not-executing-highlight"); + } - const data = { execution_argument: executionArgument, start: startNode }; - fetch( - globalUrl + "/api/v1/workflows/" + props.match.params.key + "/execute", - { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - body: JSON.stringify(data), - } - ) - .then((response) => { - setExecutionRequestStarted(false) - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } + const data = { execution_argument: executionArgument, start: startNode }; + fetch( + globalUrl + "/api/v1/workflows/" + props.match.params.key + "/execute", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(data), + } + ) + .then((response) => { + setExecutionRequestStarted(false) + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + } - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error("Failed to start: " + responseJson.reason); - setExecutionRunning(false); - setExecutionRequestStarted(false); - stop(); + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to start: " + responseJson.reason); + setExecutionRunning(false); + setExecutionRequestStarted(false); + stop(); - for (var i = 0; i < curelements.length; i++) { - curelements[i].removeClass("not-executing-highlight"); - } - return; - } else { - setExecutionRunning(true); - setExecutionRequestStarted(false); - } + for (var i = 0; i < curelements.length; i++) { + curelements[i].removeClass("not-executing-highlight"); + } + return; + } else { + setExecutionRunning(true); + setExecutionRequestStarted(false); + } - if ( - responseJson.execution_id === "" || - responseJson.execution_id === undefined || - responseJson.authorization === "" || - responseJson.authorization === undefined - ) { - alert.error("Something went wrong during execution startup"); - console.log("BAD RESPONSE FOR EXECUTION: ", responseJson); - setExecutionRunning(false); - setExecutionRequestStarted(false); - stop(); + if ( + responseJson.execution_id === "" || + responseJson.execution_id === undefined || + responseJson.authorization === "" || + responseJson.authorization === undefined + ) { + alert.error("Something went wrong during execution startup"); + console.log("BAD RESPONSE FOR EXECUTION: ", responseJson); + setExecutionRunning(false); + setExecutionRequestStarted(false); + stop(); - for (var i = 0; i < curelements.length; i++) { - curelements[i].removeClass("not-executing-highlight"); - } - return; - } + for (var i = 0; i < curelements.length; i++) { + curelements[i].removeClass("not-executing-highlight"); + } + return; + } - setExecutionRequest({ - execution_id: responseJson.execution_id, - authorization: responseJson.authorization, - }); - setExecutionData({}); - setExecutionModalOpen(true); - setExecutionModalView(1); - start(); - }) - .catch((error) => { - //alert.error(error.toString()); - setExecutionRequestStarted(false) - console.log("Execute workflow err: ", error.toString()); - }); - }) + setExecutionRequest({ + execution_id: responseJson.execution_id, + authorization: responseJson.authorization, + }); + setExecutionData({}); + setExecutionModalOpen(true); + setExecutionModalView(1); + start(); + }) + .catch((error) => { + //alert.error(error.toString()); + setExecutionRequestStarted(false) + console.log("Execute workflow err: ", error.toString()); + }); + }) }; // This can be used to only show prioritzed ones later @@ -1584,7 +1585,7 @@ const AngularWorkflow = (defaultprops) => { } if (appUpdates === true) { - console.log("Closing auth modal: Success") + console.log("Closing auth modal: Success") setAuthenticationModalOpen(false); setSelectedAction(selectedAction); @@ -1592,7 +1593,7 @@ const AngularWorkflow = (defaultprops) => { saveWorkflow(workflow); alert.info("Added and updated authentication!"); } else { - console.log("Closing auth modal? FAIL") + console.log("Closing auth modal? FAIL") alert.error("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); } @@ -1621,7 +1622,7 @@ const AngularWorkflow = (defaultprops) => { credentials: "include", }) .then((response) => { - setAppsLoaded(true) + setAppsLoaded(true) if (response.status !== 200) { console.log("Status not 200 for apps :O!"); } @@ -1629,25 +1630,25 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .then((responseJson) => { - if (responseJson === null) { - console.log("No response") - const pretend_apps = [{ - "name": "TBD", - "app_name": "TBD", - "app_version": "TBD", - "description": "TBD", - "version": "TBD", - "large_image": "", - }] - setApps(pretend_apps) - setFilteredApps(pretend_apps) + if (responseJson === null) { + console.log("No response") + const pretend_apps = [{ + "name": "TBD", + "app_name": "TBD", + "app_version": "TBD", + "description": "TBD", + "version": "TBD", + "large_image": "", + }] + setApps(pretend_apps) + setFilteredApps(pretend_apps) setPrioritizedApps(pretend_apps); - return - } + return + } - if (responseJson.success === false) { - return - } + if (responseJson.success === false) { + return + } // FIXME - handle versions on left bar //handleAppVersioning(responseJson) @@ -1672,25 +1673,25 @@ const AngularWorkflow = (defaultprops) => { // ) //); - var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name)) - //tmpFiltered = sortByKey(tmpFiltered, "activated") + var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name)) + //tmpFiltered = sortByKey(tmpFiltered, "activated") setFilteredApps(tmpFiltered) - //!(!app.activated && app.generated) + //!(!app.activated && app.generated) setPrioritizedApps( responseJson.filter((app) => internalIds.includes(app.name)) ); } }) .catch((error) => { - setAppsLoaded(true) + setAppsLoaded(true) //alert.error("App loading error: "+error.toString()); - console.log("App loading error: "+error.toString()); + console.log("App loading error: " + error.toString()); }); }; - // Searhc by username, userId, workflow, appId should all work - const getUserProfile = (username, rerun) => { + // Searhc by username, userId, workflow, appId should all work + const getUserProfile = (username, rerun) => { fetch(`${globalUrl}/api/v1/users/creators/${username}`, { method: "GET", headers: { @@ -1699,35 +1700,35 @@ const AngularWorkflow = (defaultprops) => { }, credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + } - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success !== false) { - console.log("Found creator: ", responseJson) - setCreatorProfile(responseJson) - } else { - console.log("Couldn't find the creator profile (rerun?): ", responseJson, rerun) - // If the current user is any of the Shuffle Creators - // AND the workflow doesn't have an owner: allow editing. - // else: Allow suggestions? - //console.log("User: ", userdata) - //if (rerun !== true) { - // getUserProfile(userdata.id, true) - //} - } - }) - .catch((error) => { - console.log("Get userprofile error: ", error); - }) + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + console.log("Found creator: ", responseJson) + setCreatorProfile(responseJson) + } else { + console.log("Couldn't find the creator profile (rerun?): ", responseJson, rerun) + // If the current user is any of the Shuffle Creators + // AND the workflow doesn't have an owner: allow editing. + // else: Allow suggestions? + //console.log("User: ", userdata) + //if (rerun !== true) { + // getUserProfile(userdata.id, true) + //} + } + }) + .catch((error) => { + console.log("Get userprofile error: ", error); + }) } - const getFiles = () => { + const getFiles = () => { fetch(globalUrl + "/api/v1/files", { method: "GET", headers: { @@ -1748,9 +1749,11 @@ const AngularWorkflow = (defaultprops) => { if (responseJson.files !== undefined && responseJson.files !== null) { setFiles(responseJson); } else { - setFiles({"namespaces": [ - "default" - ]}); + setFiles({ + "namespaces": [ + "default" + ] + }); } if ( @@ -1802,46 +1805,46 @@ const AngularWorkflow = (defaultprops) => { if (responseJson.public) { //alert.info("This workflow is public. Save the workflow to use it in your organization."); - - setAuthLoaded(true) - console.log("RESP: ", responseJson) - if (Object.getOwnPropertyNames(creatorProfile).length === 0) { - //getUserProfile("frikky") - getUserProfile(responseJson.id, false) - } - //{appGroup.map((data, index) => { - //const [appGroup, setAppGroup] = React.useState([]); - var appsFound = [] - for (var key in responseJson.actions) { - const parsedAction = responseJson.actions[key] - if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") { - continue - } - if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){ - appsFound.push(parsedAction) - } - } + setAuthLoaded(true) + console.log("RESP: ", responseJson) + if (Object.getOwnPropertyNames(creatorProfile).length === 0) { + //getUserProfile("frikky") + getUserProfile(responseJson.id, false) + } - setAppGroup(appsFound) + //{appGroup.map((data, index) => { + //const [appGroup, setAppGroup] = React.useState([]); + var appsFound = [] + for (var key in responseJson.actions) { + const parsedAction = responseJson.actions[key] + if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") { + continue + } + if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0) { + appsFound.push(parsedAction) + } + } - appsFound = [] - for (var key in responseJson.triggers) { - const parsedAction = responseJson.triggers[key] - if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){ - appsFound.push(parsedAction) - } - } + setAppGroup(appsFound) - setTriggerGroup(appsFound) - } else { - getAppAuthentication(); - getEnvironments(); - getWorkflowExecution(props.match.params.key, ""); - getAvailableWorkflows(-1); - getSettings(); - getFiles() - } + appsFound = [] + for (var key in responseJson.triggers) { + const parsedAction = responseJson.triggers[key] + if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0) { + appsFound.push(parsedAction) + } + } + + setTriggerGroup(appsFound) + } else { + getAppAuthentication(); + getEnvironments(); + getWorkflowExecution(props.match.params.key, ""); + getAvailableWorkflows(-1); + getSettings(); + getFiles() + } // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows. if (sourcenode.id !== undefined) { @@ -1867,9 +1870,9 @@ const AngularWorkflow = (defaultprops) => { node.data = action; - node.data.canConnect = false - node.data.is_valid = true - node.data.isValid = true + node.data.canConnect = false + node.data.is_valid = true + node.data.isValid = true node.data._id = action["id"]; node.data.type = "ACTION"; node.data.source_workflow = responseJson.id; @@ -1877,10 +1880,10 @@ const AngularWorkflow = (defaultprops) => { nodefound = true; } - if (responseJson.public) { - node.data.is_valid = true - node.is_valid = true - } + if (responseJson.public) { + node.data.is_valid = true + node.is_valid = true + } var example = ""; if ( @@ -1895,27 +1898,27 @@ const AngularWorkflow = (defaultprops) => { return node; }); - var triggers = responseJson.triggers.map((trigger) => { - const node = {}; - - console.log("Only add workflow: ", trigger.app_name) - if (trigger.app_name !== "Shuffle Workflow" && trigger.app_name !== "User Input") { - return null - } + var triggers = responseJson.triggers.map((trigger) => { + const node = {}; - node.position = trigger.position; - node.data = trigger; + console.log("Only add workflow: ", trigger.app_name) + if (trigger.app_name !== "Shuffle Workflow" && trigger.app_name !== "User Input") { + return null + } - node.data.canConnect = false - node.data.id = trigger["id"]; - node.data._id = trigger["id"]; - node.data.type = "TRIGGER"; + node.position = trigger.position; + node.data = trigger; - return node; - }); + node.data.canConnect = false + node.data.id = trigger["id"]; + node.data._id = trigger["id"]; + node.data.type = "TRIGGER"; + + return node; + }); triggers = triggers.filter((trigger) => trigger !== null); - const insertedNodes = [].concat(actions, triggers); + const insertedNodes = [].concat(actions, triggers); var edges = responseJson.branches.map((branch, index) => { const edge = {}; var conditions = responseJson.branches[index].conditions; @@ -1957,10 +1960,10 @@ const AngularWorkflow = (defaultprops) => { source_workflow: responseJson.id, }; - if (responseJson.public) { - edge.data.is_valid = true - edge.is_valid = true - } + if (responseJson.public) { + edge.data.is_valid = true + edge.is_valid = true + } return edge; }); @@ -2002,7 +2005,7 @@ const AngularWorkflow = (defaultprops) => { responseJson.errors !== undefined || responseJson.errors !== null || responseJson.errors !== // what - responseJson.errors.length > 0 + responseJson.errors.length > 0 ) { setConfigureWorkflowModalOpen(true); } @@ -2018,162 +2021,162 @@ const AngularWorkflow = (defaultprops) => { const onUnselect = (event) => { const nodedata = event.target.data(); console.log("UNSELECT: ", nodedata); - //if (nodedata.type === "ACTION") { - // setLastSelected(nodedata) - //} - // + //if (nodedata.type === "ACTION") { + // setLastSelected(nodedata) + //} + // - // Wait for new node to possibly be selected - //setTimeout(() => { - const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { - const item = typeIds[idkey] - if (item.data.isButton === true) { - //console.log("Reselect old node & return - or just return?") - - if (item.data.buttonType === "delete" && item.data.attachedTo === nodedata.id) { - //console.log("delete of same node!") - } - return - } - } + // Wait for new node to possibly be selected + //setTimeout(() => { + const typeIds = cy.elements('node:selected').jsons(); + for (var idkey in typeIds) { + const item = typeIds[idkey] + if (item.data.isButton === true) { + //console.log("Reselect old node & return - or just return?") - // Unselecting all - //cy.elements().unselect() + if (item.data.buttonType === "delete" && item.data.attachedTo === nodedata.id) { + //console.log("delete of same node!") + } + return + } + } - //if (nodedata.app_name === undefined && nodedata.source === undefined) { - // return; - //} - //event.target.removeClass("selected"); - // + // Unselecting all + //cy.elements().unselect() + + //if (nodedata.app_name === undefined && nodedata.source === undefined) { + // return; + //} + //event.target.removeClass("selected"); + // - //// If button is clicked, select current node + //// If button is clicked, select current node - // Attempt at rewrite of name in other actions in following nodes. - // Should probably be done in the onBlur for the textfield instead - /* - if (event.target.data().type === "ACTION") { - const nodeaction = event.target.data() - const curaction = workflow.actions.find(a => a.id === nodeaction.id) - console.log("workflowaction: ", curaction) - console.log("nodeaction: ", nodeaction) - if (nodeaction.label !== curaction.label) { - console.log("BEACH!") + // Attempt at rewrite of name in other actions in following nodes. + // Should probably be done in the onBlur for the textfield instead + /* + if (event.target.data().type === "ACTION") { + const nodeaction = event.target.data() + const curaction = workflow.actions.find(a => a.id === nodeaction.id) + console.log("workflowaction: ", curaction) + console.log("nodeaction: ", nodeaction) + if (nodeaction.label !== curaction.label) { + console.log("BEACH!") - var params = [] - const fixedName = "$"+curaction.label.toLowerCase().replace(" ", "_") - for (var actionkey in workflow.actions) { - if (workflow.actions[actionkey].id === curaction.id) { - continue - } + var params = [] + const fixedName = "$"+curaction.label.toLowerCase().replace(" ", "_") + for (var actionkey in workflow.actions) { + if (workflow.actions[actionkey].id === curaction.id) { + continue + } - for (var paramkey in workflow.actions[actionkey].parameters) { - const param = workflow.actions[actionkey].parameters[paramkey] - if (param.value === null || param.value === undefined || !param.value.includes("$")) { - continue - } + for (var paramkey in workflow.actions[actionkey].parameters) { + const param = workflow.actions[actionkey].parameters[paramkey] + if (param.value === null || param.value === undefined || !param.value.includes("$")) { + continue + } - const innername = param.value.toLowerCase().replace(" ", "_") - if (innername.includes(fixedName)) { - //workflow.actions[actionkey].parameters[paramkey].replace( - //console.log("FOUND!: ", innername) - } - } - } - } - } - */ + const innername = param.value.toLowerCase().replace(" ", "_") + if (innername.includes(fixedName)) { + //workflow.actions[actionkey].parameters[paramkey].replace( + //console.log("FOUND!: ", innername) + } + } + } + } + } + */ - //cy.removeListener("select"); - //cy.on("select", "node", (e) => onNodeSelect(e, appAuthentication)); - //cy.on("select", "edge", (e) => onEdgeSelect(e)); + //cy.removeListener("select"); + //cy.on("select", "node", (e) => onNodeSelect(e, appAuthentication)); + //cy.on("select", "edge", (e) => onEdgeSelect(e)); - // FIXME - check if they have value before overriding like this for no reason. - // Would save a lot of time (400~ ms -> 30ms) - //console.log("ACTION: ", selectedAction) - //console.log("APP: ", selectedApp) + // FIXME - check if they have value before overriding like this for no reason. + // Would save a lot of time (400~ ms -> 30ms) + //console.log("ACTION: ", selectedAction) + //console.log("APP: ", selectedApp) - ReactDOM.unstable_batchedUpdates(() => { - setSelectedAction({}); - setSelectedApp({}); - setSelectedTrigger({}); - setSelectedComment({}) - setSelectedEdge({}); + ReactDOM.unstable_batchedUpdates(() => { + setSelectedAction({}); + setSelectedApp({}); + setSelectedTrigger({}); + setSelectedComment({}) + setSelectedEdge({}); - setSelectedEdge({}) - setSelectedActionEnvironment({}) - setTriggerAuthentication({}) - setSelectedTriggerIndex(-1) - setTriggerFolders([]) - setSubworkflow({}) - setLocalFirstrequest(true) + setSelectedEdge({}) + setSelectedActionEnvironment({}) + setTriggerAuthentication({}) + setSelectedTriggerIndex(-1) + setTriggerFolders([]) + setSubworkflow({}) + setLocalFirstrequest(true) - // Can be used for right side view - setRightSideBarOpen(false); - setScrollConfig({ - top: 0, - left: 0, - selected: "", - }); - //console.timeEnd("UNSELECT"); - }) + // Can be used for right side view + setRightSideBarOpen(false); + setScrollConfig({ + top: 0, + left: 0, + selected: "", + }); + //console.timeEnd("UNSELECT"); + }) - sendStreamRequest({ - "item": "node", - "type": "unselect", - "userid": userdata.id, - }) - //}, 150) + sendStreamRequest({ + "item": "node", + "type": "unselect", + "userid": userdata.id, + }) + //}, 150) }; const onEdgeSelect = (event) => { - ReactDOM.unstable_batchedUpdates(() => { - setRightSideBarOpen(true); - setLastSaved(false); + ReactDOM.unstable_batchedUpdates(() => { + setRightSideBarOpen(true); + setLastSaved(false); - /* - // Used to not be able to edit trigger-based branches. - const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"]) - if (triggercheck === undefined) { - */ - if ( - event.target.data("type") !== "COMMENT" && - event.target.data().decorator - ) { - alert.info("This edge can't be edited."); - } else { - //console.log("DATA: ", event.target.data()) - const destinationId = event.target.data("target"); - //console.log("DATA: ", event.target.data()) - const curaction = workflow.actions.find((a) => a.id === destinationId); - //console.log("ACTION: ", curaction) - if (curaction !== undefined && curaction !== null) { - if ( - curaction.app_name === "Shuffle Tools" && - curaction.name === "router" - ) { - alert.info("Router action can't have incoming conditions"); - event.target.unselect(); - return; - } - } + /* + // Used to not be able to edit trigger-based branches. + const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"]) + if (triggercheck === undefined) { + */ + if ( + event.target.data("type") !== "COMMENT" && + event.target.data().decorator + ) { + alert.info("This edge can't be edited."); + } else { + //console.log("DATA: ", event.target.data()) + const destinationId = event.target.data("target"); + //console.log("DATA: ", event.target.data()) + const curaction = workflow.actions.find((a) => a.id === destinationId); + //console.log("ACTION: ", curaction) + if (curaction !== undefined && curaction !== null) { + if ( + curaction.app_name === "Shuffle Tools" && + curaction.name === "router" + ) { + alert.info("Router action can't have incoming conditions"); + event.target.unselect(); + return; + } + } - setSelectedEdgeIndex( - workflow.branches.findIndex( - (data) => data.id === event.target.data()["id"] - ) - ); - setSelectedEdge(event.target.data()); - } + setSelectedEdgeIndex( + workflow.branches.findIndex( + (data) => data.id === event.target.data()["id"] + ) + ); + setSelectedEdge(event.target.data()); + } - setSelectedAction({}); - setSelectedTrigger({}); - }) + setSelectedAction({}); + setSelectedTrigger({}); + }) }; // Comparing locations between nodes and setting views @@ -2195,9 +2198,9 @@ const AngularWorkflow = (defaultprops) => { return; } - if (nodedata.parameters === undefined) { - return - } + if (nodedata.parameters === undefined) { + return + } const workflow_id = nodedata.parameters.find( (param) => param.name === "workflow" @@ -2233,26 +2236,26 @@ const AngularWorkflow = (defaultprops) => { return; } - const connected = event.target.connectedEdges().jsons() - for (var key in connected) { - const edge = connected[key] - //console.log("EDGE:", edge) + const connected = event.target.connectedEdges().jsons() + for (var key in connected) { + const edge = connected[key] + //console.log("EDGE:", edge) - //const edge = edgeBase.json() + //const edge = edgeBase.json() const sourcenode = cy.getElementById(edge.data.source) const destinationnode = cy.getElementById(edge.data.target) - if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { - continue - } + if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { + continue + } - const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position()) + const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position()) const currentedge = cy.getElementById(edge.data.id) - if (currentedge !== undefined && currentedge !== null) { - currentedge.style('control-point-distance', edgeCurve.distance) - currentedge.style('control-point-weight', edgeCurve.weight) - } - } + if (currentedge !== undefined && currentedge !== null) { + currentedge.style('control-point-distance', edgeCurve.distance) + currentedge.style('control-point-weight', edgeCurve.weight) + } + } if (styledElements.length === 1) { console.log( @@ -2274,18 +2277,18 @@ const AngularWorkflow = (defaultprops) => { if (curElement !== null && curElement !== undefined) { curElement.style.border = curElement.style.original_border; var newValue = "$" + nodedata.label.toLowerCase().replaceAll(" ", "_"); - if (nodedata.type === "TRIGGER") { - if (nodedata.trigger_type === "WEBHOOK" || nodedata.trigger_type === "SCHEDULE" || nodedata.trigger_type === "EMAIL") { - var newValue = "$exec" - } - } + if (nodedata.type === "TRIGGER") { + if (nodedata.trigger_type === "WEBHOOK" || nodedata.trigger_type === "SCHEDULE" || nodedata.trigger_type === "EMAIL") { + var newValue = "$exec" + } + } var paramname = ""; var idnumber = -1; if (curElement.id.startsWith("rightside_field_")) { console.log("FOUND FIELD WITH NUMBER: ", curElement.id); - // Find exact position to put the text + // Find exact position to put the text const idsplit = curElement.id.split("_"); console.log(idsplit); @@ -2378,22 +2381,22 @@ const AngularWorkflow = (defaultprops) => { y: 0, }; - sendStreamRequest({ - "item": "node", - "type": "move", - "id": nodedata.id, - "location": { - "x": event.target.position("x"), - "y": event.target.position("y"), - } - }) + sendStreamRequest({ + "item": "node", + "type": "move", + "id": nodedata.id, + "location": { + "x": event.target.position("x"), + "y": event.target.position("y"), + } + }) }; const onNodeDrag = (event, selectedAction) => { const nodedata = event.target.data(); if (nodedata.finished === false) { - console.log("NOT FINISHED - ADD EXAMPLE BRANCHES TO CLOSEST!!") - return + console.log("NOT FINISHED - ADD EXAMPLE BRANCHES TO CLOSEST!!") + return } if (nodedata.app_name !== undefined) { @@ -2404,28 +2407,28 @@ const AngularWorkflow = (defaultprops) => { cy.getElementById(currentNode.data.id).remove(); } - // Calculate location - //currentNode.position.x > - //if (nodedata.position.x > 0 && nodedata.position.y > 0) { - // console.log("Positive both") - //} + // Calculate location + //currentNode.position.x > + //if (nodedata.position.x > 0 && nodedata.position.y > 0) { + // console.log("Positive both") + //} - //console.log(currentNode.position) - //console.log(nodedata.position) + //console.log(currentNode.position) + //console.log(nodedata.position) } } else { //console.log("No appid? ", nodedata) } - if (nodedata.buttonType === "edgehandler") { - console.log("Enable edgehandler!") - console.log("Find parent: ", nodedata.attachedTo) - const parentNode = cy.getElementById(nodedata.attachedTo); - if (parentNode !== null && parentNode !== undefined) { - console.log("Start parentnode tracking!") - //cy.edgehandles().start(parentNode) - } - } + if (nodedata.buttonType === "edgehandler") { + console.log("Enable edgehandler!") + console.log("Find parent: ", nodedata.attachedTo) + const parentNode = cy.getElementById(nodedata.attachedTo); + if (parentNode !== null && parentNode !== undefined) { + console.log("Start parentnode tracking!") + //cy.edgehandles().start(parentNode) + } + } if (nodedata.id === selectedAction.id) { return; @@ -2434,20 +2437,20 @@ const AngularWorkflow = (defaultprops) => { /* - // Tried looking for the closest node by position. aStar path not working entirely. - console.log("NODE: ", event.target) - const closestNode = cy.elements().aStar({ - root: nodedata.id, - goal: 'node', - directed: false, - }) + // Tried looking for the closest node by position. aStar path not working entirely. + console.log("NODE: ", event.target) + const closestNode = cy.elements().aStar({ + root: nodedata.id, + goal: 'node', + directed: false, + }) - if (closestNode.found) { - console.log("No closest node found for: ", nodedata.id) - } else { - console.log("Closest: ", closestNode) - } - */ + if (closestNode.found) { + console.log("No closest node found for: ", nodedata.id) + } else { + console.log("Closest: ", closestNode) + } + */ if ( originalLocation.x === 0 && @@ -2517,481 +2520,481 @@ const AngularWorkflow = (defaultprops) => { if (!lastSaved) { return unloadText; } else { - if (workflow.public === false) { - //document.removeEventListener("mousemove", onMouseUpdate, true); - document.removeEventListener("keydown", handleKeyDown, true); - document.removeEventListener("paste", handlePaste, true); - } - } + if (workflow.public === false) { + //document.removeEventListener("mousemove", onMouseUpdate, true); + document.removeEventListener("keydown", handleKeyDown, true); + document.removeEventListener("paste", handlePaste, true); + } + } }); // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once - // onNodeClick + // onNodeClick const onNodeSelect = (event, newAppAuth) => { - // Otherwise everything is SUPER slow - ReactDOM.unstable_batchedUpdates(() => { - const data = event.target.data(); - if (data.isButton) { - if (data.buttonType === "delete") { - const parentNode = cy.getElementById(data.attachedTo); - if (parentNode !== null && parentNode !== undefined) { - removeNode(data.attachedTo) - //parentNode.remove() - } + // Otherwise everything is SUPER slow + ReactDOM.unstable_batchedUpdates(() => { + const data = event.target.data(); + if (data.isButton) { + if (data.buttonType === "delete") { + const parentNode = cy.getElementById(data.attachedTo); + if (parentNode !== null && parentNode !== undefined) { + removeNode(data.attachedTo) + //parentNode.remove() + } - return - } else if (data.buttonType === "set_startnode" && data.type !== "TRIGGER") { - const parentNode = cy.getElementById(data.attachedTo); - if (parentNode !== null && parentNode !== undefined) { - var oldstartnode = cy.getElementById(workflow.start); - if ( - oldstartnode !== null && - oldstartnode !== undefined && - oldstartnode.length > 0 - ) { - try { - oldstartnode[0].data("isStartNode", false); - } catch (e) { - console.log("Startnode error: ", e); - } - } + return + } else if (data.buttonType === "set_startnode" && data.type !== "TRIGGER") { + const parentNode = cy.getElementById(data.attachedTo); + if (parentNode !== null && parentNode !== undefined) { + var oldstartnode = cy.getElementById(workflow.start); + if ( + oldstartnode !== null && + oldstartnode !== undefined && + oldstartnode.length > 0 + ) { + try { + oldstartnode[0].data("isStartNode", false); + } catch (e) { + console.log("Startnode error: ", e); + } + } - workflow.start = parentNode.data("id"); - setLastSaved(false); - parentNode.data("isStartNode", true); - } - - //event.target.unselect(); - setRightSideBarOpen(true); - return + workflow.start = parentNode.data("id"); + setLastSaved(false); + parentNode.data("isStartNode", true); + } - } else if (data.buttonType === "copy") { - console.log("COPY!"); + //event.target.unselect(); + setRightSideBarOpen(true); + return - // 1. Find parent - // 2. Find branches for parent - // 3. Make a new node that's moved a little bit - const parentNode = cy.getElementById(data.attachedTo); - if (parentNode !== null && parentNode !== undefined) { - var newNodeData = JSON.parse(JSON.stringify(parentNode.data())); - newNodeData.id = uuidv4(); - if (newNodeData.position !== undefined) { - newNodeData.position = { - x: newNodeData.position.x + 100, - y: newNodeData.position.y + 100, - }; - } + } else if (data.buttonType === "copy") { + console.log("COPY!"); - newNodeData.isStartNode = false; - newNodeData.errors = []; - newNodeData.is_valid = true; - newNodeData.isValid = true; - newNodeData.label = parentNode.data("label") + "_copy"; + // 1. Find parent + // 2. Find branches for parent + // 3. Make a new node that's moved a little bit + const parentNode = cy.getElementById(data.attachedTo); + if (parentNode !== null && parentNode !== undefined) { + var newNodeData = JSON.parse(JSON.stringify(parentNode.data())); + newNodeData.id = uuidv4(); + if (newNodeData.position !== undefined) { + newNodeData.position = { + x: newNodeData.position.x + 100, + y: newNodeData.position.y + 100, + }; + } - cy.add({ - group: "nodes", - data: newNodeData, - position: newNodeData.position, - }); + newNodeData.isStartNode = false; + newNodeData.errors = []; + newNodeData.is_valid = true; + newNodeData.isValid = true; + newNodeData.label = parentNode.data("label") + "_copy"; - // Readding the icon after moving the node - if ( - newNodeData.app_name !== "Testing" || - newNodeData.app_name !== "Shuffle Workflow" - ) { - } else { - const iconInfo = GetIconInfo(newNodeData); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); + cy.add({ + group: "nodes", + data: newNodeData, + position: newNodeData.position, + }); - const offset = newNodeData.isStartNode ? 36 : 44; - const decoratorNode = { - position: { - x: newNodeData.position.x + offset, - y: newNodeData.position.y + offset, - }, - locked: true, - data: { - isDescriptor: true, - isValid: true, - is_valid: true, - label: "", - image: svgpin_Url, - imageColor: iconInfo.iconBackgroundColor, - attachedTo: newNodeData.id, - }, - }; + // Readding the icon after moving the node + if ( + newNodeData.app_name !== "Testing" || + newNodeData.app_name !== "Shuffle Workflow" + ) { + } else { + const iconInfo = GetIconInfo(newNodeData); + const svg_pin = ``; + const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - cy.add(decoratorNode).unselectify(); - } + const offset = newNodeData.isStartNode ? 36 : 44; + const decoratorNode = { + position: { + x: newNodeData.position.x + offset, + y: newNodeData.position.y + offset, + }, + locked: true, + data: { + isDescriptor: true, + isValid: true, + is_valid: true, + label: "", + image: svgpin_Url, + imageColor: iconInfo.iconBackgroundColor, + attachedTo: newNodeData.id, + }, + }; - workflow.actions.push(newNodeData); + cy.add(decoratorNode).unselectify(); + } - const sourcebranches = workflow.branches.filter( - (foundbranch) => foundbranch.source_id === parentNode.data("id") - ); - const destinationbranches = workflow.branches.filter( - (foundbranch) => - foundbranch.destination_id === parentNode.data("id") - ); + workflow.actions.push(newNodeData); - for (var key in sourcebranches) { - var newbranch = JSON.parse(JSON.stringify(sourcebranches[key])); - newbranch.id = uuidv4(); - newbranch.source_id = newNodeData.id; + const sourcebranches = workflow.branches.filter( + (foundbranch) => foundbranch.source_id === parentNode.data("id") + ); + const destinationbranches = workflow.branches.filter( + (foundbranch) => + foundbranch.destination_id === parentNode.data("id") + ); - newbranch._id = newbranch.id; - newbranch.source = newbranch.source_id; - newbranch.target = newbranch.destination_id; - cy.add({ - group: "edges", - data: newbranch, - }); - } + for (var key in sourcebranches) { + var newbranch = JSON.parse(JSON.stringify(sourcebranches[key])); + newbranch.id = uuidv4(); + newbranch.source_id = newNodeData.id; - for (var key in destinationbranches) { - var newbranch = JSON.parse( - JSON.stringify(destinationbranches[key]) - ); - newbranch.id = uuidv4(); - newbranch.destination_id = newNodeData.id; + newbranch._id = newbranch.id; + newbranch.source = newbranch.source_id; + newbranch.target = newbranch.destination_id; + cy.add({ + group: "edges", + data: newbranch, + }); + } - newbranch._id = newbranch.id; - newbranch.source = newbranch.source_id; - newbranch.target = newbranch.destination_id; - cy.add({ - group: "edges", - data: newbranch, - }); - } + for (var key in destinationbranches) { + var newbranch = JSON.parse( + JSON.stringify(destinationbranches[key]) + ); + newbranch.id = uuidv4(); + newbranch.destination_id = newNodeData.id; - //event.target.unselect(); - return - } - } + newbranch._id = newbranch.id; + newbranch.source = newbranch.source_id; + newbranch.target = newbranch.destination_id; + cy.add({ + group: "edges", + data: newbranch, + }); + } - return; - } else if (data.isDescriptor) { - console.log("Can't select descriptor"); - event.target.unselect(); - return; - } + //event.target.unselect(); + return + } + } - if (data.type === "ACTION") { - setSelectedComment({}) - //var curaction = JSON.parse(JSON.stringify(data)) - // FIXME: Trust it to just work? - //event.target.data() - var curaction = workflow.actions.find((a) => a.id === data.id) - if (!curaction || curaction === undefined) { - console.log("NOT FOUND DATA: ", event.target.data()) - if (data.id !== undefined && data.app_name !== undefined) { - workflow.actions.push(data) - setWorkflow(workflow) - curaction = data - } else { - if (workflow.public !== true) { - alert.error("Action not found. Please remake it."); - } + return; + } else if (data.isDescriptor) { + console.log("Can't select descriptor"); + event.target.unselect(); + return; + } - event.target.remove(); - return; - } - } + if (data.type === "ACTION") { + setSelectedComment({}) + //var curaction = JSON.parse(JSON.stringify(data)) + // FIXME: Trust it to just work? + //event.target.data() + var curaction = workflow.actions.find((a) => a.id === data.id) + if (!curaction || curaction === undefined) { + console.log("NOT FOUND DATA: ", event.target.data()) + if (data.id !== undefined && data.app_name !== undefined) { + workflow.actions.push(data) + setWorkflow(workflow) + curaction = data + } else { + if (workflow.public !== true) { + alert.error("Action not found. Please remake it."); + } - //var newapps = JSON.parse(JSON.stringify(apps)) - var newapps = apps - if (apps === null || apps === undefined || apps.length === 0) { - newapps = filteredApps - } + event.target.remove(); + return; + } + } - // Check ID first, then names etc - // That way it always selects the right IF it exists - var curapp = newapps.find((a) => - a.id === curaction.app_id - ) + //var newapps = JSON.parse(JSON.stringify(apps)) + var newapps = apps + if (apps === null || apps === undefined || apps.length === 0) { + newapps = filteredApps + } - if (curapp === undefined || curapp === null) { - console.log("Couldn't find ID - checking with name & version") + // Check ID first, then names etc + // That way it always selects the right IF it exists + var curapp = newapps.find((a) => + a.id === curaction.app_id + ) - curapp = newapps.find((a) => - a.name === curaction.app_name && - (a.app_version === curaction.app_version || - (a.loop_versions !== null && - a.loop_versions.includes(curaction.app_version))) - ) - } + if (curapp === undefined || curapp === null) { + console.log("Couldn't find ID - checking with name & version") - if (curaction.template === true) { - //newapps. - const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase() - console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname) + curapp = newapps.find((a) => + a.name === curaction.app_name && + (a.app_version === curaction.app_version || + (a.loop_versions !== null && + a.loop_versions.includes(curaction.app_version))) + ) + } - curaction.matching_actions = [] - for (var key in newapps) { - for (var subkey in newapps[key].actions) { - const tmpaction = newapps[key].actions[subkey] - if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) { - console.log("MATCH!: ", newapps[key]) - curaction.matching_actions.push({ - "app_name": newapps[key].name, - "app_version": newapps[key].app_version, - "app_id": newapps[key].id, - "action": tmpaction, - "large_image": newapps[key].large_image, - "app_index": key, - "action_index": subkey, - }) - } - } - } - } + if (curaction.template === true) { + //newapps. + const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase() + console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname) - if (!curapp || curapp === undefined) { - console.log("APPS - couldn't find it: ", newapps) - //alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`); + curaction.matching_actions = [] + for (var key in newapps) { + for (var subkey in newapps[key].actions) { + const tmpaction = newapps[key].actions[subkey] + if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) { + console.log("MATCH!: ", newapps[key]) + curaction.matching_actions.push({ + "app_name": newapps[key].name, + "app_version": newapps[key].app_version, + "app_id": newapps[key].id, + "action": tmpaction, + "large_image": newapps[key].large_image, + "app_index": key, + "action_index": subkey, + }) + } + } + } + } - const tmpapp = { - name: curaction.app_name, - app_name: curaction.app_name, - app_version: curaction.app_version, - id: curaction.app_id, - actions: [curaction], - }; + if (!curapp || curapp === undefined) { + console.log("APPS - couldn't find it: ", newapps) + //alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`); - setSelectedApp(tmpapp); - setSelectedAction(curaction); - } else { - //if (curapp.id !== curaction.id) { - // curaction.app_id = curapp.id - // //.valueOf() - //} - curaction.app_id = curapp.id + const tmpapp = { + name: curaction.app_name, + app_name: curaction.app_name, + app_version: curaction.app_version, + id: curaction.app_id, + actions: [curaction], + }; - setAuthenticationType( - curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null ? { - type: "oauth2", - redirect_uri: curapp.authentication.redirect_uri, - refresh_uri: curapp.authentication.refresh_uri, - token_uri: curapp.authentication.token_uri, - scope: curapp.authentication.scope, - client_id: curapp.authentication.client_id, - client_secret: curapp.authentication.client_secret, - } : { - type: "", - } - ) + setSelectedApp(tmpapp); + setSelectedAction(curaction); + } else { + //if (curapp.id !== curaction.id) { + // curaction.app_id = curapp.id + // //.valueOf() + //} + curaction.app_id = curapp.id - const requiresAuth = curapp.authentication.required; //&& ((curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null)) - setRequiresAuthentication(requiresAuth); - if (curapp.authentication.required) { - //console.log("App requires auth.") - // Setup auth here :) - const authenticationOptions = []; - var findAuthId = ""; - if ( - curaction.authentication_id !== null && - curaction.authentication_id !== undefined && - curaction.authentication_id.length > 0 - ) { - findAuthId = curaction.authentication_id; - } + setAuthenticationType( + curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null ? { + type: "oauth2", + redirect_uri: curapp.authentication.redirect_uri, + refresh_uri: curapp.authentication.refresh_uri, + token_uri: curapp.authentication.token_uri, + scope: curapp.authentication.scope, + client_id: curapp.authentication.client_id, + client_secret: curapp.authentication.client_secret, + } : { + type: "", + } + ) - const tmpAuth = JSON.parse(JSON.stringify(newAppAuth)); - //var tmpAuth = newAppAuth + const requiresAuth = curapp.authentication.required; //&& ((curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null)) + setRequiresAuthentication(requiresAuth); + if (curapp.authentication.required) { + //console.log("App requires auth.") + // Setup auth here :) + const authenticationOptions = []; + var findAuthId = ""; + if ( + curaction.authentication_id !== null && + curaction.authentication_id !== undefined && + curaction.authentication_id.length > 0 + ) { + findAuthId = curaction.authentication_id; + } - for (var key in tmpAuth) { - var item = tmpAuth[key]; + const tmpAuth = JSON.parse(JSON.stringify(newAppAuth)); + //var tmpAuth = newAppAuth - const newfields = {}; - for (var filterkey in item.fields) { - newfields[item.fields[filterkey].key] = item.fields[filterkey].value; - } + for (var key in tmpAuth) { + var item = tmpAuth[key]; - item.fields = newfields; - if (item.app.name === curapp.name) { - authenticationOptions.push(item); - if (item.id === findAuthId) { - curaction.selectedAuthentication = item; - } - } - } + const newfields = {}; + for (var filterkey in item.fields) { + newfields[item.fields[filterkey].key] = item.fields[filterkey].value; + } - curaction.authentication = authenticationOptions; - if ( - curaction.selectedAuthentication === null || - curaction.selectedAuthentication === undefined || - curaction.selectedAuthentication.length === "" - ) { - curaction.selectedAuthentication = {}; - } - } else { - curaction.authentication = []; - curaction.authentication_id = ""; - curaction.selectedAuthentication = {}; - } + item.fields = newfields; + if (item.app.name === curapp.name) { + authenticationOptions.push(item); + if (item.id === findAuthId) { + curaction.selectedAuthentication = item; + } + } + } - if ( - curaction.parameters !== undefined && - curaction.parameters !== null && - curaction.parameters.length > 0 - ) { - for (var key in curaction.parameters) { - if ( - curaction.parameters[key].options !== undefined && - curaction.parameters[key].options !== null && - curaction.parameters[key].options.length > 0 && - curaction.parameters[key].value === "" - ) { - curaction.parameters[key].value = curaction.parameters[key].options[0]; - } - } - } else { - console.log("Should check APP if it has the same params as ACTION") - for (var key in curapp.actions) { - const tmpaction = curapp.actions[key] - if (tmpaction.name === curaction.name) { - console.log("Found action - needs change?", tmpaction) - if (tmpaction.parameters !== undefined && tmpaction.parameters !== null && tmpaction.parameters.length > 0) { - curaction.parameters = JSON.parse(JSON.stringify(tmpaction.parameters)) - } - break - } - } - } + curaction.authentication = authenticationOptions; + if ( + curaction.selectedAuthentication === null || + curaction.selectedAuthentication === undefined || + curaction.selectedAuthentication.length === "" + ) { + curaction.selectedAuthentication = {}; + } + } else { + curaction.authentication = []; + curaction.authentication_id = ""; + curaction.selectedAuthentication = {}; + } - console.log("ACTION CLICK: ", curaction) - setSelectedApp(curapp); - setSelectedAction(curaction); + if ( + curaction.parameters !== undefined && + curaction.parameters !== null && + curaction.parameters.length > 0 + ) { + for (var key in curaction.parameters) { + if ( + curaction.parameters[key].options !== undefined && + curaction.parameters[key].options !== null && + curaction.parameters[key].options.length > 0 && + curaction.parameters[key].value === "" + ) { + curaction.parameters[key].value = curaction.parameters[key].options[0]; + } + } + } else { + console.log("Should check APP if it has the same params as ACTION") + for (var key in curapp.actions) { + const tmpaction = curapp.actions[key] + if (tmpaction.name === curaction.name) { + console.log("Found action - needs change?", tmpaction) + if (tmpaction.parameters !== undefined && tmpaction.parameters !== null && tmpaction.parameters.length > 0) { + curaction.parameters = JSON.parse(JSON.stringify(tmpaction.parameters)) + } + break + } + } + } - cy.removeListener("drag"); - cy.removeListener("free"); - cy.on("drag", "node", (e) => onNodeDrag(e, curaction)); - cy.on("free", "node", (e) => onNodeDragStop(e, curaction)); - } + console.log("ACTION CLICK: ", curaction) + setSelectedApp(curapp); + setSelectedAction(curaction); - if (environments !== undefined && environments !== null && (typeof environments === "array" || typeof environments === "object")) { - var parsedenv = environments - //if (typeof environments === "object") { - // parsedenv = [environments] - //} + cy.removeListener("drag"); + cy.removeListener("free"); + cy.on("drag", "node", (e) => onNodeDrag(e, curaction)); + cy.on("free", "node", (e) => onNodeDragStop(e, curaction)); + } - const envs = parsedenv.find((a) => a.Name === curaction.environment); - var env = environments[defaultEnvironmentIndex] - if (envs !== undefined && envs !== null) { - env = envs - } + if (environments !== undefined && environments !== null && (typeof environments === "array" || typeof environments === "object")) { + var parsedenv = environments + //if (typeof environments === "object") { + // parsedenv = [environments] + //} - setSelectedActionEnvironment(env); - } - } else if (data.type === "TRIGGER") { - setSelectedComment({}) - if (workflow.triggers === null) { - workflow.triggers = [] - } + const envs = parsedenv.find((a) => a.Name === curaction.environment); + var env = environments[defaultEnvironmentIndex] + if (envs !== undefined && envs !== null) { + env = envs + } - var trigger_index = workflow.triggers.findIndex( - (a) => a.id === data.id - ); + setSelectedActionEnvironment(env); + } + } else if (data.type === "TRIGGER") { + setSelectedComment({}) + if (workflow.triggers === null) { + workflow.triggers = [] + } - //console.log("Trigger: ", data, trigger_index) - if (trigger_index === -1) { - workflow.triggers.push(data) - trigger_index = workflow.triggers.length-1 - setWorkflow(workflow) - } + var trigger_index = workflow.triggers.findIndex( + (a) => a.id === data.id + ); - //console.log("Trigger2: ", data, trigger_index) - //if (data.id !== undefined && data.app_name !== undefined) { - // //newapps.push(data) - // workflow.actions.push(data) - // curaction = data - //} else { - // alert.error("Action not found. Please remake it."); - // event.target.remove(); - // return; - //} + //console.log("Trigger: ", data, trigger_index) + if (trigger_index === -1) { + workflow.triggers.push(data) + trigger_index = workflow.triggers.length - 1 + setWorkflow(workflow) + } - if (data.app_name === "Shuffle Workflow") { - getAvailableWorkflows(trigger_index); - getSettings(); - } else if (data.app_name === "Webhook") { - if (workflow.triggers[trigger_index].parameters !== undefined) { - workflow.triggers[trigger_index].parameters[0] = { - name: "url", - value: referenceUrl + "webhook_" + workflow.triggers[trigger_index].id, - }; - } - } + //console.log("Trigger2: ", data, trigger_index) + //if (data.id !== undefined && data.app_name !== undefined) { + // //newapps.push(data) + // workflow.actions.push(data) + // curaction = data + //} else { + // alert.error("Action not found. Please remake it."); + // event.target.remove(); + // return; + //} - console.log("DATA: ", data) - setSelectedTriggerIndex(trigger_index); - setSelectedTrigger(data); - setSelectedActionEnvironment(data.env); - } else if (data.type === "COMMENT") { - setSelectedComment(data); - } else { - alert.error("Can't handle " + data.type); - return; - } + if (data.app_name === "Shuffle Workflow") { + getAvailableWorkflows(trigger_index); + getSettings(); + } else if (data.app_name === "Webhook") { + if (workflow.triggers[trigger_index].parameters !== undefined && workflow.triggers[trigger_index].parameters !== null && workflow.triggers[trigger_index].parameters.length > 0) { + workflow.triggers[trigger_index].parameters[0] = { + name: "url", + value: referenceUrl + "webhook_" + workflow.triggers[trigger_index].id, + }; + } + } - setRightSideBarOpen(true); - setLastSaved(false); - setScrollConfig({ - top: 0, - left: 0, - selected: "", - }); + console.log("DATA: ", data) + setSelectedTriggerIndex(trigger_index); + setSelectedTrigger(data); + setSelectedActionEnvironment(data.env); + } else if (data.type === "COMMENT") { + setSelectedComment(data); + } else { + alert.error("Can't handle " + data.type); + return; + } - console.log("DOne in the node update") - - sendStreamRequest({ - "item": "node", - "type": "select", - "id": data.id, - "userid": userdata.id, - "location": { - "x": event.target.position("x"), - "y": event.target.position("y"), - } - }) + setRightSideBarOpen(true); + setLastSaved(false); + setScrollConfig({ + top: 0, + left: 0, + selected: "", + }); - }) + console.log("DOne in the node update") + + sendStreamRequest({ + "item": "node", + "type": "select", + "id": data.id, + "userid": userdata.id, + "location": { + "x": event.target.position("x"), + "y": event.target.position("y"), + } + }) + + }) } - const activateApp = (appid, refresh) => { - fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", + const activateApp = (appid, refresh) => { + fetch(globalUrl + "/api/v1/apps/" + appid + "/activate", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Failed to activate") - } + .then((response) => { + if (response.status !== 200) { + console.log("Failed to activate") + } - return response.json() - }) - .then((responseJson) => { - if (responseJson.success === false) { - alert.error("Failed to activate the app") - } else { - alert.success("App activated for your organization! Refresh the page to use the app.") + return response.json() + }) + .then((responseJson) => { + if (responseJson.success === false) { + alert.error("Failed to activate the app") + } else { + alert.success("App activated for your organization! Refresh the page to use the app.") - if (refresh === true) { - getApps() - } - } - }) - .catch(error => { - //alert.error(error.toString()) - console.log("Activate app error: ", error.toString()) - }); - } + if (refresh === true) { + getApps() + } + } + }) + .catch(error => { + //alert.error(error.toString()) + console.log("Activate app error: ", error.toString()) + }); + } const GetExampleResult = (item) => { var exampledata = item.example === undefined ? "" : item.example; @@ -3054,12 +3057,12 @@ const AngularWorkflow = (defaultprops) => { // Finds the FIRST json only if (jsonvalid) { - try { - exampledata = JSON.parse(foundResult.result) - } catch (e) { - console.log("Result: ", exampledata) + try { + exampledata = JSON.parse(foundResult.result) + } catch (e) { + console.log("Result: ", exampledata) - } + } break; } @@ -3078,8 +3081,8 @@ const AngularWorkflow = (defaultprops) => { return ""; } - //console.log("NOT REPLACING ON PURPOSE!!") - //return "" + //console.log("NOT REPLACING ON PURPOSE!!") + //return "" // Basically just a stupid if-else :) const synonyms = { @@ -3115,7 +3118,7 @@ const AngularWorkflow = (defaultprops) => { "value", "item", ], - tags: ["tags", "taxonomies"], + tags: ["tags", "taxonomies"], }; // 1. Find the right synonym @@ -3187,8 +3190,8 @@ const AngularWorkflow = (defaultprops) => { // Takes an action as input, then runs through and updates the relevant fields // based on previous actions' - // Uses lots of synonyms - // autocomplete + // Uses lots of synonyms + // autocomplete const RunAutocompleter = (dstdata) => { // **PS: The right action should already be set here** // 1. Check execution argument @@ -3202,15 +3205,15 @@ const AngularWorkflow = (defaultprops) => { continue } - if (param.options !== undefined && param.options !== null && param.options.length > 0) { - continue - } + if (param.options !== undefined && param.options !== null && param.options.length > 0) { + continue + } const paramname = param.name.toLowerCase().trim().replaceAll("_", " "); const foundresult = GetParamMatch(paramname, exampledata, ""); if (foundresult.length > 0) { - console.log("FOUND ReS for field: ", dstdata.parameters[paramkey].name, foundresult) + console.log("FOUND ReS for field: ", dstdata.parameters[paramkey].name, foundresult) if (dstdata.parameters[paramkey].value.length === 0) { dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`; dstdata.parameters[paramkey].autocompleted = true @@ -3238,9 +3241,9 @@ const AngularWorkflow = (defaultprops) => { continue } - if (param.options !== undefined && param.options !== null && param.options.length > 0) { - continue - } + if (param.options !== undefined && param.options !== null && param.options.length > 0) { + continue + } const paramname = param.name .toLowerCase() @@ -3251,7 +3254,7 @@ const AngularWorkflow = (defaultprops) => { if (foundresult.length > 0) { if (dstdata.parameters[paramkey].value.length === 0) { dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`; - dstdata.parameters[paramkey].autocompleted = true + dstdata.parameters[paramkey].autocompleted = true } else { //dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`; } @@ -3269,58 +3272,58 @@ const AngularWorkflow = (defaultprops) => { setLastSaved(false); const edge = event.target.data(); - console.log("edge added: ", edge) - if (edge.source === undefined && edge.target === undefined) { - return - } + console.log("edge added: ", edge) + if (edge.source === undefined && edge.target === undefined) { + return + } - if (edge.readded === true) { - console.log("Readded edge - stopping") + if (edge.readded === true) { + console.log("Readded edge - stopping") - event.target.data("readded", false) - return - } + event.target.data("readded", false) + return + } - const sourcenode = cy.getElementById(edge.source) - const destinationnode = cy.getElementById(edge.target) - if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { - } else { - console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) - if (sourcenode.data("type") === "TRIGGER") { - if (sourcenode.data("app_name") !== "Shuffle Workflow" && sourcenode.data("app_name") !== "User Input") { - setTimeout(() => { - const alledges = cy.edges().jsons() - console.log("edges: ", alledges, edge) - var targetedge = alledges.findIndex( - (data) => data.data.source === edge.source && data.data.id !== edge.id - ) + const sourcenode = cy.getElementById(edge.source) + const destinationnode = cy.getElementById(edge.target) + if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { + } else { + console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) + if (sourcenode.data("type") === "TRIGGER") { + if (sourcenode.data("app_name") !== "Shuffle Workflow" && sourcenode.data("app_name") !== "User Input") { + setTimeout(() => { + const alledges = cy.edges().jsons() + console.log("edges: ", alledges, edge) + var targetedge = alledges.findIndex( + (data) => data.data.source === edge.source && data.data.id !== edge.id + ) - console.log("Node: ", targetedge) - if (targetedge !== -1) { - event.target.remove() + console.log("Node: ", targetedge) + if (targetedge !== -1) { + event.target.remove() - //console.log("Found branch already!") - alert.info("Triggers can have exactly one target node") - return - - - // name: "Shuffle Workflow", - // name: "User Input", - } else { - console.log("Node doesn't already have one") - } - }, 50) - } - } + //console.log("Found branch already!") + alert.info("Triggers can have exactly one target node") + return - const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position()) - const currentedge = cy.getElementById(edge.id) - if (currentedge !== undefined && currentedge !== null) { - currentedge.style('control-point-distance', edgeCurve.distance) - currentedge.style('control-point-weight', edgeCurve.weight) - } - } + // name: "Shuffle Workflow", + // name: "User Input", + } else { + console.log("Node doesn't already have one") + } + }, 50) + } + } + + const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position()) + const currentedge = cy.getElementById(edge.id) + if (currentedge !== undefined && currentedge !== null) { + currentedge.style('control-point-distance', edgeCurve.distance) + currentedge.style('control-point-weight', edgeCurve.weight) + } + + } var targetnode = workflow.triggers.findIndex( @@ -3338,14 +3341,14 @@ const AngularWorkflow = (defaultprops) => { } } - const eventTarget = event.target.target() - console.log("BUTTON ADDED! Find parent from: ", eventTarget) + const eventTarget = event.target.target() + console.log("BUTTON ADDED! Find parent from: ", eventTarget) if (eventTarget.data("isButton") === true) { - const parentNode = cy.getElementById(eventTarget.data("attachedTo")) - event.target.remove() - console.log("Setting it to parentnode: ", parentNode.data()) - if (parentNode !== undefined && parentNode !== null) { - //event.target.data("target", eventTarget.data("attachedTo")) + const parentNode = cy.getElementById(eventTarget.data("attachedTo")) + event.target.remove() + console.log("Setting it to parentnode: ", parentNode.data()) + if (parentNode !== undefined && parentNode !== null) { + //event.target.data("target", eventTarget.data("attachedTo")) const newEdgeUuid = uuidv4() const newcybranch = { @@ -3362,14 +3365,14 @@ const AngularWorkflow = (defaultprops) => { } cy.add(edgeToBeAdded); - } - } + } + } if ( eventTarget.data("isDescriptor") === true || - eventTarget.data("type") === "COMMENT" + eventTarget.data("type") === "COMMENT" ) { - console.log("Removing because of descriptor or comment") + console.log("Removing because of descriptor or comment") event.target.remove(); return; } @@ -3404,13 +3407,13 @@ const AngularWorkflow = (defaultprops) => { (data) => data.id === edge.source ); if (targetnode === -1) { - if (targetnode.type !== "TRIGGER") { - alert.error("Can't make arrow to starting node"); - event.target.remove(); - break; - } - - found = true; + if (targetnode.type !== "TRIGGER") { + alert.error("Can't make arrow to starting node"); + event.target.remove(); + break; + } + + found = true; } } else if (edge.source === workflow.branches[key].source_id) { // FIXME: Verify multi-target for triggers @@ -3430,18 +3433,18 @@ const AngularWorkflow = (defaultprops) => { // Find the targetnode and check if its a trigger // FIXME - do this for both actions and other types? /* - targetnode = workflow.triggers.findIndex(data => data.id === edge.target) - if (targetnode !== -1) { - console.log("TARGETNODE: ", targetnode) - if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow") { - } else { - alert.error("Can't have triggers as target of branch") - event.target.remove() - found = true - break - } - } - */ + targetnode = workflow.triggers.findIndex(data => data.id === edge.target) + if (targetnode !== -1) { + console.log("TARGETNODE: ", targetnode) + if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow") { + } else { + alert.error("Can't have triggers as target of branch") + event.target.remove() + found = true + break + } + } + */ } } @@ -3489,21 +3492,21 @@ const AngularWorkflow = (defaultprops) => { const node = event.target; const nodedata = event.target.data(); - if (Object.keys(nodedata).length === 1) { - console.log("Check if another node actually exists before adding") - } + if (Object.keys(nodedata).length === 1) { + console.log("Check if another node actually exists before adding") + } if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined) ) { - //if (nodedata.app_id === undefined) { - //console.log("Returning because node is not valid: ", nodedata) + //if (nodedata.app_id === undefined) { + //console.log("Returning because node is not valid: ", nodedata) return; } - // DONT MOVE THIS LINE RIGHT HERE v + // DONT MOVE THIS LINE RIGHT HERE v setLastSaved(false) - // Dont move the line above. May break stuff. + // Dont move the line above. May break stuff. if (node.isNode() && cy.nodes().size() === 1) { @@ -3511,7 +3514,7 @@ const AngularWorkflow = (defaultprops) => { nodedata.isStartNode = true; } else { if (workflow.actions === null) { - console.log("Returning because node has no value") + console.log("Returning because node has no value") return; } @@ -3525,13 +3528,13 @@ const AngularWorkflow = (defaultprops) => { } if (nodedata.type === "ACTION") { - /* - var curaction = workflow.actions.find((a) => a.id === nodedata.id); - if (curaction === null || curaction === undefined) { - alert.error("Node not found. Please remake it.") - event.target.remove(); - } - */ + /* + var curaction = workflow.actions.find((a) => a.id === nodedata.id); + if (curaction === null || curaction === undefined) { + alert.error("Node not found. Please remake it.") + event.target.remove(); + } + */ if ( workflow.actions.length === 1 && workflow.actions[0].id === workflow.start @@ -3586,11 +3589,11 @@ const AngularWorkflow = (defaultprops) => { ); newparam.id = uuidv4(); - if (newparam.value === undefined || newparam.value === null) { - newparam.value = ""; - } else { - newparam.value = newparam.value; - } + if (newparam.value === undefined || newparam.value === null) { + newparam.value = ""; + } else { + newparam.value = newparam.value; + } newparameters.push(newparam); } @@ -3670,42 +3673,42 @@ const AngularWorkflow = (defaultprops) => { return; } - // Check if the source is trigger and can start - console.log("Removed: ", edge.data()) + // Check if the source is trigger and can start + console.log("Removed: ", edge.data()) const allNodes = cy.nodes().jsons() - for (var key in allNodes) { - const curnode = allNodes[key] - if (curnode.data.type !== "TRIGGER") { - continue - } + for (var key in allNodes) { + const curnode = allNodes[key] + if (curnode.data.type !== "TRIGGER") { + continue + } - if (curnode.data.id === edge.data("source")) { - console.log("Found matching trigger source: ", curnode) - if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") { - // If it's started, READD the edge - if (curnode.data.status === "running") { - console.log("Edge is running - readd it: ", edge.data()) - - // Just making sure it's not running infinitely - var newdata = edge.data() - newdata.readded = true + if (curnode.data.id === edge.data("source")) { + console.log("Found matching trigger source: ", curnode) + if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") { + // If it's started, READD the edge + if (curnode.data.status === "running") { + console.log("Edge is running - readd it: ", edge.data()) - try { - cy.add({ - group: "edges", - data: newdata, - }) + // Just making sure it's not running infinitely + var newdata = edge.data() + newdata.readded = true - alert.error("You must STOP the trigger before deleting its branches") - } catch (e) { - console.log("Failed re-adding edge: ", e) - } - } + try { + cy.add({ + group: "edges", + data: newdata, + }) - //status: "uninitialized", - } - } - } + alert.error("You must STOP the trigger before deleting its branches") + } catch (e) { + console.log("Failed re-adding edge: ", e) + } + } + + //status: "uninitialized", + } + } + } workflow.branches = workflow.branches.filter( (a) => a.id !== edge.data().id @@ -3778,7 +3781,7 @@ const AngularWorkflow = (defaultprops) => { data: data, }) - //console.log("REMOVED: ", data) + //console.log("REMOVED: ", data) setHistory(history); setHistoryIndex(history.length); } @@ -3831,15 +3834,15 @@ const AngularWorkflow = (defaultprops) => { } } - if ( - event.target !== undefined && - event.target !== null - ) { - if (event.target.localName !== "body") { - console.log("Skipping because body is not targeted") - return; - } - } + if ( + event.target !== undefined && + event.target !== null + ) { + if (event.target.localName !== "body") { + console.log("Skipping because body is not targeted") + return; + } + } console.log("CTRL+C"); if (cy !== undefined) { @@ -3871,21 +3874,21 @@ const AngularWorkflow = (defaultprops) => { //console.log("CTRL+V") // The below parts are handled in the function handlePaste() /* - const clipboard = navigator.clipboard - if (clipboard === undefined || window === undefined || window === null) { - alert.error("Can only use cliboard over HTTPS (port 3443)") - return - } + const clipboard = navigator.clipboard + if (clipboard === undefined || window === undefined || window === null) { + alert.error("Can only use cliboard over HTTPS (port 3443)") + return + } - console.log("CLIPBOARD: ", window.clipboardData) - const pastedData = window.clipboardData.getData('Text'); - console.log("PASTED: ", pastedData) + console.log("CLIPBOARD: ", window.clipboardData) + const pastedData = window.clipboardData.getData('Text'); + console.log("PASTED: ", pastedData) - - //var tmpAuth = JSON.parse(JSON.stringify(appAuthentication)) - var jsonvalid = true - var parsedjson = [] - */ + + //var tmpAuth = JSON.parse(JSON.stringify(appAuthentication)) + var jsonvalid = true + var parsedjson = [] + */ } break; case 88: @@ -3920,10 +3923,10 @@ const AngularWorkflow = (defaultprops) => { //console.log("Skipping because body is not targeted") return; } - } - - //console.log("PATH2: ", event.target) - if ( + } + + //console.log("PATH2: ", event.target) + if ( event.target !== undefined && event.target !== null ) { @@ -3931,7 +3934,7 @@ const AngularWorkflow = (defaultprops) => { //console.log("Skipping because body is not targeted") return; } - } + } event.preventDefault(); @@ -3948,7 +3951,7 @@ const AngularWorkflow = (defaultprops) => { for (var key in parsedjson) { const item = parsedjson[key]; console.log("Adding: ", item); - item.data.id = uuidv4() + item.data.id = uuidv4() cy.add({ group: item.group, @@ -4017,16 +4020,16 @@ const AngularWorkflow = (defaultprops) => { } } - // FIXME: Don't allow multiple in cloud yet. Cloud -> Onprem isn't stable. - if (isCloud) { - if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { - setEnvironments(responseJson); - } else { - setEnvironments([{ Name: "Cloud", Type: "cloud" }]); - } - } else { - setEnvironments(responseJson); - } + // FIXME: Don't allow multiple in cloud yet. Cloud -> Onprem isn't stable. + if (isCloud) { + if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { + setEnvironments(responseJson); + } else { + setEnvironments([{ Name: "Cloud", Type: "cloud" }]); + } + } else { + setEnvironments(responseJson); + } }) .catch((error) => { //alert.error(error.toString()); @@ -4063,29 +4066,29 @@ const AngularWorkflow = (defaultprops) => { } } - const cytoscapeElement = document.getElementById("cytoscape_view") - if (cytoscapeElement !== undefined && cytoscapeElement !== null) { - cytoscapeElement.style.cursor = "default" - } + const cytoscapeElement = document.getElementById("cytoscape_view") + if (cytoscapeElement !== undefined && cytoscapeElement !== null) { + cytoscapeElement.style.cursor = "default" + } - // Skipping node editing if it's the selected one - if (cy !== undefined) { - const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { - const item = typeIds[idkey] - if (item.data.id === nodedata.id) { - return - } - } - } - //if (nodedata.id === selectedAction.id || nodedata.id === selectedTrigger.id) { - // return - //} + // Skipping node editing if it's the selected one + if (cy !== undefined) { + const typeIds = cy.elements('node:selected').jsons(); + for (var idkey in typeIds) { + const item = typeIds[idkey] + if (item.data.id === nodedata.id) { + return + } + } + } + //if (nodedata.id === selectedAction.id || nodedata.id === selectedTrigger.id) { + // return + //} var parsedStyle = { "border-width": "1px", "font-size": "18px", - //"cursor": "default", + //"cursor": "default", } if ((nodedata.app_name === "Testing" || nodedata.app_name === "Shuffle Tools") && !nodedata.isStartNode) { @@ -4195,7 +4198,7 @@ const AngularWorkflow = (defaultprops) => { }); }; - const addSuggestionButtons = (event) => { + const addSuggestionButtons = (event) => { var parentNode = cy.$("#" + event.target.data("id")); if (parentNode.data("isButton") || parentNode.data("buttonId")) return; @@ -4205,31 +4208,31 @@ const AngularWorkflow = (defaultprops) => { parentNode.data("circleId", circleId); - var appid = "1234" - var suggestions = [{ - app_name: "TheHive", - app_version: "1.1.0", - app_id: appid, - sharing: false, - private_id: false, - isStartNode: false, - label: "Suggestion 1", - large_image: "data:image/png;base64,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", - finished: false, - is_valid: true, - isSuggestion: true, - attachedTo: event.target.data("id"), - }] + var appid = "1234" + var suggestions = [{ + app_name: "TheHive", + app_version: "1.1.0", + app_id: appid, + sharing: false, + private_id: false, + isStartNode: false, + label: "Suggestion 1", + large_image: "data:image/png;base64,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", + finished: false, + is_valid: true, + isSuggestion: true, + attachedTo: event.target.data("id"), + }] //isButton: true, cy.add({ group: "nodes", data: suggestions[0], - position: { x: px, y: py }, + position: { x: px, y: py }, locked: true, }); - //suggestions[0].id = uuidv4() + //suggestions[0].id = uuidv4() //cy.add({ // group: "nodes", // data: suggestions[0], @@ -4313,14 +4316,14 @@ const AngularWorkflow = (defaultprops) => { const nodedata = event.target.data(); if (nodedata.finished === false) { - console.log("NODE: ", nodedata) + console.log("NODE: ", nodedata) return; } - const cytoscapeElement = document.getElementById("cytoscape_view") - if (cytoscapeElement !== undefined && cytoscapeElement !== null) { - cytoscapeElement.style.cursor = "pointer" - } + const cytoscapeElement = document.getElementById("cytoscape_view") + if (cytoscapeElement !== undefined && cytoscapeElement !== null) { + cytoscapeElement.style.cursor = "pointer" + } //var parentNode = cy.$("#" + event.target.data("id")); //if (parentNode.data("isButton") || parentNode.data("buttonId")) return; @@ -4356,18 +4359,18 @@ const AngularWorkflow = (defaultprops) => { if (!found) { addDeleteButton(event); - if (nodedata.type === "TRIGGER") { - if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") { - addCopyButton(event); - } - } else { - addCopyButton(event); - addStartnodeButton(event); - } + if (nodedata.type === "TRIGGER") { + if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") { + addCopyButton(event); + } + } else { + addCopyButton(event); + addStartnodeButton(event); + } - // autocomplete - // right click - // suggestions + // autocomplete + // right click + // suggestions //addSuggestionButtons(event); } } @@ -4376,55 +4379,55 @@ const AngularWorkflow = (defaultprops) => { "border-width": "7px", "border-opacity": ".7", "font-size": "25px", - //"cursor": "pointer", + //"cursor": "pointer", } - const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { - const item = typeIds[idkey] - if (item.data.id === nodedata.id) { - //console.log("items: ", item.data.id, nodedata.id) - parsedStyle["border-width"] = "12px" - break - } - } + const typeIds = cy.elements('node:selected').jsons(); + for (var idkey in typeIds) { + const item = typeIds[idkey] + if (item.data.id === nodedata.id) { + //console.log("items: ", item.data.id, nodedata.id) + parsedStyle["border-width"] = "12px" + break + } + } if (nodedata.type !== "COMMENT") { parsedStyle.color = "white"; - //if (!event.target.data("isButton") && !event.target.data("buttonId")) { - // const px = event.target.position("x") - 0; - // const py = event.target.position("y") - 50; - // const circleId = (newNodeId = uuidv4()); + //if (!event.target.data("isButton") && !event.target.data("buttonId")) { + // const px = event.target.position("x") - 0; + // const py = event.target.position("y") - 50; + // const circleId = (newNodeId = uuidv4()); - // console.log("Got px, py: ", px, py) - // - // cy.add({ - // group: "nodes", - // data: { - // weight: 30, - // id: circleId, - // isButton: true, - // attachedTo: event.target.data("id"), - // buttonType: "edgehandler", - // is_valid: true, - // }, - // position: { x: px, y: py }, - // locked: true, - // }) - //} + // console.log("Got px, py: ", px, py) + // + // cy.add({ + // group: "nodes", + // data: { + // weight: 30, + // id: circleId, + // isButton: true, + // attachedTo: event.target.data("id"), + // buttonType: "edgehandler", + // is_valid: true, + // }, + // position: { x: px, y: py }, + // locked: true, + // }) + //} } - if (event.target !== undefined && event.target !== null) { - event.target.animate( - { - style: parsedStyle, - }, - { - duration: animationDuration, - } - ); - } + if (event.target !== undefined && event.target !== null) { + event.target.animate( + { + style: parsedStyle, + }, + { + duration: animationDuration, + } + ); + } const outgoingEdges = event.target.outgoers("edge"); const incomingEdges = event.target.incomers("edge"); @@ -4448,10 +4451,10 @@ const AngularWorkflow = (defaultprops) => { return; } - const cytoscapeElement = document.getElementById("cytoscape_view") - if (cytoscapeElement !== undefined && cytoscapeElement !== null) { - cytoscapeElement.style.cursor = "default" - } + const cytoscapeElement = document.getElementById("cytoscape_view") + if (cytoscapeElement !== undefined && cytoscapeElement !== null) { + cytoscapeElement.style.cursor = "default" + } //event.target.removeStyle(); }; @@ -4467,10 +4470,10 @@ const AngularWorkflow = (defaultprops) => { return; } - const cytoscapeElement = document.getElementById("cytoscape_view") - if (cytoscapeElement !== undefined && cytoscapeElement !== null) { - cytoscapeElement.style.cursor = "pointer" - } + const cytoscapeElement = document.getElementById("cytoscape_view") + if (cytoscapeElement !== undefined && cytoscapeElement !== null) { + cytoscapeElement.style.cursor = "pointer" + } //const sourcecolor = cy // .getElementById(event.target.data("source")) @@ -4479,112 +4482,112 @@ const AngularWorkflow = (defaultprops) => { // .getElementById(event.target.data("target")) // .style("border-color"); - ////console.log(sourcecolor, targetcolor) + ////console.log(sourcecolor, targetcolor) //if ( // sourcecolor !== null && // sourcecolor !== undefined && // targetcolor !== null && // targetcolor !== undefined && - // !sourcecolor.includes("rgb") && - // !targetcolor.includes("rgb") + // !sourcecolor.includes("rgb") && + // !targetcolor.includes("rgb") //) { - // console.log(sourcecolor) - // console.log(targetcolor) + // console.log(sourcecolor) + // console.log(targetcolor) - // if (event.target !== null && event.target.value !== null) { - // event.target.animate({ - // style: { - // "target-arrow-color": targetcolor, - // "line-fill": "linear-gradient", - // "line-gradient-stop-colors": [sourcecolor, targetcolor], - // "line-gradient-stop-positions": [0, 1], - // }, - // duration: animationDuration, - // }) - // } else { - // event.target.animate({ - // style: { - // "target-arrow-color": targetcolor, - // "line-fill": "linear-gradient", + // if (event.target !== null && event.target.value !== null) { + // event.target.animate({ + // style: { + // "target-arrow-color": targetcolor, + // "line-fill": "linear-gradient", + // "line-gradient-stop-colors": [sourcecolor, targetcolor], + // "line-gradient-stop-positions": [0, 1], + // }, + // duration: animationDuration, + // }) + // } else { + // event.target.animate({ + // style: { + // "target-arrow-color": targetcolor, + // "line-fill": "linear-gradient", // "line-gradient-stop-colors": ["#41dcab", "#41dcab"], - // "line-gradient-stop-positions": [0, 1], - // }, - // duration: animationDuration, - // }) + // "line-gradient-stop-positions": [0, 1], + // }, + // duration: animationDuration, + // }) - // } + // } //} - if (event.target !== undefined && event.target !== null) { - //const targetcolor = "#66a8b1" - //const parsedStyle = { + if (event.target !== undefined && event.target !== null) { + //const targetcolor = "#66a8b1" + //const parsedStyle = { // "width": "10px", - // "font-size": "18px", + // "font-size": "18px", // "target-arrow-color": "#66a8b1", - // "color": "#66a8b1", - //} + // "color": "#66a8b1", + //} - //event.target.addClass("shuffle-hover-highlight"); + //event.target.addClass("shuffle-hover-highlight"); - //console.log("Style1: ", event.target) - //console.log("Style: ", event.target.style()) + //console.log("Style1: ", event.target) + //console.log("Style: ", event.target.style()) - //event.target.animate( - // { - // style: parsedStyle, - // }, - // { - // duration: animationDuration, - // } - //) - } + //event.target.animate( + // { + // style: parsedStyle, + // }, + // { + // duration: animationDuration, + // } + //) + } } - // Calculates how a trigger should curve - // Thanks to: - // https://codepen.io/guillaumethomas/pen/xxbbBKO - const calculateEdgeCurve = (sourcenodePosition, destinationnodePosition) => { - const xParsed = destinationnodePosition.x - sourcenodePosition.x - const yParsed = destinationnodePosition.y - sourcenodePosition.y + // Calculates how a trigger should curve + // Thanks to: + // https://codepen.io/guillaumethomas/pen/xxbbBKO + const calculateEdgeCurve = (sourcenodePosition, destinationnodePosition) => { + const xParsed = destinationnodePosition.x - sourcenodePosition.x + const yParsed = destinationnodePosition.y - sourcenodePosition.y - const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed); - const costheta = xParsed / z; - const alpha = 0.25; - var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta]; - var controlPointWeight = [alpha, 1-alpha] + const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed); + const costheta = xParsed / z; + const alpha = 0.25; + var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta]; + var controlPointWeight = [alpha, 1 - alpha] - //'control-point-weight': ['0.33', '0.66'], - //var controlPointWeight = ["0.33", "0.66"] - //var controlPointDistance = ["33%", "-66%"] - //var controlPointWeight = ["0.00", "1.00"] - /* - if (yParsed !== 0) { - //const degreeFound = Math.atan2(xParsed / yParsed) - const degreeFound = Math.atan2(xParsed, yParsed) * 180 / Math.PI + //'control-point-weight': ['0.33', '0.66'], + //var controlPointWeight = ["0.33", "0.66"] + //var controlPointDistance = ["33%", "-66%"] + //var controlPointWeight = ["0.00", "1.00"] + /* + if (yParsed !== 0) { + //const degreeFound = Math.atan2(xParsed / yParsed) + const degreeFound = Math.atan2(xParsed, yParsed) * 180 / Math.PI - if (degreeFound > 90 && degreeFound < 180) { - console.log("TOPRIGHT") - } else if (degreeFound < 90 && degreeFound > 0) { - console.log("BOTTOMRIGHT") - } else if (degreeFound < 0 && degreeFound > -90) { - console.log("BOTTOMLEFT") - //controlPointWeight = ["0.20", "0.80"] - //controlPointWeight = "0.7" - //controlPointDistance = "50%" + if (degreeFound > 90 && degreeFound < 180) { + console.log("TOPRIGHT") + } else if (degreeFound < 90 && degreeFound > 0) { + console.log("BOTTOMRIGHT") + } else if (degreeFound < 0 && degreeFound > -90) { + console.log("BOTTOMLEFT") + //controlPointWeight = ["0.20", "0.80"] + //controlPointWeight = "0.7" + //controlPointDistance = "50%" - } else if (degreeFound < -90 && degreeFound > -180) { - console.log("TOPLEFT") - } else { - console.log("STRAIGHT!") - } - } - */ + } else if (degreeFound < -90 && degreeFound > -180) { + console.log("TOPLEFT") + } else { + console.log("STRAIGHT!") + } + } + */ - return { - "distance": controlPointDistance, - "weight": controlPointWeight, - } - } + return { + "distance": controlPointDistance, + "weight": controlPointWeight, + } + } const setupGraph = () => { const actions = workflow.actions.map((action) => { @@ -4616,10 +4619,10 @@ const AngularWorkflow = (defaultprops) => { node.data.type = "ACTION"; node.isStartNode = action["id"] === workflow.start; - if (workflow.public === true) { - node.data.is_valid = true - node.is_valid = true - } + if (workflow.public === true) { + node.data.is_valid = true + node.is_valid = true + } var example = ""; if ( @@ -4728,28 +4731,28 @@ const AngularWorkflow = (defaultprops) => { }; // This is an attempt at prettier edges. The numbers are weird to work with. - // Bezier curves - //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html - var sourcenode = actions.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id) - var destinationnode = actions.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id) + // Bezier curves + //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html + var sourcenode = actions.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id) + var destinationnode = actions.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id) - if (sourcenode === undefined) { - sourcenode = triggers.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id) - } + if (sourcenode === undefined) { + sourcenode = triggers.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id) + } - if (destinationnode === undefined) { - destinationnode = triggers.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id) - } + if (destinationnode === undefined) { + destinationnode = triggers.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id) + } - if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) { - const edgeCurve = calculateEdgeCurve(sourcenode.position, destinationnode.position) - edge.style = { - 'control-point-distance': edgeCurve.distance, - 'control-point-weight': edgeCurve.weight, - } - } else { - console.log("FAILED node curve handling") - } + if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) { + const edgeCurve = calculateEdgeCurve(sourcenode.position, destinationnode.position) + edge.style = { + 'control-point-distance': edgeCurve.distance, + 'control-point-weight': edgeCurve.weight, + } + } else { + console.log("FAILED node curve handling") + } return edge; }); @@ -4817,13 +4820,13 @@ const AngularWorkflow = (defaultprops) => { const removeNode = (nodeId) => { const selectedNode = cy.getElementById(nodeId); if (selectedNode.data() === undefined) { - console.log("No node to remove") + console.log("No node to remove") return; } - //console.log("Removing node: ", selectedNode.data("id"), "Action: ", selectedAction.id) + //console.log("Removing node: ", selectedNode.data("id"), "Action: ", selectedAction.id) - // Get selected node + // Get selected node if (selectedNode.data().type === "TRIGGER") { console.log("Should remove trigger!"); @@ -4844,45 +4847,45 @@ const AngularWorkflow = (defaultprops) => { } } - //if (selectedNode.data("id") === selectedAction.id) { - // setSelectedApp({}); - // setSelectedAction({}); + //if (selectedNode.data("id") === selectedAction.id) { + // setSelectedApp({}); + // setSelectedAction({}); //setSelectedTrigger({}); //setSelectedTriggerIndex({}); - //} - const parsedSelection = cy.$(":selected"); + //} + const parsedSelection = cy.$(":selected"); if (selectedNode.data().decorator === true && selectedNode.data("type") !== "COMMENT") { alert.info("This node can't be deleted."); } else { selectedNode.remove(); } - // An attempt at NOT unselecting when removing - /* - setTimeout(() => { - if (parsedSelection.data() !== undefined) { - if (parsedSelection.data("id") !== selectedNode.data("id")) { - console.log("SHOULD SELECT SINCE ID IS DIFFERENT") + // An attempt at NOT unselecting when removing + /* + setTimeout(() => { + if (parsedSelection.data() !== undefined) { + if (parsedSelection.data("id") !== selectedNode.data("id")) { + console.log("SHOULD SELECT SINCE ID IS DIFFERENT") - parsedSelection.select() - } - } + parsedSelection.select() + } + } - console.log("Parsed: ", parsedSelection.data("id"), selectedNode.data("id")) - }, 2500) - */ + console.log("Parsed: ", parsedSelection.data("id"), selectedNode.data("id")) + }, 2500) + */ }; - if (isLoaded && setupSent === false) { - setSetupSent(true) + if (isLoaded && setupSent === false) { + setSetupSent(true) - sendStreamRequest({ - "item": "workflow", - "type": "enter", - "userid": userdata.id, - }) - } + sendStreamRequest({ + "item": "workflow", + "type": "enter", + "userid": userdata.id, + }) + } const fetchUsecases = () => { fetch(globalUrl + "/api/v1/workflows/usecases", { @@ -4893,201 +4896,201 @@ const AngularWorkflow = (defaultprops) => { }, credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for usecases"); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for usecases"); + } - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success !== false) { - console.log("Usecases: ", usecases) - setUsecases(responseJson) - } else { - } - }) - .catch((error) => { - //alert.error("ERROR: " + error.toString()); - console.log("ERROR getting usecases: " + error.toString()); - }) + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + console.log("Usecases: ", usecases) + setUsecases(responseJson) + } else { + } + }) + .catch((error) => { + //alert.error("ERROR: " + error.toString()); + console.log("ERROR getting usecases: " + error.toString()); + }) } // eslint-disable-next-line react-hooks/exhaustive-deps //useEffect(() => { - if (firstrequest) { - setFirstrequest(false); - getWorkflow(props.match.params.key, {}); - getApps(); - fetchUsecases() + if (firstrequest) { + setFirstrequest(false); + getWorkflow(props.match.params.key, {}); + getApps(); + fetchUsecases() - const cursearch = - typeof window === "undefined" || window.location === undefined - ? "" - : window.location.search; + const cursearch = + typeof window === "undefined" || window.location === undefined + ? "" + : window.location.search; - // FIXME: Don't check specific one here - const tmpExec = new URLSearchParams(cursearch).get("execution_highlight"); - if ( - tmpExec !== undefined && - tmpExec !== null && - tmpExec === "executions" - ) { - setExecutionModalOpen(true) - const newitem = removeParam("execution_highlight", cursearch); - navigate(curpath + newitem) - //props.history.push(curpath + newitem); - } - - const tmpView = new URLSearchParams(cursearch).get("view"); - if ( - tmpView !== undefined && - tmpView !== null && - tmpView === "executions" - ) { - setExecutionModalOpen(true); - - const newitem = removeParam("view", cursearch); - navigate(curpath + newitem) - //navigate(`?execution_highlight=${parsed_url}`) - //props.history.push(curpath + newitem); - } - return; - } - - // App length necessary cus of cy initialization + // FIXME: Don't check specific one here + const tmpExec = new URLSearchParams(cursearch).get("execution_highlight"); if ( - // First load - gets the workflow - elements.length === 0 && - workflow.actions !== undefined && - !graphSetup && - Object.getOwnPropertyNames(workflow).length > 0 + tmpExec !== undefined && + tmpExec !== null && + tmpExec === "executions" ) { - - setGraphSetup(true); - setupGraph(); - console.log("In graph setup") - - // 2nd load - configures cytoscape - } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && authLoaded) { - - console.log("In POST graph setup!") - - - //This part has to load LAST, as it's kind of not async. - //This means we need everything else to happen first. - - setEstablished(true); - // Validate if the node is just a node lol - - console.log("CY grid: ", cy.gridGuide) - - // https://www.npmjs.com/package/cytoscape-grid-guide - // - if (cy.gridGuide !== undefined) { - cy.gridGuide({ - gridSpacing: 30, - guidelinesStyle: { - strokeStyle: "#8b7d6b", // color of geometric guidelines - geometricGuidelineRange: 400, // range of geometric guidelines - range: 100, // max range of distribution guidelines - minDistRange: 10, // min range for distribution guidelines - distGuidelineOffset: 10, // shift amount of distribution guidelines - horizontalDistColor: "#ff0000", // color of horizontal distribution alignment - verticalDistColor: "#00ff00", // color of vertical distribution alignment - initPosAlignmentColor: "#0000ff", // color of alignment to initial mouse location - lineDash: [0, 0], // line style of geometric guidelines - horizontalDistLine: [0, 0], // line style of horizontal distribution guidelines - verticalDistLine: [0, 0], // line style of vertical distribution guidelines - initPosAlignmentLine: [0, 0], // line style of alignment to initial mouse position - } - }) - } else { - console.log("ERROR: Failed to render grid as it's unitialized") - } - - if (cy.edgehandles !== undefined) { - console.log("Inside edgehandles") - cy.edgehandles({ - handleNodes: (el) => { - console.log("in handlenodes") - - if (el.isNode() && - !el.data("isButton") && - !el.data("isDescriptor") && - !el.data("isSuggestion") && - el.data("type") !== "COMMENT") { - return true - } - - return false - }, - preview: false, - toggleOffOnLeave: true, - loopAllowed: function (node) { - return false; - }, - }); - - //cy.edgehandles({ - // preview: false, - //}) - - //cy.edgehandles().enable() - } else { - console.log("ERROR: Failed to initialize edgehandler") - } - // preview: true, - - cy.fit(null, 200); - - cy.on("boxselect", "node", (e) => { - if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) { - e.target.unselect(); - } - - e.target.addClass("selected"); - }); - - cy.on("boxstart", (e) => { - console.log("START"); - cy.removeListener("select"); - }); - - cy.on("boxend", (e) => { - console.log("END: ", cy) - var cydata = cy.$(":selected").jsons(); - if (cydata !== undefined && cydata !== null && cydata.length > 0) { - alert.success(`Selected ${cydata.length} element(s). CTRL+C to copy them.`); - } - }); - - cy.on("select", "node", (e) => { - onNodeSelect(e, appAuthentication); - }); - cy.on("select", "edge", (e) => onEdgeSelect(e)); - - cy.on("unselect", (e) => onUnselect(e)); - - cy.on("add", "node", (e) => onNodeAdded(e)); - cy.on("add", "edge", (e) => onEdgeAdded(e)); - cy.on("remove", "node", (e) => onNodeRemoved(e)); - cy.on("remove", "edge", (e) => onEdgeRemoved(e)); - - cy.on("mouseover", "edge", (e) => onEdgeHover(e)); - cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e)); - cy.on("mouseover", "node", (e) => onNodeHover(e)); - cy.on("mouseout", "node", (e) => onNodeHoverOut(e)); - - // Handles dragging - cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction)); - cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction)); - - cy.on("cxttap", "node", (e) => onCtxTap(e)); - - document.title = "Workflow - " + workflow.name; - registerKeys(); + setExecutionModalOpen(true) + const newitem = removeParam("execution_highlight", cursearch); + navigate(curpath + newitem) + //props.history.push(curpath + newitem); } + + const tmpView = new URLSearchParams(cursearch).get("view"); + if ( + tmpView !== undefined && + tmpView !== null && + tmpView === "executions" + ) { + setExecutionModalOpen(true); + + const newitem = removeParam("view", cursearch); + navigate(curpath + newitem) + //navigate(`?execution_highlight=${parsed_url}`) + //props.history.push(curpath + newitem); + } + return; + } + + // 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 + ) { + + setGraphSetup(true); + setupGraph(); + console.log("In graph setup") + + // 2nd load - configures cytoscape + } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && authLoaded) { + + console.log("In POST graph setup!") + + + //This part has to load LAST, as it's kind of not async. + //This means we need everything else to happen first. + + setEstablished(true); + // Validate if the node is just a node lol + + console.log("CY grid: ", cy.gridGuide) + + // https://www.npmjs.com/package/cytoscape-grid-guide + // + if (cy.gridGuide !== undefined) { + cy.gridGuide({ + gridSpacing: 30, + guidelinesStyle: { + strokeStyle: "#8b7d6b", // color of geometric guidelines + geometricGuidelineRange: 400, // range of geometric guidelines + range: 100, // max range of distribution guidelines + minDistRange: 10, // min range for distribution guidelines + distGuidelineOffset: 10, // shift amount of distribution guidelines + horizontalDistColor: "#ff0000", // color of horizontal distribution alignment + verticalDistColor: "#00ff00", // color of vertical distribution alignment + initPosAlignmentColor: "#0000ff", // color of alignment to initial mouse location + lineDash: [0, 0], // line style of geometric guidelines + horizontalDistLine: [0, 0], // line style of horizontal distribution guidelines + verticalDistLine: [0, 0], // line style of vertical distribution guidelines + initPosAlignmentLine: [0, 0], // line style of alignment to initial mouse position + } + }) + } else { + console.log("ERROR: Failed to render grid as it's unitialized") + } + + if (cy.edgehandles !== undefined) { + console.log("Inside edgehandles") + cy.edgehandles({ + handleNodes: (el) => { + console.log("in handlenodes") + + if (el.isNode() && + !el.data("isButton") && + !el.data("isDescriptor") && + !el.data("isSuggestion") && + el.data("type") !== "COMMENT") { + return true + } + + return false + }, + preview: false, + toggleOffOnLeave: true, + loopAllowed: function (node) { + return false; + }, + }); + + //cy.edgehandles({ + // preview: false, + //}) + + //cy.edgehandles().enable() + } else { + console.log("ERROR: Failed to initialize edgehandler") + } + // preview: true, + + cy.fit(null, 200); + + cy.on("boxselect", "node", (e) => { + if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) { + e.target.unselect(); + } + + e.target.addClass("selected"); + }); + + cy.on("boxstart", (e) => { + console.log("START"); + cy.removeListener("select"); + }); + + cy.on("boxend", (e) => { + console.log("END: ", cy) + var cydata = cy.$(":selected").jsons(); + if (cydata !== undefined && cydata !== null && cydata.length > 0) { + alert.success(`Selected ${cydata.length} element(s). CTRL+C to copy them.`); + } + }); + + cy.on("select", "node", (e) => { + onNodeSelect(e, appAuthentication); + }); + cy.on("select", "edge", (e) => onEdgeSelect(e)); + + cy.on("unselect", (e) => onUnselect(e)); + + cy.on("add", "node", (e) => onNodeAdded(e)); + cy.on("add", "edge", (e) => onEdgeAdded(e)); + cy.on("remove", "node", (e) => onNodeRemoved(e)); + cy.on("remove", "edge", (e) => onEdgeRemoved(e)); + + cy.on("mouseover", "edge", (e) => onEdgeHover(e)); + cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e)); + cy.on("mouseover", "node", (e) => onNodeHover(e)); + cy.on("mouseout", "node", (e) => onNodeHoverOut(e)); + + // Handles dragging + cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction)); + cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction)); + + cy.on("cxttap", "node", (e) => onCtxTap(e)); + + document.title = "Workflow - " + workflow.name; + registerKeys(); + } //}) const stopSchedule = (trigger, triggerindex) => { @@ -5137,16 +5140,16 @@ const AngularWorkflow = (defaultprops) => { return; } - var mappedStartnode = "" - const alledges = cy.edges().jsons() - for (var key in alledges) { - const tmp = alledges[key] - console.log("TMP: ", tmp, tmp.data.source) - if (tmp.data.source === trigger.id) { - mappedStartnode = tmp.data.target - break - } - } + var mappedStartnode = "" + const alledges = cy.edges().jsons() + for (var key in alledges) { + const tmp = alledges[key] + console.log("TMP: ", tmp, tmp.data.source) + if (tmp.data.source === trigger.id) { + mappedStartnode = tmp.data.target + break + } + } alert.info("Creating schedule with name " + trigger.name); const data = { @@ -5155,7 +5158,7 @@ const AngularWorkflow = (defaultprops) => { execution_argument: workflow.triggers[triggerindex].parameters[1].value, environment: workflow.triggers[triggerindex].environment, id: trigger.id, - start: mappedStartnode, + start: mappedStartnode, } fetch( @@ -5201,8 +5204,8 @@ const AngularWorkflow = (defaultprops) => { marginRight: 5, display: "flex", flexDirection: "column", - minHeight: isMobile ? bodyHeight-appBarSize*4 : "100%", - maxHeight: isMobile ? bodyHeight-appBarSize*4 : "100%", + minHeight: isMobile ? bodyHeight - appBarSize * 4 : "100%", + maxHeight: isMobile ? bodyHeight - appBarSize * 4 : "100%", }; const paperAppStyle = { @@ -5242,9 +5245,9 @@ const AngularWorkflow = (defaultprops) => { const deleteVariable = (variableIndex) => { //console.log("Delete:", variableName); - if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length > variableIndex) { - workflow.workflow_variables.splice(variableIndex, 1) - } + if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length > variableIndex) { + workflow.workflow_variables.splice(variableIndex, 1) + } //workflow.workflow_variables = workflow.workflow_variables.filter( // (data) => data.name !== variableName @@ -5253,9 +5256,9 @@ const AngularWorkflow = (defaultprops) => { }; const deleteExecutionVariable = (variableIndex) => { - if (workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > variableIndex) { - workflow.execution_variables.splice(variableIndex, 1) - } + if (workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > variableIndex) { + workflow.execution_variables.splice(variableIndex, 1) + } //workflow.execution_variables = workflow.execution_variables.filter( // (data) => data.name !== variableName @@ -5287,103 +5290,103 @@ const AngularWorkflow = (defaultprops) => { {workflow.workflow_variables === null ? null : workflow.workflow_variables.map((variable, index) => { - return ( -
- {}}> + return ( +
+ { }}> +
+
-
-
{ + setVariableInfo({ + "name": variable.name, + "description": variable.description, + "value": variable.value, + "index": index, + }) + setVariablesModalOpen(true); + }} + > + Name: {variable.name} +
+
+ + + + { - setVariableInfo({ - "name": variable.name, - "description": variable.description, - "value": variable.value, - "index": index, - }) - setVariablesModalOpen(true); + onClose={() => { + setOpen(false); + setAnchorEl(null); }} > - Name: {variable.name} -
-
- - - - { + onClick={() => { setOpen(false); - setAnchorEl(null); + setVariableInfo({ + "name": variable.name, + "description": variable.description, + "value": variable.value, + "index": index, + }) + setVariablesModalOpen(true); }} + key={"Edit"} > - { - setOpen(false); - setVariableInfo({ - "name": variable.name, - "description": variable.description, - "value": variable.value, - "index": index, - }) - setVariablesModalOpen(true); - }} - key={"Edit"} - > - {"Edit"} - - { - deleteVariable(index); - setOpen(false); - }} - key={"Delete"} - > - {"Delete"} - - -
+ {"Edit"} + + { + deleteVariable(index); + setOpen(false); + }} + key={"Delete"} + > + {"Delete"} + +
- -
- ); - })} +
+ +
+ ); + })}
+ ) + })} +
+ } + {/* + Object.getOwnPropertyNames(discoveryData).length > 0 ? +
+ + Use-cases + + {selectedUsecases.map((data, index) => { + usecasediff += 175 + + return ( + + ) + })} +
+ : null*/} + + { + Object.getOwnPropertyNames(discoveryData).length > 0 ? + + {paperTitle.length > 0 ? + + + {paperTitle} + + + + : null} + + { + //cy.elements().unselectify(); + cy.elements().unselect() + + e.preventDefault(); + setDiscoveryData({}) + setDefaultSearch("") + setPaperTitle("") + }} + > + + + + + { + e.preventDefault(); + setDiscoveryData({ + "id": discoveryData.id, + "label": discoveryData.label, + "name": "" + }) + + setNewSelectedApp({ + "animate": false, + "app_id": "", + "boxheight": "66.3px", + "boxwidth": "66.3px", + "description": "", + "errors": [], + "font_size": "9.36px", + "height": "66.3px", + "id": "", + "isValid": true, + "is_valid": true, + "label": "SIEM", + "large_image": "asd", + "margin_x": "0px", + "margin_y": "0px", + "name": "", + "text_margin_y": "46.800000000000004px", + "width": "66.3px", + "objectID": "remove", + }) + + setSelectionOpen(true) + setDefaultSearch("") + + //handleLoadNextSuggestion(frameworkData) + setInjectedApps([]) + + const foundelement = cy.getElementById(discoveryData.id) + if (foundelement !== undefined && foundelement !== null) { + console.log("element: ", foundelement) + console.log("DISC: ", discoveryData) + foundelement.data("large_image", parsedDatatypeImages[discoveryData.id.toUpperCase()]) + foundelement.data("text_margin_y", "14px") + foundelement.data("margin_x", "32px") + foundelement.data("margin_y", "19x") + foundelement.data("width", "45px") + foundelement.data("height", "45px") + } + + setTimeout(() => { + setDiscoveryData({}) + setNewSelectedApp({}) + }, 1000) + }} + > + + + +
+ {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? +
+ + {discoveryData.id} 0 ? newSelectedApp.image_url : discoveryData.large_image} style={{height: 40, width: 40, margin: "auto",}}/> +
+ : + {discoveryData.id} + } + + {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? + discoveryData.name + : + newSelectedApp.name !== undefined && newSelectedApp.name !== null && newSelectedApp.name.length > 0 ? + newSelectedApp.name + : + `Find your ${discoveryData.label} app!` + } + +
+
+ {discoveryData !== undefined && discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? + + + {discoveryData.description} + + {/*isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ? + {< + newSelectedApp={newSelectedApp} + setNewSelectedApp={setNewSelectedApp} + defaultSearch={defaultSearch} + />} + : + null + */} + + : + selectionOpen + ? + + + Click an app below to select it + + + : + + } +
+
+ {selectionOpen ? + + : null} +
+
+ : null + } + { + // FIXME: There's something specific loading when + // you do the first hover of a node. Why is this different? + //console.log("CY: ", incy) + setCy(incy) + }} + /> + {isCloud && inputUsecase !== undefined && inputUsecase !== null && isLoggedIn === true && Object.keys(inputUsecase).length > 0 ? + + : null} +
+ ) +} + +export default AppFramework; diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx new file mode 100644 index 00000000..af7ddae5 --- /dev/null +++ b/frontend/src/components/AppGrid.jsx @@ -0,0 +1,378 @@ +import React, {useEffect, useState} from 'react'; + +import ReactGA from 'react-ga4'; +import { useTheme } from '@material-ui/core/styles'; +import {Link} from 'react-router-dom'; + +import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; + +import algoliasearch from 'algoliasearch/lite'; +import { InstantSearch, Configure, connectSearchBox, connectHits, connectHitInsights } from 'react-instantsearch-dom'; + +import aa from 'search-insights' + +import { + Zoom, + Grid, + Paper, + TextField, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip +} from '@material-ui/core'; + +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +//const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") +const AppGrid = props => { + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata } = props + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows + const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 2 : parsedXs + const theme = useTheme(); + //const [apps, setApps] = React.useState([]); + //const [filteredApps, setFilteredApps] = React.useState([]); + const [formMail, setFormMail] = React.useState(""); + const [message, setMessage] = React.useState(""); + const [formMessage, setFormMessage] = React.useState(""); + + const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,} + + const innerColor = "rgba(255,255,255,0.65)" + const borderRadius = 3 + window.title = "Shuffle | Apps | Find and integrate any app" + + const submitContact = (email, message) => { + const data = { + "firstname": "", + "lastname": "", + "title": "", + "companyname": "", + "email": email, + "phone": "", + "message": message, + } + + const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly." + + fetch(globalUrl+"/api/v1/contact", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(response => { + if (response.success === true) { + setFormMessage(response.reason) + //alert.info("Thanks for submitting!") + } else { + setFormMessage(errorMessage) + } + + setFormMail("") + setMessage("") + }) + .catch(error => { + setFormMessage(errorMessage) + console.log(error) + }); + } + + const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { + useEffect(() => { + if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { + const urlSearchParams = new URLSearchParams(window.location.search) + const params = Object.fromEntries(urlSearchParams.entries()) + const foundQuery = params["q"] + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery) + refine(foundQuery) + } + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='off' + type="search" + color="primary" + defaultValue={currentRefinement} + placeholder="Find Apps..." + id="shuffle_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + var workflowDelay = -50 + const Hits = ({ hits, insights }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + //console.log(hits) + //var curhits = hits + //if (hits.length > 0 && defaultApps.length === 0) { + // setDefaultApps(hits) + //} + + //const [defaultApps, setDefaultApps] = React.useState([]) + //console.log(hits) + //if (hits.length > 0 && hits.length !== innerHits.length) { + // setInnerHits(hits) + //} + + console.log("In appgrid") + + return ( + + {hits.map((data, index) => { + + workflowDelay += 50 + + const paperStyle = { + backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : theme.palette.inputColor, + color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)", + border: `1px solid ${innerColor}`, + padding: 15, + cursor: "pointer", + position: "relative", + minHeight: 116, + } + + if (counted === 12/xs*rowHandler) { + return null + } + + counted += 1 + var parsedname = "" + for (var key = 0; key < data.name.length; key++) { + var character = data.name.charAt(key) + if (character === character.toUpperCase()) { + //console.log(data.name[key], data.name[key+1]) + if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) { + } else { + parsedname += " " + } + } + + parsedname += character + } + + parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ") + const appUrl = isCloud ? `/apps/${data.objectID}?queryID=${data.__queryID}` : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}` + return ( + + +
+ { + setMouseHoverIndex(index) + /* + ReactGA.event({ + category: "app_grid_view", + action: `search_bar_click`, + label: "", + }) + */ + }} onMouseOut={() => { + setMouseHoverIndex(-1) + }} onClick={() => { + if (isCloud) { + ReactGA.event({ + category: "app_grid_view", + action: `app_${parsedname}_${data.id}_click`, + label: "", + }) + } + + //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") + console.log(searchClient) + aa('init', { + appId: searchClient.appId, + apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] + }) + + const timestamp = new Date().getTime() + aa('sendEvents', [ + { + eventType: 'click', + eventName: 'Product Clicked', + index: 'appsearch', + objectIDs: [data.objectID], + timestamp: timestamp, + queryID: data.__queryID, + positions: [data.__position], + userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, + } + ]) + + }}> + + {data.name} + +
+ {index === mouseHoverIndex || showName === true ? + parsedname + : + null + } + {data.generated ? + + {data.invalid ? + + : + + } + + : + + + + } + + + + + ) + })} + + ) + } + + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(Hits) + //const CustomHits = connectHitInsights(aa)(Hits) + const selectButtonStyle = { + minWidth: 150, + maxWidth: 150, + minHeight: 50, + } + + return ( +
+ {/* +
+ +
+ */} +
+ +
+ +
+ + +
+ {showSuggestion === true ? +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + {formMessage} +
+ : null + } + + + + Search by + + + Algolia logo + + +
+
+ ) +} + +export default AppGrid; diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx new file mode 100644 index 00000000..9da69a70 --- /dev/null +++ b/frontend/src/components/Appsearch.jsx @@ -0,0 +1,291 @@ +import React, { useState, useEffect } from 'react'; +import ReactGA from 'react-ga4'; +import { useTheme } from '@material-ui/core/styles'; +import {Link} from 'react-router-dom'; +import { useAlert } from "react-alert"; +import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; + +//import algoliasearch from 'algoliasearch/lite'; +import algoliasearch from 'algoliasearch'; +import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; +import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core'; +import aa from 'search-insights' +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const Appsearch = props => { + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList} = props + + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + const alert = useAlert(); + const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows + const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs + const theme = useTheme(); + //const [apps, setApps] = React.useState([]); + //const [filteredApps, setFilteredApps] = React.useState([]); + const [formMail, setFormMail] = React.useState(""); + const [message, setMessage] = React.useState(""); + const [formMessage, setFormMessage] = React.useState(""); + const [selectedApp, setSelectedApp] = React.useState({}); + const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,} + + const innerColor = "rgba(255,255,255,0.65)" + const borderRadius = 3 + window.title = "Shuffle | Apps | Find and integration any app" + + const setUserSpecialzedApp = (user, data) => { + // var data = newfields] + console.log("data value", data) + const appData = {"user_id":user,"specialized_apps":[{}]} + console.log("User Check for appdata:", user) + appData["specialized_apps"][0]["name"] = data["name"] + appData["specialized_apps"][0]["image"] = data["image_url"] + appData["specialized_apps"][0]["category"] = data["categories"].toString() + console.log("AppData:",appData) + console.log("setActionImageList",setActionImageList) + console.log("actionImageList",actionImageList) + + const finalData = actionImageList.concat(appData["specialized_apps"]) + appData["specialized_apps"]=finalData + fetch(globalUrl + "/api/v1/users/updateuser", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(appData), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for set creator :O!"); + } + alert.success("Sucessfully updated specialzed app.") + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success && responseJson.reason !== undefined) { + alert.error("Failed updating user: " + responseJson.reason); + } + }) + .catch((error) => { + console.log(error); + }); + }; + const submitContact = (email, message) => { + const data = { + "firstname": "", + "lastname": "", + "title": "", + "companyname": "", + "email": email, + "phone": "", + "message": message, + } + + const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly." + + fetch(globalUrl+"/api/v1/contact", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(response => { + if (response.success === true) { + setFormMessage(response.reason) + //alert.info("Thanks for submitting!") + } else { + setFormMessage(errorMessage) + } + + setFormMail("") + setMessage("") + }) + .catch(error => { + setFormMessage(errorMessage) + console.log(error) + }); + } + + // value={currentRefinement} + const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { + useEffect(() => { + //console.log("FIRST LOAD ONLY? RUN REFINEMENT: !", currentRefinement) + if (defaultSearch !== undefined && defaultSearch !== null) { + refine(defaultSearch) + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='on' + type="search" + color="primary" + defaultValue={defaultSearch} + placeholder={`Find ${defaultSearch} Apps...`} + id="shuffle_workflow_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + //value={currentRefinement} + } + + const Hits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + return ( + + {hits.map((data, index) => { + const paperStyle = { + backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : theme.palette.inputColor, + color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)", + border: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e", + textAlign: "left", + padding: 10, + cursor: "pointer", + position: "relative", + overflow: "hidden", + width: "100%", + minHeight: 37, + maxHeight: 52, + } + + if (counted === 12/xs*rowHandler) { + return null + } + + counted += 1 + var parsedname = data.name.valueOf() + //for (var key = 0; key < data.name.length; key++) { + // var character = data.name.charAt(key) + // if (character === character.toUpperCase()) { + // //console.log(data.name[key], data.name[key+1]) + // if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) { + // } else { + // parsedname += " " + // } + // } + + // parsedname += character + //} + + parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ") + + return ( + { + setMouseHoverIndex(index) + /* + ReactGA.event({ + category: "app_grid_view", + action: `search_bar_click`, + label: "", + }) + */ + }} onMouseOut={() => { + setMouseHoverIndex(-1) + }} onClick={() => { + if(isCreatorPage === true){ + console.log("data:",data) + console.log("userdata.id",userdata.id) + console.log("is creator", isCreatorPage) + if (setNewSelectedApp !== undefined) { + // setUserSpecialzedApp = data + setUserSpecialzedApp(userdata.id, data) + //setActionImageList(userdata.id, data) + } + } + if (setNewSelectedApp !== undefined) { + setNewSelectedApp(data) + } + + if (isCloud) { + ReactGA.event({ + category: "app_search", + action: `app_${parsedname}_${data.id}_personalize_click`, + label: "", + }) + } + + const queryID = "" + + if (queryID !== undefined && queryID !== null) { + try { + aa('init', { + appId: searchClient.appId, + apiKey: searchClient.transporter.headers["x-algolia-api-key"] + }) + + const timestamp = new Date().getTime() + aa('sendEvents', [ + { + eventType: 'conversion', + eventName: 'App Framework Activation', + index: 'appsearch', + objectIDs: [data.objectID], + timestamp: timestamp, + queryID: queryID, + userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, + } + ]) + } catch (e) { + console.log("Failed algolia search update: ", e) + } + } + }}> +
+ {data.name} + + {parsedname} + +
+
+ ) + })} +
+ ) + } + + const InputHits = ConfiguredHits === undefined ? Hits : ConfiguredHits + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(InputHits) + + return ( +
+ + {/* showSearch === false ? null : +
+ +
+ */} +
+ +
+ +
+
+ ) +} + +export default Appsearch; diff --git a/frontend/src/components/AppsearchPopout.jsx b/frontend/src/components/AppsearchPopout.jsx new file mode 100644 index 00000000..25ea719c --- /dev/null +++ b/frontend/src/components/AppsearchPopout.jsx @@ -0,0 +1,190 @@ +import React, { useState, useEffect } from 'react'; + +import theme from '../theme.jsx'; +import AppSearch from './Appsearch.jsx'; + +import { + Paper, + Typography, + Divider, + IconButton, + Badge, + CircularProgress, + Tooltip, + Button, +} from "@material-ui/core"; + +import { + Close as CloseIcon, + Delete as DeleteIcon, +} from "@material-ui/icons"; + +const AppSearchPopout = (props) => { + const { + cy, + paperTitle, + setPaperTitle, + newSelectedApp, + setNewSelectedApp, + selectionOpen, + setSelectionOpen, + discoveryData, + setDiscoveryData, + userdata, + } = props; + + const [defaultSearch, setDefaultSearch] = React.useState(paperTitle !== undefined ? paperTitle : "") + + if (selectionOpen !== true) { + return null + } + + // + return ( + + {paperTitle !== undefined && paperTitle.length > 0 ? + + + {paperTitle} + + + + : null} + + { + //cy.elements().unselectify(); + if (cy !== undefined) { + cy.elements().unselect() + } + + e.preventDefault(); + setSelectionOpen(false) + }} + > + + + + {/* {/*Causes errors in Cytoscape. Removing for now.} + + { + e.preventDefault(); + setDiscoveryData({ + "id": discoveryData.id, + "label": discoveryData.label, + "name": "" + }) + setNewSelectedApp({ + "image_url": "", + "name": "", + "id": "", + "objectID": "remove", + }) + setSelectionOpen(true) + setDefaultSearch("") + + const foundelement = cy.getElementById(discoveryData.id) + if (foundelement !== undefined && foundelement !== null) { + console.log("element: ", foundelement) + foundelement.data("large_image", discoveryData.large_image) + foundelement.data("text_margin_y", "14px") + foundelement.data("margin_x", "32px") + foundelement.data("margin_y", "19x") + foundelement.data("width", "45px") + foundelement.data("height", "45px") + } + + setTimeout(() => { + setDiscoveryData({}) + setNewSelectedApp({}) + }, 1000) + }} + > + + + + */} +
+ {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? +
+ + {discoveryData.id} 0 ? newSelectedApp.image_url : discoveryData.large_image} style={{height: 40, width: 40, margin: "auto",}}/> +
+ : + {discoveryData.id} + } + + {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? + discoveryData.name + : + newSelectedApp.name !== undefined && newSelectedApp.name !== null && newSelectedApp.name.length > 0 ? + newSelectedApp.name + : + `No ${discoveryData.label} app chosen` + } + +
+
+ {discoveryData !== undefined && discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? + + + {discoveryData.description} + + {/*isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ? + {< + newSelectedApp={newSelectedApp} + setNewSelectedApp={setNewSelectedApp} + defaultSearch={defaultSearch} + />} + : + null + */} + + : + selectionOpen + ? + + + Click an app below to select it + + + : + + } +
+
+ {selectionOpen ? + + : null} +
+
+ ) +} + +export default AppSearchPopout; diff --git a/frontend/src/components/AuthenticationItem.jsx b/frontend/src/components/AuthenticationItem.jsx new file mode 100644 index 00000000..eef9806c --- /dev/null +++ b/frontend/src/components/AuthenticationItem.jsx @@ -0,0 +1,278 @@ +import React, { useState, useEffect } from "react"; + +import theme from '../theme.jsx'; +import { useAlert } from "react-alert"; +import { + Tooltip, + IconButton, + ListItem, + ListItemText, + FormGroup, + FormControl, + InputLabel, + FormLabel, + FormControlLabel, + Select, + MenuItem, + Grid, + Paper, + Typography, + TextField, + Zoom, +} from "@material-ui/core"; + +import { + Edit as EditIcon, + Delete as DeleteIcon, + SelectAll as SelectAllIcon, +} from "@material-ui/icons"; + +const AuthenticationItem = (props) => { + const { data, index, globalUrl, getAppAuthentication } = props + + const [selectedAuthentication, setSelectedAuthentication] = React.useState({}) + const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false); + const [authenticationFields, setAuthenticationFields] = React.useState([]); + + const alert = useAlert(); + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + //console.log("Auth data: ", data) + if (data.type === "oauth2") { + data.fields = [ + { + key: "url", + value: "Secret. Replaced during app execution!", + }, + { + key: "client_id", + value: "Secret. Replaced during app execution!", + }, + { + key: "client_secret", + value: "Secret. Replaced during app execution!", + }, + { + key: "scope", + value: "Secret. Replaced during app execution!", + }, + ]; + } + + const deleteAuthentication = (data) => { + alert.info("Deleting auth " + data.label); + + // Just use this one? + const url = globalUrl + "/api/v1/apps/authentication/" + data.id; + console.log("URL: ", url); + fetch(url, { + method: "DELETE", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => + response.json().then((responseJson) => { + console.log("RESP: ", responseJson); + if (responseJson["success"] === false) { + alert.error("Failed deleting auth"); + } else { + // Need to wait because query in ES is too fast + setTimeout(() => { + getAppAuthentication(); + }, 1000); + //alert.success("Successfully deleted authentication!") + } + }) + ) + .catch((error) => { + console.log("Error in userdata: ", error); + }); + } + + const editAuthenticationConfig = (id) => { + const data = { + id: id, + action: "assign_everywhere", + }; + const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config"; + + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + alert.error("Failed overwriting appauth in workflows"); + } else { + alert.success("Successfully updated auth everywhere!"); + //setSelectedUserModalOpen(false); + setTimeout(() => { + getAppAuthentication(); + }, 1000); + } + }) + ) + .catch((error) => { + alert.error("Err: " + error.toString()); + }); + }; + + const updateAppAuthentication = (field) => { + setSelectedAuthenticationModalOpen(true); + setSelectedAuthentication(field); + //{selectedAuthentication.fields.map((data, index) => { + var newfields = []; + for (var key in field.fields) { + newfields.push({ + key: field.fields[key].key, + value: "", + }); + } + setAuthenticationFields(newfields); + } + + return ( + + + style={{ minWidth: 75, maxWidth: 75 }} + /> + + + {/* + + */} + + {/* + + */} + { + return data.key; + }) + .join(", ") + } + style={{ + minWidth: 125, + maxWidth: 125, + overflow: "hidden", + }} + /> + + + { + updateAppAuthentication(data); + }} + > + + + {data.defined ? ( + + { + editAuthenticationConfig(data.id); + }} + > + + + + ) : ( + + {}} + > + + + + )} + { + deleteAuthentication(data); + }} + > + + + + + ) + } + +export default AuthenticationItem diff --git a/frontend/src/components/AuthenticationNormal.jsx b/frontend/src/components/AuthenticationNormal.jsx new file mode 100644 index 00000000..5d3a4fd3 --- /dev/null +++ b/frontend/src/components/AuthenticationNormal.jsx @@ -0,0 +1,366 @@ +import React, { useState, useEffect } from "react"; +import theme from '../theme.jsx'; +import { v4 as uuidv4 } from "uuid"; + + +import { + Button, + Divider, + Select, + MenuItem, + TextField, + DialogActions, + DialogTitle, + DialogContent, + Typography, +} from "@material-ui/core"; + +import { + LockOpen as LockOpenIcon, +} from "@material-ui/icons"; + +const AuthenticationData = (props) => { + const { + globalUrl, + saveWorkflow, + selectedApp, + workflow, + selectedAction, + authenticationType, + getAppAuthentication, + appAuthentication, + setSelectedAction, + setAuthenticationModalOpen, + isCloud, + } = props; + + const setNewAppAuth = (appAuthData) => { + console.log("DAta: ", appAuthData); + 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!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to set app auth: " + responseJson.reason); + } else { + if (getAppAuthentication !== undefined) { + getAppAuthentication() + } + + if (setAuthenticationModalOpen !== undefined) { + setAuthenticationModalOpen(false) + } + + // Needs a refresh with the new authentication.. + //alert.success("Successfully saved new app auth") + } + }) + .catch((error) => { + //alert.error(error.toString()); + console.log("New auth error: ", error.toString()); + }); + } + + const [authenticationOption, setAuthenticationOptions] = React.useState({ + app: JSON.parse(JSON.stringify(selectedApp)), + fields: {}, + label: "", + usage: [ + { + workflow_id: workflow.id, + }, + ], + id: uuidv4(), + active: true, + }); + + if ( + selectedApp.authentication === undefined || + selectedApp.authentication.parameters === null || + selectedApp.authentication.parameters === undefined || + selectedApp.authentication.parameters.length === 0 + ) { + return ( + + + {selectedApp.name} does not require authentication + + + ); + } + + authenticationOption.app.actions = []; + + for (var key in selectedApp.authentication.parameters) { + if ( + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] === undefined + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] = ""; + } + } + + const handleSubmitCheck = () => { + console.log("NEW AUTH: ", authenticationOption); + if (authenticationOption.label.length === 0) { + authenticationOption.label = `Auth for ${selectedApp.name}`; + } + + // Automatically mapping fields that already exist (predefined). + // Warning if fields are NOT filled + for (var key in selectedApp.authentication.parameters) { + if ( + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ].length === 0 + ) { + if ( + selectedApp.authentication.parameters[key].value !== undefined && + selectedApp.authentication.parameters[key].value !== null && + selectedApp.authentication.parameters[key].value.length > 0 + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] = selectedApp.authentication.parameters[key].value; + } else { + if ( + selectedApp.authentication.parameters[key].schema.type === "bool" + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] = "false"; + } else { + alert.info( + "Field " + + selectedApp.authentication.parameters[key].name + + " can't be empty" + ); + return; + } + } + } + } + + console.log("Action: ", selectedAction); + selectedAction.authentication_id = authenticationOption.id; + selectedAction.selectedAuthentication = authenticationOption; + if ( + selectedAction.authentication === undefined || + selectedAction.authentication === null + ) { + selectedAction.authentication = [authenticationOption]; + } else { + selectedAction.authentication.push(authenticationOption); + } + + setSelectedAction(selectedAction); + + var newAuthOption = JSON.parse(JSON.stringify(authenticationOption)); + var newFields = []; + for (const key in newAuthOption.fields) { + const value = newAuthOption.fields[key]; + newFields.push({ + key: key, + value: value, + }); + } + + console.log("FIELDS: ", newFields); + newAuthOption.fields = newFields; + setNewAppAuth(newAuthOption); + + //if (configureWorkflowModalOpen) { + // setSelectedAction({}); + //} + + //setUpdate(authenticationOption.id); + }; + + if ( + authenticationOption.label === null || + authenticationOption.label === undefined + ) { + authenticationOption.label = selectedApp.name + " authentication"; + } + + return ( +
+ +
+ Authentication for {selectedApp.name} +
+
+ + + What is app authentication? + +
+ These are required fields for authenticating with {selectedApp.name} +
+ Name - what is this used for? + { + authenticationOption.label = event.target.value; + }} + /> + +
+ {selectedApp.authentication.parameters.map((data, index) => { + return ( +
+ + {data.name} + + {data.schema !== undefined && + data.schema !== null && + data.schema.type === "bool" ? ( + + ) : ( + { + authenticationOption.fields[data.name] = + event.target.value; + }} + /> + )} +
+ ); + })} + + + + + +
+ ); +}; + +export default AuthenticationData diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx new file mode 100644 index 00000000..40254bbb --- /dev/null +++ b/frontend/src/components/Branding.jsx @@ -0,0 +1,83 @@ +import React, { useState, useEffect } from "react"; +import ReactGA from 'react-ga4'; +import theme from "../theme.jsx"; + +import { useTheme } from "@material-ui/core/styles"; +import { + Paper, + Typography, + Divider, + Button, + Grid, + Card, +} from "@material-ui/core"; + +import { useAlert } from "react-alert"; + +const Branding = (props) => { + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; + const alert = useAlert(); + const [publishingInfo, setPublishingInfo] = useState(""); + + // Should enable / disable org branding + const handleChangePublishing = () => { + console.log("Handle change publishing"); + } + + const isOrganizationReady = () => { + // A simple checklist to ensure the button shows up properly + if (selectedOrganization.name === selectedOrganization.org) { + return false; + } + + if (selectedOrganization.large_image === "" || selectedOrganization.large_image === theme.palette.defaultImage) { + return false; + } + + return true + } + + return ( +
+ + Branding + + + You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more. + + + +

+ Creator Network +

+
+
+ + + By changing publishing settings, you agree to our Terms of Service, and acknowledge that your organization's non-sensitive data will be turned into a creator account. Support: support@shuffler.io + + + + + {publishingInfo} + + +
+
+
+ ) +} + +export default Branding; diff --git a/frontend/src/components/CacheView.jsx b/frontend/src/components/CacheView.jsx new file mode 100644 index 00000000..873f9f7e --- /dev/null +++ b/frontend/src/components/CacheView.jsx @@ -0,0 +1,427 @@ +import React, { useState, useEffect } from "react"; +import theme from "../theme.jsx"; +import { + Tooltip, + Divider, + TextField, + Button, + Tabs, + Tab, + Grid, + List, + ListItem, + ListItemText, + IconButton, + Dialog, + DialogTitle, + DialogActions, +} from "@material-ui/core"; +import { useAlert } from "react-alert"; + +import { + Edit as EditIcon, + FileCopy as FileCopyIcon, + SelectAll as SelectAllIcon, + OpenInNew as OpenInNewIcon, + CloudDownload as CloudDownloadIcon, + Description as DescriptionIcon, + Polymer as PolymerIcon, + CheckCircle as CheckCircleIcon, + Close as CloseIcon, + Apps as AppsIcon, + Image as ImageIcon, + Delete as DeleteIcon, + Cached as CachedIcon, + AccessibilityNew as AccessibilityNewIcon, + Lock as LockIcon, + Eco as EcoIcon, + Schedule as ScheduleIcon, + Cloud as CloudIcon, + Business as BusinessIcon, + Visibility as VisibilityIcon, + VisibilityOff as VisibilityOffIcon, +} from "@material-ui/icons"; + +const CacheView = (props) => { + const { globalUrl, userdata, serverside, orgId } = props; + const [orgCache, setOrgCache] = React.useState(""); + const [listCache, setListCache] = React.useState([]); + const [addCache, setAddCache] = React.useState(""); + const [modalOpen, setModalOpen] = React.useState(false); + const [key, setKey]= React.useState(""); + const [value, setValue]= React.useState(""); + const [cacheInput, setCacheInput]= React.useState(""); + const [cacheCursor, setCacheCursor]= React.useState(""); + + const alert = useAlert(); + useEffect(() => { + listOrgCache(orgId); + console.log("orgid", orgId); + }, []); + + const listOrgCache = (orgId) => { + fetch(globalUrl + `/api/v1/orgs/${orgId}/list_cache`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return; + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + setListCache(responseJson.keys); + } + + if (responseJson.cursor !== undefined && responseJson.cursor !== null && responseJson.cursor !== "") { + setCacheCursor(responseJson.cursor); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + // const getCacheList = (orgId) => { + // fetch(`${globalUrl}/api/v1/orgs/${orgId}/get_cache`, { + // method: "GET", + // headers: { + // "Content-Type": "application/json", + // Accept: "application/json", + // }, + // credentials: "include", + // }) + // .then((response) => { + // if (response.status !== 200) { + // console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + // } + + + // return response.json(); + // }) + // .then((responseJson) => { + // if (responseJson.success !== false) { + // console.log("Found cache: ", responseJson) + // setListCache(responseJson) + // } else { + // console.log("Couldn't find the creator profile (rerun?): ", responseJson) + // // If the current user is any of the Shuffle Creators + // // AND the workflow doesn't have an owner: allow editing. + // // else: Allow suggestions? + // //console.log("User: ", userdata) + // //if (rerun !== true) { + // // getUserProfile(userdata.id, true) + // //} + // } + // }) + // .catch((error) => { + // console.log("Get userprofile error: ", error); + // }) + // } + + + const deleteCache = (orgId, key) => { + alert.info("Attempting to delete Cache"); + fetch(globalUrl + `/api/v1/orgs/${orgId}/cache/${key}`, { + method: "DELETE", + headers: { + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + alert.success("Successfully deleted Cache"); + setTimeout(() => { + listOrgCache(orgId); + }, 1000); + } else { + alert.error("Failed deleting Cache. Does it still exist?"); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const addOrgCache = (orgId) => { + const cache={key:key,value:value}; + setCacheInput([cache]); + console.log("cache input:",cacheInput) + + fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, { + + method: "POST", + body: JSON.stringify(cache), + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return; + } + + return response.json(); + }) + .then((responseJson) => { + setAddCache(responseJson); + alert.success("New Cache Added Successfully!"); + listOrgCache(orgId); + setModalOpen(false); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const modalView = ( + { + setModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + + + Add Cache + + +
+ Key + setKey(e.target.value)} + /> +
+
+ Value + setValue(e.target.value)} + /> +
+ + + + +
+ ); + + return ( + +
+ {modalView} +
+

Shuffle Datastore

+ + Datastore is a key-value store for storing data that can be used cross-workflow.  + + Learn more + + +
+ + + + + + + + + + + {listCache === undefined || listCache === null + ? null + : listCache.map((data, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + return ( + + + + + + {/* + + { + + }} + > + + + + */} + + + { + deleteCache(orgId, data.key); + //deleteFile(orgId); + }} + > + + + + + + /> + + ); + })} + +
+ + ); +} +export default CacheView; diff --git a/frontend/src/components/Countries.jsx b/frontend/src/components/Countries.jsx new file mode 100644 index 00000000..2cb9c853 --- /dev/null +++ b/frontend/src/components/Countries.jsx @@ -0,0 +1,434 @@ +const countries = [ + { code: 'GB', label: 'United Kingdom', phone: '44' }, + { + code: 'US', + label: 'United States', + phone: '1', + suggested: true, + }, + { code: 'IN', label: 'India', phone: '91' }, + { code: 'AD', label: 'Andorra', phone: '376' }, + { + code: 'AE', + label: 'United Arab Emirates', + phone: '971', + }, + { code: 'AF', label: 'Afghanistan', phone: '93' }, + { + code: 'AG', + label: 'Antigua and Barbuda', + phone: '1-268', + }, + { code: 'AI', label: 'Anguilla', phone: '1-264' }, + { code: 'AL', label: 'Albania', phone: '355' }, + { code: 'AM', label: 'Armenia', phone: '374' }, + { code: 'AO', label: 'Angola', phone: '244' }, + { code: 'AQ', label: 'Antarctica', phone: '672' }, + { code: 'AR', label: 'Argentina', phone: '54' }, + { code: 'AS', label: 'American Samoa', phone: '1-684' }, + { code: 'AT', label: 'Austria', phone: '43' }, + { + code: 'AU', + label: 'Australia', + phone: '61', + suggested: true, + }, + { code: 'AW', label: 'Aruba', phone: '297' }, + { code: 'AX', label: 'Alland Islands', phone: '358' }, + { code: 'AZ', label: 'Azerbaijan', phone: '994' }, + { + code: 'BA', + label: 'Bosnia and Herzegovina', + phone: '387', + }, + { code: 'BB', label: 'Barbados', phone: '1-246' }, + { code: 'BD', label: 'Bangladesh', phone: '880' }, + { code: 'BE', label: 'Belgium', phone: '32' }, + { code: 'BF', label: 'Burkina Faso', phone: '226' }, + { code: 'BG', label: 'Bulgaria', phone: '359' }, + { code: 'BH', label: 'Bahrain', phone: '973' }, + { code: 'BI', label: 'Burundi', phone: '257' }, + { code: 'BJ', label: 'Benin', phone: '229' }, + { code: 'BL', label: 'Saint Barthelemy', phone: '590' }, + { code: 'BM', label: 'Bermuda', phone: '1-441' }, + { code: 'BN', label: 'Brunei Darussalam', phone: '673' }, + { code: 'BO', label: 'Bolivia', phone: '591' }, + { code: 'BR', label: 'Brazil', phone: '55' }, + { code: 'BS', label: 'Bahamas', phone: '1-242' }, + { code: 'BT', label: 'Bhutan', phone: '975' }, + { code: 'BV', label: 'Bouvet Island', phone: '47' }, + { code: 'BW', label: 'Botswana', phone: '267' }, + { code: 'BY', label: 'Belarus', phone: '375' }, + { code: 'BZ', label: 'Belize', phone: '501' }, + { + code: 'CA', + label: 'Canada', + phone: '1', + suggested: true, + }, + { + code: 'CC', + label: 'Cocos (Keeling) Islands', + phone: '61', + }, + { + code: 'CD', + label: 'Congo, Democratic Republic of the', + phone: '243', + }, + { + code: 'CF', + label: 'Central African Republic', + phone: '236', + }, + { + code: 'CG', + label: 'Congo, Republic of the', + phone: '242', + }, + { code: 'CH', label: 'Switzerland', phone: '41' }, + { code: 'CI', label: "Cote d'Ivoire", phone: '225' }, + { code: 'CK', label: 'Cook Islands', phone: '682' }, + { code: 'CL', label: 'Chile', phone: '56' }, + { code: 'CM', label: 'Cameroon', phone: '237' }, + { code: 'CN', label: 'China', phone: '86' }, + { code: 'CO', label: 'Colombia', phone: '57' }, + { code: 'CR', label: 'Costa Rica', phone: '506' }, + { code: 'CU', label: 'Cuba', phone: '53' }, + { code: 'CV', label: 'Cape Verde', phone: '238' }, + { code: 'CW', label: 'Curacao', phone: '599' }, + { code: 'CX', label: 'Christmas Island', phone: '61' }, + { code: 'CY', label: 'Cyprus', phone: '357' }, + { code: 'CZ', label: 'Czech Republic', phone: '420' }, + { + code: 'DE', + label: 'Germany', + phone: '49', + suggested: true, + }, + { code: 'DJ', label: 'Djibouti', phone: '253' }, + { code: 'DK', label: 'Denmark', phone: '45' }, + { code: 'DM', label: 'Dominica', phone: '1-767' }, + { + code: 'DO', + label: 'Dominican Republic', + phone: '1-809', + }, + { code: 'DZ', label: 'Algeria', phone: '213' }, + { code: 'EC', label: 'Ecuador', phone: '593' }, + { code: 'EE', label: 'Estonia', phone: '372' }, + { code: 'EG', label: 'Egypt', phone: '20' }, + { code: 'EH', label: 'Western Sahara', phone: '212' }, + { code: 'ER', label: 'Eritrea', phone: '291' }, + { code: 'ES', label: 'Spain', phone: '34' }, + { code: 'ET', label: 'Ethiopia', phone: '251' }, + { code: 'FI', label: 'Finland', phone: '358' }, + { code: 'FJ', label: 'Fiji', phone: '679' }, + { + code: 'FK', + label: 'Falkland Islands (Malvinas)', + phone: '500', + }, + { + code: 'FM', + label: 'Micronesia, Federated States of', + phone: '691', + }, + { code: 'FO', label: 'Faroe Islands', phone: '298' }, + { + code: 'FR', + label: 'France', + phone: '33', + suggested: true, + }, + { code: 'GA', label: 'Gabon', phone: '241' }, + { code: 'GB', label: 'United Kingdom', phone: '44' }, + { code: 'GD', label: 'Grenada', phone: '1-473' }, + { code: 'GE', label: 'Georgia', phone: '995' }, + { code: 'GF', label: 'French Guiana', phone: '594' }, + { code: 'GG', label: 'Guernsey', phone: '44' }, + { code: 'GH', label: 'Ghana', phone: '233' }, + { code: 'GI', label: 'Gibraltar', phone: '350' }, + { code: 'GL', label: 'Greenland', phone: '299' }, + { code: 'GM', label: 'Gambia', phone: '220' }, + { code: 'GN', label: 'Guinea', phone: '224' }, + { code: 'GP', label: 'Guadeloupe', phone: '590' }, + { code: 'GQ', label: 'Equatorial Guinea', phone: '240' }, + { code: 'GR', label: 'Greece', phone: '30' }, + { + code: 'GS', + label: 'South Georgia and the South Sandwich Islands', + phone: '500', + }, + { code: 'GT', label: 'Guatemala', phone: '502' }, + { code: 'GU', label: 'Guam', phone: '1-671' }, + { code: 'GW', label: 'Guinea-Bissau', phone: '245' }, + { code: 'GY', label: 'Guyana', phone: '592' }, + { code: 'HK', label: 'Hong Kong', phone: '852' }, + { + code: 'HM', + label: 'Heard Island and McDonald Islands', + phone: '672', + }, + { code: 'HN', label: 'Honduras', phone: '504' }, + { code: 'HR', label: 'Croatia', phone: '385' }, + { code: 'HT', label: 'Haiti', phone: '509' }, + { code: 'HU', label: 'Hungary', phone: '36' }, + { code: 'ID', label: 'Indonesia', phone: '62' }, + { code: 'IE', label: 'Ireland', phone: '353' }, + { code: 'IL', label: 'Israel', phone: '972' }, + { code: 'IM', label: 'Isle of Man', phone: '44' }, + { code: 'IN', label: 'India', phone: '91' }, + { + code: 'IO', + label: 'British Indian Ocean Territory', + phone: '246', + }, + { code: 'IQ', label: 'Iraq', phone: '964' }, + { + code: 'IR', + label: 'Iran, Islamic Republic of', + phone: '98', + }, + { code: 'IS', label: 'Iceland', phone: '354' }, + { code: 'IT', label: 'Italy', phone: '39' }, + { code: 'JE', label: 'Jersey', phone: '44' }, + { code: 'JM', label: 'Jamaica', phone: '1-876' }, + { code: 'JO', label: 'Jordan', phone: '962' }, + { + code: 'JP', + label: 'Japan', + phone: '81', + suggested: true, + }, + { code: 'KE', label: 'Kenya', phone: '254' }, + { code: 'KG', label: 'Kyrgyzstan', phone: '996' }, + { code: 'KH', label: 'Cambodia', phone: '855' }, + { code: 'KI', label: 'Kiribati', phone: '686' }, + { code: 'KM', label: 'Comoros', phone: '269' }, + { + code: 'KN', + label: 'Saint Kitts and Nevis', + phone: '1-869', + }, + { + code: 'KP', + label: "Korea, Democratic People's Republic of", + phone: '850', + }, + { code: 'KR', label: 'Korea, Republic of', phone: '82' }, + { code: 'KW', label: 'Kuwait', phone: '965' }, + { code: 'KY', label: 'Cayman Islands', phone: '1-345' }, + { code: 'KZ', label: 'Kazakhstan', phone: '7' }, + { + code: 'LA', + label: "Lao People's Democratic Republic", + phone: '856', + }, + { code: 'LB', label: 'Lebanon', phone: '961' }, + { code: 'LC', label: 'Saint Lucia', phone: '1-758' }, + { code: 'LI', label: 'Liechtenstein', phone: '423' }, + { code: 'LK', label: 'Sri Lanka', phone: '94' }, + { code: 'LR', label: 'Liberia', phone: '231' }, + { code: 'LS', label: 'Lesotho', phone: '266' }, + { code: 'LT', label: 'Lithuania', phone: '370' }, + { code: 'LU', label: 'Luxembourg', phone: '352' }, + { code: 'LV', label: 'Latvia', phone: '371' }, + { code: 'LY', label: 'Libya', phone: '218' }, + { code: 'MA', label: 'Morocco', phone: '212' }, + { code: 'MC', label: 'Monaco', phone: '377' }, + { + code: 'MD', + label: 'Moldova, Republic of', + phone: '373', + }, + { code: 'ME', label: 'Montenegro', phone: '382' }, + { + code: 'MF', + label: 'Saint Martin (French part)', + phone: '590', + }, + { code: 'MG', label: 'Madagascar', phone: '261' }, + { code: 'MH', label: 'Marshall Islands', phone: '692' }, + { + code: 'MK', + label: 'Macedonia, the Former Yugoslav Republic of', + phone: '389', + }, + { code: 'ML', label: 'Mali', phone: '223' }, + { code: 'MM', label: 'Myanmar', phone: '95' }, + { code: 'MN', label: 'Mongolia', phone: '976' }, + { code: 'MO', label: 'Macao', phone: '853' }, + { + code: 'MP', + label: 'Northern Mariana Islands', + phone: '1-670', + }, + { code: 'MQ', label: 'Martinique', phone: '596' }, + { code: 'MR', label: 'Mauritania', phone: '222' }, + { code: 'MS', label: 'Montserrat', phone: '1-664' }, + { code: 'MT', label: 'Malta', phone: '356' }, + { code: 'MU', label: 'Mauritius', phone: '230' }, + { code: 'MV', label: 'Maldives', phone: '960' }, + { code: 'MW', label: 'Malawi', phone: '265' }, + { code: 'MX', label: 'Mexico', phone: '52' }, + { code: 'MY', label: 'Malaysia', phone: '60' }, + { code: 'MZ', label: 'Mozambique', phone: '258' }, + { code: 'NA', label: 'Namibia', phone: '264' }, + { code: 'NC', label: 'New Caledonia', phone: '687' }, + { code: 'NE', label: 'Niger', phone: '227' }, + { code: 'NF', label: 'Norfolk Island', phone: '672' }, + { code: 'NG', label: 'Nigeria', phone: '234' }, + { code: 'NI', label: 'Nicaragua', phone: '505' }, + { code: 'NL', label: 'Netherlands', phone: '31' }, + { code: 'NO', label: 'Norway', phone: '47' }, + { code: 'NP', label: 'Nepal', phone: '977' }, + { code: 'NR', label: 'Nauru', phone: '674' }, + { code: 'NU', label: 'Niue', phone: '683' }, + { code: 'NZ', label: 'New Zealand', phone: '64' }, + { code: 'OM', label: 'Oman', phone: '968' }, + { code: 'PA', label: 'Panama', phone: '507' }, + { code: 'PE', label: 'Peru', phone: '51' }, + { code: 'PF', label: 'French Polynesia', phone: '689' }, + { code: 'PG', label: 'Papua New Guinea', phone: '675' }, + { code: 'PH', label: 'Philippines', phone: '63' }, + { code: 'PK', label: 'Pakistan', phone: '92' }, + { code: 'PL', label: 'Poland', phone: '48' }, + { + code: 'PM', + label: 'Saint Pierre and Miquelon', + phone: '508', + }, + { code: 'PN', label: 'Pitcairn', phone: '870' }, + { code: 'PR', label: 'Puerto Rico', phone: '1' }, + { + code: 'PS', + label: 'Palestine, State of', + phone: '970', + }, + { code: 'PT', label: 'Portugal', phone: '351' }, + { code: 'PW', label: 'Palau', phone: '680' }, + { code: 'PY', label: 'Paraguay', phone: '595' }, + { code: 'QA', label: 'Qatar', phone: '974' }, + { code: 'RE', label: 'Reunion', phone: '262' }, + { code: 'RO', label: 'Romania', phone: '40' }, + { code: 'RS', label: 'Serbia', phone: '381' }, + { code: 'RU', label: 'Russian Federation', phone: '7' }, + { code: 'RW', label: 'Rwanda', phone: '250' }, + { code: 'SA', label: 'Saudi Arabia', phone: '966' }, + { code: 'SB', label: 'Solomon Islands', phone: '677' }, + { code: 'SC', label: 'Seychelles', phone: '248' }, + { code: 'SD', label: 'Sudan', phone: '249' }, + { code: 'SE', label: 'Sweden', phone: '46' }, + { code: 'SG', label: 'Singapore', phone: '65' }, + { code: 'SH', label: 'Saint Helena', phone: '290' }, + { code: 'SI', label: 'Slovenia', phone: '386' }, + { + code: 'SJ', + label: 'Svalbard and Jan Mayen', + phone: '47', + }, + { code: 'SK', label: 'Slovakia', phone: '421' }, + { code: 'SL', label: 'Sierra Leone', phone: '232' }, + { code: 'SM', label: 'San Marino', phone: '378' }, + { code: 'SN', label: 'Senegal', phone: '221' }, + { code: 'SO', label: 'Somalia', phone: '252' }, + { code: 'SR', label: 'Suriname', phone: '597' }, + { code: 'SS', label: 'South Sudan', phone: '211' }, + { + code: 'ST', + label: 'Sao Tome and Principe', + phone: '239', + }, + { code: 'SV', label: 'El Salvador', phone: '503' }, + { + code: 'SX', + label: 'Sint Maarten (Dutch part)', + phone: '1-721', + }, + { + code: 'SY', + label: 'Syrian Arab Republic', + phone: '963', + }, + { code: 'SZ', label: 'Swaziland', phone: '268' }, + { + code: 'TC', + label: 'Turks and Caicos Islands', + phone: '1-649', + }, + { code: 'TD', label: 'Chad', phone: '235' }, + { + code: 'TF', + label: 'French Southern Territories', + phone: '262', + }, + { code: 'TG', label: 'Togo', phone: '228' }, + { code: 'TH', label: 'Thailand', phone: '66' }, + { code: 'TJ', label: 'Tajikistan', phone: '992' }, + { code: 'TK', label: 'Tokelau', phone: '690' }, + { code: 'TL', label: 'Timor-Leste', phone: '670' }, + { code: 'TM', label: 'Turkmenistan', phone: '993' }, + { code: 'TN', label: 'Tunisia', phone: '216' }, + { code: 'TO', label: 'Tonga', phone: '676' }, + { code: 'TR', label: 'Turkey', phone: '90' }, + { + code: 'TT', + label: 'Trinidad and Tobago', + phone: '1-868', + }, + { code: 'TV', label: 'Tuvalu', phone: '688' }, + { + code: 'TW', + label: 'Taiwan, Province of China', + phone: '886', + }, + { + code: 'TZ', + label: 'United Republic of Tanzania', + phone: '255', + }, + { code: 'UA', label: 'Ukraine', phone: '380' }, + { code: 'UG', label: 'Uganda', phone: '256' }, + { + code: 'US', + label: 'United States', + phone: '1', + suggested: true, + }, + { code: 'UY', label: 'Uruguay', phone: '598' }, + { code: 'UZ', label: 'Uzbekistan', phone: '998' }, + { + code: 'VA', + label: 'Holy See (Vatican City State)', + phone: '379', + }, + { + code: 'VC', + label: 'Saint Vincent and the Grenadines', + phone: '1-784', + }, + { code: 'VE', label: 'Venezuela', phone: '58' }, + { + code: 'VG', + label: 'British Virgin Islands', + phone: '1-284', + }, + { + code: 'VI', + label: 'US Virgin Islands', + phone: '1-340', + }, + { code: 'VN', label: 'Vietnam', phone: '84' }, + { code: 'VU', label: 'Vanuatu', phone: '678' }, + { code: 'WF', label: 'Wallis and Futuna', phone: '681' }, + { code: 'WS', label: 'Samoa', phone: '685' }, + { code: 'XK', label: 'Kosovo', phone: '383' }, + { code: 'YE', label: 'Yemen', phone: '967' }, + { code: 'YT', label: 'Mayotte', phone: '262' }, + { code: 'ZA', label: 'South Africa', phone: '27' }, + { code: 'ZM', label: 'Zambia', phone: '260' }, + { code: 'ZW', label: 'Zimbabwe', phone: '263' }, +]; + +export default countries diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx new file mode 100644 index 00000000..45e1961f --- /dev/null +++ b/frontend/src/components/CreatorGrid.jsx @@ -0,0 +1,306 @@ +import React, { useEffect, useState } from 'react'; + +import ReactGA from 'react-ga4'; +import { useTheme } from '@material-ui/core/styles'; +import {Link} from 'react-router-dom'; + +import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; + +import algoliasearch from 'algoliasearch/lite'; +import { InstantSearch, Configure, connectSearchBox, connectHits } from 'react-instantsearch-dom'; +import { + Grid, + Paper, + TextField, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip, + Card, + Box, + CardContent, + IconButton, + Zoom, + CardMedia, + CardActionArea, +} from '@material-ui/core'; + +import { + Avatar, + AvatarGroup, +} from "@mui/material" + +import { + SkipNext as SkipNextIcon, + SkipPrevious as SkipPreviousIcon, + PlayArrow as PlayArrowIcon, + VerifiedUser as VerifiedUserIcon, +} from "@material-ui/icons"; + + +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const CreatorGrid = props => { + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs } = props + const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows + const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs + const theme = useTheme(); + //const [apps, setApps] = React.useState([]); + //const [filteredApps, setFilteredApps] = React.useState([]); + const [formMail, setFormMail] = React.useState(""); + const [message, setMessage] = React.useState(""); + const [formMessage, setFormMessage] = React.useState(""); + + const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,} + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + const innerColor = "rgba(255,255,255,0.65)" + const borderRadius = 3 + window.title = "Shuffle | Workflows | Discover your use-case" + + const submitContact = (email, message) => { + const data = { + "firstname": "", + "lastname": "", + "title": "", + "companyname": "", + "email": email, + "phone": "", + "message": message, + } + + const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly." + + fetch(globalUrl+"/api/v1/contact", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(response => { + if (response.success === true) { + setFormMessage(response.reason) + //alert.info("Thanks for submitting!") + } else { + setFormMessage(errorMessage) + } + + setFormMail("") + setMessage("") + }) + .catch(error => { + setFormMessage(errorMessage) + console.log(error) + }); + } + + // value={currentRefinement} + const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { + useEffect(() => { + if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { + const urlSearchParams = new URLSearchParams(window.location.search) + const params = Object.fromEntries(urlSearchParams.entries()) + const foundQuery = params["q"] + if (foundQuery !== null && foundQuery !== undefined) { + refine(foundQuery) + } + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='off' + type="search" + color="primary" + value={currentRefinement} + placeholder="Find Creators..." + id="shuffle_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + const paperAppContainer = { + display: "flex", + flexWrap: "wrap", + alignContent: "space-between", + marginTop: 5, + } + + const Hits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + return ( + + {hits.map((data, index) => { + if (counted === 12/xs*rowHandler) { + return null + } + + counted += 1 + const creatorUrl = !isCloud ? `https://shuffler.io/creators/${data.username}` : `/creators/${data.username}` + + return ( + + + + + + +
+ {"Creator + + @{data.username} + + + {data.verified === true ? + + + + : + null + } + +
+ + {data.apps === undefined || data.apps === null ? 0 : data.apps} apps {data.workflows === null || data.workflows === undefined ? 0 : data.workflows} workflows + + {data.specialized_apps !== undefined && data.specialized_apps !== null && data.specialized_apps.length > 0 ? + + {data.specialized_apps.map((app, index) => { + // Putting all this in secondary of ListItemText looked weird. + return ( +
{ + console.log("Click") + //navigate("/apps/"+app.id) + }} + > + + + +
+ ) + })} +
+ : + null} +
+
+
+
+
+
+ ) + })} +
+ ) + } + + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(Hits) + + return ( +
+ + +
+ +
+ +
+ {showSuggestion === true ? +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + {formMessage} +
+ : null + } +
+ ) +} + +export default CreatorGrid; diff --git a/frontend/src/components/DocsGrid.jsx b/frontend/src/components/DocsGrid.jsx new file mode 100644 index 00000000..ea20cd0e --- /dev/null +++ b/frontend/src/components/DocsGrid.jsx @@ -0,0 +1,356 @@ +import React, {useEffect, useState} from 'react'; + +import ReactGA from 'react-ga4'; +import { useTheme } from '@material-ui/core/styles'; +import {Link} from 'react-router-dom'; + +import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; +import aa from 'search-insights' + +import algoliasearch from 'algoliasearch/lite'; +import { InstantSearch, Configure, connectSearchBox, connectHits } from 'react-instantsearch-dom'; +import { + Zoom, + Grid, + Paper, + TextField, + Avatar, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip, + List, + ListItem, + ListItemAvatar, + ListItemText, +} from '@material-ui/core'; + +import {Close as CloseIcon, Folder as FolderIcon, Polymer as PolymerIcon, LibraryBooks as LibraryBooksIcon} from '@material-ui/icons' + +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const DocsGrid = props => { + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, } = props + const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows + const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 2 : parsedXs + const theme = useTheme(); + //const [apps, setApps] = React.useState([]); + //const [filteredApps, setFilteredApps] = React.useState([]); + const [formMail, setFormMail] = React.useState(""); + const [message, setMessage] = React.useState(""); + const [formMessage, setFormMessage] = React.useState(""); + + const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,} + + const innerColor = "rgba(255,255,255,0.65)" + const borderRadius = 3 + window.title = "Shuffle | Apps | Find and integrate any app" + + const submitContact = (email, message) => { + const data = { + "firstname": "", + "lastname": "", + "title": "", + "companyname": "", + "email": email, + "phone": "", + "message": message, + } + + const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly." + + fetch(globalUrl+"/api/v1/contact", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(response => { + if (response.success === true) { + setFormMessage(response.reason) + //alert.info("Thanks for submitting!") + } else { + setFormMessage(errorMessage) + } + + setFormMail("") + setMessage("") + }) + .catch(error => { + setFormMessage(errorMessage) + console.log(error) + }); + } + + const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { + useEffect(() => { + if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { + const urlSearchParams = new URLSearchParams(window.location.search) + const params = Object.fromEntries(urlSearchParams.entries()) + const foundQuery = params["q"] + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery) + refine(foundQuery) + } + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='off' + type="search" + color="primary" + defaultValue={currentRefinement} + placeholder="Search our Documentation..." + id="shuffle_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + var workflowDelay = -50 + const Hits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + //console.log(hits) + //var curhits = hits + //if (hits.length > 0 && defaultApps.length === 0) { + // setDefaultApps(hits) + //} + + //const [defaultApps, setDefaultApps] = React.useState([]) + //console.log(hits) + //if (hits.length > 0 && hits.length !== innerHits.length) { + // setInnerHits(hits) + //} + + var counted = 0 + return ( + + {hits.map((data, index) => { + workflowDelay += 50 + + const innerlistitemStyle = { + width: "100%", + overflowX: "hidden", + overflowY: "hidden", + borderBottom: "1px solid rgba(255,255,255,0.4)", + backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", + cursor: "pointer", + marginLeft: 5, + marginRight: 5, + maxHeight: 75, + minHeight: 75, + maxWidth: 420, + minWidth: "100%", + } + + if (counted >= 12/xs*rowHandler) { + return null + } + + counted += 1 + + var name = data.name === undefined ? + data.filename.charAt(0).toUpperCase() + data.filename.slice(1).replaceAll("_", " ") + " - " + data.title : + (data.name.charAt(0).toUpperCase()+data.name.slice(1)).replaceAll("_", " ") + + if (name.length > 96) { + name = name.slice(0, 96)+"..." + } + + //const secondaryText = data.data !== undefined ? data.data.slice(0, 100)+"..." : "" + const secondaryText = data.data !== undefined ? data.data.slice(0, 100)+"..." : "" + const baseImage = + const avatar = data.image_url === undefined ? + baseImage + : + + + var parsedUrl = data.urlpath !== undefined ? data.urlpath : "" + parsedUrl += `?queryID=${data.__queryID}` + + return ( + + { + aa('init', { + appId: searchClient.appId, + apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] + }) + + const timestamp = new Date().getTime() + aa('sendEvents', [ + { + eventType: 'click', + eventName: 'Product Clicked Appgrid', + index: 'documentation', + objectIDs: [data.objectID], + timestamp: timestamp, + queryID: data.__queryID, + positions: [data.__position], + userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, + } + ]) + + console.log("CLICK") + }}> + { + setMouseHoverIndex(index) + }}> + + {avatar} + + + {/* + + + + + + */} + + + + ) + })} + + ) + } + + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(Hits) + const selectButtonStyle = { + minWidth: 150, + maxWidth: 150, + minHeight: 50, + } + + return ( +
+ {/* +
+ +
+ */} +
+ +
+ +
+ + +
+ {showSuggestion === true ? +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + {formMessage} +
+ : null + } + + + + Search by + + + Algolia logo + + +
+
+ ) +} + +export default DocsGrid; diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx new file mode 100644 index 00000000..22a9d9ea --- /dev/null +++ b/frontend/src/components/EditWorkflow.jsx @@ -0,0 +1,516 @@ +import React, { useEffect, useContext } from "react"; +import theme from '../theme.jsx'; +import { isMobile } from "react-device-detect" +import ChipInput from "material-ui-chip-input"; +import UsecaseSearch from "../components/UsecaseSearch.jsx" + +import { + Badge, + Avatar, + Grid, + InputLabel, + Select, + ListSubheader, + Paper, + Tooltip, + Divider, + Button, + TextField, + IconButton, + Menu, + MenuItem, + FormControlLabel, + Chip, + Switch, + Typography, + Zoom, + CircularProgress, + Dialog, + DialogTitle, + DialogActions, + DialogContent, + OutlinedInput, + Checkbox, + ListItemText, + Radio, + RadioGroup, + FormControl, + FormLabel, + +} from "@material-ui/core"; + +import { + ExpandLess as ExpandLessIcon, + ExpandMore as ExpandMoreIcon, + Publish as PublishIcon, + OpenInNew as OpenInNewIcon, +} from "@material-ui/icons"; + +const EditWorkflow = (props) => { + const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, } = props + + const [submitLoading, setSubmitLoading] = React.useState(false); + const [showMoreClicked, setShowMoreClicked] = React.useState(false); + const [innerWorkflow, setInnerWorkflow] = React.useState(workflow) + const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove + const [newWorkflowTags, setNewWorkflowTags] = React.useState(workflow.tags !== undefined && workflow.tags !== null ? JSON.parse(JSON.stringify(workflow.tags)) : []) + const [selectedUsecases, setSelectedUsecases] = React.useState(workflow.usecase_ids !== undefined && workflow.usecase_ids !== null ? JSON.parse(JSON.stringify(workflow.usecase_ids)) : []); + const [foundWorkflowId, setFoundWorkflowId] = React.useState("") + const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "") + const [description, setDescription] = React.useState(workflow.description !== undefined ? workflow.description : "") + + + // Gets the generated workflow + const getGeneratedWorkflow = (workflow_id) => { + fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 when getting workflow"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.id === workflow_id) { + console.log("GOT WORKFLOW: ", responseJson) + if (name === "") { + innerWorkflow.name = responseJson.name + setName(responseJson.name) + } + + if (description === "") { + innerWorkflow.description = responseJson.description + setDescription(description) + } + + if (newWorkflowTags === []) { + innerWorkflow.tags = responseJson.tags + setNewWorkflowTags(responseJson.tags) + } + + if (selectedUsecases === []) { + selectedUsecases = responseJson.usecase_ids + } + + innerWorkflow.id = responseJson.id + innerWorkflow.blogpost = responseJson.blogpost + innerWorkflow.actions = responseJson.actions + innerWorkflow.triggers = responseJson.triggers + innerWorkflow.branches = responseJson.branches + innerWorkflow.comments = responseJson.comments + innerWorkflow.workflow_variables = responseJson.workflow_variables + innerWorkflow.execution_variables = responseJson.execution_variables + + + setInnerWorkflow(innerWorkflow) + setUpdate(Math.random()) + } + }) + .catch((error) => { + //alert.error(error.toString()); + console.log("Get workflow error: ", error.toString()); + }) + } + + if (foundWorkflowId.length > 0) { + getGeneratedWorkflow(foundWorkflowId) + + setFoundWorkflowId("") + } else { + } + + if (modalOpen !== true) { + return null + } + + const newWorkflow = isEditing === true ? false : true + + var upload = ""; + var total_count = 0 + + return ( + { + setModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: isMobile ? "90%" : 550, + maxWidth: isMobile ? "90%" : 550, + minHeight: 400, + //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, + //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, + }, + }} + > + +
+
+
+ + {newWorkflow ? "New" : "Editing"} workflow + + {newWorkflow === true ? null : +
+ + + + + +
+ } +
+ + Workflows can be built from scratch, or from templates. Usecases can help you discover next steps, and you can search for them directly. Learn more + + {showUpload === true ? +
+ + + +
+ : null} +
+ {/*newWorkflow === true ? +
+ + Use a Template + + + Start your workflow from our templating system. This uses publied workflows from our Creators to generate full Usecases or parts of your Workflow. + +
+ : null*/} +
+
+ + +
+ { + setName(event.target.value) + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + placeholder="Name" + required + margin="dense" + defaultValue={innerWorkflow.name} + label="Name" + autoFocus + fullWidth + /> + { + setDescription(event.target.value) + }} + InputProps={{ + style: { + color: "white", + }, + }} + maxRows={4} + color="primary" + defaultValue={innerWorkflow.description} + placeholder="Description" + multiline + label="Description" + margin="dense" + fullWidth + /> +
+ { + newWorkflowTags.push(chip); + setNewWorkflowTags(newWorkflowTags); + }} + onDelete={(chip, index) => { + newWorkflowTags.splice(index, 1); + setNewWorkflowTags(newWorkflowTags); + }} + /> + {usecases !== null && usecases !== undefined && usecases.length > 0 ? + + Usecases + + + : null} +
+ + {showMoreClicked === true ? + + + + Status + { + console.log("Data: ", e.target.value) + + innerWorkflow.workflow_type = e.target.value + setInnerWorkflow(innerWorkflow) + }} + > + } label="Test" /> + } label="Production" /> + + + +
+ + + Type + { + console.log("Data: ", e.target.value) + + innerWorkflow.workflow_type = e.target.value + setInnerWorkflow(innerWorkflow) + }} + > + } label="Trigger" /> + } label="Subflow" /> + } label="Standalone" /> + + + + + + { + innerWorkflow.blogpost = event.target.value + setInnerWorkflow(innerWorkflow) + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={innerWorkflow.blogpost} + placeholder="A blogpost or other reference for how this work workflow was built, and what it's for." + rows="1" + label="blogpost" + margin="dense" + fullWidth + /> + { + innerWorkflow.video = event.target.value + setInnerWorkflow(innerWorkflow) + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={innerWorkflow.video} + placeholder="A youtube or loom link to the video" + rows="1" + label="Video" + margin="dense" + fullWidth + /> + { + innerWorkflow.default_return_value = event.target.value + setInnerWorkflow(innerWorkflow) + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={innerWorkflow.default_return_value} + placeholder="Default return value (used for Subflows if the subflow fails)" + rows="3" + multiline + label="Default return value" + margin="dense" + fullWidth + /> + + : null} + + { + setShowMoreClicked(!showMoreClicked); + }} + > + {showMoreClicked ? : } + + +
+ {/*newWorkflow === true ? +
+ +
+ : null*/} + + + + + + +
+ ) +} + +export default EditWorkflow; diff --git a/frontend/src/components/ExtraApps.jsx b/frontend/src/components/ExtraApps.jsx new file mode 100644 index 00000000..c854a7ba --- /dev/null +++ b/frontend/src/components/ExtraApps.jsx @@ -0,0 +1,27 @@ +// Move this to the backend to be loaded in? +const extraApps = [{ + "name": "Cases", + "description": "Allows use of other Case Management apps without knowing how to use them.", + "app_version": "1.0.0", + "app_name": "Cases", + "type": "ACTION", + "large_image": encodeURI('data:image/svg+xml;utf-8,'), + "template": true, + "actions": [{ + "name": "Create Alert", + "description": "Create a ticket", + "parameters": [{ + "name": "id", + }, + { + "name": "name", + }, + { + "name": "description", + "multiline": true, + }, + ], + }], +}] + +export default extraApps diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx new file mode 100644 index 00000000..a3f4e520 --- /dev/null +++ b/frontend/src/components/Files.jsx @@ -0,0 +1,809 @@ +import React, { useState, useEffect } from "react"; + +import { + IconButton, + List, + ListItem, + ListItemText, + ListItemAvatar, + ListItemSecondaryAction, + Tooltip, + Button, + FormControl, + InputLabel, + TextField, + Divider, + Select, + MenuItem, +} from "@material-ui/core"; + +import { + OpenInNew as OpenInNewIcon, + Edit as EditIcon, + CloudDownload as CloudDownloadIcon, + Delete as DeleteIcon, + FileCopy as FileCopyIcon, + Cached as CachedIcon, + Publish as PublishIcon, + Clear as ClearIcon, + Add as AddIcon, +} from "@material-ui/icons"; + +import { useAlert } from "react-alert"; +import Dropzone from "../components/Dropzone.jsx"; +import CodeEditor from "../components/ShuffleCodeEditor.jsx"; +import theme from "../theme.jsx"; + +const Files = (props) => { + const { globalUrl, userdata, serverside, selectedOrganization, isCloud, } = props; + + const [files, setFiles] = React.useState([]); + const [selectedNamespace, setSelectedNamespace] = React.useState("default"); + const [openFileId, setOpenFileId] = React.useState(false); + const [fileNamespaces, setFileNamespaces] = React.useState([]); + const [fileContent, setFileContent] = React.useState(""); + const [openEditor, setOpenEditor] = React.useState(false); + const [renderTextBox, setRenderTextBox] = React.useState(false); + + const alert = useAlert(); + const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log"] + var upload = ""; + + const handleKeyDown = (event) => { + if (event.key === 'Enter') { + + console.log('do validate') + console.log("new namespace name->",event.target.value); + fileNamespaces.push(event.target.value); + setSelectedNamespace(event.target.value); + setRenderTextBox(false); + } + + if (event.key === 'Escape'){ // not working for some reasons + console.log('escape pressed') + setRenderTextBox(false); + } + + } + + const runUpdateText = (text) =>{ + fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body:text, + credentials: "include", + }).then((response) => { + if (response.status !== 200) { + console.log("Can't update file"); + } + return response.json(); + }) + //console.log(text); + } + + const getFiles = () => { + fetch(globalUrl + "/api/v1/files", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return; + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.files !== undefined && responseJson.files !== null) { + setFiles(responseJson.files); + } else { + setFiles([]); + } + + if (responseJson.namespaces !== undefined && responseJson.namespaces !== null) { + setFileNamespaces(responseJson.namespaces); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + useEffect(() => { + getFiles(); + }, []); + + const deleteFile = (file) => { + fetch(globalUrl + "/api/v1/files/" + file.id, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for file delete :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success) { + alert.info("Successfully deleted file " + file.name); + } else if ( + responseJson.reason !== undefined && + responseJson.reason !== null + ) { + alert.error("Failed to delete file: " + responseJson.reason); + } + setTimeout(() => { + getFiles(); + }, 1500); + + console.log(responseJson); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const readFileData = (file) => { + fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for file :O!"); + return ""; + } + return response.text(); + }) + .then((respdata) => { + // console.log("respdata ->", respdata); + // console.log("respdata type ->", typeof(respdata)); + + if (respdata.length === 0) { + alert.error("Failed getting file. Is it deleted?"); + return; + } + return respdata + }) + .then((responseData) => { + + setFileContent(responseData); + //console.log("filecontent state ",fileContent); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const downloadFile = (file) => { + fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { + method: "GET", + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return ""; + } + + console.log("Resp: ", response) + + return response.blob() + }) + .then((respdata) => { + if (respdata.length === 0) { + alert.error("Failed getting file. Is it deleted?"); + return; + } + + var blob = new Blob([respdata], { + type: "application/octet-stream", + }); + + var url = URL.createObjectURL(blob); + var link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", `${file.filename}`); + var event = document.createEvent("MouseEvents"); + event.initMouseEvent( + "click", + true, + true, + window, + 1, + 0, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + null + ); + link.dispatchEvent(event); + + //return response.json() + }) + .then((responseJson) => { + //console.log(responseJson) + //setSchedules(responseJson) + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const handleCreateFile = (filename, file) => { + var data = { + filename: filename, + org_id: selectedOrganization.id, + workflow_id: "global", + }; + + if ( + selectedNamespace !== undefined && + selectedNamespace !== null && + selectedNamespace.length > 0 && + selectedNamespace !== "default" + ) { + data.namespace = selectedNamespace; + } + + fetch(globalUrl + "/api/v1/files/create", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(data), + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return; + } + + return response.json(); + }) + .then((responseJson) => { + //console.log("RESP: ", responseJson) + if (responseJson.success === true) { + handleFileUpload(responseJson.id, file); + } else { + alert.error("Failed to upload file ", filename); + } + }) + .catch((error) => { + alert.error("Failed to upload file ", filename); + console.log(error.toString()); + }); + }; + + + + const handleFileUpload = (file_id, file) => { + //console.log("FILE: ", file_id, file) + fetch(`${globalUrl}/api/v1/files/${file_id}/upload`, { + method: "POST", + credentials: "include", + body: file, + }) + .then((response) => { + if (response.status !== 200 && response.status !== 201) { + console.log("Status not 200 for apps :O!"); + alert.error("File was created, but failed to upload."); + return; + } + + return response.json(); + }) + .then((responseJson) => { + //console.log("RESPONSE: ", responseJson) + //setFiles(responseJson) + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const uploadFiles = (files) => { + for (var key in files) { + try { + const filename = files[key].name; + var filedata = new FormData(); + filedata.append("shuffle_file", files[key]); + + if (typeof files[key] === "object") { + handleCreateFile(filename, filedata); + } + + /* + reader.addEventListener('load', (e) => { + var data = e.target.result; + setIsDropzone(false) + console.log(filename) + console.log(data) + console.log(files[key]) + }) + reader.readAsText(files[key]) + */ + } catch (e) { + console.log("Error in dropzone: ", e); + } + } + + setTimeout(() => { + getFiles(); + }, 2500); + }; + + const uploadFile = (e) => { + const isDropzone = + e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0; + const files = isDropzone ? e.dataTransfer.files : e.target.files; + + //const reader = new FileReader(); + //alert.info("Starting fileupload") + uploadFiles(files); + }; + + return ( + 1366 ? 1366 : 1200, + margin: "auto", + padding: 20, + }} + onDrop={uploadFile} + > +
+
+

Files

+ + Files from Workflows.{" "} + + Learn more + + +
+ + {/* */} + (upload = ref)} + onChange={(event) => { + //const file = event.target.value + //const fileObject = URL.createObjectURL(actualFile) + //setFile(fileObject) + //const files = event.target.files[0] + uploadFiles(event.target.files); + }} + /> + + + {fileNamespaces !== undefined && + fileNamespaces !== null && + fileNamespaces.length > 1 ? ( + + File Category + + + ) : null} +
+ {renderTextBox ? + + + + + : + + + } + {renderTextBox && { + handleKeyDown(event); + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + placeholder="File category name" + required + margin="dense" + defaultValue={""} + autoFocus + />}
+ + + + + + + + + + + + + + + + {files === undefined || files === null || files.length === 0 ? null : + files.map((file, index) => { + if (file.namespace === "") { + file.namespace = "default"; + } + + if (file.namespace !== selectedNamespace) { + return null; + } + + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + const filenamesplit = file.filename.split(".") + const iseditable = file.filesize < 2000000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1]) + + return ( + + + + + + + ) : ( + + + + + + + + + + ) + } + style={{ + minWidth: 100, + maxWidth: 100, + overflow: "hidden", + }} + /> + + + + + + + { + setOpenEditor(true) + setOpenFileId(file.id) + readFileData(file) + }} + > + + + + + + + { + downloadFile(file); + }} + > + + + + + + { + const elementName = "copy_element_shuffle"; + var copyText = + document.getElementById(elementName); + if ( + copyText !== null && + copyText !== undefined + ) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + alert.error( + "Can only copy over HTTPS (port 3443)" + ); + return; + } + + navigator.clipboard.writeText(file.id); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + alert.info(file.id + " copied to clipboard"); + } + }} + > + + + + + + { + deleteFile(file); + }} + > + + + + + + style={{ + minWidth: 250, + maxWidth: 250, + // overflow: "hidden", + }} + /> + + ); + }) + } + +
+
+ ) +} + +export default Files; diff --git a/frontend/src/components/FrameworkData.jsx b/frontend/src/components/FrameworkData.jsx new file mode 100644 index 00000000..456ea24f --- /dev/null +++ b/frontend/src/components/FrameworkData.jsx @@ -0,0 +1,28 @@ +const data = { + "Cases": { + "large_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAgAElEQVR4Xu19d3xUVfr+M5PJTNq0FLr0TuiEEkAQcAXFAvaOvWPHVdd1Lbui/lAUy1dBXbGhIqu7UkQUBAkBQjVIh9AS0qaXTLvn93knRBGTuXeSe2fulPtH/smZc97zvs899z1vVTDGGGLo8QYYnt5qwY09s9BbnxpDlMuT1OJKDz466MSbhdnyJLAJqhSxBtzKugC6fXMSf++vw8N9tEhRxBS/ZUWs288wc5MZ7x11w35pO2Smxg4zYw64iw85cXmxBWdlpWDn5FYwqJWyAkMsEVNc5cF1G8w46Azg5wm5GN1GEzPkxxxwJ66owo8WX5DBc/O1uD9fFzPMlhOhfo5hdqkdT/1qBxjwSLcMvDzcKCcSQ9ISU8C1eDkYv6pAUD84pZkfm9oaHTJTYobhciH0pDuAcSursc/DBUkq1KuwcmIeMlWxoS7EFHCXHnFh6noz0MBcBtzWMR3vjDIiNtgtF9gCc36x4ZFddkBZz7keaUp8XpiNwblq+RAZgpKYAu716034+Jj7N2bTvjqrlfiy0IhhrWJHP4s2MsweDu2XVMB92s02hTHMGazH/T2zok2eoPVjBri1dQFMWV2LzXb/HzfGgPu6ZeClwXqkJU0MvEInDev5bVb8fa/jDwcAqV4zu2bgxSGxwceYAe76kx5cV2xGmbdeJzv96aJR4puzc9DfmLTr8iH3iN2PKWtqsdsd+NMBMFqvwuJxOWiTLv87Q0wAl06JN/Y58PB2G+rtCWc8HMOjXTPx0nADn9wS+v8cA17d48ATpTZ4G3E7aThg43l5GBgDB0BMANflZ3i4xIL/O+JGk7cwH4eyi9qgk1aV0OAMtflKdwAzisxYUeNtnI8cwxsDdLinr1b2PIwJ4B5zBjBlTQ12OQJNA5cxTMvT4NNxOUldtwnYfXPMjUvWm/6o254+ljGM1Kdiw+RWSeCKwYGiag9Gr6qpt9+GeLQKYNFII84/K12MZeNujvzlVdhl8wGKEHwMMJRf3AZtM+St58bEiTvvVztm7rQ1fVI0QIwBV7bVYMEoI7JSk67g09+8FcdcmLLWBPDxJcDw4RAdbuglb3VB9sClO0ThskoU20OoCadJKC9FgYUjDZjcPnnqNrCF7ggFyyvxq4t4yOOqYQxDdanYOLkV3wcuql8k2QP3mMOPjv+r5FUTfuMix3BV+zR8PCZH1oyPpNS/KnPhxk0WOAUu2k6lQNG5ebK+6MoeuPN323H7dptw4ALQcAzrJuSiIOlNg9XL4fZiM76s8IAJ9IvrlcD7Qw2Y3iVDINQjP0zWwCW746jvqrDJynOhOJNvjKEgU4Wfp7SCOsG9aavK63BNkRnVxMwwnru6ZuCNoYaGUIYwfhmZobIG7lGHH6NWVqPcHx7Tg6zzc3ivwICbe8SG710KcQcY8OAmM+YddvFfbP9gFgOm5qnx/igj8mTqRZM1cJeUuXBziQXWP3t5+eXMgDEGFRafnYPWMmU+/yZaNuKoI4BByyphbsY0bVMVWDI2ByNlGi0mW+DSGXvvFgveOuhqBtvrf5IGYN4QPW7tntnsOWL5h7esrcX7FXX8loTGNhlg+GSEEdd0laeeK1vgVrkDuHmDCUurSb9tJnwYMDEnFYvG5iBXk1h23e21XgxeUcVvt22KtQy4q1Ma3hopzyRK2QK3pMaLS3+uxdHGokHCwTHH8MUIIy6X8Q05nO0IGevjGO4oMuODE3TaCvlF42PapSpwYlrb5k8g4S9lC9zPy1y4utgMdipCv9k8YAyGVCXM0+UpgGbvK8QPt1R7cfUGE/bXNedycNrEfoYt5+ZiSJ78gvRlC1y6Dc89HCIaLByJcwyv9ddhZj95uzHD2VJTYykJ8rlSO57d7WjRaRucn2O4v1MG5o6SXxKlbIFLtRMOnUrkE0OgHTVKrJuYi45Z8R32eNwVwHk/1tS7d1v6MMCoAI5Pb4sMmSVRyhK4u0xe5K+o/j0psqUCAEC5EU/2ycJT+TrZGtVF2Cbe3u3A3Tus4dltQyysCDBsmJCLEa3lpS7IErj/2mrFk/vOyIkSQaoFWSp8MtqIHnFauokuZerFFQBV1eILphHITwLuC321eGygvOpXyA64Xo6hw5IKVNOXrgU34kblwjHMHaDD/TEQ4S8QV38Y9o8dNjxDKecif9bPzUnF1+NyZaUuyA64pSYvBq6oBicy8xsk3EmlwK6L2sRM4QuhAC53+lG4qgZHyJIg8gufn56CT0cb0T9bPjUXZAfcuaU2PFRqb7kZrCmJcwyzuqTjxRHyNKwLBeqZ414oteMfu2zwiqQinD5/hgKYX2DANZ3l40WTFXBJR7t4bS2WV3mbKz9Bv0v1M+w4vxX6GOIjnb3CRUmQJqysbYGXMRTnOIYnemXh6YE6qFtqVxckIf5BsgLuAasPV643YSslRUr4KBhwZ8d0vDrcAE0chD0uOeLCjE0W2JsRRCeIzQw4x6jCkvG5sqmOKSvgLj7ixq2bzc2LBhMkgd8HdU9T4uORRoyIg2Dzv6yswvcmn2gmsMZYmcYx7L2wDTrKpMCgbIBLsaMvltrw5GmF2MLEYnjDOYbHembi2UF62Xz+wttA/eiSag8KVtYAUhdlDjB8MFSPGTKpLSYb4Np9HGasN2FJZRPFKpoj1VC/YUBrlQKrJ+bGrK5LFcXPXVmF9QITSVvEQpnVXJANcMtdAfRbWgmLVHpaY1LjGK5vn4aFY3NaJNNo/fjrMhcuLzbDH6kLk5+hclobtJJBYL5sgPtNmQuXFJ1W+zZSaPBx2HxuXsyVKfUEGC5bW4tvq6W1wPxBDAGGxSMMuLRr9APzZQPcO3824Z3jf6x9GxHsMoZx2WosO0deniG+vVMS5E0bzTjui+AnispctdLgy/G5UU/9lwVwnT6Gbt9UoFIKNy8fAgDkKIF3hxsxvWNsFBGh03bWFitepyRIkb1kfOzqoVFizaQ8tIuydUEWwF1bUYdxa2rDqp3Ax+Bw/k923WvbaTBvhFE2dspQ9B9x1t8HhBb4CIcXfGOpUtCHBQZMifJLLgvg3rfBjDeO0ukR4ePjdCkxhlXjcjFR5i2TSDG4v9iEeWVRUKuIX4zh0d5azB4Q3fDQqAPX6WcYvqJKnMBnvuMi1P85hnPzNFg5Mbcls0j+2wNWP3osr6x/yaPxnjPg8rYavDPCCGMUE1CjDtySKg+mrTdF9pLRFLw4hhWjs3GejMuUXrbehK/OaOAi+dtyxgLtUxVYNj4XA6JYuTzqwJ29y46/l9rgi6aacJpgKEXl8Y7pCJArT2aPNcCwsNyD2jDLKYm+jQDD/8bmYGoHqlwRnSeqwCXPzwMlFrwbqkR+NPgSbWCE2nOknA2haGDAQ13SMSeKnSijClyqDXbtehN+tp7RAioaYE2uGRYHzlIrcPSS6KX8RxW4RTVejPuxBknYhoUZeQz2M+w9vxV6RimmOarAfXuPHXdvC6/2rTyklqSCai482S0TzxdEp0VXVIF74apqfCtV1H4SW9JygAEdUhXYf3GbqHQ5ihpwbT4Obb6qgFsOlw1pRRy3s1OoTdHEXAzIiXwSZdSA++VBJ67YbJE0aj9uESOTjaUyhpf66fBAfuRLW0UNuJNWVuMHs0TJfTIRbCKQMb21Bp+MyY64uhAV4FIn9C7/rYQ9ESQb53ss0KrwSWHkqwNFBbhLj7hwWbEFddHwtcc5kCK9vUwF8MkoIy7uENmQ0IgDl5xSd20yY36ZC0wmbt5ICzuu1uMYXsjX4tF+uogGl0ccuGV2P64tMqEo6S2LD/wyYHJOKv5zTm5E9dyIApcK2j1YbMaCY3XwJtWE+AAuNUQMMKwan4MxbSMXdBMx4HoDDK/tsmNWqfjVBOMGAbG8kQBD8aQ8FOSqI1J/OCLApRypN/c48DB1QI+DkkexjC/JaGcMHTNS8NHIbIzNU0se4y45cOky9v5hFx7eaoGthb00JGN6cmJxOMCA/joVvjk7B10kTqaUHLjbzT6MX1VdXw8sqdeKAxA5z8IxDM5SYdP5raGSsLWcpMDdZfYFi1bskaDYsJxll/C0cQwX5Wnw5kgDOmRK0yxGMuDuqPXijmIzNlLJ0ORJm3BYpsrDN7RPw7+G6CUp2SQJcMudgSBol9Z4wZKgTTjQNmw4lbq3d83A7AKD6GeX6MB1Bxge2WzBW2VhtppPWPHG+cb9DC/ma4PNEdNEtCiJCtw6P8Os7VbM2+9Mmr3iHI9hbS/A8PIAHe7tnSUaeEUDLiVz37fNijf3it+fLCwmJQfLkgNaJTBnkB43d8sUxZQvCnDJVvvZYRfu3WqBJWmrlSVwok7UqTIVa87JwTgR2heIAtzvT9Thjs1mHPZQR8OosyhJgFw5wABDCvDN2Byc3cIWqy0G7oZKD64vMuGgNwlaueJFVnQxoE9GCuYPN2B0C8DbIuDut/hw/upaHPAl9QNZgUPmxNBHeVhWCj4dk43uuub1mms2cI86/bhjowUrqJR7Uj2QOVRkSB7HcF6OGv8enY02GSlhE9gs4FJcLVUN/N+JumSWbtgsT/7gNw5wDFPJNTzCgI5Z4bmGmwXcW9eb8B71a0im3iRR2EIOUDX4y9to8OZwA3LD6OYTFnApGPzlX2x4eo8DgWQhjxaKLPnz00/eR3rUN0tMF+hdEwxcPwcsPODAE6V2VPrlVzs2CYMY5wDH8Nc+WXimv7BG14KB+90xN27ebEE5tSdKXsZiHCUyJT/A8Le+Wjw7UMcLMUHA3VnrxcDlVYBawshgmfIySVZkOaBVAK8P1mNG99BNAEMClxSCrSYfLl9vwmGXP3kZi6wME3a1tioF3hyqx7ROGU3yICRwj7oCuKbIhPXJUqAJC6KobJwBZ6Uq8O4wAyY30U8tJHAnfV+N1SYfuKROGxX5RW1RoT0wpLQsMaC9SoFFhcZG6zWEBO5tP5vwWXmd9B0MhRopki+QtFhmQJ5KgakCYggcXg5fVnkkdUApGXBDhzTMLTBAf8b9KiRwbV4Oc0rtmHvQCZtQcIXJ2rapCowwpkIhwJlR6Q6giEqTCrT1hUlKYg8/Ffzyz3wtpnVpWrckJh2y+fHMDmuwdRXv9b+5XA0wXNchDU8P0KG7/s/xDLxWBQLvh/udmLndBqSKf+QFA4z7azE9hCLesHeHj+GlX2x4i9qBqsSnpbk8jvnfMYZumhQsGGHAmNZpIdPK6fCgfMLl1V54pdo4pX91z8TjA3TIbqJ7JS9wG2j75IADt2yxwiPgZAx7Pz4OHxUYcFX3TKh49Cbqm/fUVgte3u+EX0odK+xNxOgPGEOWUoHSya3QSRs6XsDlZ5j2YzVWmsnCJM1+UxjDAz0y8fxAfcg0H8HAJcB8ftiJR3bYUCFB7G0mA57tl4Vbe2ZBx2Mvdvg4zN3jwHN7HPAmC400G0EqBozIScWiUdnowFN5Zo/VFzxp15KqJtGBQRV27+6ZhWfytcjk+aIKBi5xh6LClp+owxXFZkkAk60E7u6SgWeH8qczEy2Ljroxs8QCayBZJSdc9KZQcEu7NPxrsA5deCKz9lr9uHuTGWuksjAxgO46Lw3W48pO6UgV8GKEBVxiDmMA2Xc7L60E6MIm8idDFWC4sUMaFozN4ZVF0EFS68Xla2txWIKvAC8BsTqAY5iWp8Zn43Oh4bnokoe/cFkltjgCktXIaJWiwILhBkw9K10wnMIGboOsyKM2Y4MJu5wBiJ7/wDHcclY6Zg8zIFdAa/miSg8eLLFgU7JqDu+rZFAAt3dKx4s8fXjpUCi1+DDphxpUUYSVFHcbBnRTKzB3iB5TBVzOT99cs4FLk9BpN2urFT/UeEU3UVGJ4Ont0zB3mAF5AsBbWuvF07/YsOSktLZFXmTIeIBRAfytjxa398pEVmrTcScE2p+qPLi3xIJddolKaDFgrF6F2YP0KGyjCZtrLQIurVbhCuD29bX4tkZ8+yrpYd11KhRPyIVBAHir3AE8ud2KBWQuS9p6fwcD6Xd+hkWjjLioYwbSeS4+e2x+TFhTgwo3nbRhY4r/Bww4NzsVrxcY0LuZvYBbDFyi0u7lcF+xGZ9WeOATe6McC9Za/fGcXHTKVPF+scjWe8cWC74ocyXNZcFLCaBXKbB8bDZG8dQzIE9vqcWLgd9V1wNWCvWAYxiuT8WSsdloH2a6jmiqwukTUaG753+x4W0paoYxhlFGNd4YqscQAe03qQL6c6V2vHvAiWoKehf7ZeI/U+QxggG9MlPwyhA9JrdNC2nF8nMMKys8uGmTGVVkY5QCtIxhevv0YPBMTlrLQmRFOXEbpGTycFiwz4HHqM8DmTTEBAxVu85MwcKRBgzK5deJSE9bdMiJx3fYcCQRLQ4cQ1u1Et+Oz8UgYyqv6fW7E3W4fbMFRz3SqAdqBtzWPQPP9dfBKEJct6jAPfVlwht7HJi53SqJoTrdz7B6Ui5GCCzj8+NxN64sMqNGHmdgZKhgwLDMFGy8oDUvYImgTZV1GLmqBoxKiIt52DTsNsDwcPdMvDTMIIgeIUwSHbi0KH2qX9njwLO/2lEnQXBOD40Srw7WY/JZ6YLuYFuqPbh1kwXbE8BcRjVpL2mtxpxhBpzFo0OSnJYec+NqcihJodNStFkK8HCPLDw2UCcEj4LHSAJcWt3HMXxQ5sajW041LRHzTWZAjzQlnsvX4spuoVM8Gjix1+LD/dus+K4yjs1lAYb7umTgkXwtb50CMs1+ctCJJ0ttOEFeBgmermolnuqXhRk9skSfXTLgNqgNays9uK7IhOMS6Jk6xjC7vw539eVvO0+iocimmymyiWy98WYu8zPMzs/CfX11yBAQOfflISduLbHCJjqk6ic0sPoev3/pQNFmYp5a9fNLCtwGnqwur8M9WyzY7RJf8afkumf7aXFXryxe9yXRQyafG9fV4j+VHjilOWgkgkLT02YogCd6Z+GRvlpeHtQFGD494MQtJRYghBOi2ZtgDB3USiwZnY0CgfeQ5qwVEeASYWsr6oJeto12v+iXNqNSgft6ZuJv+TpBsiDwUlzvq/udqKKwt1h9gqQzvDJIj7u6Z/JW+6awxAV7HXhmtx0m0f309adCQbYa84fpMTBbLSlXIwZc2sU+qy8YU7CMCuWJ/PkgNl3XMR1vFhh4BUi0kBC/POzCPVuscLbMpFgvIHobhLwEZB8lNaWlX08GdNYoMW+4AVPapgnSfF75xYZ/7nVIA1rGUGhUY8EII3rpVGKL908vQUSBS6sfd/oxY70ZP1h84r+RAYbLOqXjvQIDdAI/gxtO1uGc1bXwEHiba3TnGGZ1zcBNPbPAkXs1xEOXon/+YseXlZ4WRVt1USvxaaERIwV8jskH8/J2K5741S6JeqBkDOMNaiydFLkO6hEHbhC8Dj/u3GTBUrrhi31J4hhu6pSBOUP0MAqIbyB6iqu9uGmzGXuaE1DCgKE6FYrOy4Na4FdkY7U3eGE9QMb+cB/6HGtVmDNUj7Ft+LuVW7wcXt9lxwt7HagTSF84JGUpgCvapeGtkUZe/TqcefnGRgW4RFRNXQBP77TjrUNO0dUG+mRPpQqAwwzoyJOOEtQSGbDV4sOjJRasrg1PjaEUaupdOzRXuE5Hp+78fQ7cs9UKJsAC8JsQOYYL8zR4YYge/Yz8BZFJc3msxIIFZS5YJVDlUzkGKlb3UD8dclvowuUD6pn/jxpwiRAKiHlouxXzCbzN/UyH2HGhVoWPRmejq05Y7VWHn+Hq1TX4VmiYJsfwdJ8sPNmfLoXhKa1k556ysho/WH3C9h5gmNZGgzdGGNFOYCHk29bV4sNyCQKfiOc+hpfzszAzXwe12F9NASiOKnCD++eAB7dY8OFhFxwCCA5rCAPOy07FawUG9BIYPkfmolmbLXj/qJu3nsQInSpoq+zWSPq0EDqpmWHPbytxnK/XMccwvW0a3h5uENRe1Ozh8EiJBe8fc4v/NWNAjkqB2fla3NpLfMeCEL7RmKgDN3ghB/Dmbgee+9WOaiE3c6G7O3XbH2NIxf8bohcc31Bbx+GdfQ48/asdfvoSNHKYKvwMH4404Pquwjx3TZH8VZkLd2+xNm2WYwz3dsvE8wN0fyqK0dicVi+Hx7ZasfCoG+5w+CRkLIE2VYFXB+pweecMQdYbIdM2Z4wsgEuEk95HWcSztttQLjZ4GdA3TYnXh+kxsT3lkvI/bj/DsuNuXFZsrv+Unw5ejuHmdml4e3R2yM/kUYcf7TNVIe+fZJa7s8SCjxoJB6Wr15P9tHi4d5aggsfEtmvX1uLrSg88/FsMb8Sp/MKfxudgVK46bNUovMX4R8sGuA2k/uewCzM2mWGTQOftrlHi3yONYbUpWlZeFwxCsftZMDeU/mhVCpimtw1Zk4TUgGt/NmFCrhr39NWGVOGP2v2Y+EMNDgRz7eufoEewrxb399MKMvlSSOkFa2pQLEH6OJVC6pimxHfn5KKnwPsCP/RaNkJ2wKXtrDruxkPbbfjFKXK+Exnt1Qq8NdyIye3TBAGCwLqmwoNZ2ywosfmhT1HgjcF6XBeifiv5Ir447MS922yo9QRQNDEPo3jqca045saUIlNQmt00Sjybr8M1PDViG0R/yOHHfVus+O6kB4Hw7oj86GHAGIMKrw0zYkgOvyWDf0JxRsgSuLS1YObuVis20a07zBt7SNYwoJVKgQf6aTGrV5ZgM3Kp2YdHN1uQoVFiwUhjSBtxrYdD4ffV2EcvHurzqz4oNAbVhlDP33bYgmGGLw7S4S8dhKk0Fg+HKetqUSxFKdgAw5RcNeYUGNBH4OVWHFjyzyJb4NJJd8Dqw7jva1BBCrCY4AVAn79XB+twb48swVOT3ks39nY8VV+uWV2Dz6iS4Sl1hw5BCgR6oq825Fpk0aBPftuMFEFfAzrNOy+tAlX2Ed2cyDHMaJeG+WOyJYnu4odm6BGyBW4D2ZRFfOG6WmwhF7HYei/HMG+wHrd0y+TNfBXCaHJkfHzIiRvoQneGy5k+/1+MzsaQMBwVTa1JL/Vuiw83bjSjRAKdllwpt3VMxz8GkWMh/OZ5QnjV0jGyBy5t8LgrgJlbrcGGgH4xdThCAGN4rEcm/pqvE5QCH4rhO02+YOPC/a5GdHOOBS9qKybkCYpgC7XOfpsPMzZaUGSirp4iMoT4EWB4qk8WHuiThWyZgpZ4ExPAJUJNXg4PbrJgITUGFFltoBPmzs7peH6wHlqBwTlnAsvp4/DXrVa8XeZu+oIUYHiprxaPtiCNhTI5Jv1Yg+MSqQfvDTPg2i4ZEY07aM7pGzPAbdjc/RvMWHDMDVdzdhvqN8GEvgz8faCet1pkY9MsO1GHC9bV8r5U2Qrg23E5vDUOzlyDLBU7TF5cts6EQ3UB3nXCZU9WCvB8/3qdPwoe3HDJjZ0Tt2FnFKI3e4cVrx1woqYZwVUhORRgwdLtTw3UByvoCH3KnX5MWlWD3XyuW5qQMZzfWoMPRhgFuW8baNhQ5cHMLdagSU7QzU0o8Qxol6rAcwN1uLFrZkyANqZUhdPlQLdoqpJOtXrrRD4eqOzT1Dw1Xh1mQBcB4KUv9kU/1WBFGHls5CR+ZYAOt/cW5usvrvTgxmIz9gl5MYQClsZxDEOyVHhjuCEY1yuithwOFc0aG3Oqwum7XHzQias3WuAPJzRQCJvoImVIxX8n5YUsMEx3mff2OnD/Dlt4qssp96nz0rbICPHi0bC15XWYurYWDpH1evIADs5QYuHobORLnGYjhOXhjolp4NJmvz7iwgPbbDgidgWWAMMEYyreKTSiu65xj9HPlfUVDXeccjSExXwq/GZQYX5hdqMl7Mm0trqiLqge7HLXOzJEeziGc4ypeHWoAQNFMM+JRlcYE8U8cCmudVV5XTDVutxDZigRP3gcC3YEurFLBi5snx70llGNrTKbHysq6vDZUTd2tKTICMdwdo4aV3VKD+aN5aWngDrUl9n9+PiIC8vLPdjdmGktDAH/aSjFUHSgSuR63toLLVlG6t/GPHBPqWrYa/OhL1UZpOu3mJ9VBqQpAEpRUZJKwlEMNYMzwOAV4yU5NX8m5VCqFGCn5rcHGAJizH86gjiGGzql45UhBuQITGuSGoDNnT8ugNuw+aPOAKatrcVOux/+5nIkTn+Xyhguap+O94cbmmXukxtb4gq4xFxqtPHQFkuwDxcTUWuQm+DCoYfCdR7ulYW/5fMXDAln3miOjTvgEjNPuAJ4oNiMxVJkEUdTWs1YmzoZUYjkrT0zoRFThWoGLWL+JC6B28CgW9fVYmF5HXxi64piSkDCubQcw+tD9JjRU5i9WEJSRJ86roFLKfD/2mnH/5W5xM+/El0UIk5IqUoZSrzQX4eLOofuyyviqhGdKq6BS5yk+NZzf6jGVqpZliAn7xCtCq8M0mFcW/6CIRFFm4iLxT1wiVc/nHBj0ppaScoPiSgLUaai8/Xl/lrc1UdYrpooi0ZhkoQALvG1839P4gh5oOL81O2oVmDdxDxBFXyigDfRlkwY4H520IlrNlnEr1UmmihEmIhjuLNLBt4eYRRhMnlPkTDAPeEM4MI1NdjWnLgCecvwd+p8HI5d0gYdeJIyY2U7oehMGOBSTAOV93xujwNcPDomGMM1bTT4ZFxuPOCSdw8JA1zixE8nPbih2ISj1I8izh4NY8Hum4UC6uXGw9YTCriUXj55bS3WVv+eOh4PQqTY2il5anw8OhvZMR48I1QeCQVcYsqyI25cQBVjRM6cEMpwScYxYM5AHR7snRVTWQwt4UXCAdfLMfT95iQOStC+qiWCaMlve6cr8XlhNgYI6HPcknXk9NuEAy4x/+syF6ZtMMfHqcsx3NghHe+OMkalwHK0wJyQwCVdt2BZJXa5xe+7FnFBejmsn5SLQgH9ICJOm4QLJiRwKZ/r5VIbHtt1qsu7hAyWdGrG0DYtBeUXt5F0GTlOnpDAJUFsqPHiyvUmHGtO5xu5SDLAsHxMdrAZd6I9CQtcqgR+80YzPtmsRyIAAAPmSURBVD9RF5syZ0CBNgU/nddKULXy2Nxk01QnLHCJJUuPunFVsVn8pikRQImCY3htoA73xnkUWFOsTGjg0ql79ooqbBG78nkEgNtJo8SiwmyMzBPeXy0CZEVsiYQGLnH5i4NOXLnRgpANHSImDuELXdE+LVh/LEPsKj7CSYjqyIQHLnWqabekHFWUzx4jwTdUe+zDAgMu7RKfaTlC3oiEBy4x6d29Dtyx1RobDgkG9NAoUXJBK8GNtoUAIdbGJIFLvSZsfpy/pgb7qRqi3B8/w2sDtMFWpIn8JIELwBNgeGybFa8ddMlbXTgVjemY3haZqTGi10j0diWBe4qxy465cdNmC6qocrRcH47h2d5ZeGqQXq4URoyuJHBPsdru43Dpmlp8b6LuPhHjf1gL5aUosHx8DoYmUBRY0o4rACJfHnLiCmr1pFIKGB3hIQy4pr0m2BVTr5YhfRFmR/LEPYPhikUn6k9cmaWxqxnDiwP1eEBg+f0I4yjiyyWBewbL399rxy3bbOLW2BVBrH3TU7D8nJyYLsYsAht+myIJ3DO4WVUXwNkrq7FXTqYxjuGBbpl4pcAgV/VbTEwKmisJ3DPYRKXyn99pwzN7HPI5dX0cDl3UBl20wltYCZJ+DA9KArcR4a056cH1G0w47pOBaYxjuLSVBosnJEa9BKHvUhK4jXDK7uVwVZEJyyqpV65QVkozLo0xrJuQi2F5GmkWiNFZk8BtQnAfHXTilhJLdItCM+DCVmosGpuTsFFgSTtumCcLuYFbL66ANYonLgWLvzvMgFu6Z0b74A+Te9IPT564IXj8770O3BTFqLGBmSn4pDAb/YyNNwiUHh7yXSEJ3BCycfgYBnx7EoejUDxEyRju7JyBVwsMUMdR0xGxXoUkcHk4OXunDY9TGnuESzZpAgzfj8/B2Dguh98SECeBy8O9bSYvpv5Ui/IIm8Z6pCmx76LEq5cgFMxJ4PJwitSFe0osWHjMLZSnLR8XYFg6Ohvnd0y8eglCmZcErgBOLS5z4fYSK8zUJ1jqhwH90pXYOrV1UrcNweskcAUAkU7dwu+q8At1Mpf6CTC8M0SP23vFX1M9MVmXBK5Abr6z2447t9skv6S11yjxv7NzMDhpAgspmSRwBQLX6uXQ5ZuTMFM+pVROCQbc3DEd8woMSU8Zj1ySwBUIXBo2b5cNM3dKZxozKoH5QxO7XoJQcSSBK5RTAI44Ayj4rgrVZBqT4NQdmpmCVefmwZBMzeGVShK4vCz6fUBdgOHxrVbM3e8UH7hUxG6QHjP7acOgKHGHJoEbpuyPOgPYb/WF+Sthw4fnaaBN8HoJwjgF/H/DzoXJiXD50AAAAABJRU5ErkJggg==", + }, + "IAM": { + "large_image": undefined, + }, + "SIEM": { + "large_image": "data:image/png;base64,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", + }, + "Assets": { + "large_image": "data:image/png;base64,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", + }, + "Intel": { + "large_image": "data:image/jpg;base64,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", + }, + "Comms": { + "large_image": "data:image/jpeg;base64,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", + }, + "Network": { + "large_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4wgeDxwNTyjOPAAAFq1JREFUeNrtnXmQZEWdxz+Z76iqvquruufgGu4R5ZiRQy65ApFhAEEGhlFc0T2IDdTViI3QCGN31dUIjV1kdZdFQ13XRS5hlVHOheEcEOWQG7lhoKd7unu6q6+qeu9l5v6Rr2t6pq+q7n7VA/qNmICqfvUy8/fL/GX+zhTGGMMiwahRdPEFoqEHiIY3o0tvALqufRBuHqf5Q3itJ+M0HI7wcoBYLJIg6s8QjQ66UEMPEw7djx57HqMGAbNIhIiHLxtw0vvhNB+P23ISTuYAEH7de1M/hpgIVXqZaOB2oqH70eUtYCIQksWckTv3UYOIV03T0XjZs3CaViNkQ926kDxDjEIV/0i4/WaiwgOYaFv8B1m3Qc6h02A0wmnCaToSr/18nOZjEDKTeMuJMkSX3yTsv5lw8A5M0ANCsNushqowgTHNx+Pn1+M0Hg7CTazFRBhi1DDhwG2Efdejy6+PN5XYIJJHzBgvh5ddi5dfj/T3SKSlBWaIQY0+Q7Dtx0TDD4MJ2L1FU+3jA5CZg/E7P4PXesqCb/wLxhCjxwi3byTY9lNM0AXCqT+96gWjEU4jbvZs/M5Lkf7SBXv1gjBEB+8QdP+AcPAOMGXeW6tiOliyOQ1HkFr2OZym1SyEWJ43Q9ToU5S7rkCNPTX+ysWmVH1hNMJfTmrpZXjZNSC8eb1uHgzRRIV7KXd9F11+670toqqghZBNeJ2fwu+4ZF56y9wYYhTh9o2Uu7+Pifr50xBRs8GA8PByF5BachnCbZ3TW2pniAkJ+m4g6PkBRg3xZ2ZMhAEkXnYtqeVfRLjZmt9Qm4ZjIoLe6yj3/CfoIn9mxq4QgCEc+DUAqeVfQrhtNb2heooaTbj9FoKeH8bM+BPbvGuCZUp5679h1EhNv6yaIWHh/yhv/T5Gj/BnZsyGeKVs30jQ8yPQ5ap/WZXIUiO/p9x1JUYNkKyYMoATW4CThrHW5sQmlwAUQd/1CC+P37GBamg3K0N0+U3LjKArYUJpZOYQ/NxF4DRR8VMkBaOJhu4jGrwzwbYEmCJBz4+Q/nLc1lNn/cWMDDFqhHL3VajiswkzwyCcVlJLL8dtOT7BdnaG03g4uvwmeuy5BMcnMWqAcve/I/29kJkDZ3x6BoYYwv4biQr3kPxpyuC2no7bfHT8McToILnmhIuQKaS/FD+/ntLb34xNPkmJL4kuvkq5+yrSe38N4bRM++S0DIlGfkfQe03CchZAI/098fPrrdnBRJR7/hs18luSmQgG4S0lvfwLCC+P23oabmETUeHe2F+TEIQkGnqAsP8m/M5LmY6mUzLERP0EPT/ChH11MIk4eO3nV5ayGv0DYd81sQUgIQIJhzC9H/6SSxFOI15+A2r0D7FvP8nJFxH0XYfTuAqncdWUT0wxBQ1B/y9RI48nzwyjcRrej9d+jv2oxgh6r8VEA9YrJ5xk/qEJt/8vuvgKAG7Taty2M0n8IIHEBD0E2/4Lo4arY4gae4Gw/yZAJdw5AzKDl9+A8DoAiAr3Eg1vTlZ0xMPWwVsE/TfGgRYufn4d0t+HxMOQhEM0/Eh8upuqZzvRKCDsuw4TbCXxjdwY3OYTcFtOsh/DbYR914EuUR/FUxAN3kk08pglRHo/vNwF1GpNmtvYA4K+G9BB16Q/7UT1aORxoqH76jBDDcLL4XdsQDgNWDF5C6r4/DTHTzNHV+n476Yaj8BEA4S9P6+YN7zsWpyGw2w4UKKQ6NIrhNs3squYrIze6BJh/82YqEA9ZqjXtsZGcACq+BLh9l8ynZh0W04ls+Jf8fOX1BDxYXCbTySz4gr8zs+ASE1+RDhEI48SDd5jP3rt8SRpJPn9RBMN/AZden2nbysMUaNPoIZ/WwezhUam9sXLr7MbrAkJ+67HBO8wWUwaECm83Pm4LSfgL/ksTsPhYGbb3wzCzeEv+SvcluPxchcg/SVMuT+YMkHfdehgKwBOy4k4LSdB4vGDEh28TbjLXmIpYELCgVtj/0bSq8PFy12ITO0DQDT8O6LBu6cRkwJMEMf8gnBb8bJngUzN3ITRuM0n4DQcEg+vBxNNNzaJLv2RsP+XgEHINH7+YoTXSfKrxBAN3oUO3pnQG6zIqMvqMBqncTVe9qP2oxoi6L1mlomgCAdvszoR4LZ8GCd90Axy3iDcLF77WivejIon2/YZ2tCEAxtRY88D4DR8AC97drK0AECiy28SDT0w4RsgKmzCRH0kuzoMwmnC7/hExZMWDdyFGn1slokg0aWXKp0WXg43e9b0e4nROE1HIxsOA0CVXiUaun+WsUlMsJWw73prKhcSL3c+Mn0giR+DTUQ0eHc8KUHqsNue/ZOGMbgtp+I2HwuALr9N0H9DHEw3C3RgZ3lUAMBtPQWZWjEFsQw4jXjZtQiZAgzRwG2YsJtZJ5sQRIVNNsAPkKk98XIXJR8BLyS6+GJldUo18hi6/AbJ6h0a4S/F67jYyn+jCftvRpderq5d4aDGnqlMHCEbEE6TFVtGWeXOKBuSI3yE225bLb0RG0erogxGDceWgkEAvOxHcBqPSvgYLDBqBDX0AGBwo8G766CMSbzsuTgN7wNAjT1DOPCb2l5hygQ9P8FEA+jym6jiiwg3h0zvZ/3Wuowub0GHXQQ9V+O0nIQa2owO3qbqySYc1OiThAO343dcjHBa8Ds2UCo+E5s6krKtQTTyGF7Yg6tGH0+KCzE0Mv0+vNx5gMDoEkHfdZiwp0ZbmUSXX6Hc9R0QGbzsWXjtH0Om90c4GdAROuolKjxI2Hct0dvfjOlX68oPCftvxG0+DpneB7f5GNzW0wn7b05QYZaY4G306LNIq6UmaXZO4efXI/1lAKihh+zynOOJTjitpJZ9kfSeX8ZpPCxW4iRIH+nvgd+xnvQ+35nWmloNcXT5dWvPM8rGWuUvQvh7kOQGb/QY0fAjCRusjMZt+hBu22n2Y7Tdymg1ytwmgYuXvwQ/f8GMm63TsJLUHl9GpvadMxHDwVtRozY81skcjNd+HpCs9VuNPp4kQwzCbcPr2IBwmu0gt//GxgDPZXUYjZNZiZ87rypR5zSsxMtvmGOsrcCE/QR912LUGAB+7lyczCEJbvACXd6SLEPctjNwm44EQJdes3KYaM5vdJqPQXj5qp93W05A+nsyp1UiBNHQQ7EOA8LrxMuvB5khOQ1eJ8UQjfT3xs9dGGvLEUHfL9DBm8z5eC0Ewq2eGQDCzVoTyJzsUgJ00bojwl4A3NZTcZuPS9TOlRBDBG7bmcjMAQBEo08SDd4+z3eamqMAMRHMJ1hCSNTYs7GZHITTgJe7IBbByTAlMYbY008MXQITzvut0dB9NvWhKmiiwn1W+ZyPjc4odNTPOAOE05Ro0mdiIiscvLNi0nabjsZtPY35zSqJLj5P6a2vEg7cbt+9a4im0Rg1hBp7jnLX9yl3XYHRcz3R2XEIfzle+7n2HSaKDZWD83jnzEiI1RJdfIGw70ZSyy4HmcLr+CRq9Al0sIX5zAM19hRqy4tIbxkytRfCyyNkC8aEmKgfE3ajy1visFfmSTiJ134uTuYg2/boU0SDdyVDshgJOpAN4cBGay5vWoWTOQgvt47y1u8xPwVLggnRwRu7pFwbKuU5FqQ6hEamV8YRMQKjiwR919vwpASjcRI89gpM2EfQ+z+xIghe+9lWg16Qs7ycENoz/v9x6NBCiBPh4eU+jvSXA6CGNqOGNyfuM0r27UIQDW+uLHPhtuN3fArhtjD3/cTELtwZfj9uBZ5rG0bhNKzCazvDfowG7OpI2sxE4rE+AkyJoO9adPltANyWY3FbP1IjscaZIBHeUpzmY5HpQ6Z8TjhtuNm1OI1HxDG0htpEZOxIy19UyRMMB+9Cjf6hLomtdQhCkujSy3aDX/4FEB5+xwbUyO+rUBQNGOuSdRpX4baciNO4CuG2U9rydXTx2clEMgFedg1O4xHo4ktEww/b6kOlVydUH5qpSY3TfDxOHIWvg644cDCkHil8dWCIRTjwa5yWE3Cbj0am98fLr6e89YppIkhiRniduK2n4mXXIDMHI2QaADX6DGr0yQnEHa+1ZZ1MUWETbvOx9jDRdARefh2qcH/sN39hBuLG0Sr5i+LKP4aw/xZ06ZW6MKOODBGYqJ+w92c4De9DOM142TVEhU2okd/vPGuNRrituK2n4+U+bo+cuyhiUWETRk1Ix5YNsU6iQQjU8G/R5beQ6RWAQHqdyPw63NZTCQfvst7K8iuVvu1EkLbTK6Z7VXwpTuDUdWNI/dJo46C0cOAO+9FtQ6b3Z8deYme503QM6b2/bf0dDYdMYoYOuomGHpzwXg8/d1FcnccSTodbp4wTsNGSF5PZ97t4uYsRsmmXE5+0ES2VeLGbMOFU8WLJoW4iC7Cxw70/RYgUCBc1/IhdHUYj3Ha8/Hr8/LqKT3wq2Nn/RkwkjUztg5e/EKOG0f1vxCpJRFTYhNd+TsX0PxEytRfpPb5E1HQU5Z4foksvVN4XDt6OcHPo4C2iwh3UO8G1vgxBooN3KL3zDcaD4MAgMyvjdLbjZjzJGF20QQsmqOgbbsvJSH8ZbutpRIO3W1OJsJYCNfr09ClywsNtOxWZOYBy99VxNLpCjT5OcezZeG9LOllpKgrVHSKOErFWWKf5BDL7fAu39cRZj5V67Ln4+CmxG3BHJZHSaTwU2XBoLIJsJIdl3sxhpzK1N+k9v4Lf+enY10HcN8VipH8vYikGidt2Jum9/jHeS2aDISxs2hHlaAxOZiXCaUYHPaCLNu53nKlCoIYftcU2Z4Fwmkkt+RtSSy9HyHoEWk+POousHfCyZ8elJ6or0qLLb6OGHtoxaYVEjT1D8bXLK8+YSoUJW3NEh11Ew5vx0ytmb0D6+PmLAEHQfdWiFUhYBIbYCMbUss/VVDEnGn54UoyVUYMTrLowrotMeIBo6AG89vPiPJRZIFz8/IVgFOXu/wBTr+ShHaizyNI4DYfbSjk1+MbtfrApzgiufBv/V7AzI8yOv8Wbuy6+WH0XhYuXv9AyZhEkeh1XiEG4S/CXXo5M7VXTL9Xo0+ixCcULhGc9d9PMXqPHKtGYJioQFe7FaVpFtbNdyBR+56Wo0quxmHxP6iEOfn4DbvORtf3M6FgzH44VNo3bdtq0ud5CSMLBTQQ9V2M1d4iGH8ILLq6Y0quBcLOklvw1xdIrNsqyTqKrPgwxCqdpNV7unJoHpoMtqJFH4jBOY/PKs2sqXryp4GU9ooFbKt5JXd6CGn4UmTuvpradxsPw2s+PmVsf1MlAk8HLXTijBj4doqHNsW8+1j1SK5CpfTFqBKOGd/k3itEBwuvAaTxyglUmICrcY0VZTRB47ecg0wdQl1sbhF+HFWI0TuP7cZs/VPtP1VCs3EUVzdyEPZTe+iqTV5qteWgtwsKmHFf2HIkaexo99mJczrV6WCvA6QSllxMmlEZmDqgDQ4TAbT5uTkUh1ehTqOILEzZVazVWceDabO3uEADjm/ummjb3cbhNRxH2NmN0gikJSNymY5DCW0KimqnwEKkVtf/OhNb1O8ltKqosn7GLNI43d11+p4ZOxD/1O+fpdp51sAinBafpGKTbfFxCjUwY0Bxcn6r4KtFOiajjrtha/40TUaLLbxENPTSH/vuJBsdhNDK9P07DSly39VSiwt3JpUQbha5GxOyCqHAPJuqN+6RxGsazd2voo4Bw8G7UyKPY8KGIqHA3XvtZU5rlp4OOBueRQlFNP6UV604rrtN4KDJzMGr40WSc+CaylYVy51WdGqCDrVYzr8RZ2ZAcr31t7WN12imOPhWb7CWq+Dxq5Enc1g9X/Q5d/GOcd5gEQwzCba/48KVwWnFbTkluSQqJGnmUaKTa1DlDNHBrHAQXO6H8FThNR82peadpNU76wIpZHjVCOLCx6iOwUUOEA7ctSGzy1A3Y3H0ntni7YPMowr7r0MFbLLxqIjBqgKD7KuvbTu8349PR4D0Evddi5f+4jcoQ9t04txw/A8YEO1mJo6EHCft/Za27M0kFExH03Yga3cXvv2CIS1S1nVHJCItLjRvKXVcS9P6U5HRFg9NwGP7Sy3GbVk0SX0YNE26/jWDbjzFRz+R+zCfacdJVS/ZU43d+Bi/38Sn3ExP2EfTdQNh37TwDtmciicJp+iCZfa9EOFYtqNR+V8UXKb7+hYTtNhrhtNur6ZqOsoXLdBldfp1o+BHU2NMJF6PciRogPJzG1bgtp+BkDgSZsb75sWeJCvdaHShRz6EkveeX8XLrKt/sKMZvNKWufyHs+zmJF0s22q4Q6cdhn+X4u6nEx3SB06qKqkATsJOiOLE7ClultAGEgzEh6DHGnVzJkUHjNB5KZsWVO7kiduzkwobeR4V7MWFXgp0RlbqHO2rIi2lluZc9G6f5mMkiSxcxplhlm7bIS7j9V5M3Z+EAJvYQTuhj0qtUpvBy6yb5hXY6WjmZg/Daz7EF9+uCWQZtlJ0obWfMe1MNtv1sFwdXjX1ZSBiF03zslJWuJ9kXvPaPITMr61DmrgoISTT8MGqehj0TDRAV7q5NxCUGmy7u5z8xdczYpC/8Zfgdl0BcC3FxITBhN2H/r2aZ3TMjGrxnFyPl4sLNrsFtPmbKv03ZQ6/1tDg3YrEZYhEN3rpz+GgN0MWXCPp+Xl0ZqKRhNDKzEr/jk9Mq4lNPGZnC7/x0/RwzM0JgVIHy1u/ZILkaoINuSluvRJdfY1FD0ACr+zTjd342LmYwNabtpUytsJdbyeRysquHRJdfo7TlG0SFB6sQXwY19iKlLV+z8cOLzgwAYe1xrSfP/NSMl4KZiHLPDwh6fsziMwWsYpnFbfsoXnYtMrNfnMcRB8fpAB10ERXutaXE55nxu2AwGqflBDJ7fx3h5mZ8dNZb2owaorTln4kKd+4eg6sk8GeR6f1tdVOZBh2iwy506dXY2qB2j/4a65pN7/0tnMzBsz5e1bV5uryF0pZ/QI08sducVCoa/6QRLURK9EJBI9wO0nv9E27LCVX9oirqytRepJb/fXylxGJv8uOYxpW7OzHDaSG17PM13RpU9XR3Gg4hvcdXkP5eu4fSuFvDIGQj/pLLbOHnGiZJTfLHafogqT2/gkztuZtovbsjDEI24C/9WxsfXKMXdk534UbDj1B+5zvo0mu70Z6yO0AjZDP+0sti51ft1ezmfFu0Gn2K0jvfTviGs3cTNMLNkVr2ebzs2jm7xOd1n7ouvUqp6wpbA8S+brGpsjgwCpnel9SyL+K2njQvOsz7gnsT9lLu+aGtumZK7BZn/7rBks5pPJLU8r/DafjAvN84b4aATSULt99CsO0nmKC7LjVBFh/aBihkzyG15C/j6y3mjwVhiIVBjTxJuedq1Mhj7Igaea/Blv2Q6X3wOz+L1/bR2e8zqQELyJC4u9F2gr5fEPbfhAm3TRHx8S6G0eA04LWeht/5F/F1FguLBWeIhUaNPkPQew3R8EOgRt/lYkwDjr1zseMTuC0nVwrhLDQSYoiF0WNEQw8R9t1oK1rHl6W8e1aMrZ0iUyvw2s/Fy55VuXMxKSTKkHGYaJBo6H7C7RtRY8/ZMJvdljHjYUouMrUPbnYNXvbMuLhN8v2tC0MqQ1VDqOHfEQ7ejhp9wl6xasxuss9o2xenESez0t5e3XpSnChav77VlSE7xl5GlV4mGnoQNfwwuvT6hLioejJmPJ/dR/rLcZqOwm05Kb4Go2V+r54jFochEwhiogHU2LNEQw/Ysn/lLdTLO2lLB67GbTkep/GDViwt8uHj/wFgziEWYtazhwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOS0wOC0zMFQxNToyODo0Mi0wNDowMA9cm8cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTktMDgtMzBUMTU6Mjg6MTMtMDQ6MDCQliarAAAAAElFTkSuQmCC", + }, + "EDR & AV": { + "large_image": undefined, + }, +} + +export default data; diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx new file mode 100644 index 00000000..975df6b7 --- /dev/null +++ b/frontend/src/components/Header.jsx @@ -0,0 +1,1028 @@ +import React, {useState} from 'react'; +import {BrowserView, MobileView} from "react-device-detect"; +import { useTheme } from '@material-ui/core/styles'; + +import {Link} from 'react-router-dom'; +import ReactGA from 'react-ga4'; + +import { + Paper, + Typography, + Badge, + Tooltip, + List, + ListItem, + Avatar, + Menu, + MenuItem, + Select, + Button, + Grid, + IconButton, + Divider, + LinearProgress, +} from '@material-ui/core' + +import { + MeetingRoom as MeetingRoomIcon, + HelpOutline as HelpOutlineIcon, + Settings as SettingsIcon, + Notifications as NotificationsIcon, + Home as HomeIcon, + Polymer as PolymerIcon, + Apps as AppsIcon, + Description as DescriptionIcon, + EmojiObjects as EmojiObjectsIcon, + Business as BusinessIcon, +} from '@material-ui/icons'; + +import { + Analytics as AnalyticsIcon, + Lightbulb as LightbulbIcon, +} from "@mui/icons-material"; + +import { useAlert } from "react-alert"; + +import SearchField from '../components/Searchfield.jsx' +const hoverColor = "#f85a3e" +const hoverOutColor = "#e8eaf6" + +const Header = props => { +const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, homePage, userdata, serverside, } = props; + const theme = useTheme(); + const alert = useAlert() + + + const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); + const [SoarHoverColor, setSoarHoverColor] = useState(hoverOutColor); + const [LoginHoverColor, setLoginHoverColor] = useState(hoverOutColor); + const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor); + const [HelpHoverColor, setHelpHoverColor] = useState(hoverOutColor); + const [anchorEl, setAnchorEl] = React.useState(null); + const [anchorElAvatar, setAnchorElAvatar] = React.useState(null); + const [subAnchorEl, setSubAnchorEl] = React.useState(null); + + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + setAnchorElAvatar(null); + }; + + const hrefStyle = { + color: hoverOutColor, + textDecoration: "none", + } + + const isCloud = serverside === true || typeof window === 'undefined' ? true : window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + + const clearNotifications = () => { + // Don't really care about the logout + fetch(`${globalUrl}/api/v1/notifications/clear`, { + credentials: "include", + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(function(response) { + if (response.status !== 200) { + console.log("Error in response") + } + + return response.json(); + }).then(function(responseJson) { + if (responseJson.success === true) { + setNotifications([]) + handleClose() + } else { + alert.error("Failed dismissing notifications. Please try again later.") + } + }) + .catch(error => { + console.log("error in notification dismissal: ", error) + //removeCookie("session_token", {path: "/"}) + }) + } + + const dismissNotification = (alert_id) => { + // Don't really care about the logout + fetch(`${globalUrl}/api/v1/notifications/${alert_id}/markasread`, { + credentials: "include", + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(function(response) { + if (response.status !== 200) { + console.log("Error in response") + } + + return response.json(); + }).then(function(responseJson) { + if (responseJson.success === true) { + const newNotifications = notifications.filter(data => data.id !== alert_id) + console.log("NEW NOTIFICATIONS: ", newNotifications) + setNotifications(newNotifications) + } else { + alert.error("Failed dismissing notification. Please try again later.") + } + }) + .catch(error => { + console.log("error in notification dismissal: ", error) + //removeCookie("session_token", {path: "/"}) + }) + } + + // DEBUG HERE + const handleClickLogout = () => { + console.log("SHOULD LOG OUT") + + // Don't really care about the logout + fetch(globalUrl+"/api/v1/logout", { + credentials: "include", + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(() => { + // Log out anyway + removeCookie("session_token", {path: "/"}) + removeCookie("session_token", {path: "/"}) + removeCookie("session_token", {path: "/"}) + removeCookie("session_token", {path: "/"}) + window.location.pathname = "/" + }) + .catch(error => { + console.log(error) + }); + } + + // Rofl this is weird + const handleDocsHover = () => { + setDocsHoverColor(hoverColor) + } + + const handleDocsHoverOut = () => { + setDocsHoverColor(hoverOutColor) + } + + const handleHomeHover = () => { + setHomeHoverColor(hoverColor) + } + + const handleHelpHover = () => { + setHelpHoverColor(hoverColor) + } + + const handleHelpHoverOut = () => { + setHelpHoverColor(hoverOutColor) + } + + const handleSoarHover = () => { + setSoarHoverColor(hoverColor) + } + + const handleSoarHoverOut = () => { + setSoarHoverColor(hoverOutColor) + } + + const handleHomeHoverOut = () => { + setHomeHoverColor(hoverOutColor) + } + + const handleLoginHover = () => { + setLoginHoverColor(hoverColor) + } + + const handleLoginHoverOut = () => { + setLoginHoverColor(hoverOutColor) + } + + // Should be based on some path + const logoCheck = !homePage ? null : null + + const notificationWidth = 300 + const imagesize = 22; + const boxColor = "#86c142"; + const NotificationItem = (props) => { + const {data} = props + + var image = ""; + var orgName = ""; + var orgId = ""; + if (userdata.orgs !== undefined) { + const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0, + borderRadius: 10, + border: foundOrg.id === userdata.active_org.id ? `3px solid ${boxColor}` : null, + cursor: "pointer", + marginRight: 10, + }; + + image = + foundOrg.image === "" ? ( + {foundOrg.name} + ) : ( + {foundOrg.name} {}} + /> + ); + + orgName = foundOrg.name; + orgId = foundOrg.id; + } + } + + return ( + + {/* + {new Date(data.updated_at).toISOString()} + */} + {data.reference_url !== undefined && data.reference_url !== null && data.reference_url.length > 0 ? + + + {data.title} + + + : + + {data.title} + + } + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.title} + : + null + } + + {data.description} + + {/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ? + data.tags.map((tag, index) => { + return ( + { + }} + variant="outlined" + color="primary" + /> + ) + }) + : null */} +
+ {data.read === false ? + + : null} + +
{ + }} + > + {image} +
+
+
+
+ ) + } + + + + const notificationMenu = + + { + setAnchorEl(event.currentTarget); + }}> + + + + + { + handleClose() + }} + > + +
+ + Your Notifications ({notifications.length}) + + {notifications.length > 1 ? + + : null} +
+ + Notifications are made by Shuffle to help you discover issues or improvements. + +
+ {notifications.map((data, index) => { + return ( + + ) + })} +
+
+ + const handleClickChangeOrg = (orgId) => { + // Don't really care about the logout + //name: org.name, + //orgId = "asd" + const data = { + org_id: orgId, + } + + localStorage.setItem("globalUrl", "") + localStorage.setItem("getting_started_sidebar", "open"); + + fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { + mode: 'cors', + credentials: 'include', + crossDomain: true, + method: 'POST', + body: JSON.stringify(data), + withCredentials: true, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(function(response) { + if (response.status !== 200) { + console.log("Error in response") + } + + return response.json(); + }).then(function(responseJson) { + if (responseJson.success === true) { + if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) { + console.log("Region Change: ", responseJson.region_url) + localStorage.setItem("globalUrl", responseJson.region_url) + //globalUrl = responseJson.region_url + } + + setTimeout(() => { + window.location.reload() + }, 2000) + alert.success("Successfully changed active organization - refreshing!") + } else { + alert.error("Failed changing org: ", responseJson.reason) + } + }) + .catch(error => { + console.log("error changing: ", error) + //removeCookie("session_token", {path: "/"}) + }) + } + + const supportMenu = + + + + {}}> + Discord Community Join + + + + + + // Should be based on some path + const parsedAvatar = userdata.avatar !== undefined && userdata.avatar !== null && userdata.avatar.length > 0 ? userdata.avatar : "" + + const avatarMenu = + + { + setAnchorElAvatar(event.currentTarget); + }}> + + + { + handleClose() + }} + > + + { + handleClose(); + }} + > + Admin + + + + + { + handleClose(); + }} + > + About + + + {/* + + { + handleClose(); + }} + > + Get Started + + + */} + + { + handleClose(); + }} + > + Use Cases + + + + + { + handleClose() + }}> + Creator page + + + + { + handleClose() + }}> + Settings + + + + { + handleClickLogout() + event.preventDefault() + handleClose() + }}> +  Logout + + + + + const listItemStyle = { + textAlign: "center", + marginTop: "auto", + marginBottom: "auto", + } + + // Handle top bar or something + const loginTextBrowser = !isLoggedIn ? +
+
+ + + + + + + + + + + + + {/* + + + + + + */} + {isCloud ? + + + + + + : null} + {isCloud ? + + + + + + : + + + + + + } + + +
+
+ +
+
+ + + + + + + + {isCloud ? + + + + + + : null} + + {/* + + {supportMenu} + + */} + + {/* + + + + + + */} + +
+
+ : +
+
+
+ + + +
+ + + logo + {/* + + */} + + +
+ +
+ + +
+ {/* + + */} + Workflows +
+ +
+ + +
+ {/* + + */} + Apps +
+ +
+ {/* + + +
Dashboard
+ +
+ */} + + +
+ {/* + + */} + Docs +
+ +
+ {/* + + +
+ + Pricing +
+ +
+ */} + {/* + + +
Configure
+ +
+ */} +
+
+
+ +
+
+ + + {avatarMenu} + {notificationMenu} + {/*supportMenu*/} + {logoCheck} + + + {/* + + + + + + */} + + {/* + + + + + + */} + + {/*userdata.app_execution_limit !== 5000 && userdata.app_execution_limit !== 10000 ? + userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length > 1 ? null : + + + + + + : null*/} + + + + {userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? + null + : + + + + } + + {userdata === undefined || userdata.app_execution_limit === undefined || userdata.app_execution_usage === undefined || userdata.app_execution_usage < 1000 ? + null + : + +
{ + if (window.drift !== undefined) { + window.drift.api.startInteraction({ interactionId: 326905 }) + } else { + console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) + } + }}> + + + {(userdata.app_execution_usage/userdata.app_execution_limit*100).toFixed(0)}% + + + +
+
+ } + +
+
+
+
+ + const loginTextMobile = !isLoggedIn ? +
+ + + +
+ + + logo + {/**/} + + +
+ +
+ + +
+ About +
+ +
+ + + + + + + + + + +
+
+ : +
+
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + +
+
+
+ + +
+ Logout +
+
+ {logoCheck} + +
+
+
+ + // + const loadedCheck = +
+ + {loginTextBrowser} + + + {loginTextMobile} + +
+ //
+ return ( +
+ {loadedCheck} +
+ ) +} + +export default Header; diff --git a/frontend/src/components/OrgHeader.jsx b/frontend/src/components/OrgHeader.jsx new file mode 100644 index 00000000..2f1b765e --- /dev/null +++ b/frontend/src/components/OrgHeader.jsx @@ -0,0 +1,272 @@ +import React, { useEffect } from "react"; +import { makeStyles } from "@material-ui/styles"; +import { useTheme } from "@material-ui/core/styles"; + +import Tooltip from "@material-ui/core/Tooltip"; +import Grid from "@material-ui/core/Grid"; +import Button from "@material-ui/core/Button"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import { useAlert } from "react-alert"; +import IconButton from "@material-ui/core/IconButton"; +import ExpandLessIcon from "@material-ui/icons/ExpandLess"; +import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; +import SaveIcon from "@material-ui/icons/Save"; + +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important", + }, +}); + +const defaultImage = + "data:image/png;base64,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"; + +const OrgHeader = (props) => { + const { + userdata, + selectedOrganization, + setSelectedOrganization, + globalUrl, + isCloud, + adminTab, + handleEditOrg, + } = props; + + const theme = useTheme(); + const alert = useAlert(); + const classes = useStyles(); + + var upload = ""; + const defaultBranch = "master"; + const [orgName, setOrgName] = React.useState(selectedOrganization.name); + const [orgDescription, setOrgDescription] = React.useState( + selectedOrganization.description + ); + + + const [file, setFile] = React.useState(""); + const [fileBase64, setFileBase64] = React.useState( + selectedOrganization.image + ); + const [expanded, setExpanded] = React.useState(false); + + if (file !== "") { + const img = document.getElementById("logo"); + var canvas = document.createElement("canvas"); + canvas.width = 174; + canvas.height = 174; + var ctx = canvas.getContext("2d"); + + img.onload = function () { + // img, x, y, width, height + //ctx.drawImage(img, 174, 174) + //console.log("IMG natural: ", img.naturalWidth, img.naturalHeight) + //ctx.drawImage(img, 0, 0, 174, 174) + ctx.drawImage( + img, + 0, + 0, + img.width, + img.height, + 0, + 0, + canvas.width, + canvas.height + ); + + const canvasUrl = canvas.toDataURL(); + if (canvasUrl !== fileBase64) { + setFileBase64(canvasUrl); + selectedOrganization.image = canvasUrl; + setSelectedOrganization(selectedOrganization); + } + }; + } + + + var image = ""; + const editHeaderImage = (event) => { + const file = event.target.value; + const actualFile = event.target.files[0]; + const fileObject = URL.createObjectURL(actualFile); + setFile(fileObject); + }; + + //console.log("USER: ", userdata) + const orgSaveButton = ( + + + + ); + + var imageData = file.length > 0 ? file : fileBase64; + imageData = + imageData === undefined || imageData.length === 0 + ? defaultImage + : imageData; + const imageInfo = ( + + ); + + return ( +
+
+ +
0 + ? null + : "1px solid #f85a3e", + cursor: "pointer", + backgroundColor: + imageData !== undefined && imageData.length > 0 + ? null + : theme.palette.inputColor, + maxWidth: 174, + maxHeight: 174, + }} + onClick={() => { + upload.click(); + }} + > + (upload = ref)} + onChange={editHeaderImage} + /> + {imageInfo} +
+
+
+
+ Name + { + const invalid = ["#", ":", "."]; + for (var key in invalid) { + if (e.target.value.includes(invalid[key])) { + alert.error("Can't use " + invalid[key] + " in name"); + return; + } + } + + if (e.target.value.length > 100) { + alert.error("Choose a shorter name."); + return; + } + + setOrgName(e.target.value); + }} + color="primary" + InputProps={{ + style: { + color: "white", + height: "50px", + fontSize: "1em", + }, + classes: { + notchedOutline: classes.notchedOutline, + }, + }} + /> +
+ Description +
+ { + setOrgDescription(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> +
+ {orgSaveButton} +
+
+
+
+
+ ); +}; + +export default OrgHeader; diff --git a/frontend/src/components/PaperComponent.jsx b/frontend/src/components/PaperComponent.jsx new file mode 100644 index 00000000..76fb56ef --- /dev/null +++ b/frontend/src/components/PaperComponent.jsx @@ -0,0 +1,19 @@ +import React, {useState, useEffect, useLayoutEffect} from 'react'; + +import Draggable from "react-draggable"; +import { + Paper +} from "@material-ui/core"; + +const PaperComponent = (props) => { + return ( + + + + ) +} + +export default PaperComponent; diff --git a/frontend/src/components/Priorities.jsx b/frontend/src/components/Priorities.jsx new file mode 100644 index 00000000..88bea907 --- /dev/null +++ b/frontend/src/components/Priorities.jsx @@ -0,0 +1,91 @@ +import React, { useState, useEffect } from "react"; + +import theme from "../theme.jsx"; +import { + Paper, + Typography, + Divider, + Button, + Grid, + Card, + Switch, +} from "@material-ui/core"; + +import Priority from "../components/Priority.jsx"; +import { useAlert } from "react-alert"; + +const Priorities = (props) => { + const { globalUrl, userdata, serverside, billingInfo, stripeKey, checkLogin, } = props; + const [showDismissed, setShowDismissed] = React.useState(false); + const [showRead, setShowRead] = React.useState(false); + + if (userdata === undefined || userdata === null) { + return + } + + return ( +
+

Suggestions

+ + Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company. These range from simple configurations in Shuffle to Usecases you may have missed.  + + Learn more + + +
+ { + setShowDismissed(!showDismissed); + }} + />  Show dismissed + {userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ? + + No Suggestions found + + : + userdata.priorities.map((priority, index) => { + if (showDismissed === false && priority.active === false) { + return null + } + + return ( + + ) + }) + } + +

Notifications

+ + Notifications help you find potential problems with your workflows and apps.  + + Learn more + + +
+ { + setShowRead(!showRead); + }} + />  Show read +
+ ) +} + +export default Priorities; diff --git a/frontend/src/components/Priority.jsx b/frontend/src/components/Priority.jsx new file mode 100644 index 00000000..0123b37d --- /dev/null +++ b/frontend/src/components/Priority.jsx @@ -0,0 +1,128 @@ +import React, { useState, useEffect } from "react"; + +import theme from "../theme.jsx"; +import { useNavigate, Link } from "react-router-dom"; +import { + Paper, + Typography, + Divider, + Button, + Grid, + Card, +} from "@material-ui/core"; + +// import magic wand icon from material ui icons +import { + AutoFixHigh as AutoFixHighIcon, + ArrowForward as ArrowForwardIcon, +} from '@mui/icons-material'; +import { useAlert } from "react-alert"; + +const Priority = (props) => { + const { globalUrl, userdata, serverside, priority, checkLogin, } = props; + + + let navigate = useNavigate(); + const changeRecommendation = (recommendation, action) => { + const data = { + action: action, + name: recommendation.name, + }; + + fetch(`${globalUrl}/api/v1/recommendations/modify`, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => { + if (response.status === 200) { + } else { + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + if (checkLogin !== undefined) { + checkLogin() + } + } else { + if (responseJson.success === false && responseJson.reason !== undefined) { + alert.error("Failed change recommendation: ", responseJson.reason) + } else { + alert.error("Failed change recommendation"); + } + } + }) + .catch((error) => { + alert.info("Failed dismissing alert. Please contact support@shuffler.io if this persists."); + }); + } + + + return ( +
+
+ + {priority.type === "usecase" || priority.type == "apps" ? : null} + + {priority.name} + + + {priority.type === "usecase" && priority.description.includes("&") ? + + {priority.name} + + {priority.description.split("&")[0]} + + + {priority.description.split("&").length > 3 ? + + + {priority.name+"2"} + + {priority.description.split("&")[2]} + + + : null} + + + : + + {priority.description} + + } +
+
+ + {priority.active === true ? + + : null } +
+
+ ) +} + +export default Priority; diff --git a/frontend/src/components/RenderCytoscape.jsx b/frontend/src/components/RenderCytoscape.jsx new file mode 100644 index 00000000..c112b448 --- /dev/null +++ b/frontend/src/components/RenderCytoscape.jsx @@ -0,0 +1,157 @@ +import React, { useState, useEffect, useLayoutEffect } from "react"; +import * as cytoscape from "cytoscape"; +import CytoscapeComponent from "react-cytoscapejs"; +import cystyle from "../defaultCytoscapeStyle.jsx"; + +const surfaceColor = "#27292D"; +const CytoscapeWrapper = (props) => { + const { globalUrl, inworkflow } = props; + + const [elements, setElements] = useState([]); + const [workflow, setWorkflow] = useState(inworkflow); + const [cy, setCy] = React.useState(); + const bodyWidth = 200; + const bodyHeight = 150; + + const setupGraph = () => { + const actions = workflow.actions.map((action) => { + const node = {}; + node.position = action.position; + node.data = action; + + node.data._id = action["id"]; + node.data.type = "ACTION"; + node.isStartNode = action["id"] === workflow.start; + + var example = ""; + if ( + action.example !== undefined && + action.example !== null && + action.example.length > 0 + ) { + example = action.example; + } + + node.data.example = example; + return node; + }); + + const triggers = workflow.triggers.map((trigger) => { + const node = {}; + node.position = trigger.position; + node.data = trigger; + + node.data._id = trigger["id"]; + node.data.type = "TRIGGER"; + + return node; + }); + + // FIXME - tmp branch update + var insertedNodes = [].concat(actions, triggers); + const edges = workflow.branches.map((branch, index) => { + //workflow.branches[index].conditions = [{ + + const edge = {}; + var conditions = workflow.branches[index].conditions; + if (conditions === undefined || conditions === null) { + conditions = []; + } + + var label = ""; + if (conditions.length === 1) { + label = conditions.length + " condition"; + } else if (conditions.length > 1) { + label = conditions.length + " conditions"; + } + + edge.data = { + id: branch.id, + _id: branch.id, + source: branch.source_id, + target: branch.destination_id, + label: label, + conditions: conditions, + hasErrors: branch.has_errors, + }; + + // This is an attempt at prettier edges. The numbers are weird to work with. + /* + //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html + const sourcenode = actions.find(node => node.data._id === branch.source_id) + const destinationnode = actions.find(node => node.data._id === branch.destination_id) + if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) { + //node.data._id = action["id"] + console.log("SOURCE: ", sourcenode.position) + console.log("DESTINATIONNODE: ", destinationnode.position) + + var opposite = true + if (sourcenode.position.x > destinationnode.position.x) { + opposite = false + } else { + opposite = true + } + + edge.style = { + 'control-point-distance': opposite ? ["25%", "-75%"] : ["-10%", "90%"], + 'control-point-weight': ['0.3', '0.7'], + } + } + */ + + return edge; + }); + + setWorkflow(workflow); + + // Verifies if a branch is valid and skips others + var newedges = []; + for (var key in edges) { + var item = edges[key]; + + const sourcecheck = insertedNodes.find( + (data) => data.data.id === item.data.source + ); + const destcheck = insertedNodes.find( + (data) => data.data.id === item.data.target + ); + if (sourcecheck === undefined || destcheck === undefined) { + continue; + } + + newedges.push(item); + } + + insertedNodes = insertedNodes.concat(newedges); + setElements(insertedNodes); + }; + + if (elements.length === 0) { + setupGraph(); + } + + return ( + { + // FIXME: There's something specific loading when + // you do the first hover of a node. Why is this different? + //console.log("CY: ", incy) + setCy(incy); + }} + /> + ); +}; + +export default CytoscapeWrapper; diff --git a/frontend/src/components/Searchfield.jsx b/frontend/src/components/Searchfield.jsx new file mode 100644 index 00000000..37e44bd5 --- /dev/null +++ b/frontend/src/components/Searchfield.jsx @@ -0,0 +1,652 @@ +import React, {useState, useEffect, useRef} from 'react'; + +import { useNavigate, Link, useParams } from "react-router-dom"; +import { useTheme } from '@material-ui/core/styles'; +import SearchIcon from '@material-ui/icons/Search'; + +import { + Chip, + IconButton, + TextField, + InputAdornment, + List, + Card, + ListItem, + ListItemAvatar, + ListItemText, + Avatar, + Typography, + Tooltip, +} from '@material-ui/core'; + +import { + AvatarGroup, +} from "@mui/material" + +import {Close as CloseIcon, Folder as FolderIcon, Polymer as PolymerIcon, LibraryBooks as LibraryBooksIcon} from '@material-ui/icons' + +import algoliasearch from 'algoliasearch/lite'; +import aa from 'search-insights' +import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; +//import { InstantSearch, SearchBox, Hits, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; + +// https://www.algolia.com/doc/api-reference/widgets/search-box/react/ +const chipStyle = { + backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", +} + +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const SearchField = props => { + const { serverside, userdata } = props + + const theme = useTheme(); + let navigate = useNavigate(); + const borderRadius = 3 + const node = useRef() + const [searchOpen, setSearchOpen] = useState(false) + const [oldPath, setOldPath] = useState("") + + if (serverside === true) { + return null + } + + if (window !== undefined && window.location !== undefined && window.location.pathname === "/search") { + return null + } + + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + + if (window.location.pathname !== oldPath) { + setSearchOpen(false) + setOldPath(window.location.pathname) + } + + //useEffect(() => { + // if (searchOpen) { + // var tarfield = document.getElementById("shuffle_search_field") + // tarfield.focus() + // } + //}, searchOpen) + + const SearchBox = ({currentRefinement, refine, isSearchStalled, } ) => { + + /* + endAdornment: ( + { + event.preventDefault() + }}> + { + setSearchOpen(false) + }} /> + + ), + */ + + return ( +