4574 lines
156 KiB
JavaScript
4574 lines
156 KiB
JavaScript
import React, {useState, useEffect, useLayoutEffect} from 'react';
|
|
import { useInterval } from 'react-powerhooks';
|
|
|
|
import uuid from "uuid";
|
|
|
|
import TextField from '@material-ui/core/TextField';
|
|
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 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 [currentView, setCurrentView] = React.useState("apps")
|
|
const [, setAppSearchValue] = React.useState("")
|
|
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 [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 [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 (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 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) {
|
|
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])
|
|
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.log("Unselect?")
|
|
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)
|
|
//setSelectedActionName({})
|
|
setSelectedAction({})
|
|
setSelectedApp({})
|
|
setSelectedTrigger({})
|
|
setSelectedEdge({})
|
|
// setSelectedTriggerIndex(-1)
|
|
//setSelectedActionEnvironment({})
|
|
//setSelectedEdge({})
|
|
//setTriggerAuthentication({})
|
|
//setSelectedTriggerIndex(-1)
|
|
//setTriggerFolders([])
|
|
|
|
//setLocalFirstrequest(true)
|
|
console.timeEnd("UNSELECT")
|
|
}
|
|
|
|
const onEdgeSelect = (event) => {
|
|
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())
|
|
} 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}"]`))
|
|
console.time('ACTIONSTART');
|
|
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]
|
|
}
|
|
|
|
console.log("Selected: ", data.id)
|
|
console.log(curaction)
|
|
|
|
setRequiresAuthentication(curapp.authentication.required)
|
|
setSelectedApp(curapp)
|
|
setSelectedActionEnvironment(env)
|
|
setSelectedActionName(curaction.name)
|
|
setSelectedAction(curaction)
|
|
} 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)
|
|
setSelectedActionEnvironment(data.env)
|
|
setSelectedActionName(data.name)
|
|
} else {
|
|
console.log("Should handle type "+data.type)
|
|
}
|
|
}
|
|
|
|
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 setAppSearch = (event) => {
|
|
setFilteredApps(apps.filter(app => app.name.includes(event.target.value)))
|
|
}
|
|
|
|
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 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 paperVariableStyle = {
|
|
minHeight: "50px",
|
|
maxHeight: "50px",
|
|
minWidth: "100%",
|
|
maxWidth: "100%",
|
|
marginTop: "5px",
|
|
color: "white",
|
|
backgroundColor: surfaceColor,
|
|
cursor: "pointer",
|
|
display: "flex",
|
|
}
|
|
|
|
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 = () => {
|
|
var thisview = <AppView />
|
|
if (currentView === "triggers") {
|
|
thisview = <TriggersView />
|
|
} else if (currentView === "variables") {
|
|
thisview = <VariablesView />
|
|
}
|
|
|
|
return(
|
|
<div>
|
|
<div style={{cursor: "pointer", height: 20, marginLeft: 10, marginTop: 10}} onClick={() => {
|
|
setLeftViewOpen(false)
|
|
setLeftBarSize(50)
|
|
}}>
|
|
<Tooltip color="primary" title="Minimize" placement="top">
|
|
<KeyboardArrowLeftIcon />
|
|
</Tooltip>
|
|
</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 AppView = () => {
|
|
return(
|
|
<div style={appViewStyle}>
|
|
<div style={{flex: "1"}}>
|
|
<div style={appScrollStyle}>
|
|
{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)
|
|
|
|
selectedAction.name = newaction.name
|
|
selectedAction.parameters = 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 = () => {
|
|
const [selectedActionParameters, setSelectedActionParameters] = React.useState([])
|
|
const [selectedVariableParameter, setSelectedVariableParameter] = React.useState()
|
|
|
|
useEffect(() => {
|
|
if (selectedActionParameters !== null && selectedActionParameters.length === 0) {
|
|
if (requiresAuthentication) {
|
|
console.log("ADD AUTHENTICATION FIELDS")
|
|
}
|
|
setSelectedActionParameters(selectedAction.parameters)
|
|
}
|
|
|
|
if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && (workflow.workflow_variables !== null && workflow.workflow_variables.length > 0)) {
|
|
// FIXME - this is the bad thing
|
|
setSelectedVariableParameter(workflow.workflow_variables[0].name)
|
|
}
|
|
|
|
})
|
|
|
|
const changeActionParameter = (event, count) => {
|
|
selectedActionParameters[count].value = event.target.value
|
|
selectedAction.parameters = selectedActionParameters
|
|
setSelectedAction(selectedAction)
|
|
}
|
|
|
|
|
|
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)
|
|
}
|
|
|
|
// 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
|
|
|
|
setSelectedActionName({})
|
|
setSelectedAction({})
|
|
setSelectedTrigger({})
|
|
setSelectedApp({})
|
|
setSelectedEdge({})
|
|
// FIXME - check if startnode
|
|
|
|
// Set value
|
|
setSelectedActionName(selectedActionName)
|
|
setSelectedApp(selectedApp)
|
|
|
|
setSelectedAction(selectedAction)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters) {
|
|
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.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")
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
|
|
|
|
// 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,
|
|
}
|
|
}}
|
|
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,
|
|
}
|
|
}}
|
|
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: "40px", marginBottom: 30}}>
|
|
<div style={{flex: "1"}}>
|
|
<h3 style={{marginBottom: "5px"}}>{selectedAction.app_name}</h3>
|
|
<a href="/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}}>What are apps?</a>
|
|
</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,
|
|
}
|
|
}}
|
|
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"}}
|
|
>
|
|
{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={"hey"} />
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
: null
|
|
|
|
|
|
const headerSize = 74
|
|
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>
|
|
))}
|
|
</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,
|
|
}
|
|
}}
|
|
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>
|
|
<div style={{cursor: "pointer", color: staticcolor}} onClick={() => {
|
|
changeActionParameterVariant("STATIC_VALUE")
|
|
}}>
|
|
static
|
|
</div>
|
|
|
|
|
<div style={{cursor: "pointer", color: actioncolor}} onClick={() => {
|
|
changeActionParameterVariant("ACTION_RESULT")
|
|
}}>
|
|
action
|
|
</div>
|
|
|
|
|
<div style={{cursor: "pointer", color: varcolor}} onClick={() => {
|
|
changeActionParameterVariant("WORKFLOW_VARIABLE")
|
|
}}>
|
|
var
|
|
</div>
|
|
</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) => {
|
|
//selectedEdge.conditions.splice(conditionIndex, 1)
|
|
//setSelectedEdge(selectedEdge)
|
|
}
|
|
|
|
const paperVariableStyle = {
|
|
minHeight: "50px",
|
|
maxHeight: "50px",
|
|
minWidth: "100%",
|
|
maxWidth: "100%",
|
|
marginTop: "5px",
|
|
color: "white",
|
|
backgroundColor: surfaceColor,
|
|
cursor: "pointer",
|
|
display: "flex",
|
|
}
|
|
|
|
const menuClick = (event) => {
|
|
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>
|
|
<a href="/docs/conditions" style={{textDecoration: "none", color: "#f85a3e"}}>What are conditions?</a>
|
|
</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,
|
|
}
|
|
}}
|
|
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>
|
|
<a href="/docs/webhooks" style={{textDecoration: "none", color: "#f85a3e"}}>What are webhooks?</a>
|
|
</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>
|
|
<a href="/docs/webhooks" style={{textDecoration: "none", color: "#f85a3e"}}>What are webhooks?</a>
|
|
</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>
|
|
<a href="/docs/schedules" style={{textDecoration: "none", color: "#f85a3e"}}>What are schedules?</a>
|
|
</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>
|
|
<a href="/docs/schedules" style={{textDecoration: "none", color: "#f85a3e"}}>What are schedules?</a>
|
|
</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>Run how often (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 = 600
|
|
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}}>
|
|
<h3>Editing workflow {workflow.name}</h3>
|
|
</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 running workflow (ctrl+a)" placement="top">
|
|
<Button style={{height: boxSize, width: boxSize}} color="secondary" variant="contained" onClick={() => {
|
|
abortExecution()
|
|
}}>
|
|
<PauseIcon style={{ fontSize: 60}} />
|
|
</Button>
|
|
</Tooltip>
|
|
:
|
|
<Tooltip color="primary" title="Execute workflow once (ctrl+a)" 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:{
|
|
color: "white",
|
|
height: 50,
|
|
},
|
|
}}
|
|
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>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const RightSideBar = () => {
|
|
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") {
|
|
console.log("WEBHOOK")
|
|
return(
|
|
<div style={rightsidebarStyle}>
|
|
<WebhookSidebar />
|
|
</div>
|
|
)
|
|
} else if (selectedTrigger.trigger_type === "EMAIL") {
|
|
console.log("EMAIL")
|
|
return(
|
|
<div style={rightsidebarStyle}>
|
|
<EmailSidebar />
|
|
</div>
|
|
)
|
|
} else if (selectedTrigger.trigger_type === "USERINPUT") {
|
|
console.log("USER INPUT SIDEBAR")
|
|
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 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>
|
|
<RightSideBar />
|
|
<BottomCytoscapeBar />
|
|
<TopCytoscapeBar />
|
|
<NoActionsBar />
|
|
{debugView}
|
|
</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>
|
|
<a href="/docs/apps#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is this?</a>
|
|
<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;
|