4852 lines
165 KiB
JavaScript
4852 lines
165 KiB
JavaScript
import React, {useState, useEffect, useLayoutEffect} from 'react';
|
||
import { useInterval } from 'react-powerhooks';
|
||
|
||
import uuid from "uuid";
|
||
|
||
import {Link} from 'react-router-dom';
|
||
import TextField from '@material-ui/core/TextField';
|
||
import Drawer from '@material-ui/core/Drawer';
|
||
import Button from '@material-ui/core/Button';
|
||
import Paper from '@material-ui/core/Paper';
|
||
import Grid from '@material-ui/core/Grid';
|
||
import ButtonBase from '@material-ui/core/ButtonBase';
|
||
import Tooltip from '@material-ui/core/Tooltip';
|
||
import Select from '@material-ui/core/Select';
|
||
import MenuItem from '@material-ui/core/MenuItem';
|
||
import Divider from '@material-ui/core/Divider';
|
||
import Dialog from '@material-ui/core/Dialog';
|
||
import DialogActions from '@material-ui/core/DialogActions';
|
||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||
import DialogContent from '@material-ui/core/DialogContent';
|
||
import FormControl from '@material-ui/core/FormControl';
|
||
import IconButton from '@material-ui/core/IconButton';
|
||
import Menu from '@material-ui/core/Menu';
|
||
import Input from '@material-ui/core/Input';
|
||
import FormGroup from '@material-ui/core/FormGroup';
|
||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||
import Checkbox from '@material-ui/core/Checkbox';
|
||
import Breadcrumbs from '@material-ui/core/Breadcrumbs';
|
||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||
import ReactJson from 'react-json-view'
|
||
|
||
import DirectionsRunIcon from '@material-ui/icons/DirectionsRun';
|
||
import PolymerIcon from '@material-ui/icons/Polymer';
|
||
import CreateIcon from '@material-ui/icons/Create';
|
||
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
|
||
import AspectRatioIcon from '@material-ui/icons/AspectRatio';
|
||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||
import AppsIcon from '@material-ui/icons/Apps';
|
||
import ScheduleIcon from '@material-ui/icons/Schedule';
|
||
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
|
||
import PauseIcon from '@material-ui/icons/Pause';
|
||
import DeleteIcon from '@material-ui/icons/Delete';
|
||
import SaveIcon from '@material-ui/icons/Save';
|
||
import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft';
|
||
import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight';
|
||
|
||
import * as cytoscape from 'cytoscape';
|
||
import * as edgehandles from 'cytoscape-edgehandles';
|
||
import * as clipboard from 'cytoscape-clipboard';
|
||
import CytoscapeComponent from 'react-cytoscapejs';
|
||
import undoRedo from 'cytoscape-undo-redo';
|
||
import Draggable from 'react-draggable';
|
||
|
||
import environmentdata from './environmentdata';
|
||
import cytoscapestyle from './defaultCytoscapeStyle';
|
||
import cxtmenu from 'cytoscape-cxtmenu';
|
||
|
||
import { w3cwebsocket as W3CWebSocket } from "websocket";
|
||
import { useAlert } from "react-alert";
|
||
|
||
const hoverColor = "#f85a3e"
|
||
const hoverOutColor = "#e8eaf6"
|
||
const surfaceColor = "#27292D"
|
||
const inputColor = "#383B40"
|
||
|
||
// http://apps.cytoscape.org/apps/yfileslayoutalgorithms
|
||
cytoscape.use(edgehandles);
|
||
cytoscape.use(clipboard);
|
||
cytoscape.use(undoRedo);
|
||
cytoscape.use( cxtmenu );
|
||
|
||
// https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react
|
||
function useWindowSize() {
|
||
const [size, setSize] = useState([0, 0]);
|
||
useLayoutEffect(() => {
|
||
function updateSize() {
|
||
setSize([window.innerWidth, window.innerHeight]);
|
||
}
|
||
window.addEventListener('resize', updateSize);
|
||
updateSize();
|
||
return () => window.removeEventListener('resize', updateSize);
|
||
}, []);
|
||
return size;
|
||
}
|
||
|
||
const splitter = "|~|"
|
||
//const referenceUrl = "https://shuffler.io/functions/webhooks/"
|
||
//const referenceUrl = window.location.origin+"/api/v1/hooks/"
|
||
|
||
const AngularWorkflow = (props) => {
|
||
const { globalUrl, isLoggedIn, isLoaded } = props;
|
||
const referenceUrl = globalUrl+"/api/v1/hooks/"
|
||
const alert = useAlert()
|
||
|
||
const [bodyWidth, bodyHeight] = useWindowSize();
|
||
const appBarSize = 74
|
||
const [cystyle, ] = useState(cytoscapestyle)
|
||
const [cy, setCy] = React.useState()
|
||
|
||
const [appSearch, setAppSearch] = React.useState("")
|
||
const [currentView, setCurrentView] = React.useState("apps")
|
||
const [triggerAuthentication, setTriggerAuthentication] = React.useState({})
|
||
const [triggerFolders, setTriggerFolders] = React.useState([])
|
||
|
||
const [workflow, setWorkflow] = React.useState({});
|
||
const [leftViewOpen, setLeftViewOpen] = React.useState(true);
|
||
const [leftBarSize, setLeftBarSize] = React.useState(350)
|
||
const [executionText, setExecutionText] = React.useState("");
|
||
const [executionRequestStarted, setExecutionRequestStarted] = React.useState(false);
|
||
|
||
const [appAuthentication, setAppAuthentication] = React.useState({});
|
||
const [variablesModalOpen, setVariablesModalOpen] = React.useState(false);
|
||
const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false);
|
||
const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false);
|
||
const [newVariableName, setNewVariableName] = React.useState("");
|
||
const [newVariableDescription, setNewVariableDescription] = React.useState("");
|
||
const [newVariableValue, setNewVariableValue] = React.useState("");
|
||
const [workflowDone, setWorkflowDone] = React.useState(false)
|
||
const [localFirstrequest, setLocalFirstrequest] = React.useState(true)
|
||
const [requiresAuthentication, setRequiresAuthentication] = React.useState(true)
|
||
const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false)
|
||
|
||
const [variableAnchorEl, setVariableAnchorEl] = React.useState(null)
|
||
|
||
const [sourceValue, setSourceValue] = React.useState({})
|
||
const [destinationValue, setDestinationValue] = React.useState({})
|
||
const [conditionValue, setConditionValue] = React.useState({})
|
||
|
||
// Trigger stuff
|
||
const [selectedTrigger, setSelectedTrigger] = React.useState({});
|
||
const [selectedTriggerIndex, setSelectedTriggerIndex] = React.useState({});
|
||
const [selectedEdge, setSelectedEdge] = React.useState({});
|
||
const [selectedEdgeIndex, setSelectedEdgeIndex] = React.useState({});
|
||
|
||
|
||
const [visited, setVisited] = React.useState([]);
|
||
//const [workflow, setWorkflow] = React.useState(workflowdata);
|
||
|
||
const [apps, setApps] = React.useState([]);
|
||
const [filteredApps, setFilteredApps] = React.useState([]);
|
||
const [firstrequest, setFirstrequest] = React.useState(true)
|
||
//const [apps, setApps] = React.useState(appdata);
|
||
//const [filteredApps, setFilteredApps] = React.useState();
|
||
|
||
const [environments, setEnvironments] = React.useState([]);
|
||
const [established, setEstablished] = React.useState(false);
|
||
|
||
const [graphSetup, setGraphSetup] = React.useState(false);
|
||
|
||
const [selectedApp, setSelectedApp] = React.useState({});
|
||
const [selectedAction, setSelectedAction] = React.useState({});
|
||
const [selectedActionName, setSelectedActionName] = React.useState({});
|
||
const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({});
|
||
|
||
const [executionRequest, setExecutionRequest] = React.useState({})
|
||
|
||
const [, setExecutingNodes] = React.useState([])
|
||
const [executionRunning, setExecutionRunning] = React.useState(false)
|
||
const [executionModalOpen, setExecutionModalOpen] = React.useState(false)
|
||
const [executionModalView, setExecutionModalView] = React.useState(0)
|
||
const [executionData, setExecutionData] = React.useState({})
|
||
|
||
const [lastSaved, setLastSaved] = React.useState(true)
|
||
|
||
const [AppsHoverColor, setAppsHoverColor] = useState(hoverOutColor);
|
||
const [VariablesHoverColor, setVariablesHoverColor] = useState(hoverOutColor);
|
||
const [HookHoverColor, setHookHoverColor] = useState(hoverOutColor);
|
||
const [appAdded, setAppAdded] = useState(false)
|
||
const [update, setUpdate] = useState("");
|
||
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
|
||
|
||
const [elements, setElements] = useState([])
|
||
const { start, stop } = useInterval({
|
||
duration: 2500,
|
||
startImmediate: false,
|
||
callback: () => {
|
||
fetchUpdates()
|
||
}
|
||
});
|
||
|
||
const getWorkflowExecution = (id) => {
|
||
fetch(globalUrl+"/api/v1/workflows/"+id+"/executions", {
|
||
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.length > 0) {
|
||
// FIXME: Sort this by time
|
||
setWorkflowExecutions(responseJson)
|
||
}
|
||
//setWorkflowExecutions(responseJson)
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const debugView = workflowExecutions.length > 0 ?
|
||
<Draggable>
|
||
<div style={{color: "white", position: "fixed", top: appBarSize+65, left: leftBarSize+20, zIndex: 5000, minHeight: 100, padding: 15, maxHeight: 100, maxWidth: 500, overflowX: "hidden",}}>
|
||
{workflowExecutions.slice(0,15).map(data => {
|
||
return (
|
||
<div>
|
||
{new Date(data.started_at*1000).toISOString()}
|
||
, {data.status}
|
||
{data.result.length > 0 ? ", "+data.result : ", "}
|
||
{data.execution_argument.length > 0 ? ", "+data.execution_argument : ", "}
|
||
<Divider style={{backgroundColor: "white"}}/>
|
||
</div>
|
||
)
|
||
return
|
||
})}
|
||
</div>
|
||
</Draggable>
|
||
: null
|
||
|
||
const fetchUpdates = () => {
|
||
fetch(globalUrl+"/api/v1/streams/results", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify(executionRequest),
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for stream results :O!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
handleUpdateResults(responseJson)
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const abortExecution = () => {
|
||
setExecutionRunning(false)
|
||
|
||
alert.success("Aborting execution")
|
||
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/executions/"+executionRequest.execution_id+"/abort", {
|
||
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()
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
// Controls the colors and direction of execution results.
|
||
// Style is in defaultCytoscapeStyle.js
|
||
const handleUpdateResults = (responseJson) => {
|
||
//console.log(responseJson)
|
||
// Loop nodes and find results
|
||
// Update on every interval? idk
|
||
|
||
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) {
|
||
setExecutionData(responseJson)
|
||
}
|
||
}
|
||
if (responseJson.execution_id !== executionRequest.execution_id) {
|
||
cy.elements().removeClass('success-highlight failure-highlight executing-highlight')
|
||
return
|
||
}
|
||
|
||
if (responseJson.results !== null && responseJson.results !== []) {
|
||
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.removeClass('success-highlight failure-highlight executing-highlight')
|
||
switch (item.status) {
|
||
case "EXECUTING":
|
||
currentnode.removeClass('not-executing-highlight')
|
||
currentnode.removeClass('success-highlight')
|
||
currentnode.removeClass('failure-highlight')
|
||
currentnode.removeClass('awaiting-data-highlight')
|
||
incomingEdges.addClass('success-highlight')
|
||
currentnode.addClass('executing-highlight')
|
||
break
|
||
case "WAITING":
|
||
currentnode.removeClass('not-executing-highlight')
|
||
currentnode.removeClass('success-highlight')
|
||
currentnode.removeClass('failure-highlight')
|
||
currentnode.removeClass('awaiting-data-highlight')
|
||
currentnode.addClass('executing-highlight')
|
||
|
||
if (!visited.includes(item.action.label)) {
|
||
if (executionRunning) {
|
||
alert.show("WAITING FOR "+item.action.label+" with result "+item.result)
|
||
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('awaiting-data-highlight')
|
||
currentnode.addClass('success-highlight')
|
||
|
||
if (!visited.includes(item.action.label)) {
|
||
if (executionRunning) {
|
||
alert.show("Success in node "+item.action.label)
|
||
//+" with result "+item.result)
|
||
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('failure-highlight')
|
||
targetnode.removeClass('awaiting-data-highlight')
|
||
targetnode.addClass('executing-highlight')
|
||
}
|
||
}
|
||
|
||
// const outgoingEdges = currentnode.outgoers('edge')
|
||
}
|
||
break
|
||
case "FAILURE":
|
||
currentnode.removeClass('not-executing-highlight')
|
||
currentnode.removeClass('executing-highlight')
|
||
currentnode.removeClass('success-highlight')
|
||
currentnode.removeClass('awaiting-data-highlight')
|
||
currentnode.addClass('failure-highlight')
|
||
|
||
if (!visited.includes(item.action.label)) {
|
||
alert.error("Success 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.addClass('awaiting-data-highlight')
|
||
break
|
||
default:
|
||
console.log("DEFAULT?")
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE") {
|
||
stop()
|
||
|
||
|
||
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")
|
||
} else {
|
||
//curelements[i].removeClass('not-executing-highlight')
|
||
//curelements[i].removeClass('executing-highlight')
|
||
//curelements[i].removeClass('success-highlight')
|
||
//curelements[i].removeClass('awaiting-data-highlight')
|
||
//curelements[i].removeClass('failure-highlight')
|
||
}
|
||
}
|
||
} else if (responseJson.status === "FINISHED") {
|
||
setExecutionRunning(false)
|
||
stop()
|
||
}
|
||
}
|
||
|
||
const saveWorkflow = (curworkflow) => {
|
||
var success = false
|
||
|
||
// 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
|
||
} else {
|
||
alert.info("Saving workflow")
|
||
}
|
||
|
||
var cyelements = cy.elements()
|
||
var newActions = []
|
||
var newTriggers = []
|
||
var newBranches = []
|
||
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 parsedElement = {
|
||
id: cyelements[key].data().id,
|
||
source_id: cyelements[key].data().source,
|
||
destination_id: cyelements[key].data().target,
|
||
conditions: cyelements[key].data().conditions,
|
||
}
|
||
|
||
newBranches.push(parsedElement)
|
||
} else {
|
||
if (type === "ACTION") {
|
||
// FIXME - check whether position is new to not fuck up params etc.
|
||
var curworkflowAction = useworkflow.actions.find(a => a.id === cyelements[key].data()["id"])
|
||
if (curworkflowAction === undefined) {
|
||
curworkflowAction = cyelements[key].data()
|
||
}
|
||
|
||
curworkflowAction.position = cyelements[key].position()
|
||
|
||
// workaround to fix some edgecases
|
||
if (curworkflowAction.parameters === "" || curworkflowAction.parameters === null) {
|
||
curworkflowAction.parameters = []
|
||
}
|
||
|
||
newActions.push(curworkflowAction)
|
||
} else if (type === "TRIGGER") {
|
||
//console.log("TRIGGER")
|
||
var curworkflowTrigger = useworkflow.triggers.find(a => a.id === cyelements[key].data()["id"])
|
||
if (curworkflowTrigger === undefined) {
|
||
curworkflowTrigger = cyelements[key].data()
|
||
}
|
||
|
||
curworkflowTrigger.position = cyelements[key].position()
|
||
//console.log(curworkflowTrigger)
|
||
|
||
newTriggers.push(curworkflowTrigger)
|
||
}
|
||
}
|
||
}
|
||
|
||
useworkflow.actions = newActions
|
||
useworkflow.triggers = newTriggers
|
||
useworkflow.branches = newBranches
|
||
|
||
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) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for setting workflows :O!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
if (!responseJson.success) {
|
||
console.log(responseJson)
|
||
alert.error("Failed to save: "+responseJson.reason)
|
||
} else {
|
||
success = true
|
||
alert.success("Successfully saved workflow")
|
||
}
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
|
||
return success
|
||
}
|
||
|
||
const monitorUpdates = () => {
|
||
const firstnode = cy.getElementById(workflow.start)
|
||
if (firstnode.length === 0) {
|
||
return false
|
||
}
|
||
|
||
cy.elements().removeClass('success-highlight failure-highlight executing-highlight')
|
||
firstnode[0].addClass('executing-highlight')
|
||
|
||
return true
|
||
}
|
||
|
||
const executeWorkflowWebsocket = () => {
|
||
if (!lastSaved) {
|
||
//alert.error("You might have forgotten to save before executing.")
|
||
console.log("FIXME: Might have forgotten to save before executing.")
|
||
}
|
||
|
||
var returncheck = monitorUpdates()
|
||
if (!returncheck) {
|
||
alert.error("No startnode set.")
|
||
return
|
||
}
|
||
|
||
setVisited([])
|
||
setExecutionRunning(true)
|
||
setExecutionRequest({})
|
||
|
||
var curelements = cy.elements()
|
||
for (var i = 0; i < curelements.length; i++) {
|
||
curelements[i].addClass("not-executing-highlight")
|
||
}
|
||
|
||
if (executionText.length > 0) {
|
||
alert.success("Starting execution with argument "+executionText)
|
||
} else {
|
||
alert.success("Starting execution")
|
||
}
|
||
|
||
const data = {"execution_argument": executionText}
|
||
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/execute_fs", {
|
||
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 WORKFLOW EXECUTION :O!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
if (!responseJson.success) {
|
||
alert.error("Failed to start: "+responseJson.reason)
|
||
stop()
|
||
return
|
||
}
|
||
|
||
setExecutionRequest({
|
||
"execution_id": responseJson.execution_id,
|
||
"authorization": responseJson.authorization,
|
||
})
|
||
setExecutingNodes([workflow.start])
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const executeWorkflow = () => {
|
||
if (!lastSaved) {
|
||
//alert.error("You might have forgotten to save before executing.")
|
||
console.log("FIXME: Might have forgotten to save before executing.")
|
||
}
|
||
|
||
var returncheck = monitorUpdates()
|
||
if (!returncheck) {
|
||
alert.error("No startnode set.")
|
||
return
|
||
}
|
||
|
||
setVisited([])
|
||
setExecutionRequest({})
|
||
setExecutionRequestStarted(true)
|
||
stop()
|
||
|
||
var curelements = cy.elements()
|
||
for (var i = 0; i < curelements.length; i++) {
|
||
curelements[i].addClass("not-executing-highlight")
|
||
}
|
||
|
||
if (executionText.length > 0) {
|
||
alert.success("Starting execution with argument "+executionText)
|
||
} else {
|
||
alert.success("Starting execution")
|
||
}
|
||
|
||
const data = {"execution_argument": executionText, "start": workflow.start}
|
||
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) => {
|
||
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)
|
||
stop()
|
||
return
|
||
} else {
|
||
setExecutionRunning(true)
|
||
setExecutionRequestStarted(false)
|
||
}
|
||
|
||
setExecutionRequest({
|
||
"execution_id": responseJson.execution_id,
|
||
"authorization": responseJson.authorization,
|
||
})
|
||
setExecutingNodes([workflow.start])
|
||
setExecutionData({})
|
||
setExecutionModalOpen(true)
|
||
setExecutionModalView(1)
|
||
start()
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
//const handleAppVersioning = (apps) => {
|
||
// var newapps = []
|
||
// for (var key in apps) {
|
||
// var item = apps[key]
|
||
// const previtem = newapps.findIndex(data => data.name === item.name)
|
||
// if (previtem === -1) {
|
||
// item["versions"] = [item.app_version]
|
||
// newapps.push(item)
|
||
// continue
|
||
// }
|
||
|
||
// // THere might be duplicates for some reason..
|
||
// if (!newapps[previtem]["versions"].includes(item.app_version)) {
|
||
// newapps[previtem]["versions"].push(item.app_version)
|
||
// }
|
||
// }
|
||
//
|
||
// // FIXME - handle this, as we can't have more than one of each :)
|
||
// //setVersionedApps(newapps)
|
||
//}
|
||
|
||
// Builtin actions that should ran in Worker and not apps
|
||
const getExtraApps = () => {
|
||
const data = [{
|
||
name: "Filter",
|
||
is_valid: true,
|
||
id: "0ca8887e-b4af-4e3e-887c-87e9d3bc3d3e",
|
||
link: "https://shuffler.io",
|
||
app_version: "1.0.0",
|
||
generated: true,
|
||
downloaded: false,
|
||
sharing: false,
|
||
verified: false,
|
||
tested: false,
|
||
owner: "",
|
||
private_id: "",
|
||
description: "Filter",
|
||
environment: "Shuffle",
|
||
small_image: "",
|
||
large_image: "",
|
||
contact_info: {name: "", url: ""},
|
||
authentication: {required: false, parameters: [],},
|
||
actions: [{
|
||
description: "Filter cases",
|
||
id: "",
|
||
name: "filter_cases",
|
||
node_type: "action",
|
||
environment: "Shuffle",
|
||
sharing: false,
|
||
private_id: "",
|
||
app_id: "",
|
||
authentication: null,
|
||
tested: true,
|
||
parameters: [{
|
||
description: "",
|
||
id: "",
|
||
name: "Field to look for",
|
||
example: "$testing_1.#.id",
|
||
value: "",
|
||
multiline: true,
|
||
action_field: "",
|
||
variant: "",
|
||
required: true,
|
||
schema: {type: "string"},
|
||
}]
|
||
}],
|
||
}]
|
||
|
||
return data
|
||
}
|
||
|
||
const getApps = () => {
|
||
fetch(globalUrl+"/api/v1/workflows/apps", {
|
||
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 response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
// FIXME - handle versions on left bar
|
||
//handleAppVersioning(responseJson)
|
||
//var tmpapps = []
|
||
//tmpapps = tmpapps.concat(getExtraApps())
|
||
//tmpapps = tmpapps.concat(responseJson)
|
||
setApps(responseJson)
|
||
setFilteredApps(responseJson)
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const getWorkflow = () => {
|
||
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for workflows :O!")
|
||
window.location.pathname = "/workflows"
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
setWorkflow(responseJson)
|
||
setWorkflowDone(true)
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const onUnselect = (event) => {
|
||
console.time("UNSELECT")
|
||
|
||
// FIXME - check if they have value before overriding like this for no reason.
|
||
// Would save a lot of time (400~ ms -> 30ms)
|
||
//setSelectedAction({})
|
||
//setSelectedApp({})
|
||
//setSelectedTrigger({})
|
||
//setSelectedEdge({})
|
||
|
||
|
||
// setSelectedTriggerIndex(-1)
|
||
//setSelectedActionEnvironment({})
|
||
//setSelectedEdge({})
|
||
//setTriggerAuthentication({})
|
||
//setSelectedTriggerIndex(-1)
|
||
//setTriggerFolders([])
|
||
|
||
// Can be used for right side view
|
||
setRightSideBarOpen(false)
|
||
console.timeEnd("UNSELECT")
|
||
}
|
||
|
||
const onEdgeSelect = (event) => {
|
||
setRightSideBarOpen(true)
|
||
|
||
const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"])
|
||
if (triggercheck === undefined) {
|
||
setSelectedEdgeIndex(workflow.branches.findIndex(data => data.id === event.target.data()["id"]))
|
||
setSelectedEdge(event.target.data())
|
||
|
||
setSelectedAction({})
|
||
setSelectedTrigger({})
|
||
} else {
|
||
alert.info("Can't edit branches from triggers")
|
||
}
|
||
}
|
||
|
||
const onNodeSelect = (event) => {
|
||
const data = event.target.data()
|
||
//console.log(data)
|
||
|
||
if (data.type === "ACTION") {
|
||
// FIXME - unselect
|
||
//console.log(cy.elements('[_id!="${data._id}"]`))
|
||
// Does it choose the wrong action?
|
||
const curaction = workflow.actions.find(a => a.id === data.id)
|
||
if (!curaction || curaction === undefined) {
|
||
//console.log("Action not found error")
|
||
return
|
||
}
|
||
|
||
const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version)
|
||
if (!curapp || curapp === undefined) {
|
||
alert.error("App "+curaction.app_name+" not found. Did someone delete it?")
|
||
return
|
||
}
|
||
|
||
var env = environments.find(a => a.name === curaction.environment)
|
||
if (!env || env === undefined) {
|
||
env = environments[0]
|
||
}
|
||
|
||
setSelectedApp(curapp)
|
||
setSelectedAction(curaction)
|
||
setSelectedActionEnvironment(env)
|
||
setSelectedActionName(curaction.name)
|
||
setRequiresAuthentication(curapp.authentication.required)
|
||
} else if (data.type === "TRIGGER") {
|
||
//console.log("Should handle trigger "+data.triggertype)
|
||
//console.log(data)
|
||
|
||
setSelectedTriggerIndex(workflow.triggers.findIndex(a => a.id === data.id))
|
||
setSelectedTrigger(data)
|
||
setSelectedActionName(data.name)
|
||
setSelectedActionEnvironment(data.env)
|
||
} else {
|
||
alert.error("Can't handle "+data.type)
|
||
}
|
||
|
||
setRightSideBarOpen(true)
|
||
}
|
||
|
||
const onEdgeAdded = (event) => {
|
||
setLastSaved(false)
|
||
const edge = event.target.data()
|
||
|
||
// Check if:
|
||
// dest == source && source == dest
|
||
// dest == dest && source == source
|
||
// backend: check all children? to stop recursion
|
||
var found = false
|
||
for (var key in workflow.branches) {
|
||
if (workflow.branches[key].destination_id === edge.source && workflow.branches[key].source_id === edge.target) {
|
||
alert.error("A pointer in the opposite direction already exists")
|
||
event.target.remove()
|
||
found = true
|
||
break
|
||
} else if (workflow.branches[key].destination_id === edge.target && workflow.branches[key].source_id === edge.source) {
|
||
// Checks if NOT trigger
|
||
//workflow.triggers.find(data => data.id === workflow.branches[key].source_id).length === 0)
|
||
const triggercheck = workflow.triggers.find(data => data.id === workflow.branches[key].source_id)
|
||
if (workflow.triggers !== undefined && triggercheck !== undefined && triggercheck.length === 0) {
|
||
alert.error("That pointer already exists")
|
||
event.target.remove()
|
||
}
|
||
found = true
|
||
break
|
||
} else if (edge.target === workflow.start) {
|
||
var targetnode = workflow.triggers.findIndex(data => data.id === edge.source)
|
||
if (targetnode === -1) {
|
||
alert.error("Can't make arrow to starting node")
|
||
event.target.remove()
|
||
found = true
|
||
break
|
||
}
|
||
} else {
|
||
// 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) {
|
||
// alert.error("Can't have triggers as target")
|
||
// event.target.remove()
|
||
// found = true
|
||
// break
|
||
//}
|
||
}
|
||
}
|
||
|
||
var newbranch = {
|
||
"source_id": edge.source,
|
||
"destination_id": edge.target,
|
||
"id_": edge.id,
|
||
"id": edge.id,
|
||
"hasErrors": false,
|
||
}
|
||
|
||
if (!found) {
|
||
newbranch["hasErrors"] = false
|
||
}
|
||
|
||
workflow.branches.push(newbranch)
|
||
setWorkflow(workflow)
|
||
}
|
||
|
||
const onNodeAdded = (event) => {
|
||
//setLastSaved(false)
|
||
const node = event.target
|
||
|
||
if (node.isNode() && cy.nodes().size() === 1) {
|
||
//setStartNode(node.data('id'))
|
||
workflow.start = node.data('id')
|
||
setWorkflow(workflow)
|
||
}
|
||
}
|
||
|
||
const onEdgeRemoved = (event) => {
|
||
const edge = event.target
|
||
|
||
workflow.branches = workflow.branches.filter(a => a.id !== edge.data().id)
|
||
setWorkflow(workflow)
|
||
|
||
// trigger as source check
|
||
const indexcheck = workflow.triggers.findIndex(data => edge.data()["source"] === data.id)
|
||
if (indexcheck !== -1) {
|
||
//alert.error("Can't remove edge from a trigger")
|
||
console.log("Shouldnt remove edge from trigger")
|
||
//const edgeToBeAdded = {
|
||
// group: "edges",
|
||
// data: newcybranch,
|
||
//}
|
||
}
|
||
}
|
||
|
||
const onNodeRemoved = (event) => {
|
||
const node = event.target
|
||
const data = node.data()
|
||
|
||
//var currentnode = cy.getElementById(data.id)
|
||
//if (currentnode.length === 0) {
|
||
//}
|
||
if (workflow.start === data.id && workflow.actions.length > 1) {
|
||
// FIXME - should check branches connected to startnode, as picking random
|
||
// might just be confusing
|
||
cy.nodes().forEach(function( ele ) {
|
||
if (ele.id() !== workflow.start && ele.data()["label"] !== undefined) {
|
||
alert.success("Changed startnode to "+ele.data()["label"])
|
||
ele.data("isStartNode", true)
|
||
workflow.start = ele.id()
|
||
return
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
workflow.actions = workflow.actions.filter(a => a.id !== data.id)
|
||
workflow.triggers = workflow.triggers.filter(a => a.id !== data.id)
|
||
|
||
setWorkflow(workflow)
|
||
if (data.type === "TRIGGER") {
|
||
saveWorkflow(workflow)
|
||
}
|
||
}
|
||
|
||
var previouskey = 0
|
||
const handleKeyDown = (event) => {
|
||
// SHIFT = 16
|
||
// CTRL = 17
|
||
//console.log(event.keyCode)
|
||
switch( event.keyCode ) {
|
||
case 27:
|
||
console.log("ESCAPE")
|
||
break;
|
||
case 46:
|
||
removeNode()
|
||
console.log("DELETE")
|
||
break;
|
||
case 38:
|
||
console.log("UP")
|
||
break;
|
||
case 37:
|
||
console.log("LEFT")
|
||
break;
|
||
case 40:
|
||
console.log("DOWN")
|
||
break;
|
||
case 39:
|
||
console.log("RIGHT")
|
||
break;
|
||
case 90:
|
||
if (previouskey === 17) {
|
||
console.log("CTRL+Z")
|
||
}
|
||
break;
|
||
case 67:
|
||
if (previouskey === 17) {
|
||
console.log("CTRL+C")
|
||
}
|
||
break;
|
||
case 86:
|
||
if (previouskey === 17) {
|
||
console.log("CTRL+V")
|
||
}
|
||
break;
|
||
case 88:
|
||
if (previouskey === 17) {
|
||
console.log("CTRL+V")
|
||
}
|
||
break;
|
||
case 83:
|
||
if (previouskey === 17) {
|
||
event.preventDefault()
|
||
saveWorkflow()
|
||
}
|
||
break;
|
||
case 70:
|
||
if (previouskey === 17) {
|
||
event.preventDefault()
|
||
cy.fit(null, 50)
|
||
}
|
||
break;
|
||
case 65:
|
||
// As a poweruser myself, I found myself hitting this a few
|
||
// too many times to just edit text. Need a better bind
|
||
//
|
||
//if (previouskey === 17) {
|
||
// event.preventDefault()
|
||
// if (executionRunning || executionRequestStarted) {
|
||
// abortExecution()
|
||
// } else {
|
||
// executeWorkflow()
|
||
// }
|
||
// cy.fit(null, 50)
|
||
//}
|
||
break;
|
||
default:
|
||
//console.log(event.keyCode)
|
||
break;
|
||
}
|
||
|
||
previouskey = event.keyCode
|
||
}
|
||
|
||
const registerKeys = () => {
|
||
document.addEventListener("keydown", handleKeyDown);
|
||
}
|
||
|
||
const getEnvironments = () => {
|
||
fetch(globalUrl+"/api/v1/getenvironments", {
|
||
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) => {
|
||
setEnvironments(responseJson)
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
useEffect(() => {
|
||
if (firstrequest) {
|
||
setFirstrequest(false)
|
||
getWorkflow()
|
||
getApps()
|
||
getEnvironments()
|
||
getWorkflowExecution(props.match.params.key)
|
||
return
|
||
}
|
||
|
||
// App length necessary cus of cy initialization
|
||
if (elements.length === 0 && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
|
||
setGraphSetup(true)
|
||
setupGraph()
|
||
} else if (!established && cy !== undefined && apps.length > 0 && Object.getOwnPropertyNames(workflow).length > 0){
|
||
setEstablished(true)
|
||
cy.edgehandles({
|
||
handleNodes: (el) => el.isNode(),
|
||
preview: false,
|
||
toggleOffOnLeave: false,
|
||
loopAllowed: function( node ){
|
||
return false;
|
||
},
|
||
})
|
||
|
||
cy.fit(null, 200)
|
||
|
||
cy.on('select', 'node', (e) => onNodeSelect(e))
|
||
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))
|
||
|
||
//cy.on('mouseover', 'node', () => $(targetElement).addClass('mouseover'));
|
||
|
||
//cy.on('cxttapstart', 'node', (e) => edgeHandler.start(e.target))
|
||
//cy.on('cxttapend', 'node', (e) => edgeHandler.stop())
|
||
//cy.on('cxtdragover', 'node', (e) => edgeHandler.preview(e.target))
|
||
//cy.on('cxtdragout', 'node', (e) => edgeHandler.unpreview(e.target))
|
||
|
||
// RIGHT HERE..?
|
||
// This is wrong sometimes.. I'm mad
|
||
document.title = "Workflow - "+workflow.name
|
||
registerKeys()
|
||
//setStartNode(workflow.start)
|
||
} else if (established) {
|
||
//console.log("established - should fix colors of things")
|
||
//console.log(cy.elements())
|
||
}
|
||
})
|
||
|
||
var previousnodecolor = ""
|
||
//var previousedgecolor = ""
|
||
const animationDuration = 150
|
||
const onNodeHoverOut = (event) => {
|
||
event.target.animate({
|
||
style: {
|
||
"border-width": "1px",
|
||
}
|
||
}, {
|
||
duration: animationDuration,
|
||
})
|
||
}
|
||
|
||
const onNodeHover = (event) => {
|
||
//event.target.style("border-width", "5px")
|
||
event.target.animate({
|
||
style: {
|
||
"border-width": "5px",
|
||
"border-opacity": ".7",
|
||
}
|
||
}, {
|
||
duration: animationDuration,
|
||
})
|
||
|
||
previousnodecolor = event.target.style("border-color")
|
||
}
|
||
|
||
const onEdgeHoverOut = (event) => {
|
||
//event.target.removeStyle()
|
||
}
|
||
|
||
// This is here to have a proper transition for lines
|
||
const onEdgeHover = (event) => {
|
||
|
||
//console.log(event.target.data())
|
||
//const sourcecolor = cy.getElementById(event.target.data("source")).style("border-color")
|
||
//const targetcolor = cy.getElementById(event.target.data("target")).style("border-color")
|
||
//event.target.animate({
|
||
// style: {
|
||
// "line-fill": "linear-gradient",
|
||
// 'target-arrow-color': targetcolor,
|
||
// "line-gradient-stop-colors": [sourcecolor, targetcolor],
|
||
// "line-gradient-stop-positions": [0, 1],
|
||
// },
|
||
// duration: 0,
|
||
//})
|
||
}
|
||
|
||
|
||
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
|
||
|
||
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
|
||
};
|
||
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)
|
||
}
|
||
|
||
const removeNode = () => {
|
||
setSelectedApp({})
|
||
setSelectedAction({})
|
||
setSelectedActionName("")
|
||
|
||
const selectedNode = cy.$(':selected')
|
||
if (selectedNode.data() === undefined) {
|
||
return
|
||
}
|
||
|
||
if (selectedNode.data().type === "TRIGGER") {
|
||
console.log("Should remove trigger!")
|
||
console.log(selectedNode.data())
|
||
const triggerindex = workflow.triggers.findIndex(data => data.id === selectedNode.data().id)
|
||
setSelectedTriggerIndex(triggerindex)
|
||
if (selectedNode.data().trigger_type === "SCHEDULE") {
|
||
setSelectedTrigger(selectedNode.data())
|
||
stopSchedule(selectedNode.data(), triggerindex)
|
||
} else if (selectedNode.data().trigger_type === "WEBHOOK") {
|
||
setSelectedTrigger(selectedNode.data())
|
||
deleteWebhook(selectedNode.data(), triggerindex)
|
||
} else if (selectedNode.data().trigger_type === "EMAIL") {
|
||
setSelectedTrigger(selectedNode.data())
|
||
stopMailSub(selectedTrigger, triggerindex)
|
||
}
|
||
|
||
}
|
||
|
||
selectedNode.remove()
|
||
setSelectedTrigger({})
|
||
setSelectedTriggerIndex({})
|
||
}
|
||
|
||
const stopSchedule = (trigger, triggerindex) => {
|
||
alert.info("Stopping schedule")
|
||
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/schedule/"+trigger.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 stream results :O!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
if (!responseJson.success) {
|
||
alert.error("Failed to delete schedule: " + responseJson.reason)
|
||
} else {
|
||
alert.success("Successfully stopped schedule")
|
||
workflow.triggers[triggerindex].status = "stopped"
|
||
trigger.status = "stopped"
|
||
setSelectedTrigger(trigger)
|
||
setWorkflow(workflow)
|
||
saveWorkflow(workflow)
|
||
}
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
//const submitSchedule = (id, name, frequency, executionArg) => {
|
||
const submitSchedule = (trigger, triggerindex) => {
|
||
//const cronSplit = workflow.triggers[triggerindex].parameters[0].value.split("*")
|
||
//if (cronSplit.length <= 5 || cronSplit.length > 6) {
|
||
// alert.error("Error: Bad cron, example run every 1 minute: */1 * * * *")
|
||
// return
|
||
//}
|
||
|
||
if (trigger.name.length <= 0) {
|
||
alert.error("Error: name can't be empty")
|
||
return
|
||
}
|
||
|
||
alert.info("Attempting to create schedule with name " + trigger.name)
|
||
const data = {
|
||
"name": trigger.name,
|
||
"frequency": workflow.triggers[triggerindex].parameters[0].value,
|
||
"execution_argument": workflow.triggers[triggerindex].parameters[1].value,
|
||
"id": trigger.id,
|
||
}
|
||
|
||
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/schedule", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify(data),
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for stream results :O!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
if (!responseJson.success) {
|
||
alert.error("Failed to set schedule: " + responseJson.reason)
|
||
} else {
|
||
alert.success("Successfully created schedule")
|
||
workflow.triggers[triggerindex].status = "running"
|
||
trigger.status = "running"
|
||
setSelectedTrigger(trigger)
|
||
setWorkflow(workflow)
|
||
console.log("Should set the status to running and save")
|
||
saveWorkflow(workflow)
|
||
}
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const appViewStyle = {
|
||
marginLeft: "5px",
|
||
marginRight: "5px",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
}
|
||
|
||
const scrollStyle = {
|
||
marginTop: "10px",
|
||
overflow: "scroll",
|
||
height: "66vh",
|
||
overflowX: "auto",
|
||
overflowY: "auto",
|
||
}
|
||
|
||
const paperAppStyle = {
|
||
minHeight: "100px",
|
||
maxHeight: "100px",
|
||
minWidth: "100%",
|
||
maxWidth: "100%",
|
||
marginTop: "5px",
|
||
color: "white",
|
||
backgroundColor: surfaceColor,
|
||
cursor: "pointer",
|
||
display: "flex",
|
||
}
|
||
|
||
// All this is stupid lmao
|
||
const handleHookHover = () => {
|
||
setHookHoverColor(hoverColor)
|
||
setAppsHoverColor(hoverOutColor)
|
||
setVariablesHoverColor(hoverOutColor)
|
||
}
|
||
|
||
const handleHookHoverOut = () => {
|
||
setHookHoverColor(hoverOutColor)
|
||
}
|
||
|
||
const handleAppsHover = () => {
|
||
setAppsHoverColor(hoverColor)
|
||
setHookHoverColor(hoverOutColor)
|
||
setVariablesHoverColor(hoverOutColor)
|
||
}
|
||
|
||
const handleAppsHoverOut = () => {
|
||
setAppsHoverColor(hoverOutColor)
|
||
}
|
||
|
||
const handleVariablesHover = () => {
|
||
setVariablesHoverColor(hoverColor)
|
||
setHookHoverColor(hoverOutColor)
|
||
setAppsHoverColor(hoverOutColor)
|
||
}
|
||
|
||
const handleVariablesHoverOut = () => {
|
||
setVariablesHoverColor(hoverOutColor)
|
||
}
|
||
|
||
const paperVariableStyle = {
|
||
minHeight: "50px",
|
||
maxHeight: "50px",
|
||
minWidth: "100%",
|
||
maxWidth: "100%",
|
||
marginTop: "5px",
|
||
color: "white",
|
||
backgroundColor: surfaceColor,
|
||
cursor: "pointer",
|
||
display: "flex",
|
||
}
|
||
|
||
const VariablesView = () => {
|
||
const [open, setOpen] = React.useState(false);
|
||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||
|
||
if (workflow.workflow_variables === undefined || workflow.workflow_variables === null || workflow.workflow_variables.length === 0) {
|
||
return (
|
||
<div style={appViewStyle}>
|
||
<div style={appScrollStyle}>
|
||
<div style={{margin: 10}}>
|
||
Looks like you don't have any variables yet.
|
||
<div/>
|
||
<div style={{width: "100%", margin: "auto"}}>
|
||
<Button fullWidth style={{margin: "auto", marginTop: "10px", borderRadius: 0}} color="primary" variant="outlined" onClick={() => setVariablesModalOpen(true)}>Make a new workflow variable</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const menuClick = (event) => {
|
||
setOpen(!open)
|
||
setAnchorEl(event.currentTarget);
|
||
}
|
||
|
||
const deleteVariable = (variableName) => {
|
||
workflow.workflow_variables = workflow.workflow_variables.filter(data => data.name !== variableName)
|
||
setWorkflow(workflow)
|
||
}
|
||
|
||
const variableScrollStyle = {
|
||
marginTop: "10px",
|
||
overflow: "scroll",
|
||
height: "66vh",
|
||
overflowX: "auto",
|
||
overflowY: "auto",
|
||
flex: "10",
|
||
}
|
||
|
||
return (
|
||
<div style={appViewStyle}>
|
||
<div style={variableScrollStyle}>
|
||
{workflow.workflow_variables.map(variable=> {
|
||
return (
|
||
<div>
|
||
<Paper square style={paperVariableStyle} onClick={() => {
|
||
}}>
|
||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: "2px", backgroundColor: "orange", marginRight: "5px"}} />
|
||
<div style={{display: "flex", width: "100%"}}>
|
||
<div style={{flex: "10", marginTop: "15px", marginLeft: "10px", overflow: "hidden"}} onClick={() => {
|
||
setNewVariableName(variable.name)
|
||
setNewVariableDescription(variable.description)
|
||
setNewVariableValue(variable.value)
|
||
setVariablesModalOpen(true)}}>
|
||
Name: {variable.name}
|
||
</div>
|
||
<div style={{flex: "1", marginLeft: "0px"}}>
|
||
<IconButton
|
||
aria-label="more"
|
||
aria-controls="long-menu"
|
||
aria-haspopup="true"
|
||
onClick={menuClick}
|
||
>
|
||
<MoreVertIcon />
|
||
</IconButton>
|
||
<Menu
|
||
id="long-menu"
|
||
anchorEl={anchorEl}
|
||
keepMounted
|
||
open={open}
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: surfaceColor,
|
||
}
|
||
}}
|
||
onClose={() => {
|
||
setOpen(false)
|
||
setAnchorEl(null)
|
||
}}
|
||
>
|
||
|
||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||
setOpen(false)
|
||
setNewVariableName(variable.name)
|
||
setNewVariableDescription(variable.description)
|
||
setNewVariableValue(variable.value)
|
||
setVariablesModalOpen(true)
|
||
}} key={"Edit"}>{"Edit"}</MenuItem>
|
||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||
deleteVariable(variable.name)
|
||
setOpen(false)
|
||
}} key={"Delete"}>{"Delete"}</MenuItem>
|
||
</Menu>
|
||
</div>
|
||
</div>
|
||
</Paper>
|
||
</div>
|
||
)
|
||
})}
|
||
|
||
</div>
|
||
<div style={{flex: "1"}}>
|
||
<Button fullWidth style={{margin: "auto", marginTop: "10px",}} color="primary" variant="outlined" onClick={() => setVariablesModalOpen(true)}>New workflow variable</Button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const HandleLeftView = () => {
|
||
// Defaults to apps.
|
||
var thisview = <AppView />
|
||
if (currentView === "triggers") {
|
||
thisview = <TriggersView />
|
||
} else if (currentView === "variables") {
|
||
thisview = <VariablesView />
|
||
}
|
||
|
||
return(
|
||
<div>
|
||
<Divider style={{marginTop: 10, height: 1, width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{minHeight: bodyHeight-appBarSize-150, maxHeight: bodyHeight-appBarSize-100}}>
|
||
{thisview}
|
||
</div>
|
||
<div style={{bottom: 0, left: 0}}>
|
||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{display: "flex"}}>
|
||
<div style={{flex: "1", marginLeft: "10px", marginTop: "10px", color: AppsHoverColor, cursor: "pointer", textAlign: "center"}} onMouseOver={handleAppsHover} onMouseOut={handleAppsHoverOut} onClick={() => {setCurrentView("apps")}}>
|
||
<Grid container direction="row" alignItems="center">
|
||
<Grid item>
|
||
<AppsIcon style={{marginTop: "3px", marginRight: "5px"}} />
|
||
</Grid>
|
||
<Grid item>
|
||
Apps
|
||
</Grid>
|
||
</Grid>
|
||
</div>
|
||
<div style={{flex: "1", marginLeft: "10px", marginTop: "10px", color: HookHoverColor, cursor: "pointer"}} onMouseOver={handleHookHover} onMouseOut={handleHookHoverOut} onClick={() => {setCurrentView("triggers")}}>
|
||
<Grid container direction="row" alignItems="center">
|
||
<Grid item>
|
||
<ScheduleIcon style={{marginTop: "3px", marginRight: "5px"}} />
|
||
</Grid>
|
||
<Grid item>
|
||
Triggers
|
||
</Grid>
|
||
</Grid>
|
||
</div>
|
||
<div style={{flex: "1", marginLeft: "10px", marginTop: "10px", color: VariablesHoverColor, cursor: "pointer"}} onMouseOver={handleVariablesHover} onMouseOut={handleVariablesHoverOut} onClick={() => {setCurrentView("variables")}}>
|
||
<Grid container direction="row" alignItems="center">
|
||
<Grid item>
|
||
<FavoriteBorderIcon style={{marginTop: "3px", marginRight: "5px"}} />
|
||
</Grid>
|
||
<Grid item>
|
||
Variables
|
||
</Grid>
|
||
</Grid>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const TriggersView = () => {
|
||
const triggersViewStyle = {
|
||
marginLeft: "10px",
|
||
marginRight: "10px",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
}
|
||
|
||
// Predefined hurr
|
||
var triggers = [{
|
||
"name": "Webhook",
|
||
"type": "TRIGGER",
|
||
"status": "uninitialized",
|
||
"trigger_type": "WEBHOOK",
|
||
"errors": null,
|
||
"large_image": 'data:image/png;base64,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',
|
||
"is_valid": true,
|
||
"label": "Webhook",
|
||
"environment": environments[0].Name,
|
||
"description": "Simple HTTP webhook",
|
||
"long_description": "Execute a workflow with an unauthicated POST request",
|
||
},
|
||
{
|
||
"name": "User Input",
|
||
"type": "TRIGGER",
|
||
"status": "running",
|
||
"large_image": "data:image/png;base64,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",
|
||
"description": "Wait for user input",
|
||
"trigger_type": "USERINPUT",
|
||
"errors": null,
|
||
"is_valid": false,
|
||
"label": "User input",
|
||
"environment": environments[0].Name,
|
||
"long_description": "Take user input to continue execution",
|
||
},
|
||
{
|
||
"name": "Schedule",
|
||
"type": "TRIGGER",
|
||
"status": "uninitialized",
|
||
"description": "Schedule execution time",
|
||
"trigger_type": "SCHEDULE",
|
||
"errors": null,
|
||
"large_image": "data:image/png;base64,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",
|
||
"is_valid": true,
|
||
"label": "Schedule",
|
||
"environment": environments[0].Name,
|
||
"long_description": "Create a schedule based on cron",
|
||
},
|
||
{
|
||
"name": "Email",
|
||
"type": "TRIGGER",
|
||
"status": "uninitialized",
|
||
"description": "Add your email provider",
|
||
"trigger_type": "EMAIL",
|
||
"errors": null,
|
||
"is_valid": false,
|
||
"label": "Email",
|
||
"environment": environments[0].Name,
|
||
"large_image": 'data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoM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',
|
||
"long_description": "Execute a workflow when you get an email",
|
||
}]
|
||
|
||
return (
|
||
<div style={triggersViewStyle}>
|
||
<div style={appScrollStyle}>
|
||
{triggers.map(trigger => {
|
||
var imageline = trigger.large_image.length === 0 ?
|
||
<img alt="" style={{width: "80px"}} />
|
||
:
|
||
<img alt="" src={trigger.large_image} style={{width: 80, height: 80}} />
|
||
|
||
const color = trigger.is_valid ? "green" : "orange"
|
||
return(
|
||
<Draggable
|
||
onDrag={(e) => {handleTriggerDrag(e, trigger)}}
|
||
onStop={(e) => {handleDragStop(e)}}
|
||
dragging={false}
|
||
position={{
|
||
x: 0,
|
||
y: 0,
|
||
}}
|
||
>
|
||
<Paper square style={paperAppStyle} onClick={() => {}}>
|
||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: "2px", backgroundColor: color, marginRight: "5px"}}>
|
||
</div>
|
||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "10"}}>
|
||
<Grid item>
|
||
<ButtonBase>
|
||
{imageline}
|
||
</ButtonBase>
|
||
</Grid>
|
||
<Grid style={{display: "flex", flexDirection: "column", marginLeft: "20px"}}>
|
||
<Grid item style={{flex: "1"}}>
|
||
<h3 style={{marginBottom: "0px", marginTop: "10px"}}>{trigger.name}</h3>
|
||
</Grid>
|
||
<Grid item style={{flex: "1"}}>
|
||
{trigger.description}
|
||
</Grid>
|
||
</Grid>
|
||
</Grid>
|
||
</Paper>
|
||
</Draggable>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
var newNodeId = ""
|
||
const handleTriggerDrag = (e, data) => {
|
||
const cycontainer = cy.container()
|
||
// Chrome lol
|
||
//if (e.srcElement !== undefined && e.srcElement.localName === "canvas") {
|
||
if (e.pageX > cycontainer.offsetLeft && e.pageX < cycontainer.offsetLeft+cycontainer.offsetWidth && e.pageY > cycontainer.offsetTop && e.pageY < cycontainer.offsetTop+cycontainer.offsetHeight) {
|
||
if (newNodeId.length > 0) {
|
||
var currentnode = cy.getElementById(newNodeId)
|
||
if (currentnode.length === 0) {
|
||
return
|
||
}
|
||
|
||
currentnode[0].renderedPosition("x", e.pageX-cycontainer.offsetLeft)
|
||
currentnode[0].renderedPosition("y", e.pageY-cycontainer.offsetTop)
|
||
} else{
|
||
console.log(workflow)
|
||
if (workflow.start === "" || workflow.start === undefined) {
|
||
alert.error("Define a starting action first.")
|
||
return
|
||
}
|
||
|
||
if (data.is_valid === false) {
|
||
alert.error("Sorry, "+data.name+" is not ready yet")
|
||
return
|
||
}
|
||
|
||
newNodeId = uuid.v4()
|
||
const newposition = {
|
||
"x": e.pageX-cycontainer.offsetLeft,
|
||
"y": e.pageY-cycontainer.offsetTop,
|
||
}
|
||
|
||
console.log(data)
|
||
const newAppData = {
|
||
app_name: data.name,
|
||
app_version: "1.0.0",
|
||
environment: data.environment,
|
||
description: data.description,
|
||
long_description: data.long_description,
|
||
errors: [],
|
||
id_: newNodeId,
|
||
_id_: newNodeId,
|
||
id: newNodeId,
|
||
label: data.label,
|
||
type: data.type,
|
||
is_valid: true,
|
||
trigger_type: data.trigger_type,
|
||
large_image: data.large_image,
|
||
status: "uninitialized",
|
||
name: data.name,
|
||
isStartNode: false,
|
||
position: newposition,
|
||
}
|
||
|
||
if (data.trigger_type === "WEBHOOK") {
|
||
newAppData.status = "running"
|
||
}
|
||
|
||
// Can all the data be in here? hmm
|
||
const nodeToBeAdded = {
|
||
group: "nodes",
|
||
data: newAppData,
|
||
renderedPosition: newposition,
|
||
}
|
||
|
||
cy.add(nodeToBeAdded)
|
||
|
||
if (workflow.triggers === undefined) {
|
||
workflow.triggers = [newAppData]
|
||
} else {
|
||
workflow.triggers.push(newAppData)
|
||
}
|
||
|
||
const newEdgeUuid = uuid.v4()
|
||
const newbranch = {
|
||
"source_id": newNodeId,
|
||
"destination_id": workflow.start,
|
||
"id_": newEdgeUuid,
|
||
"id": newEdgeUuid,
|
||
"hasErrors": false,
|
||
}
|
||
|
||
const newcybranch = {
|
||
"source": newNodeId,
|
||
"target": workflow.start,
|
||
"_id": newEdgeUuid,
|
||
"id": newEdgeUuid,
|
||
"hasErrors": false,
|
||
}
|
||
|
||
const edgeToBeAdded = {
|
||
group: "edges",
|
||
data: newcybranch,
|
||
}
|
||
|
||
if (data.name !== "User Input") {
|
||
workflow.branches.push(newbranch)
|
||
cy.add(edgeToBeAdded)
|
||
}
|
||
|
||
setWorkflow(workflow)
|
||
if (data.trigger_type === "WEBHOOK") {
|
||
newWebhook(newAppData)
|
||
saveWorkflow(workflow)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleAppDrag = (e, app) => {
|
||
const cycontainer = cy.container()
|
||
// Chrome lol
|
||
//if (e.srcElement !== undefined && e.srcElement.localName === "canvas") {
|
||
if (e.pageX > cycontainer.offsetLeft && e.pageX < cycontainer.offsetLeft+cycontainer.offsetWidth && e.pageY > cycontainer.offsetTop && e.pageY < cycontainer.offsetTop+cycontainer.offsetHeight) {
|
||
if (newNodeId.length > 0) {
|
||
var currentnode = cy.getElementById(newNodeId)
|
||
if (currentnode.length === 0) {
|
||
return
|
||
}
|
||
|
||
currentnode[0].renderedPosition("x", e.pageX-cycontainer.offsetLeft)
|
||
currentnode[0].renderedPosition("y", e.pageY-cycontainer.offsetTop)
|
||
} else{
|
||
if (app.actions === undefined || app.actions === null || app.actions.length === 0) {
|
||
alert.error("App "+app.name+" currently has no actions to perform. Please go to https://shuffler.io/apps to edit it.")
|
||
return
|
||
}
|
||
|
||
newNodeId = uuid.v4()
|
||
const actionType = "ACTION"
|
||
const actionLabel = getNextActionName(app.name)
|
||
var parameters = null
|
||
|
||
if (app.actions[0].parameters !== null && app.actions[0].parameters.length > 0) {
|
||
parameters = app.actions[0].parameters
|
||
}
|
||
|
||
var newAppPopup = false
|
||
if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) {
|
||
console.log("Should make modal popup for new app")
|
||
newAppPopup = true
|
||
}
|
||
|
||
const newAppData = {
|
||
app_name: app.name,
|
||
app_version: app.app_version,
|
||
app_id: app.id,
|
||
sharing: app.sharing,
|
||
private_id: app.private_id,
|
||
environment: environments[0].Name,
|
||
errors: [],
|
||
id_: newNodeId,
|
||
_id_: newNodeId,
|
||
id: newNodeId,
|
||
is_valid: true,
|
||
label: actionLabel,
|
||
type: actionType,
|
||
name: app.actions[0].name,
|
||
parameters: parameters,
|
||
isStartNode: false,
|
||
large_image: app.large_image,
|
||
authentication: [],
|
||
}
|
||
|
||
// const image = "url("+app.large_image+")"
|
||
|
||
// FIXME - find the cytoscape offset position
|
||
// Can this be done with zoom calculations?
|
||
const nodeToBeAdded = {
|
||
group: "nodes",
|
||
data: newAppData,
|
||
renderedPosition: {
|
||
x: e.layerX,
|
||
y: e.layerY,
|
||
}
|
||
}
|
||
|
||
cy.add(nodeToBeAdded)
|
||
|
||
if (workflow.actions === undefined || workflow.actions.length === 0) {
|
||
workflow.start = newAppData.id
|
||
workflow.actions = []
|
||
newAppData.isStartNode = true
|
||
//setStartNode(newAppData.id)
|
||
}
|
||
|
||
if (workflow.actions.length > 0 && elements.length === 0) {
|
||
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
|
||
|
||
return node;
|
||
})
|
||
|
||
const tmpelements = [].concat(actions)
|
||
setElements(tmpelements)
|
||
}
|
||
|
||
if (workflow.actions.length === 1 && workflow.actions[0].id === workflow.start) {
|
||
const newEdgeUuid = uuid.v4()
|
||
const newcybranch = {
|
||
"source": workflow.start,
|
||
"target": newNodeId,
|
||
"_id": newEdgeUuid,
|
||
"id": newEdgeUuid,
|
||
"hasErrors": false,
|
||
}
|
||
|
||
const edgeToBeAdded = {
|
||
group: "edges",
|
||
data: newcybranch,
|
||
}
|
||
console.log("SHOULD STITCH WITH STARTNODE")
|
||
cy.add(edgeToBeAdded)
|
||
}
|
||
|
||
workflow.actions.push(newAppData)
|
||
setWorkflow(workflow)
|
||
|
||
if (newAppPopup) {
|
||
//alert.error("SHOULD MAKE USER AUTHENTICATE THE APP OR SET hasError")
|
||
//alert.info("Remember: set the authentication for the user itself, not the app")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleDragStop = (e) => {
|
||
newNodeId = ""
|
||
}
|
||
|
||
const appScrollStyle = {
|
||
overflow: "scroll",
|
||
maxHeight: bodyHeight-appBarSize-150,
|
||
minHeight: bodyHeight-appBarSize-150,
|
||
overflowY: "auto",
|
||
overflowX: "hidden",
|
||
}
|
||
|
||
const runAppSearch = (event) => {
|
||
setAppSearch(event.target.value)
|
||
setFilteredApps(apps.filter(app => app.name.toLowerCase().includes(event.target.value.trim().toLowerCase())))
|
||
}
|
||
|
||
const AppView = () => {
|
||
return(
|
||
<div style={appViewStyle}>
|
||
<div style={{flex: "1"}}>
|
||
<div style={appScrollStyle}>
|
||
{/*
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
minHeight: "50px",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
color="primary"
|
||
placeholder={"Search apps"}
|
||
onChange={(event) => {
|
||
runAppSearch(event)
|
||
}}
|
||
/>
|
||
*/}
|
||
{filteredApps.map(app=> {
|
||
// FIXME - add label to apps, as this might be slow with A LOT of apps
|
||
var newAppname = app.name
|
||
newAppname = newAppname.replace("_", " ").charAt(0).toUpperCase()+newAppname.substring(1)
|
||
const maxlen = 24
|
||
if (newAppname.length > maxlen) {
|
||
newAppname = newAppname.slice(0, maxlen)+".."
|
||
}
|
||
|
||
// Description fucks this up - fix overflow :)
|
||
//const maxdesclen = 5
|
||
//var desc = app.description
|
||
//if (newAppname.length > maxdesclen) {
|
||
// desc = desc.slice(0, maxdesclen)+".."
|
||
//}
|
||
|
||
const image = "url("+app.large_image+")"
|
||
return(
|
||
<Draggable
|
||
onDrag={(e) => {handleAppDrag(e, app)}}
|
||
onStop={(e) => {handleDragStop(e)}}
|
||
dragging={false}
|
||
position={{
|
||
x: 0,
|
||
y: 0,
|
||
}}
|
||
>
|
||
<Paper square style={paperAppStyle} >
|
||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: "2px", backgroundColor: "orange", marginRight: "5px"}}>
|
||
</div>
|
||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "10"}}>
|
||
<Grid item>
|
||
<div style={{height: 80, width: 80, backgroundImage: image, backgroundSize: "cover", backgroundRepeat: "no-repeat"}} />
|
||
</Grid>
|
||
<Grid style={{display: "flex", flexDirection: "column", marginLeft: "20px"}}>
|
||
<Grid item style={{flex: 1}}>
|
||
<h4 style={{marginBottom: "0px", marginTop: "5px"}}>{newAppname}</h4>
|
||
</Grid>
|
||
<Grid item style={{flex: 1, width: "100%", }}>
|
||
Short description...
|
||
</Grid>
|
||
<Grid item style={{flex: 1}}>
|
||
Version: {app.app_version}
|
||
</Grid>
|
||
</Grid>
|
||
</Grid>
|
||
</Paper>
|
||
</Draggable>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const getNextActionName = (appName) => {
|
||
var highest = ""
|
||
//label = name + _number
|
||
for (var key in workflow.actions) {
|
||
const item = workflow.actions[key]
|
||
if (item.app_name === appName) {
|
||
var number = item.label.split("_")
|
||
if (isNaN(number[-1]) && parseInt(number[number.length-1]) > highest) {
|
||
highest = number[number.length-1]
|
||
}
|
||
}
|
||
}
|
||
|
||
if (highest) {
|
||
return appName+"_"+(parseInt(highest)+1)
|
||
} else {
|
||
return appName+"_"+1
|
||
}
|
||
}
|
||
|
||
const setNewSelectedAction = (e) => {
|
||
const newaction = selectedApp.actions.find(a => a.name === e.target.value)
|
||
|
||
// Does this one find the wrong one?
|
||
selectedAction.name = newaction.name
|
||
selectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters))
|
||
|
||
// FIXME - this is broken sometimes lol
|
||
//var env = environments.find(a => a.name === newaction.environment)
|
||
//if ((!env || env === undefined) && selectedAction.environment === undefined ) {
|
||
// env = environments[0]
|
||
//}
|
||
//setSelectedActionEnvironment(env)
|
||
|
||
setSelectedAction(selectedAction)
|
||
setSelectedActionName(e.target.value)
|
||
}
|
||
|
||
// APPSELECT at top
|
||
// appname & version
|
||
// description
|
||
// ACTION select
|
||
const selectedNameChange = (event) => {
|
||
selectedAction.label = event.target.value
|
||
setSelectedAction(selectedAction)
|
||
}
|
||
|
||
const selectedTriggerChange = (event) => {
|
||
selectedTrigger.label = event.target.value
|
||
setSelectedTrigger(selectedTrigger)
|
||
}
|
||
|
||
const getParents = (action) => {
|
||
var allkeys = [action.id]
|
||
var handled = []
|
||
var results = []
|
||
|
||
while(true) {
|
||
for (var key in allkeys) {
|
||
var currentnode = cy.getElementById(allkeys[key])
|
||
if (handled.includes(currentnode.data().id)) {
|
||
continue
|
||
} else {
|
||
// Get the name / label here too?
|
||
handled.push(currentnode.data().id)
|
||
results.push(currentnode.data())
|
||
}
|
||
|
||
if (currentnode.length === 0) {
|
||
continue
|
||
}
|
||
|
||
const incomingEdges = currentnode.incomers('edge')
|
||
if (incomingEdges.length === 0) {
|
||
continue
|
||
}
|
||
|
||
for (var i = 0; i < incomingEdges.length; i++) {
|
||
var tmp = incomingEdges[i]
|
||
if (!allkeys.includes(tmp.data().source)) {
|
||
allkeys.push(tmp.data().source)
|
||
}
|
||
}
|
||
}
|
||
if (results.length === allkeys.length) {
|
||
break
|
||
}
|
||
}
|
||
|
||
// Remove self
|
||
results = results.filter(data => data.id !== action.id)
|
||
results = results.filter(data => data.type !== "TRIGGER")
|
||
results.push({"label": "Execution Argument", "type": "INTERNAL"})
|
||
return results
|
||
}
|
||
|
||
// BOLD name: type: required?
|
||
// FORM
|
||
// Dropdown -> static, action, local env, global env
|
||
// VALUE (JSON)
|
||
// {data.name}, {data.description}, {data.required}, {data.schema.type}
|
||
const AppActionArguments = (props) => {
|
||
const [selectedActionParameters, setSelectedActionParameters] = React.useState([])
|
||
const [selectedVariableParameter, setSelectedVariableParameter] = React.useState()
|
||
|
||
useEffect(() => {
|
||
if (selectedActionParameters !== null && selectedActionParameters.length === 0) {
|
||
if (requiresAuthentication) {
|
||
console.log("ADD AUTHENTICATION FIELDS")
|
||
}
|
||
|
||
if (selectedAction.parameters !== null && selectedAction.parameters.length > 0) {
|
||
setSelectedActionParameters(selectedAction.parameters)
|
||
}
|
||
}
|
||
|
||
if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && (workflow.workflow_variables !== null && workflow.workflow_variables.length > 0)) {
|
||
// FIXME - this is the bad thing
|
||
console.log("Does it go here or what?")
|
||
setSelectedVariableParameter(workflow.workflow_variables[0].name)
|
||
}
|
||
|
||
})
|
||
|
||
const changeActionParameter = (event, count) => {
|
||
selectedActionParameters[count].value = event.target.value
|
||
selectedAction.parameters[count].value = event.target.value
|
||
setSelectedAction(selectedAction)
|
||
//setUpdate(event.target.value)
|
||
}
|
||
|
||
const changeActionParameterVariable = (fieldvalue, count) => {
|
||
setSelectedVariableParameter(fieldvalue)
|
||
|
||
// this isn't updated anywhere in the workflow
|
||
// setSelectedActionName({})
|
||
// setSelectedAction({})
|
||
// setSelectedTrigger({})
|
||
// setSelectedApp({})
|
||
// setSelectedEdge({})
|
||
|
||
selectedActionParameters[count].action_field = fieldvalue
|
||
selectedAction.parameters = selectedActionParameters
|
||
|
||
setSelectedActionName(selectedActionName)
|
||
setSelectedApp(selectedApp)
|
||
setSelectedAction(selectedAction)
|
||
setUpdate(fieldvalue)
|
||
}
|
||
|
||
// Sets ACTION_RESULT things
|
||
const changeActionParameterActionResult = (fieldvalue, count) => {
|
||
//cy.nodes().forEach(function( ele ) {
|
||
// if (ele.data()["label"] === fieldvalue) {
|
||
// selectedActionParameters[count].action_field = ele.id()
|
||
// return
|
||
// }
|
||
//});
|
||
|
||
selectedActionParameters[count].action_field = fieldvalue
|
||
selectedAction.parameters = selectedActionParameters
|
||
|
||
// FIXME - check if startnode
|
||
|
||
// Set value
|
||
setSelectedActionName(selectedActionName)
|
||
setSelectedApp(selectedApp)
|
||
|
||
setSelectedAction(selectedAction)
|
||
setUpdate(fieldvalue)
|
||
}
|
||
|
||
const changeActionParameterVariant = (data, count) => {
|
||
selectedActionParameters[count].variant = data
|
||
selectedActionParameters[count].value = ""
|
||
|
||
if (data === "ACTION_RESULT") {
|
||
var parents = getParents(selectedAction)
|
||
if (parents.length > 0) {
|
||
selectedActionParameters[count].action_field = parents[0].label
|
||
} else {
|
||
selectedActionParameters[count].action_field = ""
|
||
}
|
||
} else if (data === "WORKFLOW_VARIABLE") {
|
||
if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
|
||
selectedActionParameters[count].action_field = workflow.workflow_variables[0].name
|
||
}
|
||
}
|
||
|
||
selectedAction.parameters = selectedActionParameters
|
||
|
||
// This is a stupid workaround to make it refresh rofl
|
||
setSelectedActionName({})
|
||
setSelectedAction({})
|
||
setSelectedTrigger({})
|
||
setSelectedApp({})
|
||
setSelectedEdge({})
|
||
// FIXME - check if startnode
|
||
|
||
// Set value
|
||
setSelectedActionName(selectedActionName)
|
||
setSelectedApp(selectedApp)
|
||
setSelectedAction(selectedAction)
|
||
}
|
||
|
||
// FIXME: Issue #40 - selectedActionParameters not reset
|
||
if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) {
|
||
return (
|
||
<div style={{marginTop: "30px"}}>
|
||
<b>Arguments</b>
|
||
{selectedActionParameters.map((data, count) => {
|
||
if (data.variant === "") {
|
||
data.variant = "STATIC_VALUE"
|
||
}
|
||
|
||
var staticcolor = "inherit"
|
||
var actioncolor = "inherit"
|
||
var varcolor = "inherit"
|
||
var multiline
|
||
if (data.multiline !== undefined && data.multiline !== null && data.multiline === true) {
|
||
multiline = true
|
||
}
|
||
|
||
if (data.value !== undefined && data.value.startsWith("{") && data.value.endsWith("}")) {
|
||
multiline = true
|
||
}
|
||
|
||
var placeholder = "Static value"
|
||
if (data.example !== undefined && data.example !== null && data.example.length > 0) {
|
||
placeholder = data.example
|
||
}
|
||
|
||
var datafield =
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
minHeight: "50px",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
multiline={multiline}
|
||
rows="5"
|
||
color="primary"
|
||
defaultValue={data.value}
|
||
placeholder={placeholder}
|
||
onChange={(event) => {
|
||
changeActionParameter(event, count)
|
||
}}
|
||
onBlur={(event) => {
|
||
// Super basic check
|
||
if (event.target.value.startsWith("{")) {
|
||
console.log("VALIDATING JSON")
|
||
try {
|
||
JSON.parse(event.target.value)
|
||
} catch (e) {
|
||
alert.error("Failed to parse json: ", e)
|
||
}
|
||
}
|
||
}}
|
||
/>
|
||
|
||
|
||
// Remap data based on variant
|
||
if (data.variant === "STATIC_VALUE") {
|
||
staticcolor = "#f85a3e"
|
||
} else if (data.variant === "ACTION_RESULT") {
|
||
// Gets the parents of the current node
|
||
var parents = getParents(selectedAction)
|
||
actioncolor = "#f85a3e"
|
||
// set the datafield
|
||
//var datafieldvalue = "Error: No parents. Action not eligible"
|
||
//if (parents.length > 0) {
|
||
// datafieldvalue = parents[0].label
|
||
//}
|
||
const fixedActionText = selectedActionParameters[count].value
|
||
|
||
datafield =
|
||
<div>
|
||
<Select
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: inputColor,
|
||
}
|
||
}}
|
||
SelectDisplayProps={{
|
||
style: {
|
||
marginLeft: 10,
|
||
}
|
||
}}
|
||
value={selectedActionParameters[count].action_field}
|
||
fullWidth
|
||
onChange={(e) => {
|
||
changeActionParameterActionResult(e.target.value, count)
|
||
}}
|
||
style={{backgroundColor: surfaceColor, color: "white", height: "50px"}}
|
||
>
|
||
{parents.map(data => (
|
||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data.label}>
|
||
{data.label}
|
||
</MenuItem>
|
||
))}
|
||
</Select>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
color="primary"
|
||
defaultValue={data.value}
|
||
helperText={<div style={{marginLeft: "5px", color:"white", marginBottom: "2px",}}>Example: $.body will get "data" from {'{"body": "data"}'}</div>}
|
||
placeholder="Action variable ($.)"
|
||
onChange={(event) => {
|
||
changeActionParameter(event, count)
|
||
}}
|
||
/></div>
|
||
|
||
} else if (data.variant === "WORKFLOW_VARIABLE") {
|
||
varcolor = "#f85a3e"
|
||
if (workflow.workflow_variables === null || workflow.workflow_variables === undefined || workflow.workflow_variables.length === 0) {
|
||
setCurrentView("variables")
|
||
datafield =
|
||
<div>
|
||
<div>
|
||
Looks like you don't have any variables yet.
|
||
</div>
|
||
<div style={{width: "100%", margin: "auto"}}>
|
||
<Button style={{margin: "auto", marginTop: "10px"}} color="primary" variant="outlined" onClick={() => setVariablesModalOpen(true)}>New workflow variable</Button>
|
||
</div>
|
||
</div>
|
||
} else {
|
||
// FIXME - this is a shitty solution that needs re-renders all the time
|
||
datafield =
|
||
<Select
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: inputColor,
|
||
}
|
||
}}
|
||
SelectDisplayProps={{
|
||
style: {
|
||
marginLeft: 10,
|
||
}
|
||
}}
|
||
fullWidth
|
||
value={selectedAction.parameters[count].action_field}
|
||
onChange={(e) => {
|
||
changeActionParameterVariable(e.target.value, count)
|
||
}}
|
||
style={{backgroundColor: inputColor, color: "white", height: "50px"}}
|
||
>
|
||
{workflow.workflow_variables.map(data => (
|
||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data.name}>
|
||
{data.name}
|
||
</MenuItem>
|
||
))}
|
||
</Select>
|
||
}
|
||
|
||
}
|
||
|
||
var itemColor = "#f85a3e"
|
||
if (!data.required) {
|
||
itemColor = "#ffeb3b"
|
||
}
|
||
return (
|
||
<div>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: itemColor, marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>{data.name}: </b>
|
||
</div>
|
||
<Tooltip color="primary" title="Static data" placement="top">
|
||
<div style={{cursor: "pointer", color: staticcolor}} onClick={(e) => {
|
||
e.preventDefault()
|
||
changeActionParameterVariant("STATIC_VALUE", count)
|
||
}}>
|
||
<CreateIcon />
|
||
</div>
|
||
</Tooltip>
|
||
|
|
||
<Tooltip color="primary" title="Data from previous action" placement="top">
|
||
<div style={{cursor: "pointer", color: actioncolor}} onClick={(e) => {
|
||
e.preventDefault()
|
||
changeActionParameterVariant("ACTION_RESULT", count)
|
||
}}>
|
||
<AppsIcon />
|
||
</div>
|
||
</Tooltip>
|
||
|
|
||
<Tooltip color="primary" title="Use local variable" placement="top">
|
||
<div style={{cursor: "pointer", color: varcolor}} onClick={(e) => {
|
||
e.preventDefault()
|
||
changeActionParameterVariant("WORKFLOW_VARIABLE", count)
|
||
}}>
|
||
<FavoriteBorderIcon />
|
||
</div>
|
||
</Tooltip>
|
||
</div>
|
||
{datafield}
|
||
</div>
|
||
)})}
|
||
</div>
|
||
)
|
||
}
|
||
return null
|
||
}
|
||
|
||
//height: "100%",
|
||
const appApiViewStyle = {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
backgroundColor: "#1F2023",
|
||
color: "white",
|
||
paddingRight: 10,
|
||
paddingLeft: 10,
|
||
minHeight: "100%",
|
||
}
|
||
|
||
const defineStartnode = () => {
|
||
var oldstartnode = cy.getElementById(workflow.start)
|
||
if (oldstartnode.length > 0) {
|
||
oldstartnode[0].data("isStartNode", false)
|
||
var oldnodecnt = workflow.actions.findIndex(a => a.id === workflow.start)
|
||
workflow.actions[oldnodecnt].isStartNode = false
|
||
}
|
||
|
||
var newstartnode = cy.getElementById(selectedAction.id)
|
||
if (newstartnode.length > 0) {
|
||
newstartnode[0].data("isStartNode", true)
|
||
var newnodecnt = workflow.actions.findIndex(a => a.id === selectedAction.id)
|
||
workflow.actions[newnodecnt].isStartNode = true
|
||
}
|
||
|
||
// Find branches with triggers as source nodes
|
||
// Move these targets to be the new node
|
||
// Set arrows pointing to new startnode with errors
|
||
for (var key in workflow.branches) {
|
||
var item = workflow.branches[key]
|
||
if (item.destination_id === oldstartnode[0].data()["id"]) {
|
||
var curbranch = cy.getElementById(item.id)
|
||
if (curbranch.length > 0) {
|
||
//console.log(curbranch[0].data())
|
||
//curbranch[0].data("target", selectedAction.id)
|
||
curbranch[0].data("hasErrors", true)
|
||
//workflow.branches[key].destination_id = selectedAction.id
|
||
//console.log(curbranch[0].data())
|
||
}
|
||
}
|
||
}
|
||
|
||
workflow.start = selectedAction.id
|
||
setWorkflow(workflow)
|
||
//setStartNode(selectedAction.id)
|
||
}
|
||
|
||
const appApiView = Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0 ?
|
||
<div style={appApiViewStyle}>
|
||
<div style={{display: "flex", height: 40, marginBottom: 30}}>
|
||
<div style={{flex: "1"}}>
|
||
<h3 style={{marginBottom: "5px"}}>{selectedAction.app_name}</h3>
|
||
<Link to="/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}}>What are apps?</Link>
|
||
</div>
|
||
<div style={{flex: "1"}}>
|
||
<Button disabled={selectedAction.id === workflow.start} style={{zIndex: 5000, marginTop: "15px",}} color="primary" variant="outlined" onClick={(e) => {
|
||
defineStartnode(e)
|
||
}}>Set startnode</Button>
|
||
</div>
|
||
</div>
|
||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div>
|
||
Name
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
color="primary"
|
||
placeholder={selectedAction.label}
|
||
onChange={selectedNameChange}
|
||
/>
|
||
|
||
<div style={{marginTop: "20px"}}>
|
||
Environment
|
||
<Select
|
||
value={selectedActionEnvironment === undefined || selectedActionEnvironment.Name === undefined ? "" : selectedActionEnvironment.Name}
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: inputColor,
|
||
}
|
||
}}
|
||
SelectDisplayProps={{
|
||
style: {
|
||
marginLeft: 10,
|
||
}
|
||
}}
|
||
fullWidth
|
||
onChange={(e) => {
|
||
const env = environments.find(a => a.Name === e.target.value)
|
||
console.log("FOUND: ", e.target.value)
|
||
console.log("FOUND2: ", env)
|
||
setSelectedActionEnvironment(env)
|
||
selectedAction.environment = env.Name
|
||
setSelectedAction(selectedAction)
|
||
}}
|
||
style={{backgroundColor: inputColor, color: "white", height: "50px"}}
|
||
>
|
||
{environments.map(data => (
|
||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data.Name}>
|
||
{data.Name}
|
||
</MenuItem>
|
||
))}
|
||
</Select>
|
||
</div>
|
||
{/*requiresAuthentication ?
|
||
<div style={{marginTop: "20px"}}>
|
||
<Button fullWidth style={{margin: "auto", marginTop: "10px",}} color="primary" variant="contained" onClick={() => setAuthenticationModalOpen(true)}>
|
||
AUTHENTICATE
|
||
</Button>
|
||
</div>
|
||
: null*/}
|
||
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{flex: "6", marginTop: "20px"}}>
|
||
<div>
|
||
<b>Actions</b>
|
||
</div>
|
||
<Select
|
||
value={selectedActionName}
|
||
fullWidth
|
||
onChange={setNewSelectedAction}
|
||
style={{backgroundColor: inputColor, color: "white", height: "50px"}}
|
||
SelectDisplayProps={{
|
||
style: {
|
||
marginLeft: 10,
|
||
}
|
||
}}
|
||
>
|
||
{selectedApp.actions.map(data => {
|
||
var newActionname = data.name
|
||
if (data.label !== undefined && data.label !== null && data.label.length > 0) {
|
||
newActionname = data.label
|
||
}
|
||
// ROFL FIXME - loop
|
||
newActionname = newActionname.replace("_", " ")
|
||
newActionname = newActionname.replace("_", " ")
|
||
newActionname = newActionname.replace("_", " ")
|
||
newActionname = newActionname.replace("_", " ")
|
||
newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1)
|
||
return (
|
||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data.name}>
|
||
{newActionname}
|
||
|
||
</MenuItem>
|
||
)
|
||
})}
|
||
</Select>
|
||
<div style={{marginTop: "10px", borderColor: "white", borderWidth: "2px", marginBottom: 200}}>
|
||
<AppActionArguments key={selectedAction.id} selectedAction={selectedAction} />
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
: null
|
||
|
||
|
||
const headerSize = 68
|
||
const rightsidebarStyle = {
|
||
position: "fixed",
|
||
right: 0,
|
||
top: headerSize+1,
|
||
height: "100%",
|
||
bottom: 0,
|
||
minWidth: "350px",
|
||
maxWidth: "350px",
|
||
borderLeft: "1px solid rgb(91, 96, 100)",
|
||
overflow: "scroll",
|
||
overflowX: "auto",
|
||
overflowY: "auto",
|
||
}
|
||
|
||
const setTriggerFolderWrapperMulti = event => {
|
||
const { options } = event.target;
|
||
const value = [];
|
||
for (let i = 0, l = options.length; i < l; i += 1) {
|
||
if (options[i].selected) {
|
||
value.push(options[i].value);
|
||
}
|
||
}
|
||
|
||
if (selectedTrigger.parameters === null) {
|
||
selectedTrigger.parameters = [[]]
|
||
workflow.triggers[selectedTriggerIndex].parameters = [[]]
|
||
}
|
||
|
||
// This is a dirty workaround for the static values in the go backend and datastore db
|
||
const fixedValue = value.join(splitter)
|
||
selectedTrigger.parameters[0] = {"value": fixedValue, "name": "outlookfolder"}
|
||
workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": fixedValue, "name": "outlookfolder"}
|
||
|
||
// This resets state for some reason (:
|
||
setSelectedActionName({})
|
||
setSelectedAction({})
|
||
setSelectedTrigger({})
|
||
setSelectedApp({})
|
||
setSelectedEdge({})
|
||
|
||
// Set value
|
||
setSelectedTrigger(selectedTrigger)
|
||
setWorkflow(workflow)
|
||
};
|
||
|
||
//const setTriggerFolderWrapper = (event) => {
|
||
// if (selectedTrigger.parameters === null) {
|
||
// selectedTrigger.parameters = []
|
||
// workflow.triggers[selectedTriggerIndex].parameters = []
|
||
// }
|
||
|
||
// const folder = triggerFolders.find(a => a.displayName === event.target.value)
|
||
// console.log(event.target.value)
|
||
// console.log(folder)
|
||
//
|
||
// if (folder !== undefined) {
|
||
// workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": folder.displayName, "name": "outlookfolder", "id": folder.id}
|
||
// selectedTrigger.parameters[0] = {"value": folder.displayName, "name": "outlookfolder", "id": folder.id}
|
||
// setWorkflow(workflow)
|
||
|
||
// // This resets state for some reason (:
|
||
// setSelectedActionName({})
|
||
// setSelectedActionEnvironment({})
|
||
// setSelectedAction({})
|
||
// setSelectedTrigger({})
|
||
// setSelectedApp({})
|
||
// setSelectedEdge({})
|
||
|
||
// // Set value
|
||
// setSelectedTrigger(selectedTrigger)
|
||
|
||
// } else {
|
||
// alert.error("Some error occurred with folder "+event.target.value)
|
||
// }
|
||
//}
|
||
|
||
const setTriggerCronWrapper = (value) => {
|
||
if (selectedTrigger.parameters === null) {
|
||
selectedTrigger.parameters = []
|
||
}
|
||
|
||
workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "cron"}
|
||
setWorkflow(workflow)
|
||
}
|
||
|
||
const setTriggerOptionsWrapper = (value) => {
|
||
if (selectedTrigger.parameters === null) {
|
||
selectedTrigger.parameters = []
|
||
}
|
||
|
||
const splitItems = workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",")
|
||
console.log(splitItems)
|
||
if (splitItems.includes(value)) {
|
||
for( var i = 0; i < splitItems.length; i++){
|
||
if (splitItems[i] === value) {
|
||
splitItems.splice(i, 1);
|
||
}
|
||
}
|
||
|
||
} else {
|
||
splitItems.push(value)
|
||
}
|
||
|
||
for( var i = 0; i < splitItems.length; i++){
|
||
if (splitItems[i] === "") {
|
||
splitItems.splice(i, 1);
|
||
}
|
||
}
|
||
|
||
workflow.triggers[selectedTriggerIndex].parameters[2].value = splitItems.join(",")
|
||
|
||
console.log(splitItems)
|
||
setWorkflow(workflow)
|
||
setLocalFirstrequest(!localFirstrequest)
|
||
}
|
||
|
||
const setTriggerTextInformationWrapper = (value) => {
|
||
if (selectedTrigger.parameters === null) {
|
||
selectedTrigger.parameters = []
|
||
}
|
||
|
||
workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "alertinfo"}
|
||
setWorkflow(workflow)
|
||
}
|
||
|
||
const setTriggerBodyWrapper = (value) => {
|
||
if (selectedTrigger.parameters === null) {
|
||
selectedTrigger.parameters = []
|
||
workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "cron"}
|
||
}
|
||
|
||
workflow.triggers[selectedTriggerIndex].parameters[1] = {"value": value, "name": "execution_argument"}
|
||
setWorkflow(workflow)
|
||
}
|
||
|
||
const AppConditionHandler = (props) => {
|
||
const { tmpdata, type } = props;
|
||
|
||
if (tmpdata === undefined) {
|
||
return tmpdata
|
||
}
|
||
const [data, ] = useState(tmpdata)
|
||
const [multiline, setMultiline] = useState(false)
|
||
|
||
if (data.variant === "") {
|
||
data.variant = "STATIC_VALUE"
|
||
}
|
||
|
||
var staticcolor = "inherit"
|
||
var actioncolor = "inherit"
|
||
var varcolor = "inherit"
|
||
if (data.multiline !== undefined && data.multiline !== null && data.multiline === true) {
|
||
setMultiline(true)
|
||
}
|
||
|
||
var placeholder = "Static value"
|
||
if (data.example !== undefined && data.example !== null && data.example.length > 0) {
|
||
placeholder = data.example
|
||
}
|
||
|
||
var datafield =
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
minHeight: "50px",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
multiline={multiline}
|
||
rows={5}
|
||
color="primary"
|
||
defaultValue={data.value}
|
||
placeholder={placeholder}
|
||
onClick={() => {
|
||
console.log("CHANGE FIELD")
|
||
}}
|
||
onBlur={(e) => {
|
||
changeActionVariable(data.action_field, e.target.value)
|
||
}}
|
||
/>
|
||
|
||
console.log(data)
|
||
|
||
// Remap data based on variant
|
||
if (data.variant === "STATIC_VALUE") {
|
||
staticcolor = "#f85a3e"
|
||
} else if (data.variant === "ACTION_RESULT") {
|
||
// Gets the parents of the current node
|
||
var parents = getParents(workflow.actions.find(a => a.id === selectedEdge["target"]))
|
||
actioncolor = "#f85a3e"
|
||
// set the datafield
|
||
//var datafieldvalue = "Error: No parents. Action not eligible"
|
||
//if (parents.length > 0) {
|
||
// datafieldvalue = parents[0].label
|
||
//}
|
||
|
||
datafield =
|
||
<div>
|
||
<Select
|
||
value={data.action_field}
|
||
fullWidth
|
||
onChange={(e) => {
|
||
changeActionVariable(e.target.value, data.value)
|
||
}}
|
||
style={{backgroundColor: inputColor, color: "white", height: "50px"}}
|
||
>
|
||
{parents.map(data => (
|
||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data.label}>
|
||
{data.label}
|
||
</MenuItem>
|
||
))}
|
||
SelectDisplayProps={{
|
||
style: {
|
||
marginLeft: 10,
|
||
}
|
||
}}
|
||
</Select>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
color="primary"
|
||
defaultValue={data.value}
|
||
placeholder="Action variable ($.)"
|
||
onBlur={(e) => {
|
||
changeActionVariable(data.action_field, e.target.value)
|
||
}}
|
||
/></div>
|
||
|
||
} else if (data.variant === "WORKFLOW_VARIABLE") {
|
||
varcolor = "#f85a3e"
|
||
if (workflow.workflow_variables === null || workflow.workflow_variables === undefined || workflow.workflow_variables.length === 0) {
|
||
setCurrentView("variables")
|
||
datafield =
|
||
<div>
|
||
<div>
|
||
Looks like you don't have any variables yet.
|
||
</div>
|
||
<div style={{width: "100%", margin: "auto"}}>
|
||
<Button style={{margin: "auto", marginTop: "10px",}} color="primary" variant="outlined" onClick={() => setVariablesModalOpen(true)}>New workflow variable</Button>
|
||
</div>
|
||
</div>
|
||
} else {
|
||
// FIXME - this is a shitty solution that needs re-renders all the time
|
||
datafield =
|
||
<Select
|
||
fullWidth
|
||
value={data.action_field}
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: inputColor,
|
||
}
|
||
}}
|
||
SelectDisplayProps={{
|
||
style: {
|
||
marginLeft: 10,
|
||
}
|
||
}}
|
||
onChange={(e) => {
|
||
changeActionVariable(e.target.value, data.value)
|
||
}}
|
||
style={{backgroundColor: surfaceColor, color: "white", height: "50px"}}
|
||
>
|
||
{workflow.workflow_variables.map(data => (
|
||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} value={data.name}>
|
||
{data.name}
|
||
</MenuItem>
|
||
))}
|
||
</Select>
|
||
}
|
||
|
||
}
|
||
|
||
const changeActionVariable = (variable, value) => {
|
||
// set the name
|
||
data.value = value
|
||
data.action_field = variable
|
||
|
||
//setConditionValue({})
|
||
|
||
if (type === "source") {
|
||
setSourceValue(data)
|
||
//setDestinationValue(destinationValue)
|
||
} else if (type === "destination") {
|
||
setDestinationValue(data)
|
||
//setSourceValue(sourceValue)
|
||
}
|
||
}
|
||
|
||
const changeActionParameterVariant = (variant) => {
|
||
if (data.variant === variant) {
|
||
return
|
||
}
|
||
|
||
data.variant = variant
|
||
data.value = ""
|
||
|
||
if (variant === "ACTION_RESULT") {
|
||
console.log("SHOULD FIND PARENTS OF EDGE")
|
||
|
||
// Uses the target's parents, as the target should be executing the checks (I think)
|
||
var parents = getParents(workflow.actions.find(a => a.id === selectedEdge["target"]))
|
||
if (parents.length > 0) {
|
||
console.log(parents)
|
||
data.action_field = parents[0].label
|
||
} else {
|
||
data.action_field = ""
|
||
}
|
||
} else if (variant === "WORKFLOW_VARIABLE") {
|
||
if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
|
||
data.action_field = workflow.workflow_variables[0].name
|
||
}
|
||
}
|
||
|
||
setSourceValue({})
|
||
setConditionValue({})
|
||
setDestinationValue({})
|
||
|
||
if (type === "source") {
|
||
setSourceValue(data)
|
||
setDestinationValue(destinationValue)
|
||
} else if (type === "destination") {
|
||
setDestinationValue(data)
|
||
setSourceValue(sourceValue)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>{data.name} </b>
|
||
</div>
|
||
<Tooltip color="primary" title="Static data" placement="top">
|
||
<div style={{cursor: "pointer", color: staticcolor}} onClick={(e) => {
|
||
e.preventDefault()
|
||
changeActionParameterVariant("STATIC_VALUE")
|
||
}}>
|
||
<CreateIcon />
|
||
</div>
|
||
</Tooltip>
|
||
|
|
||
<Tooltip color="primary" title="Data from previous action" placement="top">
|
||
<div style={{cursor: "pointer", color: actioncolor}} onClick={(e) => {
|
||
e.preventDefault()
|
||
changeActionParameterVariant("ACTION_RESULT")
|
||
}}>
|
||
<AppsIcon />
|
||
</div>
|
||
</Tooltip>
|
||
|
|
||
<Tooltip color="primary" title="Use local variable" placement="top">
|
||
<div style={{cursor: "pointer", color: varcolor}} onClick={(e) => {
|
||
e.preventDefault()
|
||
changeActionParameterVariant("WORKFLOW_VARIABLE")
|
||
}}>
|
||
<FavoriteBorderIcon />
|
||
</div>
|
||
</Tooltip>
|
||
</div>
|
||
{datafield}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
|
||
const menuItemStyle = {
|
||
color: "white",
|
||
}
|
||
|
||
const conditionsModal =
|
||
<Dialog modal
|
||
open={conditionsModalOpen}
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: surfaceColor,
|
||
color: "white",
|
||
minWidth: "800px",
|
||
},
|
||
}}
|
||
onClose={() => {
|
||
setConditionsModalOpen(false)
|
||
setSourceValue({})
|
||
setConditionValue({})
|
||
setDestinationValue({})
|
||
}}
|
||
>
|
||
<FormControl>
|
||
<DialogTitle><div style={{color:"white"}}>Condition</div></DialogTitle>
|
||
<DialogContent style={{display: "flex"}}>
|
||
<div style={{flex: "2"}}>
|
||
<AppConditionHandler tmpdata={sourceValue} setData={setSourceValue} type={"source"} />
|
||
</div>
|
||
<div style={{flex: "1", margin: "auto", marginBottom: "0px"}}>
|
||
<Button color="primary" variant="outlined" style={{height: "50px",}} fullWidth aria-haspopup="true" onClick={(e) => {setVariableAnchorEl(e.currentTarget)}}>
|
||
{conditionValue.value}
|
||
</Button>
|
||
<Menu
|
||
id="simple-menu"
|
||
keepMounted
|
||
open={Boolean(variableAnchorEl)}
|
||
anchorEl={variableAnchorEl}
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: surfaceColor,
|
||
}
|
||
}}
|
||
|
||
onClose={() => {
|
||
setVariableAnchorEl(null)
|
||
}}
|
||
>
|
||
<MenuItem style={menuItemStyle} onClick={(e) => {
|
||
conditionValue.value = "equals"
|
||
setConditionValue(conditionValue)
|
||
setVariableAnchorEl(null)
|
||
}} key={"equals"}>equals</MenuItem>
|
||
<MenuItem style={menuItemStyle} onClick={(e) => {
|
||
conditionValue.value = "does not equal"
|
||
setConditionValue(conditionValue)
|
||
setVariableAnchorEl(null)
|
||
}} key={"does not equal"}>does not equal</MenuItem>
|
||
<MenuItem style={menuItemStyle} onClick={(e) => {
|
||
conditionValue.value = "startswith"
|
||
setConditionValue(conditionValue)
|
||
setVariableAnchorEl(null)
|
||
}} key={"starts with"}>starts with</MenuItem>
|
||
<MenuItem style={menuItemStyle} onClick={(e) => {
|
||
conditionValue.value = "endswith"
|
||
setConditionValue(conditionValue)
|
||
setVariableAnchorEl(null)
|
||
}} key={"ends with"}>ends with</MenuItem>
|
||
<MenuItem style={menuItemStyle} onClick={(e) => {
|
||
conditionValue.value = "endswith"
|
||
setConditionValue(conditionValue)
|
||
setVariableAnchorEl(null)
|
||
}} key={"ends with"}>ends with</MenuItem>
|
||
<MenuItem style={menuItemStyle} onClick={(e) => {
|
||
conditionValue.value = "contains"
|
||
setConditionValue(conditionValue)
|
||
setVariableAnchorEl(null)
|
||
}} key={"contains"}>contains</MenuItem>
|
||
<MenuItem style={menuItemStyle} onClick={(e) => {
|
||
conditionValue.value = "matches regex"
|
||
setConditionValue(conditionValue)
|
||
setVariableAnchorEl(null)
|
||
}} key={"matches regex"}>matches regex</MenuItem>
|
||
</Menu>
|
||
|
||
</div>
|
||
<div style={{flex: "2"}}>
|
||
<AppConditionHandler tmpdata={destinationValue} setData={setDestinationValue} type={"destination"} />
|
||
</div>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Button
|
||
style={{borderRadius: "0px"}}
|
||
onClick={() => {
|
||
setSelectedEdge({})
|
||
|
||
var data = {
|
||
condition: conditionValue,
|
||
source: sourceValue,
|
||
destination: destinationValue,
|
||
}
|
||
|
||
setConditionsModalOpen(false)
|
||
if (selectedEdge.conditions === undefined) {
|
||
selectedEdge.conditions = [data]
|
||
} else {
|
||
const curedgeindex = selectedEdge.conditions.findIndex(data => data.source.id === sourceValue.id)
|
||
if (curedgeindex < 0) {
|
||
selectedEdge.conditions.push(data)
|
||
} else {
|
||
selectedEdge.conditions[curedgeindex] = data
|
||
}
|
||
}
|
||
|
||
setSelectedEdge(selectedEdge)
|
||
workflow.branches[selectedEdgeIndex] = selectedEdge
|
||
setWorkflow(workflow)
|
||
}} color="primary">
|
||
Submit
|
||
</Button>
|
||
</DialogActions>
|
||
</FormControl>
|
||
</Dialog>
|
||
|
||
const EdgeSidebar = () => {
|
||
const ConditionHandler = (condition, index) => {
|
||
const [open, setOpen] = React.useState(false);
|
||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||
|
||
const deleteCondition = (conditionIndex) => {
|
||
console.log(selectedEdge)
|
||
if (selectedEdge.conditions.length === 1) {
|
||
selectedEdge.conditions = []
|
||
} else {
|
||
selectedEdge.conditions.splice(conditionIndex, 1)
|
||
}
|
||
|
||
setSelectedEdge(selectedEdge)
|
||
setOpen(false)
|
||
setUpdate("delete "+conditionIndex)
|
||
}
|
||
|
||
const paperVariableStyle = {
|
||
minHeight: "50px",
|
||
maxHeight: "50px",
|
||
minWidth: "100%",
|
||
maxWidth: "100%",
|
||
marginTop: "5px",
|
||
color: "white",
|
||
backgroundColor: surfaceColor,
|
||
cursor: "pointer",
|
||
display: "flex",
|
||
}
|
||
|
||
const menuClick = (event) => {
|
||
console.log("MENU CLICK")
|
||
setOpen(!open)
|
||
setAnchorEl(event.currentTarget);
|
||
}
|
||
|
||
return (
|
||
<Paper square style={paperVariableStyle} onClick={() => {}}>
|
||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: "2px", backgroundColor: "orange", marginRight: "5px"}} />
|
||
<div style={{display: "flex", width: "100%"}}>
|
||
<div style={{flex: "10", display: "flex"}} onClick={() => {
|
||
setSourceValue(condition.source)
|
||
setConditionValue(condition.condition)
|
||
setDestinationValue(condition.destination)
|
||
setConditionsModalOpen(true)
|
||
}}>
|
||
<div style={{flex: "1", textAlign: "center", marginTop: "15px", marginLeft: "10px", overflow: "hidden"}}>
|
||
{condition.source.value}
|
||
</div>
|
||
<Divider style={{height: "100%", width: "1px", marginLeft: "5px", marginRight: "5px", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{flex: "1", textAlign: "center", marginTop: "15px", overflow: "hidden"}} onClick={() => {}}>
|
||
{condition.condition.value}
|
||
</div>
|
||
<Divider style={{height: "100%", width: "1px", marginLeft: "5px", marginRight: "5px", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{flex: "1", textAlign: "center", marginTop: "15px", marginLeft: "10px", overflow: "hidden"}}>
|
||
{condition.destination.value}
|
||
</div>
|
||
</div>
|
||
<div style={{flex: "1", marginLeft: "0px"}}>
|
||
<IconButton
|
||
aria-label="more"
|
||
aria-controls="long-menu"
|
||
aria-haspopup="true"
|
||
onClick={menuClick}
|
||
>
|
||
<MoreVertIcon />
|
||
</IconButton>
|
||
<Menu
|
||
id="long-menu"
|
||
anchorEl={anchorEl}
|
||
keepMounted
|
||
open={open}
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: surfaceColor,
|
||
}
|
||
}}
|
||
onClose={() => {
|
||
setOpen(false)
|
||
setAnchorEl(null)
|
||
}}
|
||
>
|
||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||
setOpen(false)
|
||
deleteCondition(index)
|
||
}} key={"Delete"}>{"Delete"}</MenuItem>
|
||
</Menu>
|
||
</div>
|
||
</div>
|
||
</Paper>
|
||
)
|
||
}
|
||
|
||
var injectedData =
|
||
<div>
|
||
</div>
|
||
|
||
if (selectedEdge.conditions !== undefined && selectedEdge.conditions !== null && selectedEdge.conditions.length > 0) {
|
||
injectedData = selectedEdge.conditions.map((condition, index) => {
|
||
return ConditionHandler(condition, index)
|
||
})
|
||
}
|
||
|
||
// FIXME - remove index
|
||
const conditionId = uuid.v4()
|
||
return(
|
||
<div style={appApiViewStyle}>
|
||
<div style={{display: "flex", height: "40px", marginBottom: "30px"}}>
|
||
<div style={{flex: "1"}}>
|
||
<h3 style={{marginBottom: "5px"}} >Branch: Conditions - {selectedEdgeIndex}</h3>
|
||
<Link to="/docs/conditions" style={{textDecoration: "none", color: "#f85a3e"}}>What are conditions?</Link>
|
||
</div>
|
||
</div>
|
||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div>
|
||
Conditions
|
||
</div>
|
||
{injectedData}
|
||
|
||
<Button style={{margin: "auto", marginTop: "10px",}} color="primary" variant="outlined" onClick={() => {
|
||
setSourceValue({"name": "source", "value": "", "variant": "STATIC_VALUE", "action_field": "", "id": conditionId})
|
||
setConditionValue({"name": "condition", "value": "equals", "id": conditionId})
|
||
setDestinationValue({"name": "destination", "value": "", "variant": "STATIC_VALUE", "action_field": "", "id": conditionId})
|
||
setConditionsModalOpen(true)
|
||
}} fullWidth>New condition</Button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 1. GET the trigger authentication data
|
||
// 2. Parse the fields that are used (outlook & gmail)
|
||
// 3. Parse the folders that are selected
|
||
// 4. Start / stop
|
||
const EmailSidebar = () => {
|
||
if (Object.getOwnPropertyNames(selectedTrigger).length === 0) {
|
||
return null
|
||
}
|
||
|
||
if (workflow.triggers[selectedTriggerIndex] === undefined) {
|
||
return null
|
||
}
|
||
|
||
if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
|
||
workflow.triggers[selectedTriggerIndex].parameters = [{"value": "No folders selected yet", "name": "outlookfolder"}]
|
||
selectedTrigger.parameters = [{"value": "No folders selected yet", "name": "outlookfolder"}]
|
||
setWorkflow(workflow)
|
||
setSelectedTrigger(selectedTrigger)
|
||
}
|
||
|
||
const setFolders = () => {
|
||
fetch(globalUrl+"/functions/outlook/getFolders?trigger_id="+selectedTrigger.id, {
|
||
method: "GET",
|
||
headers: {"content-type": "application/json"},
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
throw new Error("No folders :o!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
setTriggerFolders(responseJson)
|
||
if (workflow.triggers[selectedTriggerIndex].parameters.length === 0 && responseJson.length > 0) {
|
||
workflow.triggers[selectedTriggerIndex].parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}]
|
||
selectedTrigger.parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}]
|
||
setWorkflow(workflow)
|
||
setSelectedTrigger(selectedTrigger)
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.log(error.toString())
|
||
});
|
||
}
|
||
|
||
const getTriggerAuth = () => {
|
||
fetch(globalUrl+"/api/v1/triggers/"+selectedTrigger.id, {
|
||
method: "GET",
|
||
headers: {"content-type": "application/json"},
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
throw new Error("No trigger info :o!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
setTriggerAuthentication(responseJson)
|
||
})
|
||
.catch(error => {
|
||
console.log(error.toString())
|
||
});
|
||
}
|
||
|
||
// Getting the triggers and the folders if they exist
|
||
// This is horrible hahah
|
||
if (localFirstrequest) {
|
||
getTriggerAuth()
|
||
setFolders()
|
||
setLocalFirstrequest(false)
|
||
}
|
||
|
||
const outlookButton =
|
||
<Button variant="contained" style={{flex: "1",}} onClick={() => {
|
||
// When window closes, it should get all the folders for the user from backend
|
||
const redirectUri = "http%3A%2F%2Fshuffler.io"
|
||
const url = "https://login.microsoftonline.com/common/oauth2/authorize?client_id=70e37005-c954-4290-b573-d4b94e484336&redirect_uri="+redirectUri+"%2Ffunctions%2Foutlook%2Fregister&resource=https%3A%2F%2Fgraph.microsoft.com&response_type=code&scope=Mail.Read+User.Read+https%3A%2F%2Foutlook.office.com%2Fmail.read&state=workflow_id%3D"+props.match.params.key+"%26trigger_id%3D"+selectedTrigger.id+"%26username%3Drheyix.yt@gmail.com%26type%3Doutlook"
|
||
|
||
var newwin = window.open(url, "", "width=200,height=100")
|
||
var data = {}
|
||
|
||
// Check whether we got a callback somewhere
|
||
var id = setInterval(function () {
|
||
fetch(globalUrl+"/api/v1/triggers/"+selectedTrigger.id, {
|
||
method: "GET",
|
||
headers: {"content-type": "application/json"},
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
throw new Error("No trigger info :o!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
setTriggerAuthentication(responseJson)
|
||
clearInterval(id)
|
||
newwin.close()
|
||
setFolders()
|
||
})
|
||
.catch(error => {
|
||
console.log(error.toString())
|
||
});
|
||
}, 2500);
|
||
|
||
console.log(data)
|
||
saveWorkflow(workflow)
|
||
}} color="primary">
|
||
Outlook
|
||
</Button>
|
||
|
||
|
||
// FIXME - set everything in here to multifolder etc
|
||
var triggerInfo = "SET UP BUT NO TYPE :)"
|
||
if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) {
|
||
// Should get the folders if they don't already exist
|
||
|
||
if (triggerAuthentication.type === "outlook") {
|
||
triggerInfo = <div>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Login: </b>
|
||
</div>
|
||
</div>
|
||
{outlookButton}
|
||
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Folders: </b>(hold CTRL to select multiple)
|
||
</div>
|
||
</div>
|
||
<Select
|
||
multiple
|
||
native
|
||
rows="10"
|
||
value={selectedTrigger.parameters[0].value.split(splitter)}
|
||
style={{backgroundColor: inputColor, color: "white"}}
|
||
PaperProps={{
|
||
style: {
|
||
height: "200px",
|
||
backgroundColor: inputColor,
|
||
}
|
||
}}
|
||
SelectDisplayProps={{
|
||
style: {
|
||
marginLeft: 10,
|
||
}
|
||
}}
|
||
onChange={(e) => {
|
||
//setTriggerFolderWrapper(e)
|
||
setTriggerFolderWrapperMulti(e)
|
||
}}
|
||
fullWidth
|
||
input={<Input id="select-multiple-native" />}
|
||
key={selectedTrigger}
|
||
>
|
||
{triggerFolders.map(folder => {
|
||
var folderItem = <option key={folder.displayName} value={folder.displayName} style={{marginLeft: "10px", fontSize: "1.2em"}}>
|
||
{folder.displayName}
|
||
</option>
|
||
if (folder.childFolderCount > 0) {
|
||
// Here to handle subfolders sometime later
|
||
folderItem =
|
||
<option key={folder.displayName} value={folder.displayName} style={{marginLeft: "10px", fontSize: "1.2em"}}>
|
||
{folder.displayName}
|
||
</option>
|
||
}
|
||
|
||
return folderItem
|
||
})}
|
||
</Select>
|
||
</div>
|
||
} else if (triggerAuthentication.type === "gmail") {
|
||
triggerInfo = "SPECIAL GMAIL"
|
||
}
|
||
}
|
||
|
||
|
||
// Check
|
||
const argumentView = Object.getOwnPropertyNames(triggerAuthentication).length > 0 ?
|
||
<div>
|
||
{triggerInfo}
|
||
</div>
|
||
:
|
||
<div>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Login to either: </b>
|
||
</div>
|
||
</div>
|
||
{outlookButton}
|
||
<Button variant="contained" style={{marginLeft: "5px", flex: "1",}} onClick={() => {
|
||
alert.error("REMOVE THIS FIELD FOR GMAIL - make it oAuth something")
|
||
}} color="primary">
|
||
Gmail
|
||
</Button>
|
||
</div>
|
||
|
||
return(
|
||
<div style={appApiViewStyle}>
|
||
<div style={{display: "flex", height: "40px", marginBottom: "30px"}}>
|
||
<div style={{flex: "1"}}>
|
||
<h3 style={{marginBottom: "5px"}}>{selectedTrigger.app_name}: {selectedTrigger.status}</h3>
|
||
<Link to="/docs/triggers#webhook" style={{textDecoration: "none", color: "#f85a3e"}}>What are webhooks?</Link>
|
||
</div>
|
||
</div>
|
||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div>
|
||
Name
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
color="primary"
|
||
placeholder={selectedTrigger.label}
|
||
onChange={selectedTriggerChange}
|
||
/>
|
||
|
||
<div style={{marginTop: "20px"}}>
|
||
Environment:
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
required
|
||
disabled
|
||
fullWidth
|
||
color="primary"
|
||
value={selectedTrigger.environment}
|
||
/>
|
||
</div>
|
||
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
{argumentView}
|
||
<div style={{flex: "6", marginTop: "20px"}}>
|
||
<div>
|
||
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<Button style={{flex: "1",}} disabled={selectedTrigger.status === "running"} onClick={() => {
|
||
startMailSub(selectedTrigger, selectedTriggerIndex)
|
||
}} color="primary">
|
||
Start
|
||
</Button>
|
||
<Button style={{flex: "1",}} disabled={selectedTrigger.status !== "running" } onClick={() => {
|
||
stopMailSub(selectedTrigger, selectedTriggerIndex)
|
||
}} color="primary">
|
||
Stop
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const WebhookSidebar = () => {
|
||
if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
|
||
if (workflow.triggers[selectedTriggerIndex] === undefined) {
|
||
return null
|
||
}
|
||
|
||
if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
|
||
workflow.triggers[selectedTriggerIndex].parameters = []
|
||
workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "url", "value": referenceUrl+"webhook_"+selectedTrigger.id}
|
||
workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "tmp", "value": "webhook_"+selectedTrigger.id}
|
||
setWorkflow(workflow)
|
||
}
|
||
//const cronValue = "*/15 * * * *"
|
||
|
||
return(
|
||
<div style={appApiViewStyle}>
|
||
<div style={{display: "flex", height: "40px", marginBottom: "30px"}}>
|
||
<div style={{flex: "1"}}>
|
||
<h3 style={{marginBottom: "5px"}}>{selectedTrigger.app_name}: {selectedTrigger.status}</h3>
|
||
<Link to="/docs/triggers#webhook" style={{textDecoration: "none", color: "#f85a3e"}}>What are webhooks?</Link>
|
||
</div>
|
||
</div>
|
||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div>
|
||
Name
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
color="primary"
|
||
placeholder={selectedTrigger.label}
|
||
onChange={selectedTriggerChange}
|
||
/>
|
||
|
||
<div style={{marginTop: "20px"}}>
|
||
Environment:
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
required
|
||
disabled
|
||
fullWidth
|
||
color="primary"
|
||
value={selectedTrigger.environment}
|
||
/>
|
||
</div>
|
||
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{flex: "6", marginTop: "20px"}}>
|
||
<div>
|
||
<b>Arguments</b>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Webhook URI: </b>
|
||
</div>
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
onClick={() => {
|
||
//alert.info("Saved URI to clipboard")
|
||
console.log("Copy to clipboooooard")
|
||
}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
height: "50px",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
disabled
|
||
defaultValue={workflow.triggers[selectedTriggerIndex].parameters[0].value}
|
||
color="primary"
|
||
placeholder="defaultValue"
|
||
onBlur={(e) => {
|
||
setTriggerCronWrapper(e.target.value)
|
||
}}
|
||
/>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Execution argument: </b>
|
||
</div>
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
marginTop: "3px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
disabled
|
||
rows="6"
|
||
multiline
|
||
color="primary"
|
||
defaultValue={workflow.triggers[selectedTriggerIndex].parameters[1].value}
|
||
placeholder='{"example": {"json": "is cool"}}'
|
||
onBlur={(e) => {
|
||
setTriggerBodyWrapper(e.target.value)
|
||
}}
|
||
/>
|
||
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<Button style={{flex: "1",}} disabled={selectedTrigger.status === "running"} onClick={() => {
|
||
newWebhook(selectedTrigger)
|
||
}} color="primary">
|
||
Start
|
||
</Button>
|
||
<Button style={{flex: "1",}} disabled={selectedTrigger.status !== "running"} onClick={() => {
|
||
deleteWebhook(selectedTrigger, selectedTriggerIndex)
|
||
}} color="primary">
|
||
Stop
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
//const getTriggerAuth = (trigger_id) => {
|
||
// fetch(globalUrl+"/api/v1/triggers/"+trigger_id, {
|
||
// method: "GET",
|
||
// headers: {"content-type": "application/json"},
|
||
// credentials: "include",
|
||
// })
|
||
// .then((response) => response.json())
|
||
// .then((responseJson) => {
|
||
// if (responseJson.success) {
|
||
// console.log("SUCCESS")
|
||
// console.log(responseJson)
|
||
// } else {
|
||
// console.log("FAIL")
|
||
// }
|
||
// })
|
||
// .catch(error => {
|
||
// console.log(error.toString())
|
||
// });
|
||
//}
|
||
|
||
const stopMailSub = (trigger, triggerindex) => {
|
||
// DELETE
|
||
if (trigger.id === undefined) {
|
||
return
|
||
}
|
||
alert.info("Stopping trigger")
|
||
|
||
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/outlook/"+trigger.id, {
|
||
method: 'DELETE',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
throw new Error("Status not 200 for stream results :O!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
if (responseJson.success) {
|
||
alert.success("Successfully stopped trigger")
|
||
// Set the status
|
||
workflow.triggers[triggerindex].status = "stopped"
|
||
trigger.status = "stopped"
|
||
setWorkflow(workflow)
|
||
setSelectedTrigger(trigger)
|
||
saveWorkflow(workflow)
|
||
} else {
|
||
alert.error("Failed stopping trigger: "+responseJson.reason)
|
||
}
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const startMailSub = (trigger, triggerindex) => {
|
||
var folders = []
|
||
|
||
const splitItem = workflow.triggers[selectedTriggerIndex].parameters[0].value.split(splitter)
|
||
for (var key in splitItem) {
|
||
const item = splitItem[key]
|
||
const curfolder = triggerFolders.find(a => a.displayName === item)
|
||
if (curfolder === undefined) {
|
||
alert.error("Something went wrong with outlook folder "+item)
|
||
return
|
||
}
|
||
|
||
folders.push(curfolder.id)
|
||
}
|
||
|
||
alert.info("Creating outlook subscription with name " + trigger.name)
|
||
const data = {
|
||
"name": trigger.name,
|
||
"folders": folders,
|
||
"id": trigger.id,
|
||
}
|
||
|
||
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/outlook", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify(data),
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for stream results :O!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
if (!responseJson.success) {
|
||
alert.error("Failed to start outlook: " + responseJson.reason)
|
||
} else {
|
||
alert.success("Successfully started outlook sub")
|
||
|
||
workflow.triggers[triggerindex].status = "running"
|
||
trigger.status = "running"
|
||
setWorkflow(workflow)
|
||
setSelectedTrigger(trigger)
|
||
saveWorkflow(workflow)
|
||
}
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const newWebhook = (trigger) => {
|
||
const hookname = trigger.label
|
||
if (hookname.length === 0) {
|
||
alert.error("Missing name")
|
||
return
|
||
}
|
||
|
||
if (trigger.id.length !== 36) {
|
||
alert.error("Missing id")
|
||
return
|
||
}
|
||
|
||
alert.info("Starting webhook")
|
||
|
||
const data = {
|
||
"name": hookname,
|
||
"type": "webhook",
|
||
"id": trigger.id,
|
||
"workflow": workflow.id,
|
||
}
|
||
|
||
fetch(globalUrl+"/api/v1/hooks/new", {
|
||
method: "POST",
|
||
headers: {"content-type": "application/json"},
|
||
body: JSON.stringify(data),
|
||
credentials: "include",
|
||
})
|
||
.then((response) => response.json())
|
||
.then((responseJson) => {
|
||
if (responseJson.success) {
|
||
// Set the status
|
||
alert.success("Successfully started webhook")
|
||
workflow.triggers[selectedTriggerIndex].status = "running"
|
||
trigger.status = "running"
|
||
setWorkflow(workflow)
|
||
setSelectedTrigger(trigger)
|
||
saveWorkflow(workflow)
|
||
} else {
|
||
alert.error("Failed starting webhook: "+responseJson.reason)
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.log(error.toString())
|
||
});
|
||
}
|
||
|
||
const deleteWebhook = (trigger, triggerindex) => {
|
||
if (trigger.id === undefined) {
|
||
return
|
||
}
|
||
alert.info("Stopping webhook")
|
||
|
||
fetch(globalUrl+"/api/v1/hooks/"+trigger.id+"/delete", {
|
||
method: 'DELETE',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for stream results :O!")
|
||
}
|
||
|
||
return response.json()
|
||
})
|
||
.then((responseJson) => {
|
||
if (responseJson.success) {
|
||
alert.success("Successfully stopped webhook")
|
||
// Set the status
|
||
workflow.triggers[triggerindex].status = "stopped"
|
||
trigger.status = "stopped"
|
||
setWorkflow(workflow)
|
||
setSelectedTrigger(trigger)
|
||
saveWorkflow(workflow)
|
||
} else {
|
||
alert.error("Failed stopping webhook: "+responseJson.reason)
|
||
}
|
||
})
|
||
.catch(error => {
|
||
alert.error(error.toString())
|
||
});
|
||
}
|
||
|
||
const UserinputSidebar = () => {
|
||
if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) {
|
||
console.log(workflow.triggers[selectedTriggerIndex])
|
||
console.log(selectedTrigger)
|
||
if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
|
||
workflow.triggers[selectedTriggerIndex].parameters = []
|
||
workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "alertinfo", "value": "hello this is an alert"}
|
||
|
||
// boolean,
|
||
workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "options", "value": "boolean"}
|
||
|
||
// email,sms,app ...
|
||
workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "type", "value": "email"}
|
||
setWorkflow(workflow)
|
||
}
|
||
|
||
return(
|
||
<div style={appApiViewStyle}>
|
||
<div style={{display: "flex", height: "40px", marginBottom: "30px"}}>
|
||
<div style={{flex: "1"}}>
|
||
<h3 style={{marginBottom: "5px"}}>{selectedTrigger.app_name}: {selectedTrigger.status}</h3>
|
||
<Link to="/docs/triggers#schedule" style={{textDecoration: "none", color: "#f85a3e"}}>What are schedules?</Link>
|
||
</div>
|
||
</div>
|
||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div>
|
||
Name
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
color="primary"
|
||
placeholder={selectedTrigger.label}
|
||
onChange={selectedTriggerChange}
|
||
/>
|
||
|
||
<div style={{marginTop: "20px"}}>
|
||
Environment:
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
required
|
||
disabled
|
||
fullWidth
|
||
color="primary"
|
||
value={selectedTrigger.environment}
|
||
/>
|
||
</div>
|
||
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{flex: "6", marginTop: "20px"}}>
|
||
<b>Arguments</b>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Information: </b>
|
||
</div>
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
marginTop: "3px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
rows="4"
|
||
multiline
|
||
defaultValue={workflow.triggers[selectedTriggerIndex].parameters[0].value}
|
||
color="primary"
|
||
placeholder="defaultValue"
|
||
onBlur={(e) => {
|
||
setTriggerTextInformationWrapper(e.target.value)
|
||
}}
|
||
/>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Contact options: </b>
|
||
</div>
|
||
</div>
|
||
<FormGroup style={{paddingLeft: 10, backgroundColor: inputColor}} row>
|
||
<FormControlLabel
|
||
control={
|
||
<Checkbox
|
||
checked={workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email")}
|
||
onChange={() => {
|
||
setTriggerOptionsWrapper("email")
|
||
}}
|
||
color="primary"
|
||
value="email"
|
||
/>
|
||
}
|
||
label={<div style={{color: "white"}}>Email</div>}
|
||
/>
|
||
<FormControlLabel
|
||
control={
|
||
<Checkbox
|
||
checked={workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("sms")}
|
||
onChange={() => {
|
||
setTriggerOptionsWrapper("sms")
|
||
}}
|
||
color="primary"
|
||
value="sms" />
|
||
}
|
||
label={<div style={{color: "white"}}>SMS</div>}
|
||
/>
|
||
</FormGroup>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
const ScheduleSidebar = () => {
|
||
if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) {
|
||
if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
|
||
workflow.triggers[selectedTriggerIndex].parameters = []
|
||
workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "cron", "value": "120"}
|
||
workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "execution_argument", "value": '{"example": {"json": "is cool"}}'}
|
||
setWorkflow(workflow)
|
||
}
|
||
|
||
return(
|
||
<div style={appApiViewStyle}>
|
||
<div style={{display: "flex", height: "40px", marginBottom: "30px"}}>
|
||
<div style={{flex: "1"}}>
|
||
<h3 style={{marginBottom: "5px"}}>{selectedTrigger.app_name}: {selectedTrigger.status}</h3>
|
||
<Link to="/docs/triggers#schedule" style={{textDecoration: "none", color: "#f85a3e"}}>What are schedules?</Link>
|
||
</div>
|
||
</div>
|
||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div>
|
||
Name
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
color="primary"
|
||
placeholder={selectedTrigger.label}
|
||
onChange={selectedTriggerChange}
|
||
/>
|
||
|
||
<div style={{marginTop: "20px"}}>
|
||
Environment:
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
required
|
||
disabled
|
||
fullWidth
|
||
color="primary"
|
||
value={selectedTrigger.environment}
|
||
/>
|
||
</div>
|
||
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{flex: "6", marginTop: "20px"}}>
|
||
<div>
|
||
<b>Arguments</b>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Interval (seconds) </b>
|
||
</div>
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
height: "50px",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
disabled={workflow.triggers[selectedTriggerIndex].status === "running"}
|
||
defaultValue={workflow.triggers[selectedTriggerIndex].parameters[0].value}
|
||
color="primary"
|
||
placeholder="defaultValue"
|
||
onBlur={(e) => {
|
||
setTriggerCronWrapper(e.target.value)
|
||
}}
|
||
/>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
|
||
<div style={{flex: "10"}}>
|
||
<b>Execution argument: </b>
|
||
</div>
|
||
</div>
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
marginLeft: "5px",
|
||
maxWidth: "95%",
|
||
marginTop: "3px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
disabled={workflow.triggers[selectedTriggerIndex].status === "running"}
|
||
fullWidth
|
||
rows="6"
|
||
multiline
|
||
color="primary"
|
||
defaultValue={workflow.triggers[selectedTriggerIndex].parameters[1].value}
|
||
placeholder='{"example": {"json": "is cool"}}'
|
||
onBlur={(e) => {
|
||
setTriggerBodyWrapper(e.target.value)
|
||
}}
|
||
/>
|
||
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
|
||
<Button style={{flex: "1",}} disabled={selectedTrigger.status === "running"} onClick={() => {
|
||
submitSchedule(selectedTrigger, selectedTriggerIndex)
|
||
}} color="primary">
|
||
Start
|
||
</Button>
|
||
<Button style={{flex: "1",}} disabled={selectedTrigger.status !== "running"} onClick={() => {
|
||
stopSchedule(selectedTrigger, selectedTriggerIndex)
|
||
}} color="primary">
|
||
Stop
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
const cytoscapeViewWidths = 650
|
||
const bottomBarStyle = {
|
||
position: "fixed",
|
||
right: 20,
|
||
left: leftBarSize,
|
||
bottom: 0,
|
||
minWidth: cytoscapeViewWidths,
|
||
maxWidth: cytoscapeViewWidths,
|
||
marginLeft: 20,
|
||
marginBottom: 20,
|
||
zIndex: 10,
|
||
}
|
||
|
||
const topBarStyle= {
|
||
position: "fixed",
|
||
right: 0,
|
||
left: leftBarSize,
|
||
top: appBarSize,
|
||
minWidth: cytoscapeViewWidths,
|
||
maxWidth: cytoscapeViewWidths,
|
||
marginLeft: 20,
|
||
marginBottom: 20,
|
||
}
|
||
|
||
const TopCytoscapeBar = () => {
|
||
return (
|
||
<div style={topBarStyle}>
|
||
<div style={{margin: 10}}>
|
||
<Breadcrumbs aria-label="breadcrumb" separator="›" style={{color: "white",}}>
|
||
<Link to="/workflows" style={{textDecoration: "none", color: "inherit",}}>
|
||
<h2 style={{color: "rgba(255,255,255,0.5)"}}>
|
||
<PolymerIcon style={{marginRight: 10}} />
|
||
Workflows
|
||
</h2>
|
||
</Link>
|
||
<h2>
|
||
{workflow.name}
|
||
</h2>
|
||
</Breadcrumbs>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const NoActionsBar = () => {
|
||
if (workflow.actions.length === 0 || !appAdded) {
|
||
return (
|
||
<div style={{position: "fixed", width: "100%", top: 300}}>
|
||
<div style={{width: 300, margin: "auto"}}>
|
||
<h3></h3>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
|
||
const BottomCytoscapeBar = () => {
|
||
const boxSize = 100
|
||
const executionButton = executionRunning ?
|
||
<Tooltip color="primary" title="Stop execution" placement="top">
|
||
<Button style={{height: boxSize, width: boxSize}} color="secondary" variant="contained" onClick={() => {
|
||
abortExecution()
|
||
}}>
|
||
<PauseIcon style={{ fontSize: 60}} />
|
||
</Button>
|
||
</Tooltip>
|
||
:
|
||
<Tooltip color="primary" title="Test execution" placement="top">
|
||
<Button disabled={executionRequestStarted} style={{height: boxSize, width: boxSize}} color="primary" variant="contained" onClick={() => {
|
||
executeWorkflow()
|
||
}}>
|
||
<PlayArrowIcon style={{ fontSize: 60}} />
|
||
</Button>
|
||
</Tooltip>
|
||
|
||
return(
|
||
<div style={bottomBarStyle}>
|
||
{executionButton}
|
||
<div style={{marginLeft: "10px", left: boxSize, bottom: 0, position: "absolute"}}>
|
||
<Tooltip color="primary" title="An argument to be used for execution. This is a variable available to every node in your workflow." placement="top">
|
||
<TextField
|
||
style={{backgroundColor: inputColor, }}
|
||
InputProps={{
|
||
style:{
|
||
height: 50,
|
||
color: "white",
|
||
marginLeft: 5,
|
||
maxWidth: "95%",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
color="secondary"
|
||
placeholder={"Execution Argument"}
|
||
defaultValue={executionText}
|
||
onBlur={(e) => {
|
||
setExecutionText(e.target.value)
|
||
}}
|
||
/>
|
||
</Tooltip>
|
||
<Tooltip color="primary" title="Save (ctrl+s)" placement="top">
|
||
<Button color="primary" style={{height: 50, marginLeft: 10, }} variant="outlined" onClick={() => saveWorkflow()}>
|
||
<SaveIcon />
|
||
</Button>
|
||
</Tooltip>
|
||
<Tooltip color="secondary" title="Fit to screen (ctrl+f)" placement="top">
|
||
<Button color="primary" style={{height: 50, marginLeft: 10, }} variant="outlined" onClick={() => cy.fit(null, 50)}>
|
||
<AspectRatioIcon />
|
||
</Button>
|
||
</Tooltip>
|
||
<Tooltip color="secondary" title="Remove selected item (del)" placement="top-start">
|
||
<Button color="primary" style={{height: 50, marginLeft: 10, }} variant="outlined" onClick={() => {
|
||
removeNode()
|
||
}}>
|
||
<DeleteIcon />
|
||
</Button>
|
||
</Tooltip>
|
||
<Tooltip color="secondary" title="Show executions" placement="top-start">
|
||
<Button color="primary" style={{height: 50, marginLeft: 10, }} variant="outlined" onClick={() => {
|
||
setExecutionModalOpen(true)
|
||
}}>
|
||
<DirectionsRunIcon />
|
||
</Button>
|
||
</Tooltip>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const RightSideBar = () => {
|
||
if (!rightSideBarOpen) {
|
||
return null
|
||
}
|
||
|
||
setLastSaved(false)
|
||
if (Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0) {
|
||
//console.time('ACTIONSTART');
|
||
return(
|
||
<div style={rightsidebarStyle}>
|
||
{appApiView}
|
||
</div>
|
||
)
|
||
} else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
|
||
if (selectedTrigger.trigger_type === "SCHEDULE") {
|
||
console.log("SCHEDULE")
|
||
return(
|
||
<div style={rightsidebarStyle}>
|
||
<ScheduleSidebar />
|
||
</div>
|
||
)
|
||
} else if (selectedTrigger.trigger_type === "WEBHOOK") {
|
||
return(
|
||
<div style={rightsidebarStyle}>
|
||
<WebhookSidebar />
|
||
</div>
|
||
)
|
||
} else if (selectedTrigger.trigger_type === "EMAIL") {
|
||
return(
|
||
<div style={rightsidebarStyle}>
|
||
<EmailSidebar />
|
||
</div>
|
||
)
|
||
} else if (selectedTrigger.trigger_type === "USERINPUT") {
|
||
return(
|
||
<div style={rightsidebarStyle}>
|
||
<UserinputSidebar />
|
||
</div>
|
||
)
|
||
} else if (selectedTrigger.trigger_type === undefined) {
|
||
return null
|
||
} else {
|
||
console.log("Unable to handle invalid trigger type "+selectedTrigger.trigger_type)
|
||
return null
|
||
}
|
||
} else if (Object.getOwnPropertyNames(selectedEdge).length > 0) {
|
||
return(
|
||
<div style={rightsidebarStyle}>
|
||
<EdgeSidebar />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return(
|
||
null
|
||
)
|
||
}
|
||
|
||
// This can execute a workflow with firestore. Used for test, as datastore is old and stuff
|
||
// Too much work to move everything over alone, so won't touch it for now
|
||
//<Button style={{borderRadius: "0px"}} color="primary" variant="contained" onClick={() => {
|
||
// executeWorkflowWebsocket()
|
||
//}}>Execute websocket</Button>
|
||
//
|
||
|
||
const leftView = leftViewOpen ? <div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}>
|
||
<HandleLeftView />
|
||
</div> :
|
||
<div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}>
|
||
<div style={{cursor: "pointer", height: 20, marginTop: 10, marginLeft: 10}} onClick={() => {
|
||
setLeftViewOpen(true)
|
||
setLeftBarSize(350)
|
||
}}>
|
||
<Tooltip color="primary" title="Maximize" placement="top">
|
||
<KeyboardArrowRightIcon />
|
||
</Tooltip>
|
||
</div>
|
||
</div>
|
||
|
||
const executionPaperStyle = {
|
||
minWidth: "95%",
|
||
maxWidth: "95%",
|
||
marginTop: "5px",
|
||
color: "white",
|
||
marginBottom: 10,
|
||
padding: 5,
|
||
backgroundColor: surfaceColor,
|
||
cursor: "pointer",
|
||
display: "flex",
|
||
minHeight: 40,
|
||
maxHeight: 40,
|
||
}
|
||
|
||
const executionModal =
|
||
<Drawer anchor={"right"} open={executionModalOpen} onClose={() => setExecutionModalOpen(false)} PaperProps={{style: {minWidth: 375, maxWidth: 375, backgroundColor: "#1F2023", color: "white", fontSize: 18}}}>
|
||
{executionModalView === 0 ?
|
||
<div style={{padding: 25, }}>
|
||
<Breadcrumbs aria-label="breadcrumb" separator="›" style={{color: "white", fontSize: 16}}>
|
||
<h2 style={{color: "rgba(255,255,255,0.5)"}}>
|
||
<DirectionsRunIcon style={{marginRight: 10}} />
|
||
All Executions
|
||
</h2>
|
||
</Breadcrumbs>
|
||
<Button
|
||
style={{borderRadius: "0px"}}
|
||
onClick={() => {
|
||
getWorkflowExecution(props.match.params.key)
|
||
}} color="primary">
|
||
Refresh executions
|
||
</Button>
|
||
<Divider style={{backgroundColor: "white", marginTop: 10, marginBottom: 10,}}/>
|
||
{workflowExecutions.length > 0 ?
|
||
<div>
|
||
{workflowExecutions.map(data => {
|
||
const statusColor = data.status === "FINISHED" ? "green" : data.status === "ABORTED" ? "red" : "orange"
|
||
const timeElapsed = data.completed_at-data.started_at
|
||
const resultsLength = data.results !== undefined && data.results !== null ? data.results.length : 0
|
||
|
||
const timestamp = new Date(data.started_at*1000).toISOString().split('.')[0].split("T").join(" ")
|
||
|
||
return (
|
||
<Paper elevation={5} square style={executionPaperStyle} onMouseOver={() => {}} onMouseOut={() => {}} onClick={() => {
|
||
setExecutionModalView(1)
|
||
setExecutionData(data)
|
||
}}>
|
||
<div style={{display: "flex", flex: 1}}>
|
||
<div style={{marginLeft: 5, width: 2, backgroundColor: statusColor, marginRight: 15}} />
|
||
<div style={{ marginTop: "auto", marginBottom: "auto", marginRight: 15, }}>
|
||
{timestamp}
|
||
</div>
|
||
<Tooltip color="primary" title={resultsLength+" actions ran"} placement="top">
|
||
<div style={{marginRight: 10, marginTop: "auto", marginBottom: "auto",}}>
|
||
{resultsLength}/{data.workflow.actions.length}
|
||
</div>
|
||
</Tooltip>
|
||
</div>
|
||
<Tooltip title={"Inspect execution"} placement="top">
|
||
<KeyboardArrowRightIcon style={{marginTop: "auto", marginBottom: "auto"}}/>
|
||
</Tooltip>
|
||
</Paper>
|
||
)
|
||
return
|
||
})}
|
||
</div>
|
||
:
|
||
<div>
|
||
There are no executions yet
|
||
</div>
|
||
}
|
||
</div>
|
||
:
|
||
<div style={{padding: 25, }}>
|
||
<Breadcrumbs aria-label="breadcrumb" separator="›" style={{color: "white", fontSize: 16}}>
|
||
<h2 style={{color: "rgba(255,255,255,0.5)", cursor: "pointer"}} onClick={() => {setExecutionModalView(0)}}>
|
||
<DirectionsRunIcon style={{marginRight: 10}} />
|
||
Other Executions
|
||
</h2>
|
||
</Breadcrumbs>
|
||
<Divider style={{backgroundColor: "white", marginTop: 10, marginBottom: 10,}}/>
|
||
<h2>Executing Workflow</h2>
|
||
{executionData.execution_argument !== undefined && executionData.execution_argument.length > 0 ?
|
||
<div>
|
||
<h3>Execution Argument: </h3>{executionData.execution_argument}
|
||
</div>
|
||
: null }
|
||
{executionData.status !== undefined && executionData.status.length > 0 ?
|
||
<div>
|
||
<b>Status: </b>{executionData.status}
|
||
</div>
|
||
: null
|
||
}
|
||
{executionData.started_at !== undefined ?
|
||
<div>
|
||
<b>Started: </b>{new Date(executionData.started_at*1000).toISOString()}
|
||
</div>
|
||
: null
|
||
}
|
||
{executionData.execution_id !== undefined && executionData.execution_id.length > 0 ?
|
||
<div>
|
||
<b>ID: </b>{executionData.execution_id}
|
||
</div>
|
||
: null
|
||
}
|
||
<Divider style={{backgroundColor: "white", marginTop: 30, marginBottom: 30,}}/>
|
||
<div style={{display: "flex", marginTop: 10, marginBottom: 30,}}>
|
||
<b>Actions</b>
|
||
<div>
|
||
{executionData.status !== undefined && executionData.status !== "ABORTED" && executionData.status !== "FINISHED" ? <CircularProgress style={{marginLeft: 20}}/> : null}
|
||
</div>
|
||
</div>
|
||
{executionData.results === undefined || executionData.results === null || executionData.results.length === 0 && executionData.status === "EXECUTING" ?
|
||
<CircularProgress />
|
||
:
|
||
executionData.results.map(data => {
|
||
var showResult = data.result.trim()
|
||
showResult.split(" None").join(" \"None\"")
|
||
//showResult = replaceAll(showResult, " None", " \"None\"");
|
||
var jsonvalid = true
|
||
try {
|
||
JSON.parse(showResult)
|
||
} catch (e) {
|
||
jsonvalid = false
|
||
}
|
||
|
||
const curapp = apps.find(a => a.name === data.action.app_name && a.app_version === data.action.app_version)
|
||
const imgsize = 50
|
||
const actionimg = curapp === null ?
|
||
null :
|
||
<img alt={data.action.app_name} src={curapp.large_image} style={{marginRight: 20, width: imgsize, height: imgsize}} />
|
||
|
||
return (
|
||
<div style={{marginBottom: 40,}}>
|
||
<div style={{display: "flex", marginBottom: 15,}}>
|
||
{actionimg}
|
||
<span style={{fontSize: 24, marginTop: "auto", marginBottom: "auto"}}><b>{data.action.label}</b></span>
|
||
</div>
|
||
<div style={{marginBottom: 5}}><b>Status </b> {data.status}</div>
|
||
{jsonvalid ? <ReactJson
|
||
src={JSON.parse(showResult)}
|
||
theme="solarized"
|
||
collapsed={false}
|
||
displayDataTypes={false}
|
||
name={"Results for "+data.action.label}
|
||
/>
|
||
:
|
||
<div>
|
||
<b>Result</b>
|
||
{data.result}
|
||
</div>
|
||
}
|
||
</div>
|
||
)
|
||
})
|
||
}
|
||
</div>
|
||
}
|
||
</Drawer>
|
||
|
||
const newView = isLoggedIn ?
|
||
<div style={{color: "white"}}>
|
||
<div style={{display: "flex", borderTop: "1px solid rgba(91, 96, 100, 1)"}}>
|
||
{leftView}
|
||
<CytoscapeComponent
|
||
elements={elements}
|
||
minZoom={0.35}
|
||
maxZoom={2.00}
|
||
style={{width: bodyWidth-leftBarSize-15, height: bodyHeight-appBarSize-1, backgroundColor: surfaceColor}}
|
||
stylesheet={cystyle}
|
||
boxSelectionEnabled={true}
|
||
autounselectify={false}
|
||
cy={(incy) => {
|
||
setCy(incy)
|
||
}}
|
||
/>
|
||
</div>
|
||
{executionModal}
|
||
<RightSideBar />
|
||
<BottomCytoscapeBar />
|
||
<TopCytoscapeBar />
|
||
<NoActionsBar />
|
||
</div>
|
||
:
|
||
<div style={{color: "white"}}>
|
||
TMP FOR NOT LOGGED IN
|
||
</div>
|
||
|
||
const variablesModal = variablesModalOpen ?
|
||
<Dialog modal
|
||
open={variablesModalOpen}
|
||
onClose={() => {
|
||
setNewVariableName("")
|
||
setNewVariableDescription("")
|
||
setNewVariableValue("")
|
||
setVariablesModalOpen(false)
|
||
}}
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: surfaceColor,
|
||
color: "white",
|
||
},
|
||
}}
|
||
>
|
||
<FormControl>
|
||
<DialogTitle><span style={{color: "white"}}>Workflow Variable</span></DialogTitle>
|
||
<DialogContent>
|
||
<TextField
|
||
onBlur={(event) => setNewVariableName(event.target.value)}
|
||
color="primary"
|
||
placeholder="Name"
|
||
InputProps={{
|
||
style:{
|
||
color: "white"
|
||
}
|
||
}}
|
||
margin="dense"
|
||
fullWidth
|
||
defaultValue={newVariableName}
|
||
/>
|
||
<TextField
|
||
onBlur={(event) => setNewVariableDescription(event.target.value)}
|
||
color="primary"
|
||
placeholder="Description"
|
||
margin="dense"
|
||
fullWidth
|
||
InputProps={{
|
||
style:{
|
||
color: "white"
|
||
}
|
||
}}
|
||
defaultValue={newVariableDescription}
|
||
/>
|
||
<TextField
|
||
onChange={(event) => setNewVariableValue(event.target.value)}
|
||
rows="6"
|
||
multiline
|
||
color="primary"
|
||
placeholder="Value"
|
||
margin="dense"
|
||
InputProps={{
|
||
style:{
|
||
color: "white"
|
||
}
|
||
}}
|
||
fullWidth
|
||
defaultValue={newVariableValue}
|
||
/>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Button
|
||
style={{borderRadius: "0px"}}
|
||
onClick={() => {
|
||
setNewVariableName("")
|
||
setNewVariableDescription("")
|
||
setNewVariableValue("")
|
||
setVariablesModalOpen(false)
|
||
}} color="primary">
|
||
Cancel
|
||
</Button>
|
||
<Button style={{borderRadius: "0px"}} disabled={newVariableName.length === 0 || newVariableValue.length === 0} onClick={() => {
|
||
if (workflow.workflow_variables === undefined || workflow.workflow_variables === null) {
|
||
workflow.workflow_variables = []
|
||
}
|
||
|
||
// try to find one with the same name
|
||
const found = workflow.workflow_variables.findIndex(data => data.name === newVariableName)
|
||
//console.log(found)
|
||
if (found !== -1) {
|
||
if (newVariableName.length > 0) {
|
||
workflow.workflow_variables[found].name = newVariableName
|
||
}
|
||
if (newVariableDescription.length > 0) {
|
||
workflow.workflow_variables[found].description = newVariableDescription
|
||
}
|
||
if (newVariableValue.length > 0) {
|
||
workflow.workflow_variables[found].value = newVariableValue
|
||
}
|
||
} else {
|
||
workflow.workflow_variables.push({
|
||
"name": newVariableName,
|
||
"description": newVariableDescription,
|
||
"value": newVariableValue,
|
||
"id": uuid.v4(),
|
||
})
|
||
}
|
||
|
||
setWorkflow(workflow)
|
||
setVariablesModalOpen(false)
|
||
setNewVariableName("")
|
||
setNewVariableDescription("")
|
||
setNewVariableValue("")
|
||
}} color="primary">
|
||
Submit
|
||
</Button>
|
||
</DialogActions>
|
||
</FormControl>
|
||
</Dialog>
|
||
: null
|
||
|
||
|
||
const AuthenticationData = () => {
|
||
console.log("AUTH: ", selectedApp.authentication)
|
||
const [tmpVar, setTmpVar] = React.useState("")
|
||
if (selectedApp.authentication === undefined) {
|
||
return null
|
||
}
|
||
|
||
if (selectedApp.authentication.parameters.length === undefined ||
|
||
selectedApp.authentication.parameters.length === 0) {
|
||
return null
|
||
}
|
||
|
||
// Yes, it should be possible to have more than one, but.. :)
|
||
// This data should be written to a KMS, then have the ID point back
|
||
const currentAuth = selectedApp.authentication.parameters[0]
|
||
if (currentAuth.scheme.toLowerCase() === "bearer") {
|
||
return <div>
|
||
Insert your API token for {selectedApp.name}
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
type="password"
|
||
color="primary"
|
||
placeholder="Bearer token"
|
||
onChange={(event) => {
|
||
setTmpVar(event.target.value)
|
||
}}
|
||
onBlur={() => {
|
||
selectedApp.authentication.parameters[0].value = tmpVar
|
||
setSelectedApp(selectedApp)
|
||
}}
|
||
/>
|
||
</div>
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
NOT IMPLEMENTED <div/>
|
||
Unknown auth: {currentAuth.scheme}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const EndpointData = () => {
|
||
const [tmpVar, setTmpVar] = React.useState("")
|
||
return (
|
||
<div>
|
||
The API endpoint to use (URL) - leave this if you're unsure
|
||
<TextField
|
||
style={{backgroundColor: inputColor}}
|
||
InputProps={{
|
||
style:{
|
||
color: "white",
|
||
height: "50px",
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
fullWidth
|
||
type="text"
|
||
color="primary"
|
||
disabled={true}
|
||
placeholder="Bearer token"
|
||
defaultValue={selectedApp.link}
|
||
onChange={(event) => {
|
||
setTmpVar(event.target.value)
|
||
}}
|
||
onBlur={() => {
|
||
selectedApp.link = tmpVar
|
||
console.log("LINK: ", selectedApp.link)
|
||
setSelectedApp(selectedApp)
|
||
}}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// This whole part is redundant. Made it part of Arguments instead.
|
||
const authenticationModal = authenticationModalOpen ?
|
||
<Dialog modal
|
||
open={authenticationModalOpen}
|
||
onClose={() => {
|
||
setAuthenticationModalOpen(false)
|
||
setAppAuthentication({})
|
||
}}
|
||
PaperProps={{
|
||
style: {
|
||
backgroundColor: surfaceColor,
|
||
color: "white",
|
||
minWidth: "800px",
|
||
},
|
||
}}
|
||
>
|
||
<DialogTitle><div style={{color: "white"}}>Authentication for {selectedApp.name}</div></DialogTitle>
|
||
<DialogContent>
|
||
<Link to="/docs/apps#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is this?</Link>
|
||
<div />
|
||
{selectedApp.link.length > 0 ? <EndpointData /> : null}
|
||
<div style={{marginTop: 15, marginBottom: 15, }}/>
|
||
<AuthenticationData />
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Button
|
||
style={{borderRadius: "0px"}}
|
||
onClick={() => {
|
||
setAuthenticationModalOpen(false)
|
||
}} color="primary">
|
||
Cancel
|
||
</Button>
|
||
<Button style={{borderRadius: "0px"}} onClick={() => {setAuthenticationModalOpen(false)}} color="primary">
|
||
Submit
|
||
</Button>
|
||
</DialogActions>
|
||
</Dialog> : null
|
||
|
||
const loadedCheck = isLoaded && isLoggedIn && workflowDone ?
|
||
<div>
|
||
{newView}
|
||
{variablesModal}
|
||
{conditionsModal}
|
||
{authenticationModal}
|
||
</div>
|
||
:
|
||
<div>
|
||
</div>
|
||
|
||
|
||
return (
|
||
<div>
|
||
{loadedCheck}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default AngularWorkflow;
|