diff --git a/backend/Dockerfile b/backend/Dockerfile index ea93625e..5d7c484d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,15 +7,12 @@ WORKDIR /app ADD ./go-app/main.go /app ADD ./go-app/walkoff.go /app ADD ./go-app/docker.go /app -ADD ./go-app/codegen.go /app -ADD ./go-app/files.go /app ADD ./go-app/oauth2.go /app ADD ./go-app/go.mod /app # Required files for code generation ADD ./app_sdk/app_base.py /app_sdk -ADD ./app_sdk/static_baseline.py /app_sdk ADD ./app_sdk_kali/app_base.py /app_sdk_kali ADD ./app_sdk_kali/static_baseline.py /app_sdk_kali ADD ./app_sdk_blackarch/app_base.py /app_sdk_blackarch diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 7d7f91f2..fa5a2e35 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -2,7 +2,7 @@ module shuffle go 1.13 -replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared +//replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi require ( @@ -18,7 +18,7 @@ require ( github.com/docker/docker v1.13.1 github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.4.0 // indirect - github.com/frikky/shuffle-shared v0.0.15 + github.com/frikky/shuffle-shared v0.0.20 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-git/v5 v5.0.0 diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 7d1de1b2..097bc190 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -77,6 +77,8 @@ var registryName = "registry.hub.docker.com" var runningEnvironment = "onprem" var syncUrl = "https://shuffler.io" + +//var syncUrl = "http://localhost:5002" var syncSubUrl = "https://shuffler.io" //var syncUrl = "http://localhost:5002" @@ -5463,12 +5465,12 @@ func runInit(ctx context.Context) { } iterateOpenApiGithub(fs, dir, "", "") - log.Printf("Finished downloading extra API samples") + log.Printf("[INFO] Finished downloading extra API samples") } workflowLocation := os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION") if len(workflowLocation) > 0 { - log.Printf("Downloading WORKFLOWS from %s if no workflows - EXTRA workflows", workflowLocation) + log.Printf("[INFO] Downloading WORKFLOWS from %s if no workflows - EXTRA workflows", workflowLocation) q := datastore.NewQuery("workflow").Limit(35) var workflows []shuffle.Workflow _, err = dbclient.GetAll(ctx, q, &workflows) @@ -5918,6 +5920,138 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { resp.Write(respBody) } +func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) { + cors := shuffle.HandleCors(resp, request) + if cors { + return + } + + user, userErr := shuffle.HandleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("[WARNING] Api authentication failed in make workflow public: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + ctx := context.Background() + if strings.Contains(fileId, "?") { + fileId = strings.Split(fileId, "?")[0] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID when getting workflow is not valid"}`)) + return + } + + workflow, err := shuffle.GetWorkflow(ctx, fileId) + if err != nil { + log.Printf("[WARNING] Workflow %s doesn't exist in app publish. User: %s", fileId, user.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // CHECK orgs of user, or if user is owner + // FIXME - add org check too, and not just owner + // Check workflow.Sharing == private / public / org too + if user.Id != workflow.Owner || len(user.Id) == 0 { + if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" { + log.Printf("[INFO] User %s is accessing workflow %s as admin", user.Username, workflow.ID) + } else { + log.Printf("[WARNING] Wrong user (%s) for workflow %s (get workflow)", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + } + + if !workflow.IsValid || !workflow.PreviouslySaved { + log.Printf("[INFO] Failed uploading workflow because it's invalid or not saved") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Invalid workflows are not sharable"}`)) + return + } + + // Starting validation of the POST workflow + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("[WARNING] Body data error on mail: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + parsedWorkflow := shuffle.Workflow{} + err = json.Unmarshal(body, &parsedWorkflow) + if err != nil { + log.Printf("[WARNING] Unmarshal error on mail: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Super basic validation. Doesn't really matter. + if parsedWorkflow.ID != workflow.ID || len(parsedWorkflow.Actions) != len(workflow.Actions) { + log.Printf("[WARNING] Bad ID during publish: %s vs %s", workflow.ID, parsedWorkflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if !workflow.IsValid || !workflow.PreviouslySaved { + log.Printf("[INFO] Failed uploading new workflow because it's invalid or not saved") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Invalid workflows are not sharable"}`)) + return + } + + workflowData, err := json.Marshal(parsedWorkflow) + if err != nil { + log.Printf("[WARNING] Failed marshalling workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Sanitization is done in the frontend as well + parsedWorkflow = shuffle.SanitizeWorkflow(parsedWorkflow) + parsedWorkflow.ID = uuid.NewV4().String() + action := shuffle.CloudSyncJob{ + Type: "workflow", + Action: "publish", + OrgId: user.ActiveOrg.Id, + PrimaryItemId: workflow.ID, + SecondaryItem: string(workflowData), + FifthItem: user.Id, + } + + err = executeCloudAction(action, user.ActiveOrg.SyncConfig.Apikey) + if err != nil { + log.Printf("[WARNING] Failed cloud PUBLISH: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + log.Printf("[INFO] Successfully published workflow %s (%s) TO CLOUD", workflow.Name, workflow.ID) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + func initHandlers() { var err error ctx := context.Background() @@ -6041,6 +6175,7 @@ func initHandlers() { r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS") // NEW for 0.8.0 + r.HandleFunc("/api/v1/workflows/{key}/publish", makeWorkflowPublic).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 3ff9bdf2..86f27be7 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -981,7 +981,7 @@ func validateNewWorkerExecution(body []byte) error { execution.Status = "FINISHED" } - log.Printf("BASEEXECUTION LENGTH: %d", len(baseExecution.Workflow.Actions)+extra) + log.Printf("[INFO] BASEEXECUTION LENGTH: %d", len(baseExecution.Workflow.Actions)+extra) } // FIXME: Add extra here @@ -4782,6 +4782,7 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin workflowapp.Sharing = true workflowapp.Downloaded = true workflowapp.Hash = md5 + workflowapp.Public = true err = shuffle.SetWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID) if err != nil { diff --git a/docker-compose.yml b/docker-compose.yml index b96b2441..45656376 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,8 @@ version: '3' services: frontend: - #build: ./frontend - image: ghcr.io/frikky/shuffle-frontend:0.8.64 + build: ./frontend + image: ghcr.io/frikky/shuffle-frontend:0.8.70 container_name: shuffle-frontend hostname: shuffle-frontend ports: @@ -16,8 +16,8 @@ services: depends_on: - backend backend: - #build: ./backend - image: ghcr.io/frikky/shuffle-backend:0.8.64 + build: ./backend + image: ghcr.io/frikky/shuffle-backend:0.8.70 container_name: shuffle-backend hostname: ${BACKEND_HOSTNAME} # Here for debugging: @@ -47,7 +47,7 @@ services: - database orborus: #build: ./functions/onprem/orborus - image: ghcr.io/frikky/shuffle-orborus:0.8.63 + image: ghcr.io/frikky/shuffle-orborus:0.8.70 container_name: shuffle-orborus hostname: shuffle-orborus networks: @@ -56,7 +56,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock environment: - SHUFFLE_APP_SDK_VERSION=0.8.60 - - SHUFFLE_WORKER_VERSION=0.8.63 + - SHUFFLE_WORKER_VERSION=0.8.70 - ORG_ID=${ORG_ID} - ENVIRONMENT_NAME=${ENVIRONMENT_NAME} - BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT} diff --git a/frontend/Dockerfile b/frontend/Dockerfile index cd11e347..4204651c 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -21,7 +21,7 @@ COPY ./*.json /usr/src/app/ RUN yarn build # Production environment -FROM nginx:latest +FROM nginx:1.19 RUN mkdir -p /usr/share/nginx/html/build RUN mkdir -p /usr/share/nginx/html/css diff --git a/frontend/src/defaultCytoscapeStyle.js b/frontend/src/defaultCytoscapeStyle.js index ff9990a9..f2538a94 100644 --- a/frontend/src/defaultCytoscapeStyle.js +++ b/frontend/src/defaultCytoscapeStyle.js @@ -39,6 +39,23 @@ const data = [{ 'border-color': '#81c784', 'background-width': '100%', 'background-height': '100%', + 'border-radius': '5px', + }, + }, + { + selector: `node[app_name="Shuffle Tools"]`, + css: { + 'width': '30px', + 'height': '30px', + 'font-size': '0px', + }, + }, + { + selector: `node[app_name="Testing"]`, + css: { + 'width': '30px', + 'height': '30px', + 'font-size': '0px', }, }, { @@ -112,7 +129,7 @@ const data = [{ }, }, { - selector: 'node:selected', + selector: ':selected', css: { 'background-color': '#77b0d0', 'border-color': '#77b0d0', @@ -187,6 +204,8 @@ const data = [{ 'border-width': '12px', 'transition-property': 'border-width', 'transition-duration': '0.25s', + 'font-size': '30px', + 'label': 'data(label)', }, }, { diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index dbe016f6..8082415d 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -92,7 +92,6 @@ const AngularWorkflow = (props) => { const theme = useTheme(); const [bodyWidth, bodyHeight] = useWindowSize(); - const appBarSize = 75 var to_be_copied = "" const [cystyle, ] = useState(cytoscapestyle) @@ -190,18 +189,22 @@ const AngularWorkflow = (props) => { const [workflowExecutions, setWorkflowExecutions] = React.useState([]); const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0) + // This should all be set once, not on every iteration + // Use states and don't update lol const cloudSyncEnabled = props.userdata !== undefined && props.userdata.active_org !== null && props.userdata.active_org !== undefined ? props.userdata.active_org.cloud_sync === true : false //const triggerEnvironments = cloudSyncEnabled ? ["cloud", "onprem"] : environments const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" + const appBarSize = isCloud ? 75 : 60 const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"] - const unloadText = 'Are you sure you want to leave without saving (CTRL+S)?' + useBeforeunload(() => { if (!lastSaved) { return unloadText } }) + const [elements, setElements] = useState([]) // No point going as fast, as the nodes aren't realtime anymore, but bulk updated. // Set it from 2500 to 6000 to reduce overall load @@ -1121,7 +1124,7 @@ const AngularWorkflow = (props) => { } if (responseJson.public) { - alert.info("This workflow is public. You'll have to save it to make it your own!") + alert.info("This workflow is public. You will have to save it to make it your own!") setLastSaved(false) } @@ -1215,9 +1218,36 @@ const AngularWorkflow = (props) => { setSelectedTrigger({}) } + // Comparing locations between nodes and setting views + const onNodeDrag = (event, newAppAuth) => { + //console.log("DRAGGING: ", event.target) + //console.log("LEN2: ", event.target.edges.length) + + + /* + event.target.animate({ + style: { + "border-width": "12px", + "border-opacity": ".7", + } + }, { + duration: animationDuration, + }) + event.target.animate({ + style: { + "border-width": "12px", + "border-opacity": ".7", + } + }, { + duration: animationDuration, + }) + */ + } + // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once const onNodeSelect = (event, newAppAuth) => { + const data = event.target.data() setLastSaved(false) @@ -1619,6 +1649,17 @@ const AngularWorkflow = (props) => { }); } + + if (!firstrequest && graphSetup && established && props.match.params.key !== workflow.id && workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0) { + //console.log(props.match.params.key, workflow.id) + //getWorkflow() + //setCy() + //getWorkflowExecution(props.match.params.key, "") + //setEstablished(false) + //setGraphSetup(false) + window.location.pathname = "/workflows/"+props.match.params.key + } + useEffect(() => { if (firstrequest) { setFirstrequest(false) @@ -1642,14 +1683,14 @@ const AngularWorkflow = (props) => { } // App length necessary cus of cy initialization - if (elements.length === 0 && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) { + if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) { setGraphSetup(true) setupGraph() } else if (!established && cy !== undefined && apps !== null && apps !== undefined && apps.length > 0 && Object.getOwnPropertyNames(workflow).length > 0 && authLoaded){ //This part has to load LAST, as it's kind of not async. //This means we need everything else to happen first. - console.log("AUTH IN HERE: ", appAuthentication) - + // + //console.log("IN THIS PART AGAIN") setEstablished(true) cy.edgehandles({ @@ -1677,6 +1718,9 @@ const AngularWorkflow = (props) => { cy.on('mouseover', 'node', (e) => onNodeHover(e)) cy.on('mouseout', 'node', (e) => onNodeHoverOut(e)) + // Handles dragging + //cy.on('drag', 'node', (e) => onNodeDrag(e)) + //cy.on('mouseover', 'node', () => $(targetElement).addClass('mouseover')); //cy.on('cxttapstart', 'node', (e) => edgeHandler.start(e.target)) @@ -1711,7 +1755,7 @@ const AngularWorkflow = (props) => { const onNodeHover = (event) => { event.target.animate({ style: { - "border-width": "5px", + "border-width": "7px", "border-opacity": ".7", } }, { @@ -1727,17 +1771,23 @@ const AngularWorkflow = (props) => { // This is here to have a proper transition for lines const onEdgeHover = (event) => { + if (event === null || event === undefined) { + return + } + 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, - }) + if (sourcecolor !== null && sourcecolor !== undefined && targetcolor !== null && targetcolor !== undefined) { + 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, + }) + } } @@ -1799,6 +1849,31 @@ const AngularWorkflow = (props) => { conditions: conditions, hasErrors: branch.has_errors }; + + // This is an attempt at prettier edges. The numbers are weird to work with. + /* + //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html + const sourcenode = actions.find(node => node.data._id === branch.source_id) + const destinationnode = actions.find(node => node.data._id === branch.destination_id) + if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) { + //node.data._id = action["id"] + console.log("SOURCE: ", sourcenode.position) + console.log("DESTINATIONNODE: ", destinationnode.position) + + var opposite = true + if (sourcenode.position.x > destinationnode.position.x) { + opposite = false + } else { + opposite = true + } + + edge.style = { + 'control-point-distance': opposite ? ["25%", "-75%"] : ["-10%", "90%"], + 'control-point-weight': ['0.3', '0.7'], + } + } + */ + return edge; }) @@ -4506,7 +4581,7 @@ const AngularWorkflow = (props) => { : null } onClick={() => { - console.log("CHANGE FIELD") + //console.log("CHANGE FIELD") }} onBlur={(e) => { changeActionVariable(data.action_field, e.target.value) @@ -4803,6 +4878,7 @@ const AngularWorkflow = (props) => { onClick={() => { setSelectedEdge({}) + var data = { condition: conditionValue, source: sourceValue, @@ -4821,6 +4897,18 @@ const AngularWorkflow = (props) => { } } + var label = "" + if (selectedEdge.conditions.length === 1) { + label = selectedEdge.conditions.length+" condition" + } else if (selectedEdge.conditions.length > 1) { + label = selectedEdge.conditions.length+" conditions" + } + + var currentedge = cy.getElementById(selectedEdge.id) + if (currentedge !== undefined && currentedge !== null) { + currentedge.data().label = label + } + setSelectedEdge(selectedEdge) workflow.branches[selectedEdgeIndex] = selectedEdge setWorkflow(workflow) @@ -6494,7 +6582,7 @@ const AngularWorkflow = (props) => { {/* */} - + {workflow.configuration !== null && workflow.configuration !== undefined && workflow.configuration.exit_on_error !== undefined ? : null} ) @@ -7211,6 +7299,7 @@ const AngularWorkflow = (props) => { stylesheet={cystyle} boxSelectionEnabled={true} autounselectify={false} + showGrid={true} cy={(incy) => { // FIXME: There's something specific loading when // you do the first hover of a node. Why is this different? diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 483949be..38f40744 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -1565,9 +1565,9 @@ const AppCreator = (props) => { } // Url verification - if (currentAction.url.length === 0) { - errormessage.push("URL path can't be empty.") - } else if (!currentAction.url.startsWith("/") && baseUrl.length > 0) { + //if (currentAction.url.length === 0) { + // errormessage.push("URL path can't be empty.") + if (!currentAction.url.startsWith("/") && baseUrl.length > 0 && currentAction.url.length > 0) { errormessage.push("URL must start with /") } diff --git a/frontend/src/views/MyView.jsx b/frontend/src/views/MyView.jsx index 6ca5d0b0..b456c6ed 100644 --- a/frontend/src/views/MyView.jsx +++ b/frontend/src/views/MyView.jsx @@ -68,11 +68,9 @@ const flexContainerStyle = { } const flexBoxStyle = { - width: "333px", - height: "125px", - margin: "10px", - borderRadius: "4px", - boxShadow: "0px 1px 2px rgba(0, 0, 0, 0.16), 0px 2px 4px rgba(0, 0, 0, 0.12), 0px 1px 8px rgba(0, 0, 0, 0.1)", + width: 333, + height: 125, + borderRadius: 4, boxSizing: "border-box", letterSpacing: "0.4px", color: "#D6791E", @@ -299,37 +297,37 @@ const MyView = (props) => { } const paperAppContainer = { - cursor: "pointer", display: "flex", flexWrap: 'wrap', alignContent: "space-between", } + + const paperAppStyle = { - minHeight: "148px", - width: "333px", - margin: "10px", + minHeight: 130, + width: "100%", color: "white", backgroundColor: surfaceColor, + padding: "12px 12px 0px 15px", borderRadius: 5, - padding: "10px", - cursor: "pointer", display: "flex", boxSizing: "border-box", + position: "relative", } const gridContainer = { - cursor: "pointer", height: "auto", color: "white", margin: "10px", backgroundColor: surfaceColor, } - const workFlowActionStyle = { + const workflowActionStyle = { flex: "1", display: "flex", - width: "150px", + width: 150, + height: 44, justifyContent: "space-between", overflow: "hidden" } @@ -535,23 +533,28 @@ const MyView = (props) => { }); } - const getWorkFlowMeta = (data) => { + const getWorkflowMeta = (data) => { + let triggers = 0 let schedules = 0 let webhooks = 0 - let webhookImg = "" - let scheduleImg = "" + let subflows = 0 if (data.triggers !== undefined && data.triggers !== null && data.triggers.length > 0) { + triggers = data.triggers.length for (let key in data.triggers) { + if (data.triggers[key].app_name === "Webhook") { webhooks += 1 - webhookImg = data.triggers[key].large_image + //webhookImg = data.triggers[key].large_image } else if (data.triggers[key].app_name === "Schedule") { schedules += 1 - scheduleImg = data.triggers[key].large_image + //scheduleImg = data.triggers[key].large_image + } else if (data.triggers[key].app_name === "Subflow") { + subflows += 1 } } } - return [schedules, webhooks, webhookImg, scheduleImg]; + + return [triggers, schedules, webhooks, subflows] } // dropdown with copy etc I guess @@ -565,9 +568,9 @@ const MyView = (props) => { boxWidth = "4px" } - var boxColor = "orange" + var boxColor = "#FECC00" if (data.is_valid) { - boxColor = "green" + boxColor = "#86c142" } const menuClick = (event) => { @@ -575,45 +578,57 @@ const MyView = (props) => { setAnchorEl(event.currentTarget); } - const actions = data.actions !== null ? data.actions.length : 0 - - const [schedules, webhooks] = getWorkFlowMeta(data); + var parsedName = data.name + console.log("LEN: ", parsedName.length) + if (parsedName !== undefined && parsedName !== null && parsedName.length > 25) { + parsedName = parsedName.slice(0,25)+".." + } + + + const actions = data.actions !== null ? data.actions.length : 0 + const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data) - const imgSize = 25 return ( - { - }}> -
{ - if (selectedWorkflow.id !== data.id) { - setSelectedWorkflow(data) - //getWorkflowExecution(data.id) - } - }}/> - + + +
- -
{ - if (selectedWorkflow.id !== data.id) { - setSelectedWorkflow(data) - //getWorkflowExecution(data.id) - } - }}> - - {data.name} - -
+ + + {parsedName} + - - - + + + + + + {actions} + + - - executeWorkflow(data.id)} /> + + + + + {triggers} + + + + + + + + + {subflows} + + + + {/* - {webhooks > 0 ? @@ -623,8 +638,9 @@ const MyView = (props) => { : null} + */} - + {data.tags !== undefined ? data.tags.map((tag, index) => { if (index >= 3) { @@ -634,7 +650,7 @@ const MyView = (props) => { return ( { : null} - {data.actions !== undefined && data.actions !== null ? - + - - - - { - setOpen(false) - setAnchorEl(null) - }} - > + + + + { + setOpen(false) + setAnchorEl(null) + }} + > { setModalOpen(true) setEditingWorkflow(data) @@ -685,26 +700,25 @@ const MyView = (props) => { exportWorkflow(data) setOpen(false) }} key={"export"}>{"Export"} - { + { setDeleteModalOpen(true) setSelectedWorkflowId(data.id) setOpen(false) }} key={"delete"}>{"Delete"} - - - - - - - - - + + + + + + + + + - : null} - + : null} + + ) } @@ -772,7 +786,6 @@ borderRadius: "4px", color: "black", height: "30px", "width": "30px", fontSize: marginTop: "5px", color: "white", backgroundColor: surfaceColor, - cursor: "pointer", display: "flex", } @@ -1294,13 +1307,12 @@ borderRadius: "4px", color: "black", height: "30px", "width": "30px", fontSize: let workflowData = ""; if (workflows.length > 0) { const columns = [ - { field: 'id', headerName: 'ID', width: 70, sortable: false, }, { field: 'title', headerName: 'Title', width: 330, }, { field: 'actions', headerName: 'Actions', width: 200, sortable: false, disableClickEventBubbling: true, renderCell: (params) => { const data = params.row.record; - let [schedules, webhooks] = getWorkFlowMeta(data); + let [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data); return @@ -1340,10 +1352,11 @@ borderRadius: "4px", color: "black", height: "30px", "width": "30px", fontSize: if (index >= 3) { return null } + return ( {view === "grid" && ( -
+ {workflows.map((data, index) => { return ( ) })} -
+
)} {view === "list" && ( diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index a955924a..9e477c29 100644 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -447,12 +447,12 @@ const Workflows = (props) => { for (var branchkey in data.branches) { const branch = data.branches[branchkey] if (branch.source_id === data.actions[key].id) { - console.log("CHANGING SOURCE ID IN ACTION") + //console.log("CHANGING SOURCE ID IN ACTION") branch.source_id = newId } if (branch.destination_id === data.actions[key].id) { - console.log("CHANGING DESTINATION ID IN ACTION") + //console.log("CHANGING DESTINATION ID IN ACTION") branch.destination_id = newId } } @@ -505,10 +505,8 @@ const Workflows = (props) => { data = sanitizeWorkflow(data) alert.info("Sanitizing and publishing "+data.name) - const url = isCloud ? globalUrl : "https://shuffler.io" - // This ALWAYS talks to Shuffle cloud - fetch(url+"/api/v1/workflows/"+data.id+"/publish", { + fetch(globalUrl+"/api/v1/workflows/"+data.id+"/publish", { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -521,7 +519,11 @@ const Workflows = (props) => { if (response.status !== 200) { console.log("Status not 200 for workflow publish :O!") } else { - alert.success("Successfully published workflow") + if (isCloud) { + alert.success("Successfully published workflow") + } else { + alert.success("Successfully published workflow to https://shuffler.io") + } } return response.json() @@ -690,12 +692,10 @@ const Workflows = (props) => { setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))) } }} key={"change"}>{"Change details"} - {isCloud ? - { - console.log("Should publish", data) - publishWorkflow(data) - }} key={"publish"}>{"Publish Workflow"} - : null} + { + console.log("Should publish", data) + publishWorkflow(data) + }} key={"publish"}>{"Publish Workflow"} { copyWorkflow(data) setOpen(false) diff --git a/functions/onprem/orborus/build.sh b/functions/onprem/orborus/build.sh index 2ea4d555..48364d69 100644 --- a/functions/onprem/orborus/build.sh +++ b/functions/onprem/orborus/build.sh @@ -1,5 +1,5 @@ NAME=shuffle-orborus -VERSION=0.8.64 +VERSION=0.8.70 echo "Running docker build with $NAME:$VERSION" #docker rmi frikky/shuffle:$NAME --force diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index ad508e8a..db337158 100644 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -248,7 +248,7 @@ func initializeImages() { log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion) } if workerVersion == "" { - workerVersion = "0.8.64" + workerVersion = "0.8.70" log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion) } diff --git a/functions/onprem/worker/Dockerfile b/functions/onprem/worker/Dockerfile index 62aa2d0c..826ef4d3 100644 --- a/functions/onprem/worker/Dockerfile +++ b/functions/onprem/worker/Dockerfile @@ -11,7 +11,8 @@ RUN go get github.com/docker/docker/api/types && \ go get github.com/docker/docker/client && \ go get github.com/gorilla/mux && \ go get github.com/patrickmn/go-cache && \ - go get github.com/frikky/shuffle-shared + go get github.com/frikky/shuffle-shared && \ + go get github.com/satori/go.uuid RUN go build RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker . @@ -21,7 +22,7 @@ FROM alpine:3.12 ENV SHUFFLE_BASE_IMAGE_REGISTRY=docker.io ENV SHUFFLE_BASE_IMAGE_NAME=frikky/shuffle -ENV SHUFFLE_BASE_IMAGE_TAG_SUFFIX=0.8.5 +ENV SHUFFLE_BASE_IMAGE_TAG_SUFFIX=0.8.70 RUN apk add --no-cache bash COPY --from=builder /app/ / diff --git a/functions/onprem/worker/build.sh b/functions/onprem/worker/build.sh index 424b6207..ce343930 100644 --- a/functions/onprem/worker/build.sh +++ b/functions/onprem/worker/build.sh @@ -1,5 +1,5 @@ NAME=shuffle-worker -VERSION=0.8.64 +VERSION=0.8.70 echo "Running docker build with $NAME:$VERSION" #CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin . diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index e4de1908..cc981636 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -24,6 +24,7 @@ import ( //"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/mount" dockerclient "github.com/docker/docker/client" + "github.com/satori/go.uuid" "github.com/gorilla/mux" "github.com/patrickmn/go-cache" @@ -278,7 +279,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] // Waiting to see if it exits.. Stupid, but stable(r) if workflowExecution.ExecutionSource != "default" { - log.Printf("Handling NON-default execution source %s - NOT waiting and validating!", workflowExecution.ExecutionSource) + log.Printf("[INFO] Handling NON-default execution source %s - NOT waiting and validating!", workflowExecution.ExecutionSource) } else if workflowExecution.ExecutionSource == "default" { time.Sleep(2 * time.Second) @@ -850,12 +851,13 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { appname = strings.Replace(appname, ".", "-", -1) appversion = strings.Replace(appversion, ".", "-", -1) - image := fmt.Sprintf("%s:%s_%s", baseimagename, action.AppName, action.AppVersion) + image := fmt.Sprintf("%s:%s_%s", baseimagename, strings.ToLower(action.AppName), action.AppVersion) if strings.Contains(image, " ") { image = strings.ReplaceAll(image, " ", "-") } - identifier := fmt.Sprintf("%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId) + // Added UUID to identifier just in case + identifier := fmt.Sprintf("%s_%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId, uuid.NewV4()) if strings.Contains(identifier, " ") { identifier = strings.ReplaceAll(identifier, " ", "-") } @@ -938,15 +940,15 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { } // Uses a few ways of getting / checking if an app is available - // 1. Try original - // 2. Go to lowercase + // 1. Try original with lowercase + // 2. Go to original // 3. Add remote repo location // 4. Actually download last repo images := []string{ - fmt.Sprintf("%s/%s:%s_%s", registryName, baseimagename, strings.ToLower(action.AppName), action.AppVersion), image, - fmt.Sprintf("%s:%s_%s", baseimagename, strings.ToLower(action.AppName), action.AppVersion), + fmt.Sprintf("%s:%s_%s", baseimagename, action.AppName, action.AppVersion), + fmt.Sprintf("%s/%s:%s_%s", registryName, baseimagename, strings.ToLower(action.AppName), action.AppVersion), } // If cleanup is set, it should run for efficiency @@ -959,6 +961,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { } log.Printf("[WARNING] Failed CLEANUP execution. Downloading image remotely.") + image = images[2] reader, err := dockercli.ImagePull(context.Background(), image, pullOptions) if err != nil { log.Printf("[ERROR] Failed getting %s. The couldn't be find locally, AND is missing.", image) @@ -996,7 +999,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { } } else { - err = deployApp(dockercli, image, identifier, env, workflowExecution) + err = deployApp(dockercli, images[0], identifier, env, workflowExecution) if err != nil { if strings.Contains(err.Error(), "exited prematurely") { shutdown(workflowExecution, action.ID, err.Error(), true) @@ -1004,7 +1007,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { // Trying to replace with lowercase to deploy again. This seems to work with Dockerhub well. // FIXME: Should try to remotely download directly if this persists. - image = fmt.Sprintf("%s:%s_%s", baseimagename, strings.ToLower(action.AppName), action.AppVersion) + image = images[1] if strings.Contains(image, " ") { image = strings.ReplaceAll(image, " ", "-") } @@ -1015,7 +1018,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { shutdown(workflowExecution, action.ID, err.Error(), true) } - image = fmt.Sprintf("%s/%s:%s_%s", registryName, baseimagename, strings.ToLower(action.AppName), action.AppVersion) + image = images[2] if strings.Contains(image, " ") { image = strings.ReplaceAll(image, " ", "-") } @@ -1026,7 +1029,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { shutdown(workflowExecution, action.ID, err.Error(), true) } - log.Printf("[WARNING] Failed deploying image THRICE. Attempting to download the latter as last resort.") + log.Printf("[WARNING] Failed deploying image THREE TIMES. Attempting to download the latter as last resort.") reader, err := dockercli.ImagePull(context.Background(), image, pullOptions) if err != nil { log.Printf("[ERROR] Failed getting %s. The couldn't be find locally, AND is missing.", image)