diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go index 00202dbd..8cb07ff6 100644 --- a/backend/go-app/docker.go +++ b/backend/go-app/docker.go @@ -385,170 +385,6 @@ func buildImage(tags []string, dockerfileFolder string) error { return nil } -// FIXME - very specific for webhooks. Make it easier? -func stopWebhook(image string, identifier string) error { - ctx := context.Background() - - containername := fmt.Sprintf("%s-%s", image, identifier) - - cli, err := client.NewEnvClient() - if err != nil { - log.Println("Unable to create docker client") - return err - } - - // containers, err := cli.ContainerList(ctx, types.ContainerListOptions{ - // All: true, - // }) - - if err := cli.ContainerStop(ctx, containername, nil); err != nil { - log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err) - } - - removeOptions := types.ContainerRemoveOptions{ - RemoveVolumes: true, - Force: true, - } - - if err := cli.ContainerRemove(ctx, containername, removeOptions); err != nil { - log.Printf("Unable to remove container: %s", err) - } - - return nil -} - -// Starts a new webhook -func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) { - cors := shuffle.HandleCors(resp, request) - if cors { - 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] - } - - if len(fileId) != 32 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) - return - } - - ctx := context.Background() - hook, err := shuffle.GetHook(ctx, fileId) - if err != nil { - log.Printf("Failed getting hook %s (stop docker): %s", fileId, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - log.Printf("Status: %s", hook.Status) - log.Printf("Running: %t", hook.Running) - if !hook.Running { - message := fmt.Sprintf("Error: %s isn't running", hook.Id) - log.Println(message) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message))) - return - } - - hook.Status = "stopped" - hook.Running = false - hook.Actions = []shuffle.HookAction{} - err = shuffle.SetHook(ctx, *hook) - if err != nil { - log.Printf("Failed setting hook: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - image := "webhook" - - // This is here to force stop and remove the old webhook - err = stopWebhook(image, fileId) - if err != nil { - log.Printf("Container stop issue for %s-%s: %s", image, fileId, err) - } - - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true, "message": "Stopped webhook"}`)) -} - -// THis is an example -// Can also be used as base data? -var webhook = `{ - "id": "d6ef8912e8bd37776e654cbc14c2629c", - "info": { - "url": "http://localhost:5001", - "name": "TheHive", - "description": "Webhook for TheHive" - }, - "transforms": {}, - "actions": {}, - "type": "webhook", - "running": false, - "status": "stopped" -}` - -// Starts a new webhook -func handleDeleteHookDocker(resp http.ResponseWriter, request *http.Request) { - ctx := context.Background() - cors := shuffle.HandleCors(resp, request) - if cors { - 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] - } - - if len(fileId) != 32 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) - return - } - - err := shuffle.DeleteKey(ctx, "hooks", fileId) - if err != nil { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "message": "Can't delete"}`)) - return - } - - image := "webhook" - - // This is here to force stop and remove the old webhook - err = stopWebhook(image, fileId) - if err != nil { - log.Printf("Container stop issue for %s-%s: %s", image, fileId, err) - resp.Write([]byte(`{"success": false, "message": "Couldn't stop webhook"}`)) - return - } - - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true, "message": "Deleted webhook"}`)) -} - // Checks if an image exists func imageCheckBuilder(images []string) error { //log.Printf("[FIXME] ImageNames to check: %#v", images) @@ -594,31 +430,6 @@ func imageCheckBuilder(images []string) error { return nil } -func hookTest() { - var hook shuffle.Hook - err := json.Unmarshal([]byte(webhook), &hook) - log.Println(webhook) - if err != nil { - log.Printf("Failed hook unmarshaling: %s", err) - return - } - - ctx := context.Background() - err = shuffle.SetHook(ctx, hook) - if err != nil { - log.Printf("Failed setting hook: %s", err) - } - - returnHook, err := shuffle.GetHook(ctx, hook.Id) - if err != nil { - log.Printf("Failed getting hook %s (test): %s", hook.Id, err) - } - - if len(returnHook.Id) > 0 { - log.Printf("Success! - %s", returnHook.Id) - } -} - // https://stackoverflow.com/questions/23935141/how-to-copy-docker-images-from-one-host-to-another-without-using-a-repository func getDockerImage(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index eb328296..7a0e631e 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -1,100 +1,105 @@ -module main +module shuffle-shared + +replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared go 1.19 -replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared - require ( - cloud.google.com/go/datastore v1.10.0 - cloud.google.com/go/pubsub v1.28.0 - cloud.google.com/go/storage v1.28.1 + cloud.google.com/go/datastore v1.11.0 + cloud.google.com/go/pubsub v1.31.0 + cloud.google.com/go/storage v1.30.1 github.com/basgys/goxml2json v1.1.0 github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 - github.com/docker/docker v20.10.21+incompatible + github.com/docker/docker v24.0.2+incompatible github.com/frikky/kin-openapi v0.42.0 - github.com/fsouza/go-dockerclient v1.9.0 + github.com/fsouza/go-dockerclient v1.9.7 github.com/ghodss/yaml v1.0.0 - github.com/go-git/go-billy/v5 v5.3.1 - github.com/go-git/go-git/v5 v5.5.0 + github.com/go-git/go-billy/v5 v5.4.1 + github.com/go-git/go-git/v5 v5.7.0 github.com/gorilla/mux v1.8.0 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 github.com/shuffle/shuffle-shared v0.4.19 - golang.org/x/crypto v0.3.0 - google.golang.org/api v0.103.0 + golang.org/x/crypto v0.9.0 + google.golang.org/api v0.125.0 google.golang.org/appengine v1.6.7 - google.golang.org/grpc v1.51.0 + google.golang.org/grpc v1.55.0 gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.1 ) require ( - cloud.google.com/go v0.105.0 // indirect - cloud.google.com/go/compute v1.13.0 // indirect - cloud.google.com/go/compute/metadata v0.2.1 // indirect - cloud.google.com/go/iam v0.7.0 // indirect + cloud.google.com/go v0.110.2 // indirect + cloud.google.com/go/compute v1.19.3 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect - github.com/Microsoft/hcsshim v0.9.3 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect - github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect + github.com/acomagu/bufpipe v1.0.4 // indirect github.com/adrg/strutil v0.2.3 // indirect github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect + github.com/bitly/go-simplejson v0.5.0 // indirect github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect - github.com/cloudflare/circl v1.1.0 // indirect - github.com/containerd/cgroups v1.0.3 // indirect - github.com/containerd/containerd v1.6.6 // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/containerd/containerd v1.6.18 // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/frikky/go-elasticsearch/v8 v8.13.1 // indirect - github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/swag v0.19.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-github/v28 v28.1.1 // indirect github.com/google/go-querystring v1.0.0 // indirect + github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect + github.com/googleapis/gax-go/v2 v2.10.0 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/mailru/easyjson v0.7.0 // indirect - github.com/moby/sys/mount v0.3.3 // indirect - github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/klauspost/compress v1.11.13 // indirect + github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect + github.com/moby/patternmatcher v0.5.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect - github.com/opencontainers/runc v1.1.2 // indirect + github.com/opencontainers/runc v1.1.5 // indirect + github.com/opensearch-project/opensearch-go v1.1.0 // indirect + github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pjbgf/sha1cd v0.2.0 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect - github.com/skeema/knownhosts v1.1.0 // indirect + github.com/skeema/knownhosts v1.1.1 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/src-d/gcfg v1.4.0 // indirect - github.com/xanzy/ssh-agent v0.3.2 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect go.opencensus.io v0.24.0 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.2.0 // indirect - golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.2.0 // indirect - golang.org/x/text v0.4.0 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 71249839..3782d239 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -37,9 +37,6 @@ import ( "cloud.google.com/go/storage" "google.golang.org/appengine/mail" - //"github.com/elastic/go-elasticsearch/v7" - //"github.com/elastic/go-elasticsearch/v8/esapi" - "github.com/frikky/kin-openapi/openapi2" "github.com/frikky/kin-openapi/openapi2conv" "github.com/frikky/kin-openapi/openapi3" @@ -3908,8 +3905,8 @@ func runInitEs(ctx context.Context) { } if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") { - log.Printf("[INFO] Waiting 10 seconds during init to make sure the opensearch instance is up and running with security features properly") - time.Sleep(10 * time.Second) + log.Printf("[INFO] Waiting during init to make sure the opensearch instance is up and running with security features properly") + time.Sleep(30 * time.Second) } _ = setUsers @@ -6055,7 +6052,9 @@ func initHandlers() { r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS") // Docker orborus specific - downloads an image r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 2a6f8057..631c653d 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -165,7 +165,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque executionRequests, err := shuffle.GetWorkflowQueue(ctx, id, 100) if err != nil { log.Printf("[WARNING] (1) Failed reading body for workflowqueue: %s", err) - resp.WriteHeader(401) + resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Entity parsing error - confirm"}`))) return } @@ -179,8 +179,8 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque body, err := ioutil.ReadAll(request.Body) if err != nil { - log.Println("Failed reading body for stream result queue") - resp.WriteHeader(401) + log.Println("[WARNING] Failed reading body for stream result queue") + resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -190,16 +190,16 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque var removeExecutionRequests shuffle.ExecutionRequestWrapper err = json.Unmarshal(body, &removeExecutionRequests) if err != nil { - log.Printf("Failed executionrequest in queue unmarshaling: %s", err) - resp.WriteHeader(401) + log.Printf("[WARNING] Failed executionrequest in queue unmarshaling: %s", err) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } if len(removeExecutionRequests.Data) == 0 { - log.Printf("No requests to fix remove from DB") - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Some removal error"}`))) + log.Printf("[WARNING] No requests to fix remove from DB") + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Queue removal error"}`))) return } @@ -636,7 +636,6 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { } //log.Printf("Actionresult unmarshal: %s", string(body)) - log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body)) ctx := context.Background() err = shuffle.ValidateNewWorkerExecution(ctx, body) if err == nil { @@ -647,6 +646,8 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { log.Printf("[DEBUG] Handling other execution variant (subflow?): %s", err) } + log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body)) + var actionResult shuffle.ActionResult err = json.Unmarshal(body, &actionResult) if err != nil { @@ -698,60 +699,63 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { } } - if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" { - log.Printf("[INFO] SHOULD WAIT A BIT AND RUN CLOUD STUFF WITH USER INPUT! WAITING!") + /* + // Removed as UserInput is now handled as an app + if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" { + log.Printf("[INFO] SHOULD WAIT A BIT AND RUN USER INPUT! WAITING!") - var trigger shuffle.Trigger - err = json.Unmarshal([]byte(actionResult.Result), &trigger) - if err != nil { - log.Printf("[WARNING] Failed unmarshaling actionresult for user input: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } + var trigger shuffle.Trigger + err = json.Unmarshal([]byte(actionResult.Result), &trigger) + if err != nil { + log.Printf("[WARNING] Failed unmarshaling actionresult for user input: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } - orgId := workflowExecution.ExecutionOrg - if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 { - orgId = workflowExecution.Workflow.OrgId - } + orgId := workflowExecution.ExecutionOrg + if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 { + orgId = workflowExecution.Workflow.OrgId + } - err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId) - if err != nil { - log.Printf("[WARNING] Failed userinput handler: %s", err) + err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId) + if err != nil { + log.Printf("[WARNING] Failed userinput handler: %s", err) - actionResult.Result = fmt.Sprintf(`{"success": False, "reason": "%s"}`, err) + actionResult.Result = fmt.Sprintf(`{"success": false, "reason": "%s"}`, err) - workflowExecution.Results = append(workflowExecution.Results, actionResult) - workflowExecution.Status = "ABORTED" - err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) - if err != nil { - log.Printf("[WARNING] Failed to set execution during wait: %s", err) - } else { - log.Printf("[INFO] Successfully set the execution %s to waiting.", workflowExecution.ExecutionId) + workflowExecution.Results = append(workflowExecution.Results, actionResult) + workflowExecution.Status = "ABORTED" + err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) + if err != nil { + log.Printf("[WARNING] Failed to set execution during wait: %s", err) + } else { + log.Printf("[INFO] Successfully set the execution %s to waiting.", workflowExecution.ExecutionId) + } + + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err))) + return + } else { + log.Printf("[INFO] Successful userinput handler") + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`))) + + actionResult.Result = `{"success": True, "reason": "Waiting for user feedback based on configuration"}` + + workflowExecution.Results = append(workflowExecution.Results, actionResult) + workflowExecution.Status = actionResult.Status + err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) + if err != nil { + log.Printf("[WARNING] Failed setting userinput: %s", err) + } else { + log.Printf("[DEBUG] Successfully set the execution to waiting.") + } + } + + return } - - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err))) - return - } else { - log.Printf("[INFO] Successful userinput handler") - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`))) - - actionResult.Result = `{"success": True, "reason": "Waiting for user feedback based on configuration"}` - - workflowExecution.Results = append(workflowExecution.Results, actionResult) - workflowExecution.Status = actionResult.Status - err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) - if err != nil { - log.Printf("[WARNING] Failed setting userinput: %s", err) - } else { - log.Printf("[DEBUG] Successfully set the execution to waiting.") - } - } - - return - } + */ runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp) } @@ -1049,14 +1053,16 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request workflow = *tmpworkflow } - if len(workflow.ExecutingOrg.Id) == 0 { - if len(orgId) > 0 { - workflow.ExecutingOrg.Id = orgId - } else { - log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID) - return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined") + /* + if len(workflow.ExecutingOrg.Id) == 0 { + if len(orgId) > 0 { + workflow.ExecutingOrg.Id = orgId + } else { + log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID) + return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined") + } } - } + */ if len(workflow.Actions) == 0 { workflow.Actions = []shuffle.Action{} @@ -1100,8 +1106,13 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request workflowExecution, execInfo, _, err := shuffle.PrepareWorkflowExecution(ctx, workflow, request, 10) if err != nil { - log.Printf("[WARNING] Failed in prepareExecution for execution Id '%s': %s", workflowExecution.ExecutionId, err) - return workflowExecution, fmt.Sprintf("Failed preparration: %s", err), err + if strings.Contains(fmt.Sprintf("%s", err), "User Input") { + // Special for user input callbacks + return workflowExecution, fmt.Sprintf("%s", err), nil + } else { + log.Printf("[WARNING] Failed in prepareExecution: %s", err) + return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err + } } err = imageCheckBuilder(execInfo.ImageNames) @@ -1293,7 +1304,8 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { return } - //memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) + log.Printf("[INFO] Inside execute workflow for ID %s", fileId) + ctx := context.Background() workflow, err := shuffle.GetWorkflow(ctx, fileId) if err != nil && workflow.ID == "" { @@ -1310,6 +1322,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { // 1. Parent workflow contains this workflow ID in the source trigger? // 2. Parent workflow's owner is same org? // 3. Parent execution auth is correct + log.Printf("[INFO] Inside execute workflow access validation!") executionAuthValid, newOrgId = shuffle.RunExecuteAccessValidation(request, workflow) if !executionAuthValid { @@ -1344,6 +1357,12 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { workflow.ExecutingOrg = user.ActiveOrg workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request, user.ActiveOrg.Id) if err == nil { + if strings.Contains(executionResp, "User Input:") { + resp.WriteHeader(400) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp))) + return + } + resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization))) return @@ -2691,7 +2710,7 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId if len(triggerType) == 0 { log.Printf("[WARNING] No type specified for user input node") - return errors.New("No type specified for user input node") + //return errors.New("No type specified for user input node") } // FIXME: This is not the right time to send them, BUT it's well served for testing. Save -> send email / sms diff --git a/docker-compose.yml b/docker-compose.yml index 18c96895..a6732013 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,7 +62,7 @@ services: container_name: shuffle-opensearch environment: - bootstrap.memory_lock=true - - "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM + - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM - cluster.initial_master_nodes=shuffle-opensearch - cluster.routing.allocation.disk.threshold_enabled=false - cluster.name=shuffle-cluster diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 936a6dad..d4e116ba 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -503,6 +503,7 @@ const App = (message, props) => { path="/workflows" element={ { : null } - + Search by diff --git a/frontend/src/components/AppsearchPopout.jsx b/frontend/src/components/AppsearchPopout.jsx index 8a9b66bf..25ea719c 100644 --- a/frontend/src/components/AppsearchPopout.jsx +++ b/frontend/src/components/AppsearchPopout.jsx @@ -39,8 +39,9 @@ const AppSearchPopout = (props) => { return null } + // return ( - + {paperTitle !== undefined && paperTitle.length > 0 ? diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 51baafdb..b7b9f2b0 100644 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -79,6 +79,17 @@ const ConfigureWorkflow = (props) => { }, }); + // ONLY when component is being unloaded, run stop() function + // This is to prevent the interval from running when the component is not being used + + /* + useEffect(() => { + return () => { + stop() + } + }, []) + */ + // Where is this from? if (workflow === undefined || workflow === null) { return null; diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index ed342cde..0ff30f85 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -2729,10 +2729,7 @@ const ParsedAction = (props) => { // Look for the ID const found = false; for (let [key,keyval] in Object.entries(workflowExecutions)) { - if ( - workflowExecutions[key].results === undefined || - workflowExecutions[key].results === null - ) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { continue; } diff --git a/frontend/src/components/Priorities.jsx b/frontend/src/components/Priorities.jsx index 4cc2358e..88bea907 100644 --- a/frontend/src/components/Priorities.jsx +++ b/frontend/src/components/Priorities.jsx @@ -15,7 +15,7 @@ import Priority from "../components/Priority.jsx"; import { useAlert } from "react-alert"; const Priorities = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, checkLogin, } = props; + const { globalUrl, userdata, serverside, billingInfo, stripeKey, checkLogin, } = props; const [showDismissed, setShowDismissed] = React.useState(false); const [showRead, setShowRead] = React.useState(false); @@ -25,9 +25,9 @@ const Priorities = (props) => { return (
-

Priorities

+

Suggestions

- Priorities identified by Shuffle to help you discover ways to protect yourself.  + Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company. These range from simple configurations in Shuffle to Usecases you may have missed.  { />  Show dismissed {userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ? - No Priorities found + No Suggestions found : userdata.priorities.map((priority, index) => { @@ -67,7 +67,7 @@ const Priorities = (props) => {

Notifications

- Notifications help you find potential problems with your workflows and apps + Notifications help you find potential problems with your workflows and apps. 
{ @@ -60,18 +65,51 @@ const Priority = (props) => { }); } + return (
- - {priority.name} - - - {priority.description} - + + {priority.type === "usecase" || priority.type == "apps" ? : null} + + {priority.name} + + + {priority.type === "usecase" && priority.description.includes("&") ? + + {priority.name} + + {priority.description.split("&")[0]} + + + {priority.description.split("&").length > 3 ? + + + {priority.name+"2"} + + {priority.description.split("&")[2]} + + + : null} + + + : + + {priority.description} + + }
- {priority.active === true ? diff --git a/frontend/src/components/WelcomeForm2.jsx b/frontend/src/components/WelcomeForm2.jsx index d198ac5f..da66fb08 100644 --- a/frontend/src/components/WelcomeForm2.jsx +++ b/frontend/src/components/WelcomeForm2.jsx @@ -693,52 +693,8 @@ const WelcomeForm = (props) => { These are some of our Workflow templates, used to start new Workflows. Use the right and left buttons to find new Usecases, and click the orange button to build it. - {/**/} - {/* -
- {usecaseButtons.map((usecase, index) => { - return ( - { - console.log("Clicked: ", usecase.name) - if (defaultSearch === usecase.name) { - //setSelectedUsecaseCategory("") - } else { - handleSetSearch(usecase.name, usecase.usecase) - } - //addFilter(usecase.name.slice(3,usecase.name.length)) - }} - variant="outlined" - color="primary" - /> - ) - })} -
- */}
- {/* - - */} -
{ const [secret2FA, setSecret2FA] = React.useState(""); const [show2faSetup, setShow2faSetup] = useState(false); - const [adminTab, setAdminTab] = React.useState(1); + const [adminTab, setAdminTab] = React.useState(2); const [showApiKey, setShowApiKey] = useState(false); const [billingInfo, setBillingInfo] = React.useState({}); const [selectedStatus, setSelectedStatus] = React.useState([]); @@ -189,8 +189,6 @@ const Admin = (props) => { } }, [isDropzone]); - - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const get2faCode = (userId) => { @@ -299,6 +297,165 @@ const Admin = (props) => { ) } + // Basically just a simple way to get a generated email + // This also may help understand how to communicate with users + // both inside and outside Shuffle + // This could also be generated on the backend + const mailsendingButton = (org) => { + if (org === undefined || org === null) { + return "" + } + + if (users.length === 0) { + return "" + } + + // 1 mail based on users that have only apps + // Another based on those doing workflows + // Another based on those trying usecases(?) or templates + // + // Start based on edr, siem & ticketing + // Talk about enrichment? + // Check suggested usecases + // Check suggested workflows + var your_apps = "- Connecting " + + var subject_add = 0 + var subject = "Want to automate " + + if (org.security_framework !== undefined && org.security_framework !== null) { + if (org.security_framework.cases.name !== undefined && org.security_framework.cases.name !== null && org.security_framework.cases.name !== "") { + your_apps += org.security_framework.cases.name.replace("_", " ", -1) + ", " + + if (subject_add < 2) { + if (subject_add === 1) { + subject += " & " + } + + subject_add += 1 + subject += org.security_framework.cases.name.replace("_", " ", -1) + } + } + + if (org.security_framework.siem.name !== undefined && org.security_framework.siem.name !== null && org.security_framework.siem.name !== "") { + your_apps += org.security_framework.siem.name.replace("_", " ", -1) + ", " + + if (subject_add < 2) { + if (subject_add === 1) { + subject += " & " + } + + subject_add += 1 + subject += org.security_framework.siem.name.replace("_", " ", -1) + } + } + + if (org.security_framework.communication.name !== undefined && org.security_framework.communication.name !== null && org.security_framework.communication.name !== "") { + your_apps += org.security_framework.communication.name.replace("_", " ", -1) + ", " + + if (subject_add < 2) { + if (subject_add === 1) { + subject += " & " + } + + subject_add += 1 + subject += org.security_framework.communication.name.replace("_", " ", -1) + } + } + + if (org.security_framework.edr.name !== undefined && org.security_framework.edr.name !== null && org.security_framework.edr.name !== "") { + your_apps += org.security_framework.edr.name.replace("_", " ", -1) + ", " + + if (subject_add < 2) { + if (subject_add === 1) { + subject += " & " + } + + subject_add += 1 + subject += org.security_framework.edr.name.replace("_", " ", -1) + } + } + + if (org.security_framework.intel.name !== undefined && org.security_framework.intel.name !== null && org.security_framework.intel.name !== "") { + your_apps += org.security_framework.intel.name.replace("_", " ", -1) + ", " + + if (subject_add < 2) { + if (subject_add === 1) { + subject += " & " + } + + subject_add += 1 + subject += org.security_framework.intel.name.replace("_", " ", -1) + } + } + + + // Remove comma + subject += "?" + your_apps = your_apps.substring(0, your_apps.length - 2) + } + + + // Add usecases they may not have tried (from recommendations): org.priorities where item type is usecase + var usecases = "- Building usecases like " + const active_usecase = org.priorities.filter((item) => item.type === "usecase" && item.active === true) + if (active_usecase.length > 0) { + for (var i = 0; i < active_usecase.length; i++) { + if (active_usecase[i].name.includes("Suggested Usecase: ")) { + usecases += active_usecase[i].name.replace("Suggested Usecase: ", "", -1) + ", " + } else { + usecases += active_usecase[i].name + ", " + } + } + + usecases = usecases.substring(0, usecases.length - 2) + } + + if (your_apps.length <= 15) { + your_apps = "" + } + + if (usecases.length <= 30) { + usecases = "" + } + + var workflow_amount = "a few" + var admins = "" + + // Loop users + for (var i = 0; i < users.length; i++) { + if (users[i].role === "admin") { + admins += users[i].username + "," + } + } + + // Remove last comma + admins = admins.substring(0, admins.length - 1) + + if (your_apps.length > 5) { + your_apps += "%0D%0A" + } + + if (usecases.length > 5) { + usecases += "%0D%0A" + } + + // Get drift username from userdata.username before @ in email + const username = userdata.username.substring(0, userdata.username.indexOf("@")) + + var body = `Hey,%0D%0AI saw you trying to use Shuffle, and thought we may be able to help. Right now, it looks like you have ${workflow_amount} workflows made, but I'm not sure if you're getting the most out of Shuffle.%0D%0A%0D%0AIf you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A + +Some of the things we can help with:%0D%0A +${your_apps} +- Properly authenticating and custom building apps%0D%0A +${usecases} +- Creating special usecases%0D%0A%0D%0A + +Let me know if you're interested, or set up a call here: https://drift.me/${username}` + + return `mailto:${admins}?subject=${subject}&body=${body}` + } + const deleteAuthentication = (data) => { alert.info("Deleting auth " + data.label); @@ -367,6 +524,13 @@ const Admin = (props) => { }); }; + + if (userdata.support === true && selectedOrganization.id !== "" && selectedOrganization.id !== undefined && selectedOrganization.id !== null && selectedOrganization.id !== userdata.active_org.id) { + alert.info("Refreshing window to fix org support access") + window.location.reload() + return null + } + const handleVerify2FA = (userId, code) => { const data = { code: code, @@ -799,6 +963,10 @@ const Admin = (props) => { if (responseJson.lead_info !== undefined && responseJson.lead_info !== null) { var leads = [] + if (responseJson.lead_info.contacted) { + leads.push("contacted") + } + if (responseJson.lead_info.customer) { leads.push("customer") } @@ -1181,11 +1349,6 @@ const Admin = (props) => { var localData = ""; - // useEffect(() => { - // console.log('confirm', fileContent); - // }, [fileContent]) - - const getSchedules = () => { fetch(globalUrl + "/api/v1/workflows/schedules", { @@ -1322,6 +1485,10 @@ const Admin = (props) => { }); }; + useEffect(() => { + getUsers() + }, []); + const getSettings = () => { fetch(globalUrl + "/api/v1/getsettings", { method: "GET", @@ -2189,27 +2356,45 @@ const Admin = (props) => { */} {userdata.support === true ? - - Status - - + + + + + + Status + + + : null} { userdata={userdata} adminTab={adminTab} globalUrl={globalUrl} - handleGetOrg={handleGetOrg} - selectedOrganization={selectedOrganization} - selectedOrganization={selectedOrganization} - setSelectedOrganization={setSelectedOrganization} checkLogin={checkLogin} /> : adminTab === 3 ? diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index e0231ff1..ca7181be 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -11484,7 +11484,7 @@ const AngularWorkflow = (defaultprops) => { >

- {selectedTrigger.app_name}: {selectedTrigger.status} + {selectedTrigger.app_name}

{ onChange={selectedTriggerChange} /> -
+ {/*
Environment: { value={selectedTrigger.environment} />
+ */} { >

- All Executions + All Workflow Runs

{ style={{ color: "rgba(255,255,255,0.5)", cursor: "pointer" }} onClick={() => { }} > - See other Executions + See more runs @@ -14781,7 +14782,7 @@ const AngularWorkflow = (defaultprops) => { {curapp === null ? null : ( {selectedResult.action.app_name} {
- Authentication for {selectedApp.name} + Authentication for {selectedApp.name.replace("_", " ", -1)}
diff --git a/frontend/src/views/AngularWorkflows.jsx b/frontend/src/views/AngularWorkflows.jsx deleted file mode 100644 index 7b80d767..00000000 --- a/frontend/src/views/AngularWorkflows.jsx +++ /dev/null @@ -1,15429 +0,0 @@ -import React, { useState, useEffect, useLayoutEffect } from "react"; -import ReactDOM from "react-dom" - -import { useInterval } from "react-powerhooks"; -import { makeStyles, useTheme } from "@material-ui/core/styles"; - -import { v4 as uuidv4 } from "uuid"; -import { useNavigate, Link, useParams } from "react-router-dom"; -// import { Prompt } from "react-router"; // FIXME -import { useBeforeunload } from "react-beforeunload"; -import ReactJson from "react-json-view"; -import NestedMenuItem from "material-ui-nested-menu-item"; -import ReactMarkdown from "react-markdown"; -import { useAlert } from "react-alert"; -import theme from '../theme'; -import { isMobile } from "react-device-detect" -import aa from 'search-insights' -import Drift from "react-driftjs"; - -//import { Cron } from 'react-js-cron'; -//import 'react-js-cron/dist/styles.css' - - -import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; -import algoliasearch from 'algoliasearch/lite'; - -import { - Zoom, - Avatar, - Popover, - TextField, - Drawer, - Button, - Paper, - Grid, - Tabs, - InputAdornment, - Tab, - ButtonBase, - Tooltip, - Select, - MenuItem, - Divider, - Dialog, - DialogActions, - DialogTitle, - InputLabel, - DialogContent, - FormControl, - IconButton, - Menu, - Input, - Fade, - FormGroup, - FormControlLabel, - Typography, - Checkbox, - Breadcrumbs, - CircularProgress, - SwipeableDrawer, - Switch, - Chip, - Card, - List, - ListItem, - ListItemText, - ListItemAvatar, - Badge, -} from "@material-ui/core"; - -import { - AvatarGroup, -} from "@mui/material" - -import { - Folder as FolderIcon, - LibraryBooks as LibraryBooksIcon, - OpenInNew as OpenInNewIcon, - Undo as UndoIcon, - GetApp as GetAppIcon, - Search as SearchIcon, - Visibility as VisibilityIcon, - Done as DoneIcon, - Close as CloseIcon, - Error as ErrorIcon, - ArrowLeft as ArrowLeftIcon, - Cached as CachedIcon, - DirectionsRun as DirectionsRunIcon, - Polymer as PolymerIcon, - FormatListNumbered as FormatListNumberedIcon, - PlayArrow as PlayArrowIcon, - AspectRatio as AspectRatioIcon, - MoreVert as MoreVertIcon, - Apps as AppsIcon, - Schedule as ScheduleIcon, - FavoriteBorder as FavoriteBorderIcon, - Pause as PauseIcon, - Delete as DeleteIcon, - AddCircleOutline as AddCircleOutlineIcon, - Save as SaveIcon, - KeyboardArrowRight as KeyboardArrowRightIcon, - ArrowBack as ArrowBackIcon, - Settings as SettingsIcon, - LockOpen as LockOpenIcon, - ExpandMore as ExpandMoreIcon, - VpnKey as VpnKeyIcon, - AddComment as AddCommentIcon, - Edit as EditIcon, -} from "@material-ui/icons"; - -import { - Preview as PreviewIcon, - ContentCopy as ContentCopyIcon, -} from '@mui/icons-material'; - -import Autocomplete from "@material-ui/lab/Autocomplete"; - -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 cytoscapestyle from "../defaultCytoscapeStyle"; -import cxtmenu from "cytoscape-cxtmenu"; - -import { validateJson, GetIconInfo } from "./Workflows.jsx"; -import { GetParsedPaths } from "./Apps.jsx"; -import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; -import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; -import ParsedAction from "../components/ParsedAction.jsx"; -import PaperComponent from "../components/PaperComponent.jsx" -import ExtraApps from "../components/ExtraApps.jsx" -import EditWorkflow from "../components/EditWorkflow.jsx" -// import AppStats from "../components/AppStats.jsx"; - -const surfaceColor = "#27292D"; -const inputColor = "#383B40"; - -// http://apps.cytoscape.org/apps/yfileslayoutalgorithms -cytoscape.use(edgehandles); -cytoscape.use(clipboard); -cytoscape.use(undoRedo); -cytoscape.use(cxtmenu); - -// Adds specific text to items -//import popper from 'cytoscape-popper'; -//cytoscape.use(popper); - -// 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; -} - -export function sortByKey(array, key) { - if (array === undefined) { - return []; - } - - if (key.startsWith("-") && key.length > 2) { - key = key.slice(1, key.length); - return array - .sort(function (a, b) { - var x = a[key]; - var y = b[key]; - return x < y ? -1 : x > y ? 1 : 0; - }) - .reverse(); - } - - if (array === undefined || array === null) { - return []; - } - - return array.sort(function (a, b) { - var x = a[key]; - var y = b[key]; - return x < y ? -1 : x > y ? 1 : 0; - }); -} - -export function removeParam(key, sourceURL) { - if (sourceURL === undefined) { - return; - } - - var rtn = sourceURL.split("?")[0], - param, - params_arr = [], - queryString = sourceURL.indexOf("?") !== -1 ? sourceURL.split("?")[1] : ""; - - if (queryString !== "") { - params_arr = queryString.split("&"); - for (let i = params_arr.length - 1; i >= 0; i -= 1) { - param = params_arr[i].split("=")[0]; - if (param === key) { - params_arr.splice(i, 1); - } - } - rtn = rtn + "?" + params_arr.join("&"); - } - - if (rtn === "?") { - return ""; - } - - return rtn; -} - -const useStyles = makeStyles({ - notchedOutline: { - borderColor: "#f85a3e !important", - }, - root: { - "& .MuiAutocomplete-listbox": { - border: "2px solid #f85a3e", - color: "white", - fontSize: 18, - "& li:nth-child(even)": { - backgroundColor: "#CCC", - }, - "& li:nth-child(odd)": { - backgroundColor: "#FFF", - }, - }, - }, - inputRoot: { - color: "white", - // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90 - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: "#f86a3e", - }, - }, -}); - -const splitter = "|~|"; -const svgSize = 24; -//const referenceUrl = "https://shuffler.io/functions/webhooks/" -//const referenceUrl = window.location.origin+"/api/v1/hooks/" - -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") -const AngularWorkflow = (defaultprops) => { - const { globalUrl, isLoggedIn, isLoaded, userdata, data_id } = defaultprops; - const referenceUrl = globalUrl + "/api/v1/hooks/"; - const alert = useAlert() - let navigate = useNavigate(); - const params = useParams(); - var props = JSON.parse(JSON.stringify(defaultprops)) - props.match = {} - props.match.params = params - - const green = "#86c142"; - const yellow = "#FECC00"; - //const theme = useTheme(); - - - var to_be_copied = ""; - const [firstrequest, setFirstrequest] = React.useState(true); - const [cystyle] = useState(cytoscapestyle); - const [cy, setCy] = React.useState(); - - const [currentView, setCurrentView] = React.useState(0); - const [triggerAuthentication, setTriggerAuthentication] = React.useState({}); - const [triggerFolders, setTriggerFolders] = React.useState([]); - const [workflows, setWorkflows] = React.useState([]); - const [showEnvironment, setShowEnvironment] = React.useState(false); - const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false); - - const [workflow, setWorkflow] = React.useState({}); - const [userSettings, setUserSettings] = React.useState({}); - const [subworkflow, setSubworkflow] = React.useState({}); - const [subworkflowStartnode, setSubworkflowStartnode] = React.useState(""); - const [leftViewOpen, setLeftViewOpen] = React.useState(isMobile ? false : true); - const [leftBarSize, setLeftBarSize] = React.useState(isMobile ? 0 : 350); - const [creatorProfile, setCreatorProfile] = React.useState({}); - const [usecases, setUsecases] = React.useState([]); - const [files, setFiles] = React.useState({ - "namespaces": [ - "default", - ] - }); - const [appGroup, setAppGroup] = React.useState([]); - const [triggerGroup, setTriggerGroup] = React.useState([]); - const [executionText, setExecutionText] = React.useState(""); - const [executionRequestStarted, setExecutionRequestStarted] = - React.useState(false); - const [scrollConfig, setScrollConfig] = React.useState({ - top: 0, - left: 0, - selected: "", - }); - - const [history, setHistory] = React.useState([]); - const [historyIndex, setHistoryIndex] = React.useState(history.length); - const [variableInfo, setVariableInfo] = React.useState({}) - - const [appAuthentication, setAppAuthentication] = React.useState(undefined); - const [variablesModalOpen, setVariablesModalOpen] = React.useState(false); - const [executionVariablesModalOpen, setExecutionVariablesModalOpen] = - React.useState(false); - const [authenticationModalOpen, setAuthenticationModalOpen] = - React.useState(false); - const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false); - const [authenticationType, setAuthenticationType] = React.useState(""); - - - const [workflowDone, setWorkflowDone] = React.useState(false); - const [localFirstrequest, setLocalFirstrequest] = React.useState(true); - const [requiresAuthentication, setRequiresAuthentication] = - React.useState(false); - const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false); - const [showSkippedActions, setShowSkippedActions] = React.useState(false); - const [lastExecution, setLastExecution] = React.useState(""); - const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = - React.useState(false); - - const curpath = - typeof window === "undefined" || window.location === undefined - ? "" - : window.location.pathname; - - // 0 = normal, 1 = just done, 2 = normal - const [savingState, setSavingState] = React.useState(0); - - const [selectedResult, setSelectedResult] = React.useState({}); - const [codeModalOpen, setCodeModalOpen] = React.useState(false); - - const [variableAnchorEl, setVariableAnchorEl] = React.useState(null); - - const [sourceValue, setSourceValue] = React.useState({}); - const [destinationValue, setDestinationValue] = React.useState({}); - const [conditionValue, setConditionValue] = React.useState({}); - const [dragging, setDragging] = React.useState(false); - const [dragPosition, setDragPosition] = React.useState({ - x: 0, - y: 0, - }); - - // Trigger stuff - const [selectedComment, setSelectedComment] = React.useState({}); - 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 [apps, setApps] = React.useState([]); - const [filteredApps, setFilteredApps] = React.useState([]); - const [prioritizedApps, setPrioritizedApps] = React.useState([]); - - const [environments, setEnvironments] = React.useState([]); - const [established, setEstablished] = React.useState(false); - const [setupSent, setSetupSent] = React.useState(false); - - const [graphSetup, setGraphSetup] = React.useState(false); - - const [selectedApp, setSelectedApp] = React.useState({}); - const [selectedAction, setSelectedAction] = React.useState({}); - const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); - - const [executionRequest, setExecutionRequest] = React.useState({}); - - const [executionRunning, setExecutionRunning] = React.useState(false); - const [executionModalOpen, setExecutionModalOpen] = React.useState(false); - const [executionModalView, setExecutionModalView] = React.useState(0); - const [executionData, setExecutionData] = React.useState({}); - const [appsLoaded, setAppsLoaded] = React.useState(false); - const [showVideo, setShowVideo] = React.useState(""); - const [editWorkflowModalOpen, setEditWorkflowModalOpen] = React.useState(false); - const [userediting, setUserediting] = React.useState(false) - - const [lastSaved, setLastSaved] = React.useState(true); - const [selectionOpen, setSelectionOpen] = React.useState(false); - - // eslint-disable-next-line no-unused-vars - const [_, setUpdate] = useState(""); // Used for rendring, don't remove - - 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 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)?"; - const classes = useStyles(); - - const [bodyWidth, bodyHeight] = useWindowSize() - //console.log("Mobile: ", isMobile, bodyWidth, bodyHeight) - const cytoscapeWidth = isMobile ? bodyWidth - leftBarSize : bodyWidth - leftBarSize - 25 - - 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 - const { start, stop } = useInterval({ - duration: 3000, - startImmediate: false, - callback: () => { - fetchUpdates(); - }, - }); - - const getAppDocs = (appname, location, version) => { - fetch(`${globalUrl}/api/v1/docs/${appname}?location=${location}&version=${version}`, { - headers: { - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status === 200) { - //alert.success("Successfully GOT app "+appId) - } else { - //alert.error("Failed getting app"); - } - - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success === true) { - if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) { - if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) { - selectedApp.documentation = responseJson.reason - setSelectedApp(selectedApp) - setUpdate(Math.random()) - } - } - } - - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; - - useEffect(() => { - if (authenticationModalOpen === true && selectedAction.app_name !== undefined) { - console.log(`Should get app docs for: ${selectedAction.app_name}`) - //console.log(selectedAction) - //console.log("APP: ", selectedApp) - - if (selectedAction.documentation === undefined || selectedAction.documentation === null || selectedAction.documentation.length === 0) { - // SelectedApp.documentation = Markdown? If so, it works - // - const apptype = selectedApp.generated === false ? "python" : "openapi" - getAppDocs(selectedAction.app_name, apptype, selectedAction.app_version) - } - } - }, [authenticationModalOpen]) - - const getAvailableWorkflows = (trigger_index) => { - fetch(globalUrl + "/api/v1/workflows", { - 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!"); - return; - } - return response.json(); - }) - .then((responseJson) => { - if (responseJson !== undefined) { - setWorkflows(responseJson); - - // Sets up subflow trigger with the right info - if (trigger_index > -1) { - var baseSubflow = {} - const trigger = workflow.triggers[trigger_index]; - if (trigger.parameters.length >= 3) { - for (let paramkey in trigger.parameters) { - const param = trigger.parameters[paramkey]; - - if (param.name === "workflow") { - if (param.value === workflow.id) { - setSubworkflow(workflow); - baseSubflow = workflow - } else { - const sub = responseJson.find( - (data) => data.id === param.value - ); - if (sub !== undefined && subworkflow.id !== sub.id) { - baseSubflow = sub - setSubworkflow(sub); - } - } - } - - if ( - param.name === "startnode" && - param.value !== undefined && - param.value !== null - ) { - if (Object.getOwnPropertyNames(baseSubflow).length > 0) { - const foundAction = baseSubflow.actions.find(action => action.id === param.value) - if (foundAction !== null && foundAction !== undefined) { - setSubworkflowStartnode(foundAction); - } - } else { - setSubworkflowStartnode(param.value); - } - } - } - } - } - } - }) - .catch((error) => { - //alert.error(error.toString()); - console.log("Workflow error: ", error.toString()) - }); - }; - - function OuterLink(props) { - if (props.href.includes("http") || props.href.includes("mailto")) { - return ( -
- {props.children} - - ); - } - return ( - - {props.children} - - ); - } - - function Img(props) { - return {props.alt}; - } - - function CodeHandler(props) { - return ( -
-        {props.value}
-      
- ); - } - - function Heading(props) { - const element = React.createElement( - `h${props.level}`, - { style: { marginTop: 40 } }, - props.children - ); - return ( - - {props.level !== 1 ? ( - - ) : null} - {element} - - ); - } - - const generateApikey = () => { - fetch(globalUrl + "/api/v1/generateapikey", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for APIKEY gen :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - setUserSettings(responseJson); - }) - .catch((error) => { - console.log("Apikey error: ", error); - }); - }; - - const getSettings = () => { - fetch(globalUrl + "/api/v1/getsettings", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for get settings :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - if ( - responseJson.success === true && - (responseJson.apikey === undefined || - responseJson.apikey.length === 0 || - responseJson.apikey === null) - ) { - generateApikey(); - } - - if (responseJson.success === true) { - setUserSettings(responseJson) - } - }) - .catch((error) => { - console.log("Settings error: ", error); - }); - }; - - const setNewAppAuth = (appAuthData) => { - fetch(globalUrl + "/api/v1/apps/authentication", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(appAuthData), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for setting app auth :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error("Failed to set app auth: " + responseJson.reason); - } else { - getAppAuthentication(true, false); - setAuthenticationModalOpen(false); - - // Needs a refresh with the new authentication.. - //alert.success("Successfully saved new app auth") - } - }) - .catch((error) => { - //alert.error(error.toString()); - console.log("New auth error: ", error.toString()); - }); - }; - - const getWorkflowExecution = (id, execution_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 - - // - means it's opposite - const newkeys = sortByKey(responseJson, "-started_at"); - setWorkflowExecutions(newkeys); - - const cursearch = - typeof window === "undefined" || window.location === undefined - ? "" - : window.location.search; - - var tmpView = new URLSearchParams(cursearch).get("execution_id"); - if ( - execution_id !== undefined && execution_id !== null && - execution_id.length > 0 && (tmpView === undefined || tmpView === null || tmpView.length === 0) - ) { - tmpView = execution_id; - } - - if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) { - const execution = responseJson.find((data) => data.execution_id === tmpView); - - if (execution !== null && execution !== undefined) { - - if (execution.execution_argument.includes("too large")) { - setExecutionData({}); - setExecutionRunning(true); - setExecutionRequestStarted(false); - } else { - setExecutionData(execution); - } - - setExecutionModalView(1); - start(); - - setExecutionRequest({ - execution_id: execution.execution_id, - authorization: execution.authorization, - }); - - const newitem = removeParam("execution_id", cursearch); - navigate(curpath + newitem) - //props.history.push(curpath + newitem); - } else { - console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView) - - //setExecutionRequestStarted(true); - const cur_execution = { - execution_id: tmpView, - //authorization: data.authorization, - } - setExecutionModalView(1); - setExecutionRequest(cur_execution); - start(); - - const newitem = removeParam("execution_id", cursearch); - navigate(curpath + newitem) - - setTimeout(() => { - stop() - }, 5000); - } - } - } - }) - .catch((error) => { - //alert.error(error.toString()); - console.log("Get execution error: ", error.toString()); - }); - }; - - 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!"); - stop(); - } - - return response.json(); - }) - .then((responseJson) => { - //console.log("RESPONSE: ", responseJson) - handleUpdateResults(responseJson, executionRequest); - }) - .catch((error) => { - console.log("Execution result Error: ", error); - stop(); - }); - }; - - const abortExecution = () => { - setExecutionRunning(false); - - 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 ABORT EXECUTION :O!"); - } - - return response.json(); - }) - .catch((error) => { - //alert.error(error.toString()); - console.log("Abort error: ", error.toString()); - }); - }; - - const handleColoring = (actionId, status, label) => { - if (cy === undefined) { - return - } - - var currentnode = cy.getElementById(actionId); - if (currentnode.length === 0) { - return - //continue; - } - - currentnode = currentnode[0]; - const outgoingEdges = currentnode.outgoers("edge"); - const incomingEdges = currentnode.incomers("edge"); - - switch (status) { - case "EXECUTING": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - incomingEdges.addClass("success-highlight"); - currentnode.addClass("executing-highlight"); - break; - case "SKIPPED": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.addClass("skipped-highlight"); - break; - case "WAITING": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.addClass("executing-highlight"); - - if (!visited.includes(label)) { - if (executionRunning) { - visited.push(label); - setVisited(visited); - } - } - - // FIXME - add outgoing nodes to executing - //const outgoingNodes = outgoingEdges.find().data().target - if (outgoingEdges.length > 0) { - outgoingEdges.addClass("success-highlight"); - } - break; - case "SUCCESS": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.addClass("success-highlight"); - incomingEdges.addClass("success-highlight"); - outgoingEdges.addClass("success-highlight"); - - if (visited !== undefined && visited !== null && !visited.includes(label)) { - if (executionRunning) { - visited.push(label); - setVisited(visited); - } - } - - // FIXME - add outgoing nodes to executing - //const outgoingNodes = outgoingEdges.find().data().target - if (outgoingEdges.length > 0) { - for (let i = 0; i < outgoingEdges.length; i++) { - const edge = outgoingEdges[i]; - const targetnode = cy.getElementById(edge.data().target); - if ( - targetnode !== undefined && - !targetnode.classes().includes("success-highlight") && - !targetnode.classes().includes("failure-highlight") - ) { - targetnode.removeClass("not-executing-highlight"); - targetnode.removeClass("success-highlight"); - targetnode.removeClass("shuffle-hover-highlight"); - targetnode.removeClass("failure-highlight"); - targetnode.removeClass("awaiting-data-highlight"); - targetnode.addClass("executing-highlight"); - } - } - } - break; - case "FAILURE": - //When status comes as failure, allow user to start workflow execution - if (executionRunning) { - setExecutionRunning(false); - } - - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.addClass("failure-highlight"); - - if (!visited.includes(label)) { - //if (item.action.result !== undefined && item.action.result !== null && !item.action.result.includes("failed condition")) { - // alert.error("Error for " + item.action.label + " with result " + item.result); - //} - visited.push(label); - setVisited(visited); - } - break; - case "AWAITING_DATA": - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.addClass("awaiting-data-highlight"); - break; - default: - currentnode.removeClass("not-executing-highlight"); - currentnode.removeClass("executing-highlight"); - currentnode.removeClass("success-highlight"); - currentnode.removeClass("failure-highlight"); - currentnode.removeClass("shuffle-hover-highlight"); - currentnode.removeClass("awaiting-data-highlight"); - currentnode.addClass("not-executing-highlight"); - //console.log("DEFAULT -> Clearing!"); - break; - } - } - - // Controls the colors and direction of execution results. - // Style is in defaultCytoscapeStyle.js - const handleUpdateResults = (responseJson, executionRequest) => { - if (responseJson === undefined || responseJson === null || responseJson.success === false) { - return - } - //console.log(responseJson) - // Loop nodes and find results - // Update on every interval? idk - - ReactDOM.unstable_batchedUpdates(() => { - if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) { - // FIXME: If another is selected, don't edit.. - // Doesn't work because this is some async garbage - if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) { - if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) { - console.log("Updating data!") - setExecutionData(responseJson) - } else { - console.log("NOT updating executiondata state."); - } - } - } - - if (responseJson.execution_id !== executionRequest.execution_id) { - cy.elements().removeClass("success-highlight failure-highlight executing-highlight"); - return; - } - - if (responseJson.results !== null && responseJson.results.length > 0) { - // First clear current nodes - if (responseJson.workflow.actions !== undefined && responseJson.workflow.actions !== null) { - // In clearing of actions - for (let actionKey in responseJson.workflow.actions) { - var item = responseJson.workflow.actions[actionKey]; - - handleColoring(item.id, "", item.label) - } - } - - for (let resultKey in responseJson.results) { - var item = responseJson.results[resultKey]; - - handleColoring(item.action.id, item.status, item.action.label) - } - } - - if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") { - stop(); - - if (executionRunning) { - setExecutionRunning(false); - } - - var curelements = cy.elements(); - for (let i = 0; i < curelements.length; i++) { - if (curelements[i].classes().includes("executing-highlight")) { - curelements[i].removeClass("executing-highlight"); - curelements[i].addClass("failure-highlight"); - } - } - - getWorkflowExecution(props.match.params.key, ""); - } else if (responseJson.status === "FINISHED") { - setExecutionRunning(false); - stop(); - getWorkflowExecution(props.match.params.key, ""); - setUpdate(Math.random()); - } - }) - }; - - const sendStreamRequest = (body) => { - console.log("Stream not activated yet.") - return - - // Session may be important here huh - body.user_id = userdata.id - - fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key + "/stream", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(body), - credentials: "include", - }) - .then((response) => { - setSavingState(0); - if (response.status !== 200) { - console.log("Status not 200 for setting workflows :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - console.log("RESP: ", responseJson) - }) - .catch((error) => { - console.log("Stream send error: ", error.toString()) - //alert.error(error.toString()); - }) - } - - const saveWorkflow = (curworkflow, executionArgument, startNode) => { - var success = false; - - if (isCloud && !isLoggedIn) { - console.log("Should redirect to register with redirect."); - window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle`; - return; - } - - setSavingState(2); - - // This might not be the right course of action, but seems logical, as items could be running already - // Makes it possible to update with a version in current render - stop(); - var useworkflow = workflow; - if (curworkflow !== undefined) { - useworkflow = curworkflow; - } - - var cyelements = cy.elements(); - var newActions = []; - var newTriggers = []; - var newBranches = []; - var newVBranches = []; - var newComments = []; - for (let cyelementsKey in cyelements) { - if (cyelements[cyelementsKey].data === undefined) { - continue; - } - - var type = cyelements[cyelementsKey].data()["type"]; - if (type === undefined) { - if ( - cyelements[cyelementsKey].data().source === undefined || - cyelements[cyelementsKey].data().target === undefined - ) { - continue; - } - - var parsedElement = { - id: cyelements[cyelementsKey].data().id, - source_id: cyelements[cyelementsKey].data().source, - destination_id: cyelements[cyelementsKey].data().target, - conditions: cyelements[cyelementsKey].data().conditions, - decorator: cyelements[cyelementsKey].data().decorator, - }; - - if (parsedElement.decorator) { - newVBranches.push(parsedElement); - } else { - newBranches.push(parsedElement); - } - } else { - if (type === "ACTION") { - const cyelement = cyelements[cyelementsKey].data(); - const elementid = - cyelement.id === undefined || cyelement.id === null - ? cyelement["_id"] - : cyelement.id; - - var curworkflowAction = useworkflow.actions.find( - (a) => - a !== undefined && - (a["id"] === elementid || a["_id"] === elementid) - ); - if (curworkflowAction === undefined) { - curworkflowAction = cyelements[cyelementsKey].data(); - } - - curworkflowAction.position = cyelements[cyelementsKey].position(); - - // workaround to fix some edgecases - if ( - curworkflowAction.parameters === "" || - curworkflowAction.parameters === null - ) { - curworkflowAction.parameters = []; - } - - if ( - curworkflowAction.example === undefined || - curworkflowAction.example === "" || - curworkflowAction.example === null - ) { - if (cyelements[cyelementsKey].data().example !== undefined) { - curworkflowAction.example = cyelements[cyelementsKey].data().example; - } - } - - // Override just in this place - curworkflowAction.errors = []; - curworkflowAction.isValid = true; - - // Cleans up OpenAPI items - var newparams = []; - for (let parametersKey in curworkflowAction.parameters) { - const thisitem = curworkflowAction.parameters[parametersKey]; - if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) { - continue; - } - - newparams.push(thisitem); - } - - curworkflowAction.parameters = newparams; - newActions.push(curworkflowAction); - } else if (type === "TRIGGER") { - if (useworkflow.triggers === undefined || useworkflow.triggers === null) { - useworkflow.triggers = []; - } - - var curworkflowTrigger = useworkflow.triggers.find( - (a) => a.id === cyelements[cyelementsKey].data()["id"] - ); - if (curworkflowTrigger === undefined) { - curworkflowTrigger = cyelements[cyelementsKey].data(); - } - - curworkflowTrigger.position = cyelements[cyelementsKey].position(); - if (curworkflowTrigger.canConnect === false) { - continue - } - - newTriggers.push(curworkflowTrigger); - } else if (type === "COMMENT") { - if (useworkflow.comments === undefined || useworkflow.comments === null) { - useworkflow.comments = []; - } - - var curworkflowComment = useworkflow.comments.find( - (a) => a.id === cyelements[cyelementsKey].data()["id"] - ) - - if (curworkflowComment === undefined) { - curworkflowComment = cyelements[cyelementsKey].data(); - try { - curworkflowComment.position.x = parseInt(curworkflowComment.position.x) - } catch (e) { - console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x) - } - - try { - curworkflowComment.position.y = parseInt(curworkflowComment.position.y) - } catch (e) { - console.log("Failed to parse position Y of comment: ", curworkflowComment.position.y) - } - } - - const parsedHeight = parseInt(curworkflowComment["height"]) - if (!isNaN(parsedHeight)) { - curworkflowComment.height = parsedHeight - } else { - curworkflowComment.width = 150 - } - - const parsedWidth = parseInt(curworkflowComment["width"]) - if (!isNaN(parsedWidth)) { - curworkflowComment.width = parsedWidth - } else { - curworkflowComment.width = 200 - } - - curworkflowComment.position = cyelements[cyelementsKey].position(); - //console.log(curworkflowComment) - - newComments.push(curworkflowComment); - } else { - alert.info("No handler for type: " + type); - } - } - } - - if (userediting === true) { - useworkflow.user_editing = true - } - - useworkflow.actions = newActions; - useworkflow.triggers = newTriggers; - useworkflow.branches = newBranches; - useworkflow.comments = newComments; - useworkflow.visual_branches = newVBranches; - - // Errors are backend defined - useworkflow.errors = []; - useworkflow.previously_saved = true; - - if (cy !== undefined) { - // scale: 0.3, - // bg: "#27292d", - const cyImageData = cy.png({ - output: "base64uri", - maxWidth: 480, - maxHeight: 270, - }) - - if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) { - useworkflow.image = cyImageData - } - } - - setLastSaved(true); - fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(useworkflow), - credentials: "include", - }) - .then((response) => { - setSavingState(0); - if (response.status !== 200) { - console.log("Status not 200 for setting workflows :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - if (executionArgument !== undefined && startNode !== undefined) { - //console.log("Running execution AFTER saving"); - executeWorkflow(executionArgument, startNode, true); - return; - } - - if (!responseJson.success) { - console.log(responseJson); - if (responseJson.reason !== undefined && responseJson.reason !== null) { - alert.error("Failed to save: " + responseJson.reason); - } else { - alert.error("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.") - } - } else { - - sendStreamRequest({ - "item": "workflow", - "type": "save", - "id": workflow.id, - }) - - if ( - responseJson.new_id !== undefined && - responseJson.new_id !== null - ) { - window.location.pathname = "/workflows/" + responseJson.new_id; - } - - success = true; - if (responseJson.errors !== undefined) { - workflow.errors = responseJson.errors; - if (responseJson.errors.length === 0) { - workflow.isValid = true; - workflow.is_valid = true; - - const cyelements = cy.elements(); - - for (let i = 0; i < cyelements.length; i++) { - //cyelements[i].removeStyle(); - cyelements[i].data().is_valid = true; - cyelements[i].data().errors = []; - } - - for (let actionkey in workflow.actions) { - workflow.actions[actionkey].is_valid = true; - workflow.actions[actionkey].errors = []; - } - } - - setWorkflow(workflow); - } - - setSavingState(1); - setTimeout(() => { - setSavingState(0); - }, 1500); - } - }) - .catch((error) => { - setSavingState(0); - //alert.error(error.toString()); - console.log("Save workflow error: ", error.toString()); - }); - - return success; - }; - - const monitorUpdates = () => { - var firstnode = cy.getElementById(workflow.start); - if (firstnode.length === 0) { - var found = false; - for (let actionkey in workflow.actions) { - if (workflow.actions[actionkey].isStartNode) { - console.log("Updating startnode"); - workflow.start = workflow.actions[actionkey].id; - firstnode = cy.getElementById(workflow.actions[actionkey].id); - found = true; - break; - } - } - - if (!found) { - return false; - } - } - - cy.elements().removeClass( - "success-highlight failure-highlight executing-highlight" - ); - firstnode[0].addClass("executing-highlight"); - - return true; - }; - - const executeWorkflow = (executionArgument, startNode, hasSaved) => { - ReactDOM.unstable_batchedUpdates(() => { - if (hasSaved === false) { - setExecutionRequestStarted(true); - saveWorkflow(workflow, executionArgument, startNode); - console.log("FIXME: Might have forgotten to save before executing."); - return; - } - - if (workflow.public) { - alert.info("Save it to get a new version"); - } - - var returncheck = monitorUpdates(); - if (!returncheck) { - alert.error("No startnode set."); - return; - } - - setVisited([]) - setExecutionRequest({}) - stop() - - var curelements = cy.elements(); - for (let i = 0; i < curelements.length; i++) { - curelements[i].addClass("not-executing-highlight"); - } - - const data = { execution_argument: executionArgument, start: startNode }; - fetch( - globalUrl + "/api/v1/workflows/" + props.match.params.key + "/execute", - { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - body: JSON.stringify(data), - } - ) - .then((response) => { - setExecutionRequestStarted(false) - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error("Failed to start: " + responseJson.reason); - setExecutionRunning(false); - setExecutionRequestStarted(false); - stop(); - - for (let i = 0; i < curelements.length; i++) { - curelements[i].removeClass("not-executing-highlight"); - } - return; - } else { - setExecutionRunning(true); - setExecutionRequestStarted(false); - } - - if ( - responseJson.execution_id === "" || - responseJson.execution_id === undefined || - responseJson.authorization === "" || - responseJson.authorization === undefined - ) { - alert.error("Something went wrong during execution startup"); - console.log("BAD RESPONSE FOR EXECUTION: ", responseJson); - setExecutionRunning(false); - setExecutionRequestStarted(false); - stop(); - - for (let i = 0; i < curelements.length; i++) { - curelements[i].removeClass("not-executing-highlight"); - } - return; - } - - setExecutionRequest({ - execution_id: responseJson.execution_id, - authorization: responseJson.authorization, - }); - setExecutionData({}); - setExecutionModalOpen(true); - setExecutionModalView(1); - start(); - }) - .catch((error) => { - //alert.error(error.toString()); - setExecutionRequestStarted(false) - console.log("Execute workflow err: ", error.toString()); - }); - }) - }; - - // This can be used to only show prioritzed ones later - // Right now, it can prioritize authenticated ones - //"Testing", - const internalIds = ["Shuffle Tools", "http", "email"]; - - const getAppAuthentication = (reset, updateAction) => { - fetch(globalUrl + "/api/v1/apps/authentication", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for app auth :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success) { - var newauth = []; - for (let authkey in responseJson.data) { - if (responseJson.data[authkey].defined === false) { - continue; - } - - newauth.push(responseJson.data[authkey]); - } - - setAppAuthentication(newauth); - - if (cy !== undefined) { - // Remove the old listener for select, run with new one - cy.removeListener("select"); - cy.on("select", "node", (e) => onNodeSelect(e, newauth)); - cy.on("select", "edge", (e) => onEdgeSelect(e)); - } - - if (updateAction === true) { - if (selectedApp.authentication.required) { - // Setup auth here :) - var appUpdates = false; - const authenticationOptions = []; - - var tmpAuth = JSON.parse(JSON.stringify(newauth)); - var latest = 0; - for (let authkey in tmpAuth) { - var item = tmpAuth[authkey]; - - const newfields = {}; - for (let filterkey in item.fields) { - newfields[item.fields[filterkey].key] = item.fields[filterkey].value; - } - - item.fields = newfields; - if (item.app.name === selectedApp.name) { - authenticationOptions.push(item); - - // Always becoming the last one - if (item.edited > latest) { - latest = item.edited; - selectedAction.selectedAuthentication = item; - - for (let actionkey in workflow.actions) { - if (workflow.actions[actionkey].app_name === selectedApp.name) { - workflow.actions[actionkey].selectedAuthentication = item; - workflow.actions[actionkey].authentication_id = item.id; - appUpdates = true; - } - } - } - } - } - - selectedAction.authentication = authenticationOptions; - if ( - selectedAction.selectedAuthentication === null || - selectedAction.selectedAuthentication === undefined || - selectedAction.selectedAuthentication.length === "" - ) { - selectedAction.selectedAuthentication = {}; - } - - if (appUpdates === true) { - console.log("Closing auth modal: Success") - - setAuthenticationModalOpen(false); - setSelectedAction(selectedAction); - setWorkflow(workflow); - saveWorkflow(workflow); - alert.info("Added and updated authentication!"); - } else { - console.log("Closing auth modal? FAIL") - - alert.error("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); - } - } else { - alert.info("No authentication to update"); - } - } - } else { - setAppAuthentication([]); - } - }) - .catch((error) => { - setAppAuthentication([]); - //alert.error("Auth loading error: " + error.toString()); - console.log("AppAuth error: " + error.toString()); - }); - }; - - const getApps = () => { - fetch(globalUrl + "/api/v1/apps", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - setAppsLoaded(true) - if (response.status !== 200) { - console.log("Status not 200 for apps :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - if (responseJson === null) { - console.log("No response") - const pretend_apps = [{ - "name": "TBD", - "app_name": "TBD", - "app_version": "TBD", - "description": "TBD", - "version": "TBD", - "large_image": "", - }] - setApps(pretend_apps) - setFilteredApps(pretend_apps) - setPrioritizedApps(pretend_apps); - return - } - - if (responseJson.success === false) { - return - } - - // FIXME - handle versions on left bar - //handleAppVersioning(responseJson) - //var tmpapps = [] - //tmpapps = tmpapps.concat(getExtraApps()) - //tmpapps = tmpapps.concat(responseJson) - setApps(responseJson); - - if (isCloud) { - setFilteredApps( - responseJson.filter((app) => !internalIds.includes(app.name)) - ); - setPrioritizedApps( - responseJson.filter((app) => internalIds.includes(app.name)) - ); - } else { - //setFilteredApps( - // responseJson.filter( - // (app) => - // !internalIds.includes(app.name) && - // !(!app.activated && app.generated) - // ) - //); - - var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name)) - //tmpFiltered = sortByKey(tmpFiltered, "activated") - setFilteredApps(tmpFiltered) - - //!(!app.activated && app.generated) - setPrioritizedApps( - responseJson.filter((app) => internalIds.includes(app.name)) - ); - } - }) - .catch((error) => { - setAppsLoaded(true) - //alert.error("App loading error: "+error.toString()); - console.log("App loading error: " + error.toString()); - }); - }; - - // Searhc by username, userId, workflow, appId should all work - const getUserProfile = (username, rerun) => { - fetch(`${globalUrl}/api/v1/users/creators/${username}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } - - - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success !== false) { - console.log("Found creator: ", responseJson) - setCreatorProfile(responseJson) - } else { - console.log("Couldn't find the creator profile (rerun?): ", responseJson, rerun) - // If the current user is any of the Shuffle Creators - // AND the workflow doesn't have an owner: allow editing. - // else: Allow suggestions? - //console.log("User: ", userdata) - //if (rerun !== true) { - // getUserProfile(userdata.id, true) - //} - } - }) - .catch((error) => { - console.log("Get userprofile error: ", error); - }) - } - - const getFiles = () => { - fetch(globalUrl + "/api/v1/files", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - //console.log("Status not 200 for apps :O!"); - return; - } - - return response.json(); - }) - .then((responseJson) => { - if (responseJson.files !== undefined && responseJson.files !== null) { - setFiles(responseJson); - } else { - setFiles({ - "namespaces": [ - "default" - ] - }); - } - - if ( - responseJson.namespaces !== undefined && - responseJson.namespaces !== null - ) { - //setFileNamespaces(responseJson.namespaces); - } - }) - .catch((error) => { - //alert.error(error.toString()); - console.log("Error loading files: ", error) - }); - }; - - const getWorkflow = (workflow_id, sourcenode) => { - fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for workflows :O!"); - window.location.pathname = "/workflows"; - } - - return response.json(); - }) - .then((responseJson) => { - // Not sure why this is necessary. - if (responseJson.isValid === undefined) { - responseJson.isValid = true; - } - - if (responseJson.errors === undefined) { - responseJson.errors = []; - } - - if (responseJson.actions === undefined || responseJson.actions === null) { - responseJson.actions = []; - } - - if (responseJson.triggers === undefined || responseJson.triggers === null) { - responseJson.triggers = []; - } - - if (responseJson.public) { - //alert.info("This workflow is public. Save the workflow to use it in your organization."); - - setAppAuthentication([]) - console.log("RESP: ", responseJson) - if (Object.getOwnPropertyNames(creatorProfile).length === 0) { - //getUserProfile("frikky") - getUserProfile(responseJson.id, false) - } - - //{appGroup.map((data, index) => { - //const [appGroup, setAppGroup] = React.useState([]); - var appsFound = [] - for (let actionkey in responseJson.actions) { - const parsedAction = responseJson.actions[actionkey] - if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") { - continue - } - if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){ - appsFound.push(parsedAction) - } - } - - setAppGroup(appsFound) - - appsFound = [] - for (let triggerkey in responseJson.triggers) { - const parsedAction = responseJson.triggers[triggerkey] - if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){ - appsFound.push(parsedAction) - } - } - - setTriggerGroup(appsFound) - } else { - getAppAuthentication(); - getEnvironments(); - getWorkflowExecution(props.match.params.key, ""); - getAvailableWorkflows(-1); - getSettings(); - getFiles() - } - - // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows. - if (sourcenode.id !== undefined) { - console.log("WORKFLOW: ", responseJson); - - var nodefound = false; - const target = sourcenode.parameters.find( - (item) => item.name === "startnode" - ); - console.log(sourcenode.parameters); - console.log(target); - const target_id = target === undefined ? "" : target.value; - const actions = responseJson.actions.map((action) => { - const node = { - group: "nodes", - }; - - // Set it dynamically? - node.position = { - x: sourcenode.position.x + action.position.x, - y: sourcenode.position.y + action.position.y, - }; - - node.data = action; - - node.data.canConnect = false - node.data.is_valid = true - node.data.isValid = true - node.data._id = action["id"]; - node.data.type = "ACTION"; - node.data.source_workflow = responseJson.id; - if (action.id === target_id) { - nodefound = true; - } - - if (responseJson.public) { - node.data.is_valid = true - node.is_valid = true - } - - var example = ""; - if ( - action.example !== undefined && - action.example !== null && - action.example.length > 0 - ) { - example = action.example; - } - - node.data.example = example; - return node; - }); - - var triggers = responseJson.triggers.map((trigger) => { - const node = {}; - - console.log("Only add workflow: ", trigger.app_name) - if (trigger.app_name !== "Shuffle Workflow" && trigger.app_name !== "User Input") { - return null - } - - node.position = trigger.position; - node.data = trigger; - - node.data.canConnect = false - node.data.id = trigger["id"]; - node.data._id = trigger["id"]; - node.data.type = "TRIGGER"; - - return node; - }); - - triggers = triggers.filter((trigger) => trigger !== null); - const insertedNodes = [].concat(actions, triggers); - var edges = responseJson.branches.map((branch, index) => { - const edge = {}; - var conditions = responseJson.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"; - } - - const sourceFound = insertedNodes.findIndex( - (action) => action.data.id === branch.source_id - ); - if (sourceFound < 0) { - return null; - } - - const destinationFound = insertedNodes.findIndex( - (action) => action.data.id === branch.destination_id - ); - if (destinationFound < 0) { - return null; - } - - - edge.data = { - id: branch.id, - _id: branch.id, - source: branch.source_id, - target: branch.destination_id, - label: label, - conditions: conditions, - hasErrors: branch.has_errors, - decorator: false, - source_workflow: responseJson.id, - }; - - if (responseJson.public) { - edge.data.is_valid = true - edge.is_valid = true - } - - return edge; - }); - - edges = edges.filter((edge) => edge !== null); - cy.removeListener("add"); - cy.add(insertedNodes) - cy.add(edges); - - if (nodefound === true) { - const newId = uuidv4(); - cy.add({ - group: "edges", - data: { - id: newId, - _id: newId, - source: sourcenode.id, - target: target_id, - label: "Subflow", - decorator: true, - source_workflow: responseJson.id, - }, - }); - } - - cy.fit(null, 100); - cy.on("add", "node", (e) => onNodeAdded(e)); - cy.on("add", "edge", (e) => onEdgeAdded(e)); - } else { - setWorkflow(responseJson); - setWorkflowDone(true); - - // Add error checks - console.log("Workflow: ", responseJson); - if (!responseJson.public) { - if ( - !responseJson.previously_saved || - !responseJson.is_valid || - responseJson.errors !== undefined || - responseJson.errors !== null || - responseJson.errors !== // what - responseJson.errors.length > 0 - ) { - console.log("Setting configure Modal to open") - setConfigureWorkflowModalOpen(true); - } - } - } - }) - .catch((error) => { - //alert.error(error.toString()); - console.log("Get workflows error: ", error.toString()); - }); - }; - - const onUnselect = (event) => { - const nodedata = event.target.data(); - console.log("UNSELECT: ", nodedata); - //if (nodedata.type === "ACTION") { - // setLastSelected(nodedata) - //} - // - - // Wait for new node to possibly be selected - //setTimeout(() => { - const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { - const item = typeIds[idkey] - if (item.data.isButton === true) { - //console.log("Reselect old node & return - or just return?") - - if (item.data.buttonType === "delete" && item.data.attachedTo === nodedata.id) { - //console.log("delete of same node!") - } - return - } - } - - // Unselecting all - //cy.elements().unselect() - - //if (nodedata.app_name === undefined && nodedata.source === undefined) { - // return; - //} - //event.target.removeClass("selected"); - // - - - - - - //// If button is clicked, select current node - - // Attempt at rewrite of name in other actions in following nodes. - // Should probably be done in the onBlur for the textfield instead - /* - if (event.target.data().type === "ACTION") { - const nodeaction = event.target.data() - const curaction = workflow.actions.find(a => a.id === nodeaction.id) - console.log("workflowaction: ", curaction) - console.log("nodeaction: ", nodeaction) - if (nodeaction.label !== curaction.label) { - console.log("BEACH!") - - var params = [] - const fixedName = "$"+curaction.label.toLowerCase().replace(" ", "_") - for (var actionkey in workflow.actions) { - if (workflow.actions[actionkey].id === curaction.id) { - continue - } - - for (var paramkey in workflow.actions[actionkey].parameters) { - const param = workflow.actions[actionkey].parameters[paramkey] - if (param.value === null || param.value === undefined || !param.value.includes("$")) { - continue - } - - const innername = param.value.toLowerCase().replace(" ", "_") - if (innername.includes(fixedName)) { - //workflow.actions[actionkey].parameters[paramkey].replace( - //console.log("FOUND!: ", innername) - } - } - } - } - } - */ - - - // FIXME - check if they have value before overriding like this for no reason. - // Would save a lot of time (400~ ms -> 30ms) - //console.log("ACTION: ", selectedAction) - //console.log("APP: ", selectedApp) - - ReactDOM.unstable_batchedUpdates(() => { - setSelectedAction({}); - setSelectedApp({}); - setSelectedTrigger({}); - setSelectedComment({}) - setSelectedEdge({}); - - setSelectedEdge({}) - setSelectedActionEnvironment({}) - setTriggerAuthentication({}) - setSelectedTriggerIndex(-1) - setTriggerFolders([]) - setSubworkflow({}) - setLocalFirstrequest(true) - - // Can be used for right side view - setRightSideBarOpen(false); - setScrollConfig({ - top: 0, - left: 0, - selected: "", - }); - //console.timeEnd("UNSELECT"); - }) - - sendStreamRequest({ - "item": "node", - "type": "unselect", - "userid": userdata.id, - }) - //}, 150) - }; - - const onEdgeSelect = (event) => { - ReactDOM.unstable_batchedUpdates(() => { - setRightSideBarOpen(true); - setLastSaved(false); - - /* - // Used to not be able to edit trigger-based branches. - const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"]) - if (triggercheck === undefined) { - */ - if ( - event.target.data("type") !== "COMMENT" && - event.target.data().decorator - ) { - alert.info("This edge can't be edited."); - } else { - //console.log("DATA: ", event.target.data()) - const destinationId = event.target.data("target"); - //console.log("DATA: ", event.target.data()) - const curaction = workflow.actions.find((a) => a.id === destinationId); - //console.log("ACTION: ", curaction) - if (curaction !== undefined && curaction !== null) { - if ( - curaction.app_name === "Shuffle Tools" && - curaction.name === "router" - ) { - alert.info("Router action can't have incoming conditions"); - event.target.unselect(); - return; - } - } - - setSelectedEdgeIndex( - workflow.branches.findIndex( - (data) => data.id === event.target.data()["id"] - ) - ); - setSelectedEdge(event.target.data()); - } - - setSelectedAction({}); - setSelectedTrigger({}); - }) - }; - - // Comparing locations between nodes and setting views - var styledElements = []; - var originalLocation = { - x: 0, - y: 0, - }; - - const onCtxTap = (event) => { - const nodedata = event.target.data(); - console.log(nodedata); - if ( - nodedata.type === "TRIGGER" && - nodedata.app_name === "Shuffle Workflow" - ) { - if (nodedata.parameters === null) { - alert.error("Set a workflow first"); - return; - } - - if (nodedata.parameters === undefined) { - return - } - - const workflow_id = nodedata.parameters.find( - (param) => param.name === "workflow" - ); - - if (workflow.id === workflow_id.valu) { - return; - } - - cy.animation({ - zoom: 0, - center: { - eles: event.target, - }, - }) - .play() - .promise() - .then(() => { - console.log("DONE: ", workflow_id); - getWorkflow(workflow_id.value, nodedata); - cy.fit(null, 50); - }); - } - }; - - const onNodeDragStop = (event, selectedAction) => { - const nodedata = event.target.data(); - if (nodedata.id === selectedAction.id) { - return; - } - - if (nodedata.finished === false) { - return; - } - - const connected = event.target.connectedEdges().jsons() - if (connected.length > 0 && connected !== undefined) { - for (let connectkey in connected) { - const edge = connected[connectkey] - //console.log("EDGE:", edge) - - //const edge = edgeBase.json() - - const sourcenode = cy.getElementById(edge.data.source) - const destinationnode = cy.getElementById(edge.data.target) - if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { - continue - } - - const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position()) - const currentedge = cy.getElementById(edge.data.id) - if (currentedge !== undefined && currentedge !== null) { - currentedge.style('control-point-distance', edgeCurve.distance) - currentedge.style('control-point-weight', edgeCurve.weight) - } - } - } - - if (styledElements.length === 1) { - console.log( - "Should reset location and autofill: ", - styledElements, - selectedAction - ); - if (originalLocation.x !== 0 || originalLocation.y !== 0) { - const currentnode = cy.getElementById(nodedata.id); - if (currentnode !== null && currentnode !== undefined) { - currentnode.position("x", originalLocation.x); - currentnode.position("y", originalLocation.y); - } - - originalLocation = { x: 0, y: 0 }; - } - - const curElement = document.getElementById(styledElements[0]); - if (curElement !== null && curElement !== undefined) { - curElement.style.border = curElement.style.original_border; - var newValue = "$" + nodedata.label.toLowerCase().replaceAll(" ", "_"); - if (nodedata.type === "TRIGGER") { - if (nodedata.trigger_type === "WEBHOOK" || nodedata.trigger_type === "SCHEDULE" || nodedata.trigger_type === "EMAIL") { - var newValue = "$exec" - } - } - var paramname = ""; - var idnumber = -1; - if (curElement.id.startsWith("rightside_field_")) { - console.log("FOUND FIELD WITH NUMBER: ", curElement.id); - - - // Find exact position to put the text - - const idsplit = curElement.id.split("_"); - console.log(idsplit); - if (idsplit.length === 3 && !isNaN(idsplit[2])) { - console.log("ADDING TO PARAM ", idsplit[2]); - - selectedAction.parameters[idsplit[2]].value += newValue; - paramname = selectedAction.parameters[idsplit[2]].name; - idnumber = idsplit[2]; - } - } - - if (idnumber >= 0 && paramname.length > 0) { - const exampledata = GetExampleResult(nodedata); - const parsedname = paramname - .toLowerCase() - .trim() - .replaceAll("_", " "); - - const foundresult = GetParamMatch(parsedname, exampledata, ""); - if (foundresult.length > 0) { - console.log("FOUND RESULT: ", paramname, foundresult); - newValue = `${newValue}${foundresult}`; - } - - selectedAction.parameters[idnumber].value = newValue; - } - - curElement.value = newValue; - } - } - - if ( - nodedata.app_name !== undefined && - ((nodedata.app_name !== "Shuffle Tools" && - nodedata.app_name !== "Testing" && - nodedata.app_name !== "Shuffle Workflow" && - nodedata.app_name !== "User Input" && - nodedata.app_name !== "Webhook" && - nodedata.app_name !== "Schedule" && - nodedata.app_name !== "Email") || - nodedata.isStartNode) - ) { - const allNodes = cy.nodes().jsons(); - var found = false; - for (let nodekey in allNodes) { - const currentNode = allNodes[nodekey]; - if ( - currentNode.data.attachedTo === nodedata.id && - currentNode.data.isDescriptor - ) { - found = true; - console.log("FOUND THE NODE!"); - break; - } - } - - // Readding the icon after moving the node - if (!found) { - const iconInfo = GetIconInfo(nodedata); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - - const offset = nodedata.isStartNode ? 36 : 44; - const decoratorNode = { - position: { - x: event.target.position().x + offset, - y: event.target.position().y + offset, - }, - locked: true, - data: { - isDescriptor: true, - isValid: true, - is_valid: true, - label: "", - image: svgpin_Url, - imageColor: iconInfo.iconBackgroundColor, - attachedTo: nodedata.id, - }, - }; - - cy.add(decoratorNode).unselectify(); - } else { - console.log("Node already exists - don't add descriptor node"); - } - } - - originalLocation = { - x: 0, - y: 0, - }; - - sendStreamRequest({ - "item": "node", - "type": "move", - "id": nodedata.id, - "location": { - "x": event.target.position("x"), - "y": event.target.position("y"), - } - }) - }; - - const onNodeDrag = (event, selectedAction) => { - const nodedata = event.target.data(); - if (nodedata.finished === false) { - console.log("NOT FINISHED - ADD EXAMPLE BRANCHES TO CLOSEST!!") - return - } - - if (nodedata.app_name !== undefined) { - const allNodes = cy.nodes().jsons(); - for (var nodekey in allNodes) { - const currentNode = allNodes[nodekey]; - if (currentNode.data.attachedTo === nodedata.id) { - cy.getElementById(currentNode.data.id).remove(); - } - - // Calculate location - //currentNode.position.x > - //if (nodedata.position.x > 0 && nodedata.position.y > 0) { - // console.log("Positive both") - //} - - //console.log(currentNode.position) - //console.log(nodedata.position) - } - } else { - //console.log("No appid? ", nodedata) - } - - if (nodedata.buttonType === "edgehandler") { - console.log("Enable edgehandler!") - console.log("Find parent: ", nodedata.attachedTo) - const parentNode = cy.getElementById(nodedata.attachedTo); - if (parentNode !== null && parentNode !== undefined) { - console.log("Start parentnode tracking!") - //cy.edgehandles().start(parentNode) - } - } - - if (nodedata.id === selectedAction.id) { - return; - } - - - - /* - // Tried looking for the closest node by position. aStar path not working entirely. - console.log("NODE: ", event.target) - const closestNode = cy.elements().aStar({ - root: nodedata.id, - goal: 'node', - directed: false, - }) - - if (closestNode.found) { - console.log("No closest node found for: ", nodedata.id) - } else { - console.log("Closest: ", closestNode) - } - */ - - if ( - originalLocation.x === 0 && - originalLocation.y === 0 && - nodedata.position !== undefined - ) { - originalLocation.x = nodedata.position.x; - originalLocation.y = nodedata.position.y; - } - - // Part of autocomplete. Styles elements in frontend to indicate - // what and where we may input data for the user. - const onMouseUpdate = (e) => { - const x = e.pageX; - const y = e.pageY; - - const elementMouseIsOver = document.elementFromPoint(x, y); - if (elementMouseIsOver !== undefined && elementMouseIsOver !== null) { - // Color for #f85a3e translated to rgb - const newBorder = "3px solid rgb(248, 90, 62)"; - if ( - elementMouseIsOver.style.border !== newBorder && - elementMouseIsOver.id.includes("rightside") - ) { - if (elementMouseIsOver.style.border !== undefined) { - elementMouseIsOver.style.original_border = - elementMouseIsOver.style.border; - } else { - elementMouseIsOver.style.original_border = ""; - } - - elementMouseIsOver.style.border = newBorder; - console.log("STYLED: ", styledElements); - for (var styledElementsKey in styledElements) { - const curElement = document.getElementById(styledElements[styledElementsKey]); - if (curElement !== null && curElement !== undefined) { - curElement.style.border = curElement.style.original_border; - } - } - - styledElements = []; - styledElements.push(elementMouseIsOver.id); - } else if ( - elementMouseIsOver.id === "cytoscape_view" || - elementMouseIsOver.id === "" - ) { - for (var index in styledElements) { - const curElement = document.getElementById(styledElements[index]); - if (curElement !== null && curElement !== undefined) { - curElement.style.border = curElement.style.original_border; - } - } - - styledElements = []; - } - } - - // Ensure it only happens once - document.removeEventListener("mousemove", onMouseUpdate, false); - }; - - document.addEventListener("mousemove", onMouseUpdate, false); - }; - - - useBeforeunload(() => { - if (!lastSaved) { - return unloadText; - } else { - if (workflow.public === false) { - //document.removeEventListener("mousemove", onMouseUpdate, true); - document.removeEventListener("keydown", handleKeyDown, true); - document.removeEventListener("paste", handlePaste, true); - } - } - }); - - // Nodeselectbatching: - // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once - // onNodeClick - const onNodeSelect = (event, newAppAuth) => { - // Forces all states to update at the same time, - // Otherwise everything is SUPER slow - ReactDOM.unstable_batchedUpdates(() => { - const data = event.target.data(); - if (data.isButton) { - if (data.buttonType === "delete") { - const parentNode = cy.getElementById(data.attachedTo); - if (parentNode !== null && parentNode !== undefined) { - removeNode(data.attachedTo) - //parentNode.remove() - } - - return - } else if (data.buttonType === "set_startnode" && data.type !== "TRIGGER") { - const parentNode = cy.getElementById(data.attachedTo); - if (parentNode !== null && parentNode !== undefined) { - var oldstartnode = cy.getElementById(workflow.start); - if ( - oldstartnode !== null && - oldstartnode !== undefined && - oldstartnode.length > 0 - ) { - try { - oldstartnode[0].data("isStartNode", false); - } catch (e) { - console.log("Startnode error: ", e); - } - } - - workflow.start = parentNode.data("id"); - setLastSaved(false); - parentNode.data("isStartNode", true); - } - - //event.target.unselect(); - setRightSideBarOpen(true); - return - - } else if (data.buttonType === "copy") { - console.log("COPY!"); - - // 1. Find parent - // 2. Find branches for parent - // 3. Make a new node that's moved a little bit - const parentNode = cy.getElementById(data.attachedTo); - if (parentNode !== null && parentNode !== undefined) { - var newNodeData = JSON.parse(JSON.stringify(parentNode.data())); - newNodeData.id = uuidv4(); - if (newNodeData.position !== undefined) { - newNodeData.position = { - x: newNodeData.position.x + 100, - y: newNodeData.position.y + 100, - }; - } - - newNodeData.isStartNode = false; - newNodeData.errors = []; - newNodeData.is_valid = true; - newNodeData.isValid = true; - newNodeData.label = parentNode.data("label") + "_copy"; - - cy.add({ - group: "nodes", - data: newNodeData, - position: newNodeData.position, - }); - - // Readding the icon after moving the node - if ( - newNodeData.app_name !== "Testing" || - newNodeData.app_name !== "Shuffle Workflow" - ) { - } else { - const iconInfo = GetIconInfo(newNodeData); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - - const offset = newNodeData.isStartNode ? 36 : 44; - const decoratorNode = { - position: { - x: newNodeData.position.x + offset, - y: newNodeData.position.y + offset, - }, - locked: true, - data: { - isDescriptor: true, - isValid: true, - is_valid: true, - label: "", - image: svgpin_Url, - imageColor: iconInfo.iconBackgroundColor, - attachedTo: newNodeData.id, - }, - }; - - cy.add(decoratorNode).unselectify(); - } - - workflow.actions.push(newNodeData); - - const sourcebranches = workflow.branches.filter( - (foundbranch) => foundbranch.source_id === parentNode.data("id") - ); - const destinationbranches = workflow.branches.filter( - (foundbranch) => - foundbranch.destination_id === parentNode.data("id") - ); - - for (var sourceBranchesKey in sourcebranches) { - var newbranch = JSON.parse(JSON.stringify(sourcebranches[sourceBranchesKey])); - newbranch.id = uuidv4(); - newbranch.source_id = newNodeData.id; - - newbranch._id = newbranch.id; - newbranch.source = newbranch.source_id; - newbranch.target = newbranch.destination_id; - cy.add({ - group: "edges", - data: newbranch, - }); - } - - for (var destinationBranchesKey in destinationbranches) { - var newbranch = JSON.parse( - JSON.stringify(destinationbranches[destinationBranchesKey]) - ); - newbranch.id = uuidv4(); - newbranch.destination_id = newNodeData.id; - - newbranch._id = newbranch.id; - newbranch.source = newbranch.source_id; - newbranch.target = newbranch.destination_id; - cy.add({ - group: "edges", - data: newbranch, - }); - } - - //event.target.unselect(); - return - } - } - - return; - } else if (data.isDescriptor) { - console.log("Can't select descriptor"); - event.target.unselect(); - return; - } - - if (data.type === "ACTION") { - setSelectedComment({}) - //var curaction = JSON.parse(JSON.stringify(data)) - // FIXME: Trust it to just work? - //event.target.data() - var curaction = workflow.actions.find((a) => a.id === data.id) - if (!curaction || curaction === undefined) { - if (data.id !== undefined && data.app_name !== undefined) { - workflow.actions.push(data) - setWorkflow(workflow) - curaction = data - } else { - if (workflow.public !== true) { - alert.error("Action not found. Please remake it."); - } - - event.target.remove(); - return; - } - } - - //var newapps = JSON.parse(JSON.stringify(apps)) - var newapps = apps - if (apps === null || apps === undefined || apps.length === 0) { - newapps = filteredApps - } - - // Check ID first, then names etc - // That way it always selects the right IF it exists - var curapp = newapps.find((a) => - a.id === curaction.app_id - ) - - if (curapp === undefined || curapp === null) { - console.log("Couldn't find ID - checking with name & version") - - curapp = newapps.find((a) => - a.name === curaction.app_name && - (a.app_version === curaction.app_version || - (a.loop_versions !== null && - a.loop_versions.includes(curaction.app_version))) - ) - } - - if (curaction.template === true) { - //newapps. - const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase() - console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname) - - curaction.matching_actions = [] - for (var newAppskey in newapps) { - for (let actionsSubkey in newapps[newAppskey].actions) { - const tmpaction = newapps[newAppskey].actions[actionsSubkey] - if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) { - console.log("MATCH!: ", newapps[newAppskey]) - curaction.matching_actions.push({ - "app_name": newapps[newAppskey].name, - "app_version": newapps[newAppskey].app_version, - "app_id": newapps[newAppskey].id, - "action": tmpaction, - "large_image": newapps[newAppskey].large_image, - "app_index": newAppskey, - "action_index": actionsSubkey, - }) - } - } - } - } - - if (!curapp || curapp === undefined) { - console.log("APPS - couldn't find it: ", newapps) - //alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`); - - const tmpapp = { - name: curaction.app_name, - app_name: curaction.app_name, - app_version: curaction.app_version, - id: curaction.app_id, - actions: [curaction], - }; - - setSelectedApp(tmpapp); - setSelectedAction(curaction); - } else { - //if (curapp.id !== curaction.id) { - // curaction.app_id = curapp.id - // //.valueOf() - //} - curaction.app_id = curapp.id - - setAuthenticationType( - curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null ? { - type: "oauth2", - redirect_uri: curapp.authentication.redirect_uri, - refresh_uri: curapp.authentication.refresh_uri, - token_uri: curapp.authentication.token_uri, - scope: curapp.authentication.scope, - client_id: curapp.authentication.client_id, - client_secret: curapp.authentication.client_secret, - } : { - type: "", - } - ) - - const requiresAuth = curapp.authentication.required; //&& ((curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null)) - setRequiresAuthentication(requiresAuth); - if (curapp.authentication.required) { - //console.log("App requires auth.") - // Setup auth here :) - const authenticationOptions = []; - var findAuthId = ""; - if ( - curaction.authentication_id !== null && - curaction.authentication_id !== undefined && - curaction.authentication_id.length > 0 - ) { - findAuthId = curaction.authentication_id; - } - - const tmpAuth = JSON.parse(JSON.stringify(newAppAuth)); - //console.log("FOUND AUTH OPTIONS: ", tmpAuth) - - const curappName = curapp.name.toLowerCase() - for (let tmpAuthKey in tmpAuth) { - var item = tmpAuth[tmpAuthKey]; - - const newfields = {}; - if (item.app.name.toLowerCase() !== curappName) { - continue - } - - // Makes list into key:value object - for (let fieldkey in item.fields) { - if (item.fields[fieldkey] === undefined) { - console.log("Problem with filterkey in Node select", fieldkey) - continue - } - - const filterkey = item.fields[fieldkey]["key"] - if (filterkey === null || filterkey === undefined) { - console.log("Problem with filterkey 2. Null or undefined 3") - continue - } - - newfields[filterkey] = item.fields[fieldkey]["value"]; - } - - item.fields = newfields; - if (item.app.name.toLowerCase() === curappName) { - authenticationOptions.push(item); - if (item.id === findAuthId) { - curaction.selectedAuthentication = item; - } - } - } - - curaction.authentication = authenticationOptions; - if ( - curaction.selectedAuthentication === null || - curaction.selectedAuthentication === undefined || - curaction.selectedAuthentication.length === "" - ) { - curaction.selectedAuthentication = {}; - } - } else { - curaction.authentication = []; - curaction.authentication_id = ""; - curaction.selectedAuthentication = {}; - } - - if ( - curaction.parameters !== undefined && - curaction.parameters !== null && - curaction.parameters.length > 0 - ) { - for (var curActionParamKey in curaction.parameters) { - if ( - curaction.parameters[curActionParamKey].options !== undefined && - curaction.parameters[curActionParamKey].options !== null && - curaction.parameters[curActionParamKey].options.length > 0 && - curaction.parameters[curActionParamKey].value === "" - ) { - curaction.parameters[curActionParamKey].value = curaction.parameters[curActionParamKey].options[0]; - } - } - } else { - console.log("Should check APP if it has the same params as ACTION") - for (let actionKey in curapp.actions) { - const tmpaction = curapp.actions[actionKey] - if (tmpaction.name === curaction.name) { - console.log("Found action - needs change?", tmpaction) - if (tmpaction.parameters !== undefined && tmpaction.parameters !== null && tmpaction.parameters.length > 0) { - curaction.parameters = JSON.parse(JSON.stringify(tmpaction.parameters)) - } - break - } - } - } - - //curaction["authentication"] = [] - //curaction["authentication_id"] = "" - // Fix parameters that are... Not ideal - //var paramnames = [] - //var newparams = [] - //for (let paramKey in curaction.parameters) { - // console.log("Name: ", curaction.parameters[paramKey].name) - // if (paramnames.includes(curaction.parameters[paramKey].name)) { - // continue - // } - - // paramnames.push(curaction.parameters[paramKey].name) - // newparams.push(curaction.parameters[paramKey]) - //} - - //curaction.parameters = newparams - console.log("ACTION CLICK: ", curaction) - - setSelectedApp(curapp); - setSelectedAction(curaction); - - cy.removeListener("drag"); - cy.removeListener("free"); - cy.on("drag", "node", (e) => onNodeDrag(e, curaction)); - cy.on("free", "node", (e) => onNodeDragStop(e, curaction)); - } - - if (environments !== undefined && environments !== null && (typeof environments === "array" || typeof environments === "object")) { - var parsedenv = environments - //if (typeof environments === "object") { - // parsedenv = [environments] - //} - - const envs = parsedenv.find((a) => a.Name === curaction.environment); - var env = environments[defaultEnvironmentIndex] - if (envs !== undefined && envs !== null) { - env = envs - } - - setSelectedActionEnvironment(env); - } - } else if (data.type === "TRIGGER") { - setSelectedComment({}) - if (workflow.triggers === null) { - workflow.triggers = [] - } - - var trigger_index = workflow.triggers.findIndex( - (a) => a.id === data.id - ); - - //console.log("Trigger: ", data, trigger_index) - if (trigger_index === -1) { - workflow.triggers.push(data) - trigger_index = workflow.triggers.length - 1 - setWorkflow(workflow) - } - - //console.log("Trigger2: ", data, trigger_index) - //if (data.id !== undefined && data.app_name !== undefined) { - // //newapps.push(data) - // workflow.actions.push(data) - // curaction = data - //} else { - // alert.error("Action not found. Please remake it."); - // event.target.remove(); - // return; - //} - - if (data.app_name === "Shuffle Workflow") { - getAvailableWorkflows(trigger_index); - getSettings(); - } else if (data.app_name === "Webhook") { - if (workflow.triggers[trigger_index].parameters !== undefined && workflow.triggers[trigger_index].parameters !== null && workflow.triggers[trigger_index].parameters.length > 0) { - console.log("Can set params here!") - workflow.triggers[trigger_index].parameters[0] = { - name: "url", - value: referenceUrl + "webhook_" + workflow.triggers[trigger_index].id, - }; - - if (workflow.triggers[trigger_index].parameters.length < 5) { - console.log("Adding to webhook params!") - workflow.triggers[trigger_index].parameters.push({ - name: "await_response", - value: "v1," - }) - } - } - } - - setSelectedTriggerIndex(trigger_index); - setSelectedTrigger(data); - setSelectedActionEnvironment(data.env); - } else if (data.type === "COMMENT") { - setSelectedComment(data); - } else { - alert.error("Can't handle " + data.type); - return; - } - - setRightSideBarOpen(true); - setLastSaved(false); - setScrollConfig({ - top: 0, - left: 0, - selected: "", - }); - - console.log("DOne in the node update") - - sendStreamRequest({ - "item": "node", - "type": "select", - "id": data.id, - "userid": userdata.id, - "location": { - "x": event.target.position("x"), - "y": event.target.position("y"), - } - }) - - }) - } - - const activateApp = (appid, refresh) => { - fetch(globalUrl + "/api/v1/apps/" + appid + "/activate", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Failed to activate") - } - - return response.json() - }) - .then((responseJson) => { - if (responseJson.success === false) { - alert.error("Failed to activate the app") - } else { - alert.success("App activated for your organization! Refresh the page to use the app.") - - if (refresh === true) { - getApps() - } - } - }) - .catch(error => { - //alert.error(error.toString()) - console.log("Activate app error: ", error.toString()) - }); - } - - const GetExampleResult = (item) => { - var exampledata = item.example === undefined ? "" : item.example; - if (workflowExecutions.length > 0) { - // Look for the ID - for (let execkey in workflowExecutions) { - if ( - workflowExecutions[execkey].results === undefined || - workflowExecutions[execkey].results === null - ) { - continue; - } - - var foundResult = { result: "" }; - if (item.id === "exec") { - if ( - workflowExecutions[execkey].execution_argument !== undefined && - workflowExecutions[execkey].execution_argument !== null && - workflowExecutions[execkey].execution_argument.length > 0 - ) { - foundResult.result = workflowExecutions[execkey].execution_argument; - } else { - continue; - } - } else { - foundResult = workflowExecutions[execkey].results.find( - (result) => result.action.id === item.id - ); - if (foundResult === undefined) { - continue; - } - } - - foundResult.result = foundResult.result.trim(); - foundResult.result = foundResult.result.split(" None").join(' "None"'); - foundResult.result = foundResult.result.split(" False").join(" false"); - foundResult.result = foundResult.result.split(" True").join(" true"); - - var jsonvalid = true; - try { - if ( - !foundResult.result.includes("{") && - !foundResult.result.includes("[") - ) { - jsonvalid = false; - } - } catch (e) { - try { - foundResult.result = foundResult.result.split("'").join('"'); - if ( - !foundResult.result.includes("{") && - !foundResult.result.includes("[") - ) { - jsonvalid = false; - } - } catch (e) { - jsonvalid = false; - } - } - - // Finds the FIRST json only - if (jsonvalid) { - try { - exampledata = JSON.parse(foundResult.result) - } catch (e) { - console.log("Result: ", exampledata) - - } - - break; - } - } - } - - return exampledata; - }; - - const GetParamMatch = (paramname, exampledata, basekey) => { - if (typeof exampledata !== "object") { - return ""; - } - - if (exampledata === null) { - return ""; - } - - //console.log("NOT REPLACING ON PURPOSE!!") - //return "" - - // Basically just a stupid if-else :) - const synonyms = { - id: [ - "id", - "ref", - "sourceref", - "reference", - "sourcereference", - "alert id", - "case id", - "incident id", - "service id", - "sid", - "uid", - "uuid", - "team id", - "message id", - "message_id", - ], - title: ["title", "name", "message"], - description: ["description", "explanation", "story", "details"], - email: ["mail", "email", "sender", "receiver", "recipient"], - data: [ - "data", - "ip", - "domain", - "url", - "hash", - "md5", - "sha2", - "sha256", - "value", - "item", - ], - tags: ["tags", "taxonomies"], - }; - - // 1. Find the right synonym - // 2. Replace with an autocomplete if it exists - var selectedsynonyms = [paramname]; - for (const [key, value] of Object.entries(synonyms)) { - if (key === paramname || value.includes(paramname)) { - if (!value.includes(key)) { - value.push(key.toLowerCase()); - } - - selectedsynonyms = value; - break; - } - } - - var toreturn = ""; - - for (const [key, value] of Object.entries(exampledata)) { - // Check if loop or JSON - - if (typeof value === "object") { - if (Array.isArray(value)) { - var selectedkey = ""; - if (isNaN(key)) { - selectedkey = `.${key}`; - } - - for (let [subitem,subitemval] in Object.entries(value)) { - toreturn = GetParamMatch( - paramname, - value[subitem], - `${basekey}${selectedkey}.#` - ); - if (toreturn.length > 0) { - break; - } - } - - if (toreturn.length > 0) { - break; - } - - } else { - var selectedkey = ""; - if (isNaN(key)) { - selectedkey = `.${key}`; - } - - toreturn = GetParamMatch( - paramname, - value, - `${basekey}${selectedkey}` - ); - if (toreturn.length > 0) { - break; - } - } - } else { - if (selectedsynonyms.includes(key.toLowerCase())) { - toreturn = `${basekey}.${key}` - break - } - } - } - - return toreturn; - }; - - // Takes an action as input, then runs through and updates the relevant fields - // based on previous actions' - // Uses lots of synonyms - // autocomplete - const RunAutocompleter = (dstdata) => { - // **PS: The right action should already be set here** - // 1. Check execution argument - // 2. Check parents in order - var exampledata = GetExampleResult({ id: "exec", name: "exec" }); - var parentlabel = "exec"; - for (var dstdataParamKey in dstdata.parameters) { - const param = dstdata.parameters[dstdataParamKey]; - // Skip authentication params - if (param.configuration) { - continue - } - - if (param.options !== undefined && param.options !== null && param.options.length > 0) { - continue - } - - const paramname = param.name.toLowerCase().trim().replaceAll("_", " "); - - const foundresult = GetParamMatch(paramname, exampledata, ""); - if (foundresult.length > 0) { - console.log("FOUND ReS for field: ", dstdata.parameters[dstdataParamKey].name, foundresult) - if (dstdata.parameters[dstdataParamKey].value.length === 0) { - dstdata.parameters[dstdataParamKey].value = `$${parentlabel}${foundresult}`; - dstdata.parameters[dstdataParamKey].autocompleted = true - } - } - } - - var parents = getParents(dstdata); - if (parents.length > 1) { - for (let parentkey in parents) { - const item = parents[parentkey]; - if (item.label === "Execution Argument") { - continue; - } - - parentlabel = - item.label === undefined - ? "" - : item.label.toLowerCase().trim().replaceAll(" ", "_"); - - exampledata = GetExampleResult(item); - if (dstdata.parameters !== undefined && dstdata.parameters !== null) { - for (let [paramkey,paramkeyval] in Object.entries(dstdata.parameters)) { - const param = dstdata.parameters[paramkey]; - // Skip authentication params - if (param.configuration) { - continue - } - - if (param.options !== undefined && param.options !== null && param.options.length > 0) { - continue - } - - const paramname = param.name - .toLowerCase() - .trim() - .replaceAll("_", " "); - - const foundresult = GetParamMatch(paramname, exampledata, ""); - if (foundresult.length > 0) { - if (dstdata.parameters[paramkey].value.length === 0) { - dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`; - dstdata.parameters[paramkey].autocompleted = true - } else { - //dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`; - } - } - } - } - // Check agains every param - } - } - - return dstdata; - }; - - // Checks for errors in edges when they're added - const onEdgeAdded = (event) => { - setLastSaved(false); - const edge = event.target.data(); - - console.log("edge added: ", edge) - if (edge.source === undefined && edge.target === undefined) { - return - } - - if (edge.readded === true) { - console.log("Readded edge - stopping") - - event.target.data("readded", false) - return - } - - const sourcenode = cy.getElementById(edge.source) - const destinationnode = cy.getElementById(edge.target) - if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { - } else { - console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) - if (sourcenode.data("type") === "TRIGGER") { - if (sourcenode.data("app_name") !== "Shuffle Workflow" && sourcenode.data("app_name") !== "User Input") { - setTimeout(() => { - const alledges = cy.edges().jsons() - console.log("edges: ", alledges, edge) - var targetedge = alledges.findIndex( - (data) => data.data.source === edge.source && data.data.id !== edge.id - ) - - console.log("Node: ", targetedge) - if (targetedge !== -1) { - event.target.remove() - - //console.log("Found branch already!") - alert.info("Triggers can have exactly one target node") - return - - - // name: "Shuffle Workflow", - // name: "User Input", - } else { - console.log("Node doesn't already have one") - } - }, 50) - } - } - - const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position()) - const currentedge = cy.getElementById(edge.id) - if (currentedge !== undefined && currentedge !== null) { - currentedge.style('control-point-distance', edgeCurve.distance) - currentedge.style('control-point-weight', edgeCurve.weight) - } - - } - - - var targetnode = workflow.triggers.findIndex( - (data) => data.id === edge.target - ); - if (targetnode !== -1) { - console.log("TARGETNODE: ", targetnode); - if ( - workflow.triggers[targetnode].app_name === "User Input" || - workflow.triggers[targetnode].app_name === "Shuffle Workflow" - ) { - } else { - alert.error("Can't have triggers as target of branch"); - event.target.remove(); - } - } - - const eventTarget = event.target.target() - console.log("BUTTON ADDED! Find parent from: ", eventTarget) - if (eventTarget.data("isButton") === true) { - const parentNode = cy.getElementById(eventTarget.data("attachedTo")) - event.target.remove() - console.log("Setting it to parentnode: ", parentNode.data()) - if (parentNode !== undefined && parentNode !== null) { - //event.target.data("target", eventTarget.data("attachedTo")) - - const newEdgeUuid = uuidv4() - const newcybranch = { - source: event.target.data("source"), - target: eventTarget.data("attachedTo"), - _id: newEdgeUuid, - id: newEdgeUuid, - hasErrors: event.target.data("hasErrors"), - }; - - const edgeToBeAdded = { - group: "edges", - data: newcybranch, - } - - cy.add(edgeToBeAdded); - } - } - - if ( - eventTarget.data("isDescriptor") === true || - eventTarget.data("type") === "COMMENT" - ) { - console.log("Removing because of descriptor or comment") - event.target.remove(); - return; - } - - targetnode = -1; - - // Check if: - // dest == source && source == dest - // dest == dest && source == source - // backend: check all children? to stop recursion - var found = false; - for (let branchkey in workflow.branches) { - if ( - workflow.branches[branchkey].destination_id === edge.source && - workflow.branches[branchkey].source_id === edge.target - ) { - alert.error("A branch in the opposite direction already exists"); - event.target.remove(); - found = true; - break; - } else if ( - workflow.branches[branchkey].destination_id === edge.target && - workflow.branches[branchkey].source_id === edge.source - ) { - console.log(edge.source); - alert.error("That branch already exists"); - event.target.remove(); - found = true; - break; - } else if (edge.target === workflow.start) { - targetnode = workflow.triggers.findIndex( - (data) => data.id === edge.source - ); - if (targetnode === -1) { - if (targetnode.type !== "TRIGGER") { - alert.error("Can't make arrow to starting node"); - event.target.remove(); - break; - } - - found = true; - } - } else if (edge.source === workflow.branches[branchkey].source_id) { - // FIXME: Verify multi-target for triggers - // 1. Check if destination exists - // 2. Check if source is a trigger - // targetnode = workflow.triggers.findIndex(data => data.id === edge.source) - // console.log("Destination: ", edge.target) - // console.log("CHECK SOURCE IF ITS A TRIGGER: ", targetnode) - // if (targetnode !== -1) { - // alert.error("Triggers can only target one target (startnode)") - // event.target.remove() - // found = true - // break - // } - } else { - //console.log("INSIDE LAST CHECK: ", edge) - // Find the targetnode and check if its a trigger - // FIXME - do this for both actions and other types? - /* - targetnode = workflow.triggers.findIndex(data => data.id === edge.target) - if (targetnode !== -1) { - console.log("TARGETNODE: ", targetnode) - if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow") { - } else { - alert.error("Can't have triggers as target of branch") - event.target.remove() - found = true - break - } - } - */ - } - } - - // 1. Guess what the next node's action should be - // 2. Get result from previous nodes (if any) - // 3. TRY to automatically map them in based on synonyms - const newsource = cy.getElementById(edge.source); - const newdst = cy.getElementById(edge.target); - if ( - newsource !== undefined && - newsource !== null && - newdst !== undefined && - newdst !== null - ) { - const dstdata = RunAutocompleter(newdst.data()); - console.log("AUTO DST: ", dstdata); - } - - var newbranch = { - source_id: edge.source, - destination_id: edge.target, - id_: edge.id, - id: edge.id, - hasErrors: false, - decorator: false, - }; - - if (!found) { - newbranch["hasErrors"] = false; - - workflow.branches.push(newbranch); - setWorkflow(workflow); - } - - history.push({ - type: "edge", - action: "added", - data: edge, - }); - setHistory(history); - setHistoryIndex(history.length); - }; - - const onNodeAdded = (event) => { - const node = event.target; - const nodedata = event.target.data(); - - //if (Object.keys(nodedata).length === 1) { - // console.log("Check if another node actually exists before adding") - //} - - if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined) - ) { - //if (nodedata.app_id === undefined) { - //console.log("Returning because node is not valid: ", nodedata) - return; - } - - - // DONT MOVE THIS LINE RIGHT HERE v - setLastSaved(false) - // Dont move the line above. May break stuff. - - - if (node.isNode() && cy.nodes().size() === 1) { - workflow.start = node.data("id"); - nodedata.isStartNode = true; - } else { - if (workflow.actions === null) { - console.log("Returning because node has no value") - return; - } - - // Remove bad startnode - for (let actionkey in workflow.actions) { - const action = workflow.actions[actionkey]; - if (action.isStartNode && workflow.start !== action.id) { - action.isStartNode = false; - } - } - } - - if (nodedata.type === "ACTION") { - /* - var curaction = workflow.actions.find((a) => a.id === nodedata.id); - if (curaction === null || curaction === undefined) { - alert.error("Node not found. Please remake it.") - event.target.remove(); - } - */ - if ( - workflow.actions.length === 1 && - workflow.actions[0].id === workflow.start - ) { - const newEdgeUuid = uuidv4(); - const newcybranch = { - source: workflow.start, - target: nodedata.id, - _id: newEdgeUuid, - id: newEdgeUuid, - hasErrors: false, - }; - - const edgeToBeAdded = { - group: "edges", - data: newcybranch, - }; - - console.log("SHOULD STITCH WITH STARTNODE"); - cy.add(edgeToBeAdded); - } - - if (nodedata.app_name === "Shuffle Tools") { - const iconInfo = GetIconInfo(nodedata); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - nodedata.large_image = svgpin_Url; - nodedata.fillGradient = iconInfo.fillGradient; - nodedata.fillstyle = "solid"; - if ( - nodedata.fillGradient !== undefined && - nodedata.fillGradient !== null && - nodedata.fillGradient.length > 0 - ) { - nodedata.fillstyle = "linear-gradient"; - } else { - nodedata.iconBackground = iconInfo.iconBackgroundColor; - } - } - - - if ( - nodedata.parameters !== undefined && - nodedata.parameters !== null && - !nodedata.label.endsWith("_copy") - ) { - var newparameters = []; - - for (let [subkey,subkeyval] in Object.entries(nodedata.parameters)) { - var newparam = JSON.parse( - JSON.stringify(nodedata.parameters[subkey]) - ); - newparam.id = uuidv4(); - - if (newparam.value === undefined || newparam.value === null) { - newparam.value = ""; - } else { - newparam.value = newparam.value; - } - - newparameters.push(newparam); - } - - nodedata.parameters = newparameters; - } - - if (workflow.actions === undefined || workflow.actions === null) { - workflow.actions = [nodedata]; - } else { - workflow.actions.push(nodedata); - } - - setWorkflow(workflow); - } else if (nodedata.type === "TRIGGER") { - if (nodedata.is_valid === false) { - alert.info("This trigger is not available to you"); - node.remove(); - return; - } - - if (workflow.triggers === undefined) { - workflow.triggers = [nodedata]; - } else { - workflow.triggers.push(nodedata); - } - - const newEdgeUuid = uuidv4(); - const newcybranch = { - source: nodedata.id, - target: workflow.start, - source_id: nodedata.id, - destination_id: workflow.start, - _id: newEdgeUuid, - id: newEdgeUuid, - hasErrors: false, - decorator: false, - }; - - const edgeToBeAdded = { - group: "edges", - data: newcybranch, - }; - - if ( - nodedata.name !== "User Input" && - nodedata.name !== "Shuffle Workflow" - ) { - if ( - workflow.actions !== undefined && - workflow.actions !== null && - workflow.actions.length > 0 - ) { - cy.add(edgeToBeAdded); - } - } - - setWorkflow(workflow); - } - - if (nodedata.app_name !== undefined) { - history.push({ - type: "node", - action: "added", - data: nodedata, - }); - setHistory(history); - setHistoryIndex(history.length); - } - }; - - const onEdgeRemoved = (event) => { - setLastSaved(false); - - const edge = event.target; - if (edge.data("decorator") === true) { - return; - } - - // Check if the source is trigger and can start - console.log("Removed: ", edge.data()) - const allNodes = cy.nodes().jsons() - for (let nodekey in allNodes) { - const curnode = allNodes[nodekey] - if (curnode.data.type !== "TRIGGER") { - continue - } - - if (curnode.data.id === edge.data("source")) { - console.log("Found matching trigger source: ", curnode) - if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") { - // If it's started, READD the edge - if (curnode.data.status === "running") { - console.log("Edge is running - readd it: ", edge.data()) - - // Just making sure it's not running infinitely - var newdata = edge.data() - newdata.readded = true - - try { - cy.add({ - group: "edges", - data: newdata, - }) - - alert.error("You must STOP the trigger before deleting its branches") - } catch (e) { - console.log("Failed re-adding edge: ", e) - } - } - - //status: "uninitialized", - } - } - } - - workflow.branches = workflow.branches.filter( - (a) => a.id !== edge.data().id - ); - setWorkflow(workflow); - event.target.remove(); - - // trigger as source check - const indexcheck = workflow.triggers.findIndex( - (data) => edge.data("source") === data.id - ); - if (indexcheck !== -1) { - console.log("Shouldnt remove edge from trigger? "); - } - - if (edge.data().source !== undefined) { - history.push({ - type: "edge", - action: "removed", - data: edge.data(), - }); - - setHistory(history); - setHistoryIndex(history.length); - } - }; - - const onNodeRemoved = (event) => { - const node = event.target; - const data = node.data(); - - if (data.finished === false) { - return - } - - workflow.actions = workflow.actions.filter((a) => a.id !== data.id); - workflow.triggers = workflow.triggers.filter((a) => a.id !== data.id); - if (workflow.start === data.id && workflow.actions.length > 0) { - // FIXME - should check branches connected to startnode, as picking random - // is just confusing - if (workflow.actions[0].id !== data.id) { - const ele = cy.getElementById(workflow.actions[0].id); - if (ele !== undefined && ele !== null) { - ele.data("isStartNode", true); - workflow.start = ele.id(); - } - } else { - if (workflow.actions.length > 1) { - const ele = cy.getElementById(workflow.actions[1].id); - if (ele !== undefined && ele !== null) { - ele.data("isStartNode", true); - workflow.start = ele.id(); - } - } - } - } - - if (data.app_name !== undefined) { - const allNodes = cy.nodes().jsons(); - for (let allNodesKey in allNodes) { - const currentNode = allNodes[allNodesKey]; - if (currentNode.data.attachedTo === data.id) { - cy.getElementById(currentNode.data.id).remove(); - } - } - - history.push({ - type: "node", - action: "removed", - data: data, - }) - - //console.log("REMOVED: ", data) - setHistory(history); - setHistoryIndex(history.length); - } - - setWorkflow(workflow); - if (data.type === "TRIGGER") { - saveWorkflow(workflow); - } - }; - - //var previouskey = 0 - const handleKeyDown = (event) => { - switch (event.keyCode) { - case 27: - if (configureWorkflowModalOpen === true) { - setConfigureWorkflowModalOpen(false); - } - break; - case 46: - 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 (event.ctrlKey) { - console.log("CTRL+Z"); - } - - break; - case 67: - if (event.ctrlKey && !event.shiftKey) { - if ( - event.path !== undefined && - event.path !== null && - event.path.length > 0 - ) { - if (event.path[0].localName !== "body") { - console.log("Skipping because body is not targeted"); - return; - } - } - - if ( - event.target !== undefined && - event.target !== null - ) { - if (event.target.localName !== "body") { - console.log("Skipping because body is not targeted") - return; - } - } - - console.log("CTRL+C"); - if (cy !== undefined) { - var cydata = cy.$(":selected").jsons(); - if (cydata !== undefined && cydata !== null && cydata.length > 0) { - console.log(cydata); - - const elementName = "copy_element_shuffle"; - var copyText = document.getElementById(elementName); - if (copyText !== null && copyText !== undefined) { - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)"); - return; - } - - navigator.clipboard.writeText(JSON.stringify(cydata)); - copyText.select(); - copyText.setSelectionRange(0, 99999); /* For mobile devices */ - document.execCommand("copy"); - alert.success(`Copied ${cydata.length} element(s)`); - } - } - } - } - break; - case 86: - if (event.ctrlKey) { - //console.log("CTRL+V") - // The below parts are handled in the function handlePaste() - /* - const clipboard = navigator.clipboard - if (clipboard === undefined || window === undefined || window === null) { - alert.error("Can only use cliboard over HTTPS (port 3443)") - return - } - - console.log("CLIPBOARD: ", window.clipboardData) - const pastedData = window.clipboardData.getData('Text'); - console.log("PASTED: ", pastedData) - - - //var tmpAuth = JSON.parse(JSON.stringify(appAuthentication)) - var jsonvalid = true - var parsedjson = [] - */ - } - break; - case 88: - if (event.ctrlKey) { - console.log("CTRL+X"); - } - break; - case 83: - break; - case 70: - 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, which does NOT work while inside a field - break; - default: - break; - } - - //previouskey = event.keyCode - }; - - const handlePaste = (event) => { - //console.log("EV: ", event) - if ( - event.path !== undefined && - event.path !== null && - event.path.length > 0 - ) { - //console.log("PATH: ", event.path[0]) - if (event.path[0].localName !== "body") { - //console.log("Skipping because body is not targeted") - return; - } - } - - //console.log("PATH2: ", event.target) - if ( - event.target !== undefined && - event.target !== null - ) { - if (event.target.localName !== "body") { - //console.log("Skipping because body is not targeted") - return; - } - } - - - event.preventDefault(); - const clipboard = (event.originalEvent || event).clipboardData.getData( - "text/plain" - ); - //console.log("Text: ", clipboard) - //window.document.execCommand('insertText', false, text); - // - try { - const parsedjson = JSON.parse(clipboard); - //console.log("Parsed: ", parsedjson) - - for (let jsonkey in parsedjson) { - const item = parsedjson[jsonkey]; - console.log("Adding: ", item); - item.data.id = uuidv4() - - cy.add({ - group: item.group, - data: item.data, - position: item.position, - }); - } - } catch (e) { - console.log("Error pasting: ", e); - //alert.info("Failed parsing clipboard: ", e) - } - }; - - const registerKeys = () => { - document.addEventListener("keydown", handleKeyDown); - document.addEventListener("paste", handlePaste); - }; - - 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 envs :O!"); - if (isCloud) { - setEnvironments([{ Name: "Cloud", Type: "cloud" }]); - } else { - setEnvironments([{ Name: "Onprem", Type: "onprem" }]); - } - - return; - } - - return response.json(); - }) - .then((responseJson) => { - var found = false; - var showEnvCnt = 0; - for (let jsonkey in responseJson) { - if (responseJson[jsonkey].default && !found) { - setDefaultEnvironmentIndex(jsonkey); - found = true; - } - - if (responseJson[jsonkey].archived === false) { - showEnvCnt += 1; - } - } - - if (showEnvCnt > 1) { - setShowEnvironment(true); - } - - if (!found) { - for (let jsonkey in responseJson) { - if (!responseJson[jsonkey].archived) { - setDefaultEnvironmentIndex(jsonkey); - break; - } - } - } - - // FIXME: Don't allow multiple in cloud yet. Cloud -> Onprem isn't stable. - if (isCloud) { - if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { - setEnvironments(responseJson); - } else { - setEnvironments([{ Name: "Cloud", Type: "cloud" }]); - } - } else { - setEnvironments(responseJson); - } - }) - .catch((error) => { - //alert.error(error.toString()); - console.log("Get environments error: ", error.toString()); - }); - }; - - if ( - !firstrequest && - graphSetup && - established && - props.match.params.key !== workflow.id && - workflow.id !== undefined && - workflow.id !== null && - workflow.id.length > 0 - ) { - window.location.pathname = "/workflows/" + props.match.params.key; - } - - const animationDuration = 150; - const onNodeHoverOut = (event) => { - // console.log("Hover out: ", event.target.data()); - - const nodedata = event.target.data(); - // console.log("nodedata", nodedata); - // console.log("nodedata.app_name: ", nodedata.app_name); - if (nodedata.app_name !== undefined) { - - const allNodes = cy.nodes().jsons(); - // console.log("allNodes: ", allNodes) - for (var nodekey in allNodes) { - const currentNode = allNodes[nodekey]; - // console.log("Current node: ", currentNode); - if ( - currentNode.data.isButton && - currentNode.data.attachedTo !== nodedata.id - ) { - cy.getElementById(currentNode.data.id).remove(); - } - - } - } - - const cytoscapeElement = document.getElementById("cytoscape_view") - if (cytoscapeElement !== undefined && cytoscapeElement !== null) { - cytoscapeElement.style.cursor = "default" - } - - // Skipping node editing if it's the selected one - if (cy !== undefined) { - const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { - const item = typeIds[idkey] - if (item.data.id === nodedata.id) { - return - } - } - } - //if (nodedata.id === selectedAction.id || nodedata.id === selectedTrigger.id) { - // return - //} - - var parsedStyle = { - "border-width": "1px", - "font-size": "18px", - //"cursor": "default", - } - - if ((nodedata.app_name === "Testing" || nodedata.app_name === "Shuffle Tools") && !nodedata.isStartNode) { - parsedStyle = { - "border-width": "1px", - "font-size": "0px", - }; - } - - event.target.animate( - { - style: parsedStyle, - }, - { - duration: animationDuration, - } - ); - - const outgoingEdges = event.target.outgoers("edge"); - const incomingEdges = event.target.incomers("edge"); - if (outgoingEdges.length > 0) { - outgoingEdges.removeClass("hover-highlight"); - } - - if (incomingEdges.length > 0) { - outgoingEdges.removeClass("hover-highlight"); - } - }; - - const buttonColor = "rgba(255,255,255,0.9)"; - const buttonBackgroundColor = "#1f2023"; - const addStartnodeButton = (event) => { - var parentNode = cy.$("#" + event.target.data("id")); - if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - - if (parentNode.data("isStartNode")) { - return; - } - - const px = parentNode.position("x") - 65; - const py = parentNode.position("y") - 45; - const circleId = (newNodeId = uuidv4()); - - parentNode.data("circleId", circleId); - - const iconInfo = { - icon: "M9.4 2H15V12H8L7.6 10H2V17H0V0H9L9.4 2ZM9 10H11V8H13V6H11V4H9V6L8 4V2H6V4H4V2H2V4H4V6H2V8H4V6H6V8H8V6L9 8V10ZM6 6V4H8V6H6ZM9 6H11V8H9V6Z", - iconColor: buttonColor, - iconBackgroundColor: buttonBackgroundColor, - }; - - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - - cy.add({ - group: "nodes", - data: { - weight: 30, - id: circleId, - name: "TEEEXT", - isButton: true, - buttonType: "set_startnode", - attachedTo: event.target.data("id"), - icon: svgpin_Url, - iconBackground: iconInfo.iconBackgroundColor, - is_valid: true, - }, - position: { x: px, y: py }, - locked: true, - }); - }; - - const addCopyButton = (event) => { - var parentNode = cy.$("#" + event.target.data("id")); - if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - - const px = parentNode.position("x") - 65; - const py = parentNode.position("y") - 5; - const circleId = (newNodeId = uuidv4()); - - parentNode.data("circleId", circleId); - - const iconInfo = { - icon: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z", - iconColor: buttonColor, - iconBackgroundColor: buttonBackgroundColor, - }; - - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - - cy.add({ - group: "nodes", - data: { - weight: 30, - id: circleId, - name: "TEEEXT", - isButton: true, - buttonType: "copy", - attachedTo: event.target.data("id"), - icon: svgpin_Url, - iconBackground: iconInfo.iconBackgroundColor, - is_valid: true, - }, - position: { x: px, y: py }, - locked: true, - }); - }; - - const addSuggestionButtons = (event) => { - var parentNode = cy.$("#" + event.target.data("id")); - if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - - const px = parentNode.position("x") + 300; - const py = parentNode.position("y") + 100; - const circleId = (newNodeId = uuidv4()); - - parentNode.data("circleId", circleId); - - var appid = "1234" - var suggestions = [{ - app_name: "TheHive", - app_version: "1.1.0", - app_id: appid, - sharing: false, - private_id: false, - isStartNode: false, - label: "Suggestion 1", - large_image: "data:image/png;base64,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", - finished: false, - is_valid: true, - isSuggestion: true, - attachedTo: event.target.data("id"), - }] - //isButton: true, - - cy.add({ - group: "nodes", - data: suggestions[0], - position: { x: px, y: py }, - locked: true, - }); - - //suggestions[0].id = uuidv4() - //cy.add({ - // group: "nodes", - // data: suggestions[0], - // position: { x: parentNode.position("x") + 300, y: parentNode.position("y") - 100}, - // locked: true, - //}); - } - - const addDeleteButton2 = (event) => { - var parentNode = cy.$("#" + event.target.data("id")); - if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - - const px = parentNode.position("x") + 100; - const py = parentNode.position("y") + 35; - const circleId = (newNodeId = uuidv4()); - - parentNode.data("circleId", circleId); - - const iconInfo = { - icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z", - iconColor: buttonColor, - iconBackgroundColor: buttonBackgroundColor, - }; - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - - cy.add({ - group: "nodes", - data: { - weight: 30, - id: circleId, - name: "This is autocomplete", - buttonType: "delete", - attachedTo: event.target.data("id"), - icon: svgpin_Url, - iconBackground: iconInfo.iconBackgroundColor, - is_valid: true, - }, - position: { x: px, y: py }, - locked: true, - }); - }; - - const addDeleteButton = (event) => { - var parentNode = cy.$("#" + event.target.data("id")); - if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - - const px = parentNode.position("x") - 65; - const py = parentNode.position("y") + 35; - const circleId = (newNodeId = uuidv4()); - - parentNode.data("circleId", circleId); - - const iconInfo = { - icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z", - iconColor: buttonColor, - iconBackgroundColor: buttonBackgroundColor, - }; - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - - cy.add({ - group: "nodes", - data: { - weight: 30, - id: circleId, - name: "TEEEXT", - isButton: true, - buttonType: "delete", - attachedTo: event.target.data("id"), - icon: svgpin_Url, - iconBackground: iconInfo.iconBackgroundColor, - is_valid: true, - }, - position: { x: px, y: py }, - locked: true, - }); - }; - - const onNodeHover = (event) => { - const nodedata = event.target.data(); - - if (nodedata.finished === false) { - console.log("NODE: ", nodedata) - return; - } - - const cytoscapeElement = document.getElementById("cytoscape_view") - if (cytoscapeElement !== undefined && cytoscapeElement !== null) { - cytoscapeElement.style.cursor = "pointer" - } - - //var parentNode = cy.$("#" + event.target.data("id")); - //if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - - if (nodedata.app_name !== undefined && !workflow.public === true) { - const allNodes = cy.nodes().jsons(); - - var found = false; - for (var _key in allNodes) { - const currentNode = allNodes[_key]; - // console.log("CURRENT NODE: ", currentNode) - if ( - (currentNode.data.isButton || currentNode.data.isSuggestion) && - currentNode.data.attachedTo !== nodedata.id - ) { - cy.getElementById(currentNode.data.id).remove(); - } - - /*if ( - currentNode.data.isSuggestion && - currentNode.data.attachedTo !== nodedata.id - ) { - cy.getElementById(currentNode.data.id).remove(); - }*/ - - if ( - currentNode.data.isButton && - currentNode.data.attachedTo === nodedata.id - ) { - found = true; - } - } - - if (!found) { - addDeleteButton(event); - - if (nodedata.type === "TRIGGER") { - if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") { - addCopyButton(event); - } - } else { - addCopyButton(event); - addStartnodeButton(event); - } - - // autocomplete - // right click - // suggestions - //addSuggestionButtons(event); - } - } - - var parsedStyle = { - "border-width": "7px", - "border-opacity": ".7", - "font-size": "25px", - //"cursor": "pointer", - } - - const typeIds = cy.elements('node:selected').jsons(); - for (var idkey in typeIds) { - const item = typeIds[idkey] - if (item.data.id === nodedata.id) { - //console.log("items: ", item.data.id, nodedata.id) - parsedStyle["border-width"] = "12px" - break - } - } - - if (nodedata.type !== "COMMENT") { - parsedStyle.color = "white"; - - //if (!event.target.data("isButton") && !event.target.data("buttonId")) { - // const px = event.target.position("x") - 0; - // const py = event.target.position("y") - 50; - // const circleId = (newNodeId = uuidv4()); - - // console.log("Got px, py: ", px, py) - // - // cy.add({ - // group: "nodes", - // data: { - // weight: 30, - // id: circleId, - // isButton: true, - // attachedTo: event.target.data("id"), - // buttonType: "edgehandler", - // is_valid: true, - // }, - // position: { x: px, y: py }, - // locked: true, - // }) - //} - } - - if (event.target !== undefined && event.target !== null) { - event.target.animate( - { - style: parsedStyle, - }, - { - duration: animationDuration, - } - ); - } - - const outgoingEdges = event.target.outgoers("edge"); - const incomingEdges = event.target.incomers("edge"); - if (outgoingEdges.length > 0) { - outgoingEdges.addClass("hover-highlight"); - } - - if (incomingEdges.length > 0) { - outgoingEdges.addClass("hover-highlight"); - } - }; - - const onEdgeHoverOut = (event) => { - if (event === null || event === undefined) { - event.target.removeStyle(); - return; - } - - const edgeData = event.target.data(); - if (edgeData.decorator === true) { - return; - } - - const cytoscapeElement = document.getElementById("cytoscape_view") - if (cytoscapeElement !== undefined && cytoscapeElement !== null) { - cytoscapeElement.style.cursor = "default" - } - - //event.target.removeStyle(); - }; - - // This is here to have a proper transition for lines - const onEdgeHover = (event) => { - if (event === null || event === undefined) { - return; - } - - const edgeData = event.target.data(); - if (edgeData.decorator === true) { - return; - } - - const cytoscapeElement = document.getElementById("cytoscape_view") - if (cytoscapeElement !== undefined && cytoscapeElement !== null) { - cytoscapeElement.style.cursor = "pointer" - } - - //const sourcecolor = cy - // .getElementById(event.target.data("source")) - // .style("border-color"); - //const targetcolor = cy - // .getElementById(event.target.data("target")) - // .style("border-color"); - - ////console.log(sourcecolor, targetcolor) - //if ( - // sourcecolor !== null && - // sourcecolor !== undefined && - // targetcolor !== null && - // targetcolor !== undefined && - // !sourcecolor.includes("rgb") && - // !targetcolor.includes("rgb") - //) { - // console.log(sourcecolor) - // console.log(targetcolor) - - // if (event.target !== null && event.target.value !== null) { - // event.target.animate({ - // style: { - // "target-arrow-color": targetcolor, - // "line-fill": "linear-gradient", - // "line-gradient-stop-colors": [sourcecolor, targetcolor], - // "line-gradient-stop-positions": [0, 1], - // }, - // duration: animationDuration, - // }) - // } else { - // event.target.animate({ - // style: { - // "target-arrow-color": targetcolor, - // "line-fill": "linear-gradient", - // "line-gradient-stop-colors": ["#41dcab", "#41dcab"], - // "line-gradient-stop-positions": [0, 1], - // }, - // duration: animationDuration, - // }) - - // } - //} - - if (event.target !== undefined && event.target !== null) { - //const targetcolor = "#66a8b1" - //const parsedStyle = { - // "width": "10px", - // "font-size": "18px", - // "target-arrow-color": "#66a8b1", - // "color": "#66a8b1", - //} - - //event.target.addClass("shuffle-hover-highlight"); - - //console.log("Style1: ", event.target) - //console.log("Style: ", event.target.style()) - - //event.target.animate( - // { - // style: parsedStyle, - // }, - // { - // duration: animationDuration, - // } - //) - } - } - - // Calculates how a trigger should curve - // Thanks to: - // https://codepen.io/guillaumethomas/pen/xxbbBKO - const calculateEdgeCurve = (sourcenodePosition, destinationnodePosition) => { - const xParsed = destinationnodePosition.x - sourcenodePosition.x - const yParsed = destinationnodePosition.y - sourcenodePosition.y - - const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed); - const costheta = xParsed / z; - const alpha = 0.25; - var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta]; - var controlPointWeight = [alpha, 1 - alpha] - - //'control-point-weight': ['0.33', '0.66'], - //var controlPointWeight = ["0.33", "0.66"] - //var controlPointDistance = ["33%", "-66%"] - //var controlPointWeight = ["0.00", "1.00"] - /* - if (yParsed !== 0) { - //const degreeFound = Math.atan2(xParsed / yParsed) - const degreeFound = Math.atan2(xParsed, yParsed) * 180 / Math.PI - - if (degreeFound > 90 && degreeFound < 180) { - console.log("TOPRIGHT") - } else if (degreeFound < 90 && degreeFound > 0) { - console.log("BOTTOMRIGHT") - } else if (degreeFound < 0 && degreeFound > -90) { - console.log("BOTTOMLEFT") - //controlPointWeight = ["0.20", "0.80"] - //controlPointWeight = "0.7" - //controlPointDistance = "50%" - - } else if (degreeFound < -90 && degreeFound > -180) { - console.log("TOPLEFT") - } else { - console.log("STRAIGHT!") - } - } - */ - - return { - "distance": controlPointDistance, - "weight": controlPointWeight, - } - } - - const setupGraph = () => { - const actions = workflow.actions.map((action) => { - const node = {}; - - if (!action.isStartNode && action.app_name === "Shuffle Tools") { - const iconInfo = GetIconInfo(action); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - action.large_image = svgpin_Url; - action.fillGradient = iconInfo.fillGradient; - action.fillstyle = "solid"; - if ( - action.fillGradient !== undefined && - action.fillGradient !== null && - action.fillGradient.length > 0 - ) { - action.fillstyle = "linear-gradient"; - } else { - action.iconBackground = iconInfo.iconBackgroundColor; - } - } - - node.position = action.position; - node.data = action; - - node.data.id = action["id"]; - node.data._id = action["id"]; - node.data.type = "ACTION"; - node.isStartNode = action["id"] === workflow.start; - - if (workflow.public === true) { - node.data.is_valid = true - node.is_valid = true - } - - var example = ""; - if ( - action.example !== undefined && - action.example !== null && - action.example.length > 0 - ) { - example = action.example; - } - - node.data.example = example; - - return node; - }); - - const decoratorNodes = workflow.actions.map((action) => { - if (!action.isStartNode) { - if (action.app_name === "Testing") { - return null; - } else if (action.app_name === "Shuffle Tools") { - return null; - } - } - - const iconInfo = GetIconInfo(action); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - - const offset = action.isStartNode ? 36 : 44; - const decoratorNode = { - position: { - x: action.position.x + offset, - y: action.position.y + offset, - }, - locked: true, - data: { - isDescriptor: true, - isValid: true, - is_valid: true, - label: "", - image: svgpin_Url, - imageColor: iconInfo.iconBackgroundColor, - attachedTo: action.id, - }, - }; - return decoratorNode; - }); - - const triggers = workflow.triggers.map((trigger) => { - const node = {}; - node.position = trigger.position; - node.data = trigger; - - node.data._id = trigger["id"]; - node.data.id = trigger["id"]; - node.data.type = "TRIGGER"; - - return node; - }); - - var comments = []; - if ( - workflow.comments !== undefined && - workflow.comments !== null && - workflow.comments.length > 0 - ) { - comments = workflow.comments.map((comment) => { - const node = {}; - node.position = comment.position; - node.data = comment; - - node.data._id = comment["id"]; - node.data.type = "COMMENT"; - - return node; - }); - } - - // FIXME - tmp branch update - var insertedNodes = [].concat(actions, triggers, decoratorNodes, comments); - insertedNodes = insertedNodes.filter((node) => node !== null); - - var edges = workflow.branches.map((branch, index) => { - 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, - decorator: false, - }; - - // This is an attempt at prettier edges. The numbers are weird to work with. - // Bezier curves - //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html - var sourcenode = actions.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id) - var destinationnode = actions.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id) - - if (sourcenode === undefined) { - sourcenode = triggers.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id) - } - - if (destinationnode === undefined) { - destinationnode = triggers.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id) - } - - if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) { - const edgeCurve = calculateEdgeCurve(sourcenode.position, destinationnode.position) - edge.style = { - 'control-point-distance': edgeCurve.distance, - 'control-point-weight': edgeCurve.weight, - } - } else { - console.log("FAILED node curve handling") - } - - return edge; - }); - - if ( - workflow.visual_branches !== undefined && - workflow.visual_branches !== null && - workflow.visual_branches.length > 0 - ) { - const visualedges = workflow.visual_branches.map((branch, index) => { - const edge = {}; - - if (workflow.branches[index] === undefined) { - return {}; - } - - var conditions = workflow.branches[index].conditions; - if (conditions === undefined || conditions === null) { - conditions = []; - } - - const label = "Subflow"; - edge.data = { - id: branch.id, - _id: branch.id, - source: branch.source_id, - target: branch.destination_id, - label: label, - decorator: true, - }; - - return edge; - }); - - edges = edges.concat(visualedges); - } - - setWorkflow(workflow); - - // Verifies if a branch is valid and skips others - var newedges = []; - for (var edgeKey in edges) { - var item = edges[edgeKey]; - if (item.data === undefined) { - continue; - } - - 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 = (nodeId) => { - const selectedNode = cy.getElementById(nodeId); - // console.log("selected node in removenode", selectedNode); - if (selectedNode.data() === undefined) { - console.log("No node to remove") - return; - } - - //console.log("Removing node: ", selectedNode.data("id"), "Action: ", selectedAction.id) - - // Get selected node - - 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); - } - } - - //if (selectedNode.data("id") === selectedAction.id) { - // setSelectedApp({}); - // setSelectedAction({}); - //setSelectedTrigger({}); - //setSelectedTriggerIndex({}); - //} - const parsedSelection = cy.$(":selected"); - if (selectedNode.data().decorator === true && selectedNode.data("type") !== "COMMENT") { - alert.info("This node can't be deleted."); - } else { - console.log("Deleted.") - selectedNode.remove(); - - setSelectedTrigger({}) - setSelectedEdge({}) - setSelectedAction({}) - } - - // An attempt at NOT unselecting when removing - /* - setTimeout(() => { - if (parsedSelection.data() !== undefined) { - if (parsedSelection.data("id") !== selectedNode.data("id")) { - console.log("SHOULD SELECT SINCE ID IS DIFFERENT") - - parsedSelection.select() - } - } - - console.log("Parsed: ", parsedSelection.data("id"), selectedNode.data("id")) - }, 2500) - */ - }; - - - if (isLoaded && setupSent === false) { - setSetupSent(true) - - sendStreamRequest({ - "item": "workflow", - "type": "enter", - "userid": userdata.id, - }) - } - - const fetchUsecases = () => { - fetch(globalUrl + "/api/v1/workflows/usecases", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for usecases"); - } - - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success !== false) { - console.log("Usecases: ", usecases) - setUsecases(responseJson) - } else { - } - }) - .catch((error) => { - //alert.error("ERROR: " + error.toString()); - console.log("ERROR getting usecases: " + error.toString()); - }) - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - //useEffect(() => { - if (firstrequest) { - setFirstrequest(false); - getWorkflow(props.match.params.key, {}); - getApps(); - fetchUsecases() - - const cursearch = - typeof window === "undefined" || window.location === undefined - ? "" - : window.location.search; - - // FIXME: Don't check specific one here - const tmpExec = new URLSearchParams(cursearch).get("execution_highlight"); - if ( - tmpExec !== undefined && - tmpExec !== null && - tmpExec === "executions" - ) { - setExecutionModalOpen(true) - const newitem = removeParam("execution_highlight", cursearch); - navigate(curpath + newitem) - //props.history.push(curpath + newitem); - } - - const tmpView = new URLSearchParams(cursearch).get("view"); - if ( - tmpView !== undefined && - tmpView !== null && - tmpView === "executions" - ) { - setExecutionModalOpen(true); - - const newitem = removeParam("view", cursearch); - navigate(curpath + newitem) - //navigate(`?execution_highlight=${parsed_url}`) - //props.history.push(curpath + newitem); - } - return; - } - - // App length necessary cus of cy initialization - if ( - // First load - gets the workflow - elements.length === 0 && - workflow.actions !== undefined && - !graphSetup && - Object.getOwnPropertyNames(workflow).length > 0 - ) { - - setGraphSetup(true); - setupGraph(); - console.log("In graph setup") - - // 2nd load - configures cytoscape - } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined) { - console.log("In POST graph setup!") - - - //This part has to load LAST, as it's kind of not async. - //This means we need everything else to happen first. - - setEstablished(true); - // Validate if the node is just a node lol - - console.log("CY grid: ", cy.gridGuide) - - // https://www.npmjs.com/package/cytoscape-grid-guide - // - if (cy.gridGuide !== undefined) { - cy.gridGuide({ - gridSpacing: 30, - guidelinesStyle: { - strokeStyle: "#8b7d6b", // color of geometric guidelines - geometricGuidelineRange: 400, // range of geometric guidelines - range: 100, // max range of distribution guidelines - minDistRange: 10, // min range for distribution guidelines - distGuidelineOffset: 10, // shift amount of distribution guidelines - horizontalDistColor: "#ff0000", // color of horizontal distribution alignment - verticalDistColor: "#00ff00", // color of vertical distribution alignment - initPosAlignmentColor: "#0000ff", // color of alignment to initial mouse location - lineDash: [0, 0], // line style of geometric guidelines - horizontalDistLine: [0, 0], // line style of horizontal distribution guidelines - verticalDistLine: [0, 0], // line style of vertical distribution guidelines - initPosAlignmentLine: [0, 0], // line style of alignment to initial mouse position - } - }) - } else { - console.log("ERROR: Failed to render grid as it's unitialized") - } - - if (cy.edgehandles !== undefined) { - cy.edgehandles({ - handleNodes: (el) => { - if (el.isNode() && - !el.data("isButton") && - !el.data("isDescriptor") && - !el.data("isSuggestion") && - el.data("type") !== "COMMENT") { - return true - } - - return false - }, - preview: false, - toggleOffOnLeave: true, - loopAllowed: function (node) { - return false; - }, - }); - - //cy.edgehandles({ - // preview: false, - //}) - - //cy.edgehandles().enable() - } else { - console.log("ERROR: Failed to initialize edgehandler") - } - // preview: true, - - cy.fit(null, 200); - - cy.on("boxselect", "node", (e) => { - if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) { - e.target.unselect(); - } - - e.target.addClass("selected"); - }); - - cy.on("boxstart", (e) => { - console.log("START"); - cy.removeListener("select"); - }); - - cy.on("boxend", (e) => { - console.log("END: ", cy) - var cydata = cy.$(":selected").jsons(); - if (cydata !== undefined && cydata !== null && cydata.length > 0) { - alert.success(`Selected ${cydata.length} element(s). CTRL+C to copy them.`); - } - }); - - cy.on("select", "node", (e) => { - onNodeSelect(e, appAuthentication); - }); - cy.on("select", "edge", (e) => onEdgeSelect(e)); - - cy.on("unselect", (e) => onUnselect(e)); - - cy.on("add", "node", (e) => onNodeAdded(e)); - cy.on("add", "edge", (e) => onEdgeAdded(e)); - cy.on("remove", "node", (e) => onNodeRemoved(e)); - cy.on("remove", "edge", (e) => onEdgeRemoved(e)); - - cy.on("mouseover", "edge", (e) => onEdgeHover(e)); - cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e)); - cy.on("mouseover", "node", (e) => onNodeHover(e)); - cy.on("mouseout", "node", (e) => onNodeHoverOut(e)); - - // Handles dragging - cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction)); - cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction)); - - cy.on("cxttap", "node", (e) => onCtxTap(e)); - - document.title = "Workflow - " + workflow.name; - registerKeys(); - } - //}) - - const stopSchedule = (trigger, triggerindex) => { - 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) => { - // No matter what, it's being stopped. - if (!responseJson.success) { - if (responseJson.reason !== undefined) { - alert.error("Failed to stop 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()); - console.log("Stop schedule error: ", error.toString()); - }); - }; - - const submitSchedule = (trigger, triggerindex) => { - if (trigger.name.length <= 0) { - alert.error("Error: name can't be empty"); - return; - } - - var mappedStartnode = "" - const alledges = cy.edges().jsons() - if (alledges !== undefined && alledges !== null && alledges.length > 0) { - for (let edgekey in alledges) { - const tmp = alledges[edgekey] - console.log("TMP: ", tmp, tmp.data.source) - if (tmp.data.source === trigger.id) { - mappedStartnode = tmp.data.target - break - } - } - } - - alert.info("Creating schedule with name " + trigger.name); - const data = { - name: trigger.name, - frequency: workflow.triggers[triggerindex].parameters[0].value, - execution_argument: workflow.triggers[triggerindex].parameters[1].value, - environment: workflow.triggers[triggerindex].environment, - id: trigger.id, - start: mappedStartnode, - } - - 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()); - console.log("Get schedule error: ", error.toString()); - }); - }; - - const appViewStyle = { - marginLeft: 5, - marginRight: 5, - display: "flex", - flexDirection: "column", - minHeight: isMobile ? bodyHeight - appBarSize * 4 : "100%", - maxHeight: isMobile ? bodyHeight - appBarSize * 4 : "100%", - }; - - const paperAppStyle = { - borderRadius: theme.palette.borderRadius, - minHeight: isMobile ? 50 : 100, - maxHeight: isMobile ? 50 : 100, - minWidth: isMobile ? 50 : "100%", - maxWidth: isMobile ? 50 : "100%", - marginTop: "5px", - color: "white", - backgroundColor: surfaceColor, - cursor: "pointer", - display: "flex", - }; - - const paperVariableStyle = { - borderRadius: theme.palette.borderRadius, - minHeight: 50, - maxHeight: 50, - minWidth: "100%", - maxWidth: "100%", - marginTop: "5px", - color: "white", - backgroundColor: surfaceColor, - cursor: "pointer", - display: "flex", - }; - - const VariablesView = () => { - const [open, setOpen] = React.useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); - - const menuClick = (event) => { - setOpen(!open); - setAnchorEl(event.currentTarget); - }; - - const deleteVariable = (variableIndex) => { - //console.log("Delete:", variableName); - if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length > variableIndex) { - workflow.workflow_variables.splice(variableIndex, 1) - } - - //workflow.workflow_variables = workflow.workflow_variables.filter( - // (data) => data.name !== variableName - //); - setWorkflow(workflow); - }; - - const deleteExecutionVariable = (variableIndex) => { - if (workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > variableIndex) { - workflow.execution_variables.splice(variableIndex, 1) - } - - //workflow.execution_variables = workflow.execution_variables.filter( - // (data) => data.name !== variableName - //); - setWorkflow(workflow); - }; - - const variableScrollStyle = { - margin: 15, - overflow: "scroll", - height: isMobile ? "100%" : "66vh", - overflowX: "auto", - overflowY: "auto", - flex: "10", - }; - - return ( -
-
- What are{" "} - - WORKFLOW variables? - - {workflow.workflow_variables === null - ? null - : workflow.workflow_variables.map((variable, index) => { - return ( -
- { }}> -
-
-
{ - setVariableInfo({ - "name": variable.name, - "description": variable.description, - "value": variable.value, - "index": index, - }) - setVariablesModalOpen(true); - }} - > - Name: {variable.name} -
-
- - - - { - setOpen(false); - setAnchorEl(null); - }} - > - { - setOpen(false); - setVariableInfo({ - "name": variable.name, - "description": variable.description, - "value": variable.value, - "index": index, - }) - setVariablesModalOpen(true); - }} - key={"Edit"} - > - {"Edit"} - - { - deleteVariable(index); - setOpen(false); - }} - key={"Delete"} - > - {"Delete"} - - -
-
- -
- ); - })} -
- -
- - What are{" "} - - EXECUTION variables? - - {workflow.execution_variables === null || - workflow.execution_variables === undefined - ? null - : workflow.execution_variables.map((variable, index) => { - return ( -
- { }}> -
-
-
{ - //setNewVariableName(variable.name); - setVariableInfo({ - "name": variable.name, - "description": variable.description, - "value": variable.value, - "index": index, - }) - setExecutionVariablesModalOpen(true); - }} - > - Name: {variable.name} -
-
- - - - { - setOpen(false); - setAnchorEl(null); - }} - > - { - setOpen(false); - //setNewVariableName(variable.name); - setVariableInfo({ - "name": variable.name, - "description": variable.description, - "value": variable.value, - "index": index, - }) - setExecutionVariablesModalOpen(true); - }} - key={"Edit"} - > - {"Edit"} - - { - deleteExecutionVariable(index); - setOpen(false); - }} - key={"Delete"} - > - {"Delete"} - - -
-
- -
- ); - })} -
- -
-
-
- ); - }; - - const handleSetTab = (event, newValue) => { - setCurrentView(newValue); - }; - - const HandleLeftView = () => { - // Defaults to apps. - var thisview = ( - - ); - if (currentView === 1) { - thisview = ; - } else if (currentView === 2) { - thisview = ; - } - - const tabStyle = { - maxWidth: isMobile ? leftBarSize : leftBarSize / 3, - minWidth: isMobile ? leftBarSize : leftBarSize / 3, - flex: 1, - textTransform: "none", - }; - - const iconStyle = { - marginTop: 3, - marginRight: 5, - }; - - const parsedHeight = isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 50 - return ( -
-
- {thisview} -
- - - - - - - {isMobile ? null : Apps} - - } - style={tabStyle} - /> - - - - - {isMobile ? null : Triggers} - - } - style={tabStyle} - /> - - - - - {isMobile ? null : Variables} - - } - style={tabStyle} - /> - -
- ); - }; - - const 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: "onprem", - description: "Custom HTTP input", - long_description: "Execute a workflow with an unauthicated POST request", - }, - { - name: "Schedule", - type: "TRIGGER", - status: "uninitialized", - description: "Specify time", - trigger_type: "SCHEDULE", - errors: null, - large_image: - "data:image/png;base64,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", - label: "Schedule", - is_valid: true, - environment: "onprem", - long_description: "Create a schedule based on cron", - }, - { - name: "Shuffle Workflow", - type: "TRIGGER", - status: "uninitialized", - trigger_type: "SUBFLOW", - errors: null, - large_image: - "data:image/png;base64,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", - is_valid: true, - label: "Subflow", - environment: "onprem", - description: "Control a workflow", - long_description: "Execute another workflow from this workflow", - }, - { - 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: cloudSyncEnabled || isCloud ? true : false, - label: "User input", - environment: "cloud", - long_description: "Take user input to continue execution", - }, - { - name: "Office365", - type: "TRIGGER", - status: "uninitialized", - description: "O365 email trigger", - trigger_type: "EMAIL", - errors: null, - is_valid: isCloud ? true : false, - label: "Email", - environment: "cloud", - large_image: - "data:image/jpg;base64,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", - long_description: "Execute a workflow when you get an email", - }, - { - name: "Gmail", - type: "TRIGGER", - status: "uninitialized", - description: "Gmail email trigger", - trigger_type: "EMAIL", - errors: null, - is_valid: isCloud ? true : false, - label: "Email", - environment: "cloud", - large_image: - "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAzFodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QTIyMjgyMEYwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QTIyMjgyMTAwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQTMwMDQxRTAwMEUxMUVCQkE4QTk5QkEzQzMxMDZEMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBMjIyODIwRTAwMkMxMUVCQkE4QTk5QkEzQzMxMDZEMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAwICAwMCAwMDAwMEBwUEBAQECQYHBQcKCQsL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", - long_description: "Execute a workflow when you get an email", - }, - ]; - - const TriggersView = () => { - const triggersViewStyle = { - marginLeft: "10px", - marginRight: "10px", - display: "flex", - flexDirection: "column", - }; - - // Predefined hurr - - return ( -
-
- {triggers.map((trigger, index) => { - var imageline = - trigger.large_image.length === 0 ? ( - - ) : ( - - ); - - const color = trigger.is_valid ? green : yellow; - return ( - { - handleTriggerDrag(e, trigger); - }} - onStop={(e) => { - handleDragStop(e); - }} - dragging={false} - position={{ - x: 0, - y: 0, - }} - > - { }}> -
- - - {imageline} - - {isMobile ? null : - - -

- {trigger.name} -

-
- - {trigger.description} - -
- } -
-
-
- ); - })} -
-
- ); - }; - - var newNodeId = ""; - var parsedApp = {}; - const handleTriggerDrag = (e, data) => { - const cycontainer = cy.container(); - // Chrome lol - 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 (workflow.start === "" || workflow.start === undefined) { - alert.error("Define a starting action first."); - return; - } - - const triggerLabel = getNextActionName(data.name); - - newNodeId = uuidv4(); - const newposition = { - x: e.pageX - cycontainer.offsetLeft, - y: e.pageY - cycontainer.offsetTop, - }; - - const newAppData = { - app_name: data.name, - app_version: "1.0.0", - environment: isCloud ? "cloud" : data.environment, - description: data.description, - long_description: data.long_description, - errors: [], - id_: newNodeId, - _id_: newNodeId, - id: newNodeId, - finished: false, - label: triggerLabel, - 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, - }; - - // Can all the data be in here? hmm - const nodeToBeAdded = { - group: "nodes", - data: newAppData, - renderedPosition: newposition, - }; - - cy.add(nodeToBeAdded); - parsedApp = nodeToBeAdded; - return; - } - } - }; - - const handleDragStop = (e, app) => { - var currentnode = cy.getElementById(newNodeId); - if ( - currentnode === undefined || - currentnode === null || - currentnode.length === 0 - ) { - return; - } - - // Using remove & replace, as this triggers the function - // onNodeAdded() with this node after it's added - - currentnode.remove(); - parsedApp.data.finished = true; - parsedApp.data.position = currentnode.renderedPosition(); - parsedApp.position = currentnode.renderedPosition(); - parsedApp.renderedPosition = currentnode.renderedPosition(); - - var newAppData = parsedApp.data; - if (newAppData.type === "ACTION") { - - //const activateApp = (appid) => { - if (newAppData.activated === false) { - activateApp(newAppData.app_id, false) - } - - // AUTHENTICATION - if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) { - - // Setup auth here :) - const authenticationOptions = []; - var findAuthId = ""; - if ( - newAppData.authentication_id !== null && - newAppData.authentication_id !== undefined && - newAppData.authentication_id.length > 0 - ) { - findAuthId = newAppData.authentication_id; - } - - const tmpAuth = JSON.parse(JSON.stringify(appAuthentication)); - for (let authkey in tmpAuth) { - if (authkey === undefined) { - continue - } - - var item = tmpAuth[authkey]; - const newfields = {}; - for (let fieldkey in item.fields) { - if (item.fields[fieldkey] === undefined) { - console.log("Problem with filterkey in Node select", fieldkey) - continue - } - - const filterkey = item.fields[fieldkey]["key"] - if (filterkey === null || filterkey === undefined) { - console.log("Problem with filterkey 2. Null or undefined 3") - continue - } - - newfields[filterkey] = item.fields[fieldkey]["value"]; - } - - item.fields = newfields; - if (item.app.id === app.id || item.app.name === app.name) { - authenticationOptions.push(item); - - if (item.id === findAuthId) { - newAppData.selectedAuthentication = item - newAppData.authentication_id = item.id - - } else if (findAuthId === "") { - // Will always be set to the last one if one isn't found. - // Last = timestamp too - newAppData.selectedAuthentication = item - newAppData.authentication_id = item.id - } - } - } - - if ( - authenticationOptions !== undefined && - authenticationOptions !== null && - authenticationOptions.length > 0 - ) { - for (let authkey in authenticationOptions) { - const option = authenticationOptions[authkey]; - - if (option.active && newAppData.authentication_id === "") { - newAppData.selectedAuthentication = option; - newAppData.authentication_id = option.id; - break; - } - } - } - } else { - newAppData.authentication = []; - newAppData.authentication_id = ""; - newAppData.selectedAuthentication = {}; - } - - parsedApp.data = newAppData; - cy.add(parsedApp); - } else if (newAppData.type === "TRIGGER") { - cy.add(parsedApp); - } - - newNodeId = ""; - parsedApp = {}; - }; - - const appScrollStyle = { - overflow: "scroll", - maxHeight: isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 55 - 50, - minHeight: isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 55 - 50, - marginTop: 1, - overflowY: "auto", - overflowX: "hidden", - }; - - const handleAppDrag = (e, app) => { - const cycontainer = cy.container(); - - //console.log("e: ", e) - //console.log("Offset: ", cycontainer) - - // Chrome lol - 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 === undefined || - currentnode === null || - currentnode.length === 0 - ) { - return; - } - - currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft); - currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop); - } else { - if (workflow.public) { - console.log("workflow is public - not adding"); - return; - } - - 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 = uuidv4(); - const actionType = "ACTION"; - const actionLabel = getNextActionName(app.name); - console.log("Next action name: ", actionLabel) - var parameters = null; - var example = ""; - var description = "" - - if ( - app.actions[0].parameters !== undefined && - app.actions[0].parameters !== null && - app.actions[0].parameters.length > 0 - ) { - parameters = app.actions[0].parameters; - } - - if ( - app.actions[0].returns !== undefined && - app.actions[0].returns !== null && - app.actions[0].returns.example !== undefined && - app.actions[0].returns.example !== null && - app.actions[0].returns.example.length > 0 - ) { - example = app.actions[0].returns.example; - } - - if ( - app.actions[0].description !== undefined && - app.actions[0].description !== null && - app.actions[0].description.length > 0 - ) { - description = app.actions[0].description - } - - const parsedEnvironments = - environments === null || environments === [] - ? "cloud" - : environments[defaultEnvironmentIndex] === undefined - ? "cloud" - : environments[defaultEnvironmentIndex].Name; - - // activated: app.generated === true ? app.activated === false ? false : true : true, - const newAppData = { - name: app.actions[0].name, - label: actionLabel, - app_name: app.name, - app_version: app.app_version, - app_id: app.id, - sharing: app.sharing, - private_id: app.private_id, - description: description, - environment: parsedEnvironments, - errors: [], - finished: false, - id_: newNodeId, - _id_: newNodeId, - id: newNodeId, - is_valid: true, - type: actionType, - parameters: parameters, - isStartNode: false, - large_image: app.large_image, - run_magic_output: false, - authentication: [], - execution_variable: undefined, - example: example, - category: - app.categories !== null && - app.categories !== undefined && - app.categories.length > 0 - ? app.categories[0] - : "", - authentication_id: "", - finished: false, - template: app.template === true ? true : false, - }; - - // FIXME: overwrite category if the ACTION chosen has a different category - - // 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.pageX - cycontainer.offsetLeft, - y: e.pageY - cycontainer.offsetTop, - }, - }; - - parsedApp = nodeToBeAdded; - cy.add(nodeToBeAdded); - return; - } - } - }; - - const AppView = (props) => { - const { allApps, prioritizedApps, filteredApps, extraApps } = props; - //extraApps, - const [visibleApps, setVisibleApps] = React.useState( - Array.prototype.concat.apply( - prioritizedApps, - filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id)), - ) - ); - - var delay = -75 - var runDelay = false - - const ParsedAppPaper = (props) => { - const app = props.app; - const [hover, setHover] = React.useState(false); - - if (app.id === "" || app.name === "") { - return null - } - - const maxlen = 24; - var newAppname = app.name; - newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1); - if (newAppname.length > maxlen) { - newAppname = newAppname.slice(0, maxlen) + ".."; - } - - newAppname = newAppname.replaceAll("_", " "); - - if (app.large_image === undefined || app.large_image === null || app.large_image === "") { - app.large_image = theme.palette.defaultImage - } - - const image = app.large_image !== undefined && app.large_image !== null && app.large_image !== "" ? app.large_image : theme.palette.defaultImage - - const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle)); - const pixelSize = !hover ? "2px" : "4px"; - //newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0 && !(app.activated && app.generated) - newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0 - ? `${pixelSize} solid ${green}` - : `${pixelSize} solid ${yellow}`; - - if (!app.activated && app.generated) { - newAppStyle.borderLeft = `${pixelSize} solid ${yellow}`; - } - - return ( - { - handleAppDrag(e, app); - }} - onStop={(e) => { - handleDragStop(e, app); - }} - key={app.id} - dragging={false} - position={{ - x: 0, - y: 0, - }} - > - { - setHover(true); - }} - onMouseOut={() => { - setHover(false); - }} - onClick={() => { - if (isMobile) { - newNodeId = uuidv4(); - const actionType = "ACTION"; - const actionLabel = getNextActionName(app.name); - var parameters = null; - var example = ""; - var description = "" - - if ( - app.actions[0].parameters !== null && - app.actions[0].parameters.length > 0 - ) { - parameters = app.actions[0].parameters; - } - - if ( - app.actions[0].returns.example !== undefined && - app.actions[0].returns.example !== null && - app.actions[0].returns.example.length > 0 - ) { - example = app.actions[0].returns.example; - } - - if ( - app.actions[0].description !== undefined && - app.actions[0].description !== null && - app.actions[0].description.length > 0 - ) { - description = app.actions[0].description - } - - const parsedEnvironments = - environments === null || environments === [] - ? "cloud" - : environments[defaultEnvironmentIndex] === undefined - ? "cloud" - : environments[defaultEnvironmentIndex].Name; - - // activated: app.generated === true ? app.activated === false ? false : true : true, - const newAppData = { - app_name: app.name, - app_version: app.app_version, - app_id: app.id, - sharing: app.sharing, - private_id: app.private_id, - description: description, - environment: parsedEnvironments, - errors: [], - finished: false, - id_: newNodeId, - _id_: newNodeId, - id: newNodeId, - is_valid: true, - label: actionLabel, - type: actionType, - name: app.actions[0].name, - parameters: parameters, - isStartNode: false, - large_image: image, - run_magic_output: false, - authentication: [], - execution_variable: undefined, - example: example, - category: - app.categories !== null && - app.categories !== undefined && - app.categories.length > 0 - ? app.categories[0] - : "", - authentication_id: "", - finished: false, - }; - - const nodeToBeAdded = { - group: "nodes", - data: newAppData, - renderedPosition: { - x: 100, - y: 100, - }, - }; - - parsedApp = nodeToBeAdded; - cy.add(nodeToBeAdded); - - } - }} - > - - - {newAppname} - - {isMobile ? null : - - - - {newAppname} - - - - - Version: {app.app_version} - - - - - {app.description} - - - - } - - - - ); - }; - - const runSearch = (value) => { - if (value.length > 0) { - var newApps = allApps.filter( - (app) => - app.name.toLowerCase().includes(value.trim().toLowerCase()) - || - app.description.toLowerCase().includes(value.trim().toLowerCase()) - ) - - // Extend search - if (newApps.length === 0) { - const searchvalue = value.trim().toLowerCase(); - newApps = allApps.filter((app) => { - if (app.actions !== undefined && app.actions !== null) { - for (let actionkey in app.actions) { - const inneraction = app.actions[actionkey]; - if (inneraction.name.toLowerCase().includes(searchvalue)) { - return true; - } - } - } - - return false; - }); - } - - setVisibleApps(newApps); - } else { - setVisibleApps( - prioritizedApps.concat( - filteredApps.filter( - (innerapp) => !internalIds.includes(innerapp.id) - ) - ) - ); - } - }; - - const SearchBox = ({ currentRefinement, refine, isSearchStalled, }) => { - - useEffect(() => { - if (document !== undefined) { - const appsearchValue = document.getElementById("appsearch") - if (appsearchValue !== undefined && appsearchValue !== null) { - if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) { - refine(appsearchValue.value) - } - } - //} - } - }, []) - - return ( -