From 178987e55371acb7496f8b363509637fa1a92ea3 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 May 2024 14:50:51 +0200 Subject: [PATCH 001/336] Orborus updates --- functions/onprem/orborus/go.mod | 2 ++ functions/onprem/orborus/orborus.go | 53 +++-------------------------- 2 files changed, 6 insertions(+), 49 deletions(-) diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod index 6de48847..ac5f5875 100644 --- a/functions/onprem/orborus/go.mod +++ b/functions/onprem/orborus/go.mod @@ -4,6 +4,8 @@ go 1.22.0 toolchain go1.22.2 +replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared + require ( github.com/docker/docker v26.1.0+incompatible github.com/docker/go-connections v0.5.0 diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 25fc4f27..f5c06267 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -48,13 +48,6 @@ import ( //"github.com/mackerelio/go-osstat/memory" //"github.com/shirou/gopsutil/cpu" - //k8s deps - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" - "path/filepath" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -685,13 +678,13 @@ func deployWorker(image string, identifier string, env []string, executionReques } - clientset, config, err := getKubernetesClient() + clientset, config, err := shuffle.GetKubernetesClient() if err != nil { log.Printf("[ERROR] Error getting kubernetes client:", err) return err } - env = append(env, fmt.Sprintf("KUBERNETES_CONFIG=%s", config.String())) + //env = append(env, fmt.Sprintf("KUBERNETES_CONFIG=%s", config.String())) // FIXME: When a service account is used, the account is also mounted in the pod // The volume mount location is: @@ -1289,46 +1282,8 @@ func getOrborusStats(ctx context.Context) shuffle.OrborusStats { return newStats } -func isRunningInCluster() bool { - _, existsHost := os.LookupEnv("KUBERNETES_SERVICE_HOST") - _, existsPort := os.LookupEnv("KUBERNETES_SERVICE_PORT") - return existsHost && existsPort -} -func getKubernetesClient() (*kubernetes.Clientset, *rest.Config, error) { - config := &rest.Config{} - var err error - - if isRunningInCluster() { - config, err := rest.InClusterConfig() - if err != nil { - return nil, config, err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, config, err - } - - return clientset, config, nil - - } - - home := homedir.HomeDir() - kubeconfigPath := filepath.Join(home, ".kube", "config") - config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath) - if err != nil { - return nil, config, err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, config, err - } - - return clientset, config, nil -} func sendRemoveRequest(client *http.Client, toBeRemoved shuffle.ExecutionRequestWrapper, baseUrl, environment, auth, org string, sleepTime int) error { @@ -1404,7 +1359,7 @@ func main() { //defer cleanup() // Block until a signal is received - if isRunningInCluster() { + if shuffle.IsRunningInCluster() { log.Printf("[INFO] Running inside k8s cluster") } @@ -2615,7 +2570,7 @@ func getRunningWorkers(ctx context.Context, workerTimeout int) int { thresholdTime := time.Now().Add(time.Duration(-workerTimeout) * time.Second) - clientset, _, err := getKubernetesClient() + clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Printf("[ERROR] Failed getting kubernetes client: %s", err) return 0 From 35cf1132c8962702833729989c3c0db46283bd5a Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 May 2024 14:57:27 +0200 Subject: [PATCH 002/336] Updated orborus & worker to use config the same way --- functions/onprem/orborus/go.mod | 4 +- functions/onprem/orborus/orborus.go | 2 +- functions/onprem/worker/go.mod | 2 +- functions/onprem/worker/worker.go | 165 +++++++++++----------------- 4 files changed, 66 insertions(+), 107 deletions(-) diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod index abf409b2..9101801f 100644 --- a/functions/onprem/orborus/go.mod +++ b/functions/onprem/orborus/go.mod @@ -4,13 +4,13 @@ go 1.22.0 toolchain go1.22.2 -replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared +//replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared require ( github.com/docker/docker v26.1.0+incompatible github.com/docker/go-connections v0.5.0 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.6.29 + github.com/shuffle/shuffle-shared v0.6.37 k8s.io/api v0.30.0 k8s.io/apimachinery v0.30.0 k8s.io/client-go v0.30.0 diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 4b291a6b..60a82a1f 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -678,7 +678,7 @@ func deployWorker(image string, identifier string, env []string, executionReques } - clientset, config, err := shuffle.GetKubernetesClient() + clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Printf("[ERROR] Error getting kubernetes client:", err) return err diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod index 248a40f5..c6bfe36b 100644 --- a/functions/onprem/worker/go.mod +++ b/functions/onprem/worker/go.mod @@ -6,7 +6,7 @@ require ( github.com/docker/docker v26.1.0+incompatible github.com/gorilla/mux v1.8.1 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.6.30 + github.com/shuffle/shuffle-shared v0.6.37 k8s.io/api v0.30.0 k8s.io/apimachinery v0.30.0 k8s.io/client-go v0.30.0 diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index fe145066..7ecbed96 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -3,6 +3,7 @@ package main import ( "github.com/shuffle/shuffle-shared" + "bytes" "context" "encoding/json" @@ -21,8 +22,8 @@ import ( "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" dockerclient "github.com/docker/docker/client" // This is for automatic removal of certain code :) @@ -34,10 +35,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" - "path/filepath" ) // This is getting out of hand :) @@ -56,7 +53,6 @@ var kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") // var baseimagename = os.Getenv("SHUFFLE_BASE_IMAGE_NAME") - // var baseimagename = "registry.hub.docker.com/frikky/shuffle" var registryName = "registry.hub.docker.com" var sleepTime = 2 @@ -81,7 +77,6 @@ var startAction string //var allLogs map[string]string //var containerIds []string var downloadedImages []string - type ImageDownloadBody struct { Image string `json:"image"` } @@ -93,6 +88,7 @@ type ImageRequest struct { var finishedExecutions []string var imagesDistributed []string + // Images to be autodeployed in the latest version of Shuffle. var autoDeploy = map[string]string{ "http:1.4.0": "frikky/shuffle:http_1.4.0", @@ -139,6 +135,7 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo return err } + handleExecutionResult(workflowExecution) validated := shuffle.ValidateFinished(ctx, -1, workflowExecution) if validated { @@ -178,7 +175,7 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo } } - if len(subflowId) == 0 { + if len(subflowId) == 0 { log.Printf("[DEBUG][%s] No waiting result found. Not polling", workflowExecution.ExecutionId) for _, action := range workflowExecution.Workflow.Actions { @@ -186,17 +183,19 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo workflowExecution.Workflow.Triggers = append(workflowExecution.Workflow.Triggers, shuffle.Trigger{ AppName: action.AppName, Parameters: action.Parameters, - ID: action.ID, + ID: action.ID, }) } } + for _, trigger := range workflowExecution.Workflow.Triggers { //log.Printf("[DEBUG] Found trigger %s", trigger.AppName) if trigger.AppName != "User Input" && trigger.AppName != "Shuffle Workflow" && trigger.AppName != "shuffle-subflow" { continue } + // check if it has wait for results in params wait := false for _, param := range trigger.Parameters { @@ -215,9 +214,9 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo //log.Printf("[DEBUG][%s] Found result %s", workflowExecution.ExecutionId, result.Action.ID) if result.Action.ID == trigger.ID && result.Status != "SUCCESS" && result.Status != "FAILURE" { //log.Printf("[DEBUG][%s] Found subflow result that is not handled. Waiting for results", workflowExecution.ExecutionId) - + subflowId = result.Action.ID - found = true + found = true break } } @@ -236,20 +235,21 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo if len(subflowId) > 0 { // Under rerun period timeout - timeComparison := 120 + timeComparison := 120 log.Printf("[DEBUG][%s] Starting polling for %d seconds to see if new subflow updates are found on the backend that are not handled. Subflow ID: %s", workflowExecution.ExecutionId, timeComparison, subflowId) timestart := time.Now() streamResultUrl := fmt.Sprintf("%s/api/v1/streams/results", baseUrl) for { - err = handleSubflowPoller(ctx, workflowExecution, streamResultUrl, subflowId) + err = handleSubflowPoller(ctx, workflowExecution, streamResultUrl, subflowId) if err == nil { log.Printf("[DEBUG] Subflow is finished and we are breaking the thingy") - + if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" && workflowExecution.ExecutionSource != "default" { log.Printf("[DEBUG] Force shutdown of worker due to optimized run with webserver. Expecting reruns to take care of this") os.Exit(0) } + break } @@ -271,6 +271,7 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo return nil } + // removes every container except itself (worker) func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason string, handleResultSend bool) { log.Printf("[DEBUG][%s] Shutdown (%s) started with reason %#v. Result amount: %d. ResultsSent: %d, Send result: %#v, Parent: %#v", workflowExecution.ExecutionId, workflowExecution.Status, reason, len(workflowExecution.Results), requestsSent, handleResultSend, workflowExecution.ExecutionParent) @@ -310,7 +311,7 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason } */ } else { - + } if len(reason) > 0 && len(nodeId) > 0 { @@ -393,7 +394,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] } } - clientset, err := getKubernetesClient() + clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Printf("[ERROR] Failed getting kubernetes: %s", err) return err @@ -499,7 +500,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] if !strings.Contains(param.Value, "shuffle-backend") { continue - } + } // Automatic replacement as this is default if len(os.Getenv("BASE_URL")) > 0 { @@ -514,6 +515,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] } } + // Max 10% CPU every second //CPUShares: 128, //CPUQuota: 10000, @@ -540,7 +542,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] // Get environment for certificates volumeBinds := []string{} - volumeBindString := os.Getenv("SHUFFLE_VOLUME_BINDS") + volumeBindString:= os.Getenv("SHUFFLE_VOLUME_BINDS") if len(volumeBindString) > 0 { volumeBindSplit := strings.Split(volumeBindString, ",") for _, volumeBind := range volumeBindSplit { @@ -581,6 +583,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] Env: env, } + // Checking as late as possible, just in case. newExecId := fmt.Sprintf("%s_%s", workflowExecution.ExecutionId, action.ID) _, err := shuffle.GetCache(ctx, newExecId) @@ -860,7 +863,7 @@ func askOtherWorkersToDownloadImage(image string) { // Check environment SHUFFLE_AUTO_IMAGE_DOWNLOAD if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") == "false" { log.Printf("[DEBUG] SHUFFLE_AUTO_IMAGE_DOWNLOAD is false. NOT distributing images %s", image) - return + return } if shuffle.ArrayContains(imagesDistributed, image) { @@ -893,7 +896,7 @@ func askOtherWorkersToDownloadImage(image string) { req, err := http.NewRequest( "POST", url, - bytes.NewBuffer(imageJSON), + bytes.NewBuffer(imageJSON), ) if err != nil { @@ -933,6 +936,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { return } + startAction, extra, children, parents, visited, executed, nextActions, environments := shuffle.GetExecutionVariables(ctx, workflowExecution.ExecutionId) dockercli, err := dockerclient.NewEnvClient() @@ -1000,7 +1004,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { // marshal action and put it in there rofl //log.Printf("[INFO][%s] Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", workflowExecution.ExecutionId, action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters)) - + log.Printf("[DEBUG][%s] Action: Send, Label: '%s', Action: '%s', Run status: %s, Extra=", workflowExecution.ExecutionId, action.Label, action.AppName, workflowExecution.Status) actionData, err := json.Marshal(action) @@ -1086,9 +1090,10 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { } if len(os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")) > 0 { - env = append(env, fmt.Sprintf("SHUFFLE_APP_SDK_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_SDK_TIMEOUT"))) + env = append(env, fmt.Sprintf("SHUFFLE_APP_SDK_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_SDK_TIMEOUT"))) } + // Fixes issue: // standard_go init_linux.go:185: exec user process caused "argument list too long" // https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083 @@ -1116,6 +1121,8 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { fmt.Sprintf("%s:%s_%s", baseimagename, parsedAppname, action.AppVersion), } + + // If cleanup is set, it should run for efficiency pullOptions := types.ImagePullOptions{} if cleanupEnv == "true" { @@ -1378,7 +1385,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { log.Printf("[DEBUG][%s] Shutting down (17)", workflowExecution.ExecutionId) if isKubernetes == "true" { // log.Printf("workflow execution: %#v", workflowExecution) - clientset, err := getKubernetesClient() + clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Println("[ERROR] Error getting kubernetes client (1):", err) os.Exit(1) @@ -1587,7 +1594,7 @@ func handleSubflowPoller(ctx context.Context, workflowExecution shuffle.Workflow log.Printf("[DEBUG] Shutting down (20)") if isKubernetes == "true" { // log.Printf("workflow execution: %#v", workflowExecution) - clientset, err := getKubernetesClient() + clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Println("[ERROR] Error getting kubernetes client (2):", err) os.Exit(1) @@ -1618,6 +1625,7 @@ func handleSubflowPoller(ctx context.Context, workflowExecution shuffle.Workflow } } + if workflowExecution.Status == "WAITING" && workflowExecution.ExecutionSource != "default" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" { log.Printf("[INFO][%s] Workflow execution is waiting. Exiting worker, as backend will restart it.", workflowExecution.ExecutionId) shutdown(workflowExecution, "", "", true) @@ -1683,7 +1691,7 @@ func handleDefaultExecutionWrapper(ctx context.Context, workflowExecution shuffl log.Printf("[DEBUG] Shutting down (20)") if isKubernetes == "true" { // log.Printf("workflow execution: %#v", workflowExecution) - clientset, err := getKubernetesClient() + clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Println("[ERROR] Error getting kubernetes client (2):", err) os.Exit(1) @@ -1700,7 +1708,7 @@ func handleDefaultExecutionWrapper(ctx context.Context, workflowExecution shuffl log.Printf("[DEBUG] Shutting down (21)") if isKubernetes == "true" { // log.Printf("workflow execution: %#v", workflowExecution) - clientset, err := getKubernetesClient() + clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Println("[ERROR] Error getting kubernetes client (3):", err) os.Exit(1) @@ -1888,62 +1896,6 @@ func buildEnvVars(envMap map[string]string) []corev1.EnvVar { return envVars } -func getKubernetesClient() (*kubernetes.Clientset, error) { - - // Gets the config content from Orborus. - kubeconfigContent := os.Getenv("KUBERNETES_CONFIG") - if len(kubeconfigContent) > 0 { - log.Printf("[INFO] Using KUBERNETES_CONFIG to set up Kubernetes client: %#v", os.Getenv("KUBERNETES_CONFIG")) - config, err := rest.InClusterConfig() - if err != nil { - log.Printf("[ERROR] Failed to create Kubernetes client from in-cluster config: %s", err) - } else { - // Replace client configuration with kubeconfig content - config, err = clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfigContent)) - if err != nil { - log.Printf("[ERROR] Failed to create Kubernetes client from KUBERNETES_CONFIG: %s", err) - } else { - // Create Kubernetes client - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - - return clientset, nil - } - } - } - - // Fallback - if isRunningInCluster() { - config, err := rest.InClusterConfig() - if err != nil { - return nil, err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - - return clientset, nil - } - - home := homedir.HomeDir() - kubeconfigPath := filepath.Join(home, ".kube", "config") - config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) - if err != nil { - return nil, err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - - return clientset, nil -} - func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { if request.Body == nil { resp.WriteHeader(http.StatusBadRequest) @@ -2050,9 +2002,10 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Execution is not executing, but %s"}`, workflowExecution.Status))) } + log.Printf("[DEBUG][%s] Shutting down (35)", workflowExecution.ExecutionId) - // Force sending result + // Force sending result shutdownData, err := json.Marshal(workflowExecution) if err != nil { log.Printf("[ERROR][%s] Failed marshalling execution (35): %s", workflowExecution.ExecutionId, err) @@ -2128,11 +2081,11 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl attempts += 1 log.Printf("[DEBUG][%s] Rerunning transaction as results has changed. %d vs %d", workflowExecution.ExecutionId, len(parsedValue.Results), resultLength) /* - if len(workflowExecution.Results) <= len(workflowExecution.Workflow.Actions) { - log.Printf("[DEBUG][%s] Rerunning transaction as results has changed. %d vs %d", workflowExecution.ExecutionId, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) - runWorkflowExecutionTransaction(ctx, attempts, workflowExecutionId, actionResult, resp) - return - } + if len(workflowExecution.Results) <= len(workflowExecution.Workflow.Actions) { + log.Printf("[DEBUG][%s] Rerunning transaction as results has changed. %d vs %d", workflowExecution.ExecutionId, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) + runWorkflowExecutionTransaction(ctx, attempts, workflowExecutionId, actionResult, resp) + return + } */ } } @@ -2165,6 +2118,8 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl } func sendSelfRequest(actionResult shuffle.ActionResult) { + + data, err := json.Marshal(actionResult) if err != nil { log.Printf("[ERROR][%s] Shutting down (24): Failed to unmarshal data for backend: %s", actionResult.ExecutionId, err) @@ -2223,10 +2178,10 @@ func sendResult(workflowExecution shuffle.WorkflowExecution, data []byte) { // Basically to reduce backend strain /* - if shuffle.ArrayContains(finishedExecutions, workflowExecution.ExecutionId) { - log.Printf("[INFO][%s] NOT sending backend info since it's already been sent before.", workflowExecution.ExecutionId) - return - } + if shuffle.ArrayContains(finishedExecutions, workflowExecution.ExecutionId) { + log.Printf("[INFO][%s] NOT sending backend info since it's already been sent before.", workflowExecution.ExecutionId) + return + } */ // Take it down again @@ -2237,7 +2192,6 @@ func sendResult(workflowExecution shuffle.WorkflowExecution, data []byte) { } finishedExecutions = append(finishedExecutions, workflowExecution.ExecutionId) - */ streamUrl := fmt.Sprintf("%s/api/v1/streams", baseUrl) @@ -2294,7 +2248,6 @@ func sendResult(workflowExecution shuffle.WorkflowExecution, data []byte) { if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || (len(environments) == 1 && requestsSent == 0 && len(workflowExecution.Results) >= 1 && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm") || (len(workflowExecution.Results) >= len(workflowExecution.Workflow.Actions)+extra && len(workflowExecution.Workflow.Actions) > 0) { - if workflowExecution.Status == "FINISHED" { for _, result := range workflowExecution.Results { if result.Status == "EXECUTING" || result.Status == "WAITING" { @@ -2303,7 +2256,8 @@ func sendResult(workflowExecution shuffle.WorkflowExecution, data []byte) { } } } - + + log.Printf("[DEBUG][%s] Should send full result to %s", workflowExecution.ExecutionId, baseUrl) //data = fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, executionId, authorization) @@ -2387,7 +2341,7 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { // GetLocalIP returns the non loopback local IP of the host func getLocalIP() string { - + addrs, err := net.InterfaceAddrs() if err != nil { return "" @@ -2432,7 +2386,8 @@ func webserverSetup(workflowExecution shuffle.WorkflowExecution) net.Listener { } log.Printf("[DEBUG] OLD HOSTNAME: %s", appCallbackUrl) - + + port := listener.Addr().(*net.TCPAddr).Port // Set the port environment variable os.Setenv("WORKER_PORT", fmt.Sprintf("%d", port)) @@ -2447,22 +2402,21 @@ func webserverSetup(workflowExecution shuffle.WorkflowExecution) net.Listener { func downloadDockerImageBackend(client *http.Client, imageName string) error { // Check environment SHUFFLE_AUTO_IMAGE_DOWNLOAD if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") == "false" { - //log.Printf("[DEBUG] SHUFFLE_AUTO_IMAGE_DOWNLOAD is false. Not downloading image %s", imageName) + log.Printf("[DEBUG] SHUFFLE_AUTO_IMAGE_DOWNLOAD is false. Not downloading image %s", imageName) return nil } if arrayContains(downloadedImages, imageName) { - log.Printf("[DEBUG] Image %s already downloaded", imageName) + log.Printf("[DEBUG] Image %s already downloaded - not re-downloading", imageName) return nil } + log.Printf("[DEBUG] Trying to download image %s from backend %s as it doesn't exist. All images: %#v", imageName, baseUrl, downloadedImages) downloadedImages = append(downloadedImages, imageName) data := fmt.Sprintf(`{"name": "%s"}`, imageName) dockerImgUrl := fmt.Sprintf("%s/api/v1/get_docker_image", baseUrl) - - log.Printf("[DEBUG] Trying to download image %s from backend %s as it doesn't exist. Data sent: %#v, All images: %#v", imageName, baseUrl, data, downloadedImages) req, err := http.NewRequest( "POST", @@ -2591,6 +2545,7 @@ func downloadDockerImageBackend(client *http.Client, imageName string) error { */ } + // Runs data discovery func sendAppRequest(ctx context.Context, incomingUrl, appName string, port int, action *shuffle.Action, workflowExecution *shuffle.WorkflowExecution) error { @@ -2854,7 +2809,7 @@ func getStreamResultsWrapper(client *http.Client, req *http.Request, workflowExe if newresp.StatusCode != 200 { log.Printf("[ERROR] %sStatusCode (1): %d", string(body), newresp.StatusCode) time.Sleep(time.Duration(sleepTime) * time.Second) - return environments, errors.New(fmt.Sprintf("Bad status code: %d", newresp.StatusCode)) + return environments, errors.New(fmt.Sprintf("Bad status code: %d", newresp.StatusCode) ) } err = json.Unmarshal(body, &workflowExecution) @@ -2937,6 +2892,7 @@ func getStreamResultsWrapper(client *http.Client, req *http.Request, workflowExe // Set environment variable + //log.Printf("Before wait") //wg := sync.WaitGroup{} //wg.Add(1) @@ -3008,6 +2964,7 @@ func main() { swarmConfig := os.Getenv("SHUFFLE_SWARM_CONFIG") log.Printf("[INFO] Running with timezone %s and swarm config %#v", timezone, swarmConfig) + authorization := "" executionId := "" @@ -3312,6 +3269,7 @@ func handleDownloadImage(resp http.ResponseWriter, request *http.Request) { return } + for _, img := range images { for _, tag := range img.RepoTags { splitTag := strings.Split(tag, ":") @@ -3324,7 +3282,7 @@ func handleDownloadImage(resp http.ResponseWriter, request *http.Request) { possibleNames = append(possibleNames, fmt.Sprintf("frikky/shuffle:%s", baseTag)) possibleNames = append(possibleNames, fmt.Sprintf("registry.hub.docker.com/frikky/shuffle:%s", baseTag)) - if arrayContains(possibleNames, image.Image) { + if (arrayContains(possibleNames, image.Image)) { log.Printf("[DEBUG] Image %s already downloaded that has been requested to download", image.Image) resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "image already present"}`))) @@ -3349,6 +3307,7 @@ func runWebserver(listener net.Listener) { r.HandleFunc("/api/v1/run", handleRunExecution).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/download", handleDownloadImage).Methods("POST", "OPTIONS") + if strings.ToLower(os.Getenv("SHUFFLE_DEBUG_MEMORY")) == "true" { r.HandleFunc("/debug/pprof/", pprof.Index) r.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP) From e216dfd6c67baec1b919bcf8e6ef96d60edb8b93 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 May 2024 15:13:02 +0200 Subject: [PATCH 003/336] Update dockerbuild.yaml --- .github/workflows/dockerbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index af5ee982..1ade4e48 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -11,7 +11,7 @@ on: - "!docker-compose.yml" jobs: main: - runs-on: ubuntu-nightly + runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false From f2d99c60918445f28f39b7f2022b2ca04cd86666 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 May 2024 15:14:23 +0200 Subject: [PATCH 004/336] Rerun --- functions/onprem/orborus/orborus.go | 1 - 1 file changed, 1 deletion(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 60a82a1f..f9e28d44 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -677,7 +677,6 @@ func deployWorker(image string, identifier string, env []string, executionReques env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_PORT=%s", os.Getenv("KUBERNETES_SERVICE_PORT"))) } - clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Printf("[ERROR] Error getting kubernetes client:", err) From ceca675140757bafaac873e6e1ec40617d84ecd7 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Fri, 7 Jun 2024 06:55:21 +0000 Subject: [PATCH 005/336] fix: Table rendering --- frontend/src/index.css | 6 ++---- frontend/src/views/Docs.jsx | 12 ++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 9b1fc96c..3ab58ef1 100755 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -18,11 +18,9 @@ code { margin-right: -13px; } -.toc:hover { - background-color: gray; - color: white; +table th, table td { + border: 1px solid; } - /* .cm-string{ z-index: -1; } diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 565f051f..b040a11f 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -1,15 +1,13 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react" - import { toast } from 'react-toastify'; import Markdown from 'react-markdown' - import theme from '../theme.jsx'; import ReactJson from "react-json-view"; import { isMobile } from "react-device-detect"; import { BrowserView, MobileView } from "react-device-detect"; import { useParams, useNavigate, Link } from "react-router-dom"; import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; - +import remarkGfm from 'remark-gfm' import { Grid, TextField, @@ -26,7 +24,6 @@ import { ListItemButton, ListItemText } from "@mui/material"; - import { Link as LinkIcon, Edit as EditIcon, @@ -36,6 +33,7 @@ import { } from "@mui/icons-material"; import { fontGrid } from "@mui/material/styles/cssUtils.js"; import { active } from "d3"; +import style from "./../index.css"; const Body = { //maxWidth: 1000, @@ -158,6 +156,7 @@ export const Img = (props) => { return {props.alt}; } + export const CodeHandler = (props) => { const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : "" @@ -369,8 +368,7 @@ const Docs = (defaultprops) => { hash = hash.split('?')[0] } if (hash) { - console.log("HASH: ", hash) - const element = document.getElementById(hash) + const element = document.getElementById(hash.toLowerCase()) if (element) { element.scrollIntoView({ behavior: "instant", @@ -1022,8 +1020,10 @@ const Docs = (defaultprops) => { Date: Tue, 11 Jun 2024 19:17:19 +0530 Subject: [PATCH 006/336] docs: Updating documentation on k8s --- functions/kubernetes/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/functions/kubernetes/README.md b/functions/kubernetes/README.md index e8e315af..a54884e8 100644 --- a/functions/kubernetes/README.md +++ b/functions/kubernetes/README.md @@ -34,3 +34,35 @@ Step 2: Open the ```all-in-one.yaml``` file and review the configuration values. Step 3: Now, open ```https://:30008``` or ```http://:30007```. You should be seeing a signup page. NODE_IP should be where the frontend is deployed. +### Dev Mode + +1. Run backend and orborus with the environment variable `IS_KUBERNETES=true`: + +```bash +export IS_KUBERNETES=true +``` + +2. Turn on the k8s engine with minikube: + +```bash +minikube start +``` + +3. To use the worker scale feature, build the image with the following command: + +```bash +$NAME=shuffle-worker-scale +$VERSION=1.2.0 + +minikube build . -t shuffle/shuffle:$NAME -t shuffle/shuffle:$NAME_$VERSION -t docker.pkg.github.com/shuffle/shuffle/$NAME:$VERSION -t ghcr.io/shuffle/$NAME:$VERSION -t ghcr.io/shuffle/$NAME:nightly -t ghcr.io/shuffle/$NAME:$VERSION -t ghcr.io/shuffle/$NAME:nightly -t ghcr.io/shuffle/$NAME:latest +``` + +4. To run executions, Make sure to do the following: + +```bash +kubectl create role pod-creator --namespace=default --verb=create --resource=pods +kubectl create rolebinding pod-creator-binding --namespace=default --role=pod-creator --serviceaccount=default:default +``` + + + From 26ee1d4da57c95595726b9b3c3b07135bbeff25f Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 11 Jun 2024 16:05:15 +0200 Subject: [PATCH 007/336] Minor sdk fix --- backend/app_sdk/app_base.py | 22 +++++++++- backend/go-app/main.go | 3 +- backend/go-app/walkoff.go | 6 +++ functions/onprem/orborus/go.mod | 2 +- functions/onprem/orborus/go.sum | 2 + functions/onprem/orborus/orborus.go | 63 +++++++++++++++++++++++------ 6 files changed, 82 insertions(+), 16 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 6ef571aa..a7e0d4f8 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -652,7 +652,14 @@ class AppBase: sleeptime = float(random.randint(0, 10) / 10) try: - ret = requests.post(url, headers=headers, json=action_result, timeout=10, verify=False, proxies=self.proxy_config) + ret = requests.post( + url, + headers=headers, + json=action_result, + timeout=10, + verify=False, + proxies=self.proxy_config, + ) #self.logger.info(f"""[DEBUG] Successful result request: Status= {ret.status_code} (break on 200/201) & Action status: {action_result["status"]}. Response= {ret.text}""") if ret.status_code == 200 or ret.status_code == 201: @@ -660,7 +667,18 @@ class AppBase: break else: # FIXME: Add a checker for 403, and Proxy logs failing - self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}'") + headerauth = "" + if "Authorization" in headers: + headerauth = headers["Authorization"] + + try: + + self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}'. Execution ID: %d, Authorization: %d, Header Auth: %d" % (len(action_result["execution_id"]), len(action_result["authorization"]), len(headerauth))) + + except Exception as e: + self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}' (no detail)") + pass + time.sleep(sleeptime) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 98718c19..d51535fa 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5017,6 +5017,7 @@ func initHandlers() { r.HandleFunc("/api/v1/workflows/{key}/schedule/{schedule}", stopSchedule).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/stream", shuffle.HandleStreamWorkflow).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/stream", shuffle.HandleStreamWorkflowUpdate).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/duplicate", shuffle.DuplicateWorkflow).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}", deleteWorkflow).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}", shuffle.SaveWorkflow).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}", shuffle.GetSpecificWorkflow).Methods("GET", "OPTIONS") @@ -5029,6 +5030,7 @@ func initHandlers() { r.HandleFunc("/api/v1/recommendations/get_actions", shuffle.HandleActionRecommendation).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/recommendations/modify", shuffle.HandleRecommendationAction).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/child_workflows", shuffle.GetChildWorkflows).Methods("GET", "OPTIONS") // Triggers r.HandleFunc("/api/v1/hooks/new", shuffle.HandleNewHook).Methods("POST", "OPTIONS") @@ -5111,7 +5113,6 @@ func initHandlers() { // Important for email, IDS etc. Create this by: // PS: For cloud, this has to use cloud storage. // https://developer.box.com/reference/get-files-id-content/ - // 1. Creating the "get file" option. Make it possible to run this in the frontend. r.HandleFunc("/api/v1/files/download_remote", shuffle.HandleDownloadRemoteFiles).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/files/namespaces/{namespace}", shuffle.HandleGetFileNamespace).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}/content", shuffle.HandleGetFileContent).Methods("GET", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 10df25b2..b6b3258c 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -921,6 +921,12 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { return } + if len(workflow.ParentWorkflowId) > 0 { + resp.WriteHeader(403) + resp.Write([]byte(`{"success": false, "reason": "Can't delete a workflow distributed from your parent org"}`)) + return + } + if user.Id != workflow.Owner || len(user.Id) == 0 { if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" { log.Printf("[INFO] User %s is deleting workflow %s as admin. Owner: %s", user.Username, workflow.ID, workflow.Owner) diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod index 9101801f..8faab828 100644 --- a/functions/onprem/orborus/go.mod +++ b/functions/onprem/orborus/go.mod @@ -4,7 +4,7 @@ go 1.22.0 toolchain go1.22.2 -//replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared +replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared require ( github.com/docker/docker v26.1.0+incompatible diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum index 923438f9..1c27a7a1 100644 --- a/functions/onprem/orborus/go.sum +++ b/functions/onprem/orborus/go.sum @@ -393,6 +393,8 @@ github.com/shuffle/shuffle-shared v0.6.18 h1:mKc3vGuCz9ubdqMwaLocSbEUZyso620CY73 github.com/shuffle/shuffle-shared v0.6.18/go.mod h1:00QOcSPlUWMXzJj1D7pjcV9h6nVRWfSWqTM10+fhTd0= github.com/shuffle/shuffle-shared v0.6.27 h1:q4qZD6bGZFIvZ5Y10unGr3N3rZ7OryWyvvaGgANZJZU= github.com/shuffle/shuffle-shared v0.6.27/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= +github.com/shuffle/shuffle-shared v0.6.37 h1:IB8tJqubJmJwwpLYbXNMVWck5jgVO9SKcKo/BBu+wTc= +github.com/shuffle/shuffle-shared v0.6.37/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index f9e28d44..eddcfd71 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -683,6 +683,7 @@ func deployWorker(image string, identifier string, env []string, executionReques return err } + //env = append(env, fmt.Sprintf("KUBERNETES_CONFIG=%s", config.String())) // FIXME: When a service account is used, the account is also mounted in the pod @@ -697,6 +698,7 @@ func deployWorker(image string, identifier string, env []string, executionReques // use k8s downward API to find it if we are in a pod } + // Check if namespace exist as variable. If so, make it if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 && !namespacemade { kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") @@ -720,6 +722,18 @@ func deployWorker(image string, identifier string, env []string, executionReques } } + if len(kubernetesNamespace) == 0 { + foundNamespace, err := shuffle.GetKubernetesNamespace() + if err != nil { + //log.Printf("[ERROR] Failed getting Kubernetes namespace: %s", err) + } + + if len(foundNamespace) > 0 { + kubernetesNamespace = foundNamespace + os.Setenv("KUBERNETES_NAMESPACE", kubernetesNamespace) + } + } + if len(kubernetesNamespace) == 0 { kubernetesNamespace = "default" } @@ -739,12 +753,40 @@ func deployWorker(image string, identifier string, env []string, executionReques } } + containerLabels := map[string]string{ + "container": "shuffle-worker", + } + + containerAttachment := corev1.Container{ + Name: identifier, + Image: kubernetesImage, + Env: buildEnvVars(envMap), + + //ImagePullPolicy: "Never", + ImagePullPolicy: corev1.PullIfNotPresent, + } + + podname := shuffle.GetPodName() + + ctx := context.Background() + + if len(podname) > 0 { + currentPodStatus, err := shuffle.GetCurrentPodNetworkConfig(ctx, clientset, kubernetesNamespace, podname) + if err != nil { + log.Printf("[ERROR] Failed getting current pod network: %s", err) + } else { + log.Printf("[DEBUG] Current pod found!") + // currentPodStatus = k8s.io/api/core/v1.PodStatus + } + } + + // While testing: // kubectl delete pods --all --all-namespaces; kubectl delete services --all --all-namespaces pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: identifier, - Labels: map[string]string{"app": "shuffle-worker"}, + Labels: containerLabels, }, Spec: corev1.PodSpec{ RestartPolicy: "Never", @@ -753,23 +795,20 @@ func deployWorker(image string, identifier string, env []string, executionReques // "node": "master", // }, Containers: []corev1.Container{ - { - Name: identifier, - Image: kubernetesImage, - Env: buildEnvVars(envMap), - - //ImagePullPolicy: "Never", - ImagePullPolicy: corev1.PullIfNotPresent, - //ImagePullPolicy: "Always", - }, + containerAttachment, }, }, } // Check if running on ARM or x86 to download the correct image - // Add environment variables - // pod.Spec.Containers[0].Env = buildEnvVars(envMap) + // Get current pod's network so we can make the pod in it + + networks, err := clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Printf("[ERROR] Failed listing pods: %s", err) + } + createdPod, err := clientset.CoreV1().Pods(kubernetesNamespace).Create(context.Background(), pod, metav1.CreateOptions{}) if err != nil { From 0455b59bd8d57aad533201d83ddc01a50cdd5274 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 11 Jun 2024 16:30:38 +0200 Subject: [PATCH 008/336] More verbose app sdk --- functions/onprem/worker/worker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index 7ecbed96..ace7c908 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -1898,6 +1898,7 @@ func buildEnvVars(envMap map[string]string) []corev1.EnvVar { func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { if request.Body == nil { + log.Printf("[WARNING] (2) No body in request for workflowqueue") resp.WriteHeader(http.StatusBadRequest) return } From 5e7e120b8982c4b2b2d803682f5599dec6db1a6a Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Thu, 13 Jun 2024 02:48:39 +0530 Subject: [PATCH 009/336] fix: working my way towards a stable k8s release --- functions/onprem/orborus/orborus.go | 307 ++++++++++++++-------------- 1 file changed, 158 insertions(+), 149 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index eddcfd71..0660dfd2 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -659,6 +659,161 @@ func handleBackendImageDownload(ctx context.Context, images string) error { return nil } +func deployK8sWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) error { + env = append(env, fmt.Sprintf("IS_KUBERNETES=true")) + env = append(env, fmt.Sprintf("KUBERNETES_NAMESPACE=%s", os.Getenv("KUBERNETES_NAMESPACE"))) + + if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 { + env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_HOST=%s", os.Getenv("KUBERNETES_SERVICE_HOST"))) + } + + if len(os.Getenv("KUBERNETES_SERVICE_PORT")) > 0 { + env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_PORT=%s", os.Getenv("KUBERNETES_SERVICE_PORT"))) + } + + clientset, _, err := shuffle.GetKubernetesClient() + if err != nil { + log.Printf("[ERROR] Error getting kubernetes client:", err) + return err + } + + + //env = append(env, fmt.Sprintf("KUBERNETES_CONFIG=%s", config.String())) + + // FIXME: When a service account is used, the account is also mounted in the pod + // The volume mount location is: + // /var/run/secrets/kubernetes.io/serviceaccount + + // Look for if there is a default service account in use + if len(os.Getenv("KUBERNETES_SERVICE_ACCOUNT")) > 0 { + log.Printf("[DEBUG] Using Kubernetes service account %s", os.Getenv("KUBERNETES_SERVICE_ACCOUNT")) + env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_ACCOUNT=%s", os.Getenv("KUBERNETES_SERVICE_ACCOUNT"))) + + // use k8s downward API to find it if we are in a pod + } + + + // Check if namespace exist as variable. If so, make it + if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 && !namespacemade { + kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") + + // Make the namespace + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: os.Getenv("KUBERNETES_NAMESPACE"), + }, + } + + _, err := clientset.CoreV1().Namespaces().Create(context.Background(), namespace, metav1.CreateOptions{}) + if err != nil { + if !strings.Contains(strings.ToLower(fmt.Sprintf("%s", err)), "already exists") { + log.Printf("[ERROR] Failed creating Kubernetes namespace: %s", err) + } else { + namespacemade = true + } + } else { + namespacemade = true + } + } + + if len(kubernetesNamespace) == 0 { + foundNamespace, err := shuffle.GetKubernetesNamespace() + if err != nil { + //log.Printf("[ERROR] Failed getting Kubernetes namespace: %s", err) + } + + if len(foundNamespace) > 0 { + kubernetesNamespace = foundNamespace + os.Setenv("KUBERNETES_NAMESPACE", kubernetesNamespace) + } + } + + if len(kubernetesNamespace) == 0 { + kubernetesNamespace = "default" + } + + kubernetesImage := os.Getenv("SHUFFLE_KUBERNETES_WORKER") + if len(kubernetesImage) == 0 { + kubernetesImage = image + } + log.Printf("[DEBUG] Using Kubernetes worker image '%s'", kubernetesImage) + // image = "shuffle-worker:v1" //hard coded image name to test locally + + envMap := make(map[string]string) + for _, envStr := range env { + parts := strings.SplitN(envStr, "=", 2) + if len(parts) == 2 { + envMap[parts[0]] = parts[1] + } + } + + containerLabels := map[string]string{ + "container": "shuffle-worker", + } + + containerAttachment := corev1.Container{ + Name: identifier, + Image: kubernetesImage, + Env: buildEnvVars(envMap), + + //ImagePullPolicy: "Never", + ImagePullPolicy: corev1.PullIfNotPresent, + } + + podname := shuffle.GetPodName() + + ctx := context.Background() + + if len(podname) > 0 { + _, err := shuffle.GetCurrentPodNetworkConfig(ctx, clientset, kubernetesNamespace, podname) + if err != nil { + log.Printf("[ERROR] Failed getting current pod network: %s", err) + } else { + log.Printf("[DEBUG] Current pod found!") + // currentPodStatus = k8s.io/api/core/v1.PodStatus + } + } + + + // While testing: + // kubectl delete pods --all --all-namespaces; kubectl delete services --all --all-namespaces + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: identifier, + Labels: containerLabels, + }, + Spec: corev1.PodSpec{ + RestartPolicy: "Never", + DNSPolicy: "Default", + // NodeSelector: map[string]string{ + // "node": "master", + // }, + Containers: []corev1.Container{ + containerAttachment, + }, + }, + } + + // Check if running on ARM or x86 to download the correct image + + // Get current pod's network so we can make the pod in it + + _, err = clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Printf("[ERROR] Failed listing pods: %s", err) + } + + + createdPod, err := clientset.CoreV1().Pods(kubernetesNamespace).Create(context.Background(), pod, metav1.CreateOptions{}) + if err != nil { + //log.Printf("[ERROR] Failed creating pod: %v", err) + return err + } + + log.Printf("[INFO] Created pod %q in namespace %q\n", createdPod.Name, createdPod.Namespace) + return nil +} + func deployWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) error { if len(os.Getenv("REGISTRY_URL")) > 0 && os.Getenv("REGISTRY_URL") != "" { @@ -666,158 +821,12 @@ func deployWorker(image string, identifier string, env []string, executionReques } if isKubernetes == "true" { - env = append(env, fmt.Sprintf("IS_KUBERNETES=true")) - env = append(env, fmt.Sprintf("KUBERNETES_NAMESPACE=%s", os.Getenv("KUBERNETES_NAMESPACE"))) - - if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 { - env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_HOST=%s", os.Getenv("KUBERNETES_SERVICE_HOST"))) - } - - if len(os.Getenv("KUBERNETES_SERVICE_PORT")) > 0 { - env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_PORT=%s", os.Getenv("KUBERNETES_SERVICE_PORT"))) - } - - clientset, _, err := shuffle.GetKubernetesClient() + err := deployK8sWorker(image, identifier, env, executionRequest) if err != nil { - log.Printf("[ERROR] Error getting kubernetes client:", err) - return err + log.Printf("[ERROR] Failed deploying Kubernetes worker: %s", err) } - - //env = append(env, fmt.Sprintf("KUBERNETES_CONFIG=%s", config.String())) - - // FIXME: When a service account is used, the account is also mounted in the pod - // The volume mount location is: - // /var/run/secrets/kubernetes.io/serviceaccount - - // Look for if there is a default service account in use - if len(os.Getenv("KUBERNETES_SERVICE_ACCOUNT")) > 0 { - log.Printf("[DEBUG] Using Kubernetes service account %s", os.Getenv("KUBERNETES_SERVICE_ACCOUNT")) - env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_ACCOUNT=%s", os.Getenv("KUBERNETES_SERVICE_ACCOUNT"))) - - // use k8s downward API to find it if we are in a pod - } - - - // Check if namespace exist as variable. If so, make it - if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 && !namespacemade { - kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") - - // Make the namespace - namespace := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: os.Getenv("KUBERNETES_NAMESPACE"), - }, - } - - _, err := clientset.CoreV1().Namespaces().Create(context.Background(), namespace, metav1.CreateOptions{}) - if err != nil { - if !strings.Contains(strings.ToLower(fmt.Sprintf("%s", err)), "already exists") { - log.Printf("[ERROR] Failed creating Kubernetes namespace: %s", err) - } else { - namespacemade = true - } - } else { - namespacemade = true - } - } - - if len(kubernetesNamespace) == 0 { - foundNamespace, err := shuffle.GetKubernetesNamespace() - if err != nil { - //log.Printf("[ERROR] Failed getting Kubernetes namespace: %s", err) - } - - if len(foundNamespace) > 0 { - kubernetesNamespace = foundNamespace - os.Setenv("KUBERNETES_NAMESPACE", kubernetesNamespace) - } - } - - if len(kubernetesNamespace) == 0 { - kubernetesNamespace = "default" - } - - kubernetesImage := os.Getenv("SHUFFLE_KUBERNETES_WORKER") - if len(kubernetesImage) == 0 { - kubernetesImage = image - } - log.Printf("[DEBUG] Using Kubernetes worker image '%s'", kubernetesImage) - // image = "shuffle-worker:v1" //hard coded image name to test locally - - envMap := make(map[string]string) - for _, envStr := range env { - parts := strings.SplitN(envStr, "=", 2) - if len(parts) == 2 { - envMap[parts[0]] = parts[1] - } - } - - containerLabels := map[string]string{ - "container": "shuffle-worker", - } - - containerAttachment := corev1.Container{ - Name: identifier, - Image: kubernetesImage, - Env: buildEnvVars(envMap), - - //ImagePullPolicy: "Never", - ImagePullPolicy: corev1.PullIfNotPresent, - } - - podname := shuffle.GetPodName() - - ctx := context.Background() - - if len(podname) > 0 { - currentPodStatus, err := shuffle.GetCurrentPodNetworkConfig(ctx, clientset, kubernetesNamespace, podname) - if err != nil { - log.Printf("[ERROR] Failed getting current pod network: %s", err) - } else { - log.Printf("[DEBUG] Current pod found!") - // currentPodStatus = k8s.io/api/core/v1.PodStatus - } - } - - - // While testing: - // kubectl delete pods --all --all-namespaces; kubectl delete services --all --all-namespaces - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: identifier, - Labels: containerLabels, - }, - Spec: corev1.PodSpec{ - RestartPolicy: "Never", - DNSPolicy: "Default", - // NodeSelector: map[string]string{ - // "node": "master", - // }, - Containers: []corev1.Container{ - containerAttachment, - }, - }, - } - - // Check if running on ARM or x86 to download the correct image - - // Get current pod's network so we can make the pod in it - - networks, err := clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{}) - if err != nil { - log.Printf("[ERROR] Failed listing pods: %s", err) - } - - - createdPod, err := clientset.CoreV1().Pods(kubernetesNamespace).Create(context.Background(), pod, metav1.CreateOptions{}) - if err != nil { - //log.Printf("[ERROR] Failed creating pod: %v", err) - return err - } - - log.Printf("[INFO] Created pod %q in namespace %q\n", createdPod.Name, createdPod.Namespace) - return nil + return err } // Binds is the actual "-v" volume. From fe43b683b91f748d291398127a6e495184c354da Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 13 Jun 2024 18:49:18 +0200 Subject: [PATCH 010/336] Bunch of fixes for MSSP feature buildout --- frontend/src/components/BillingStats.jsx | 3 + frontend/src/components/ConfigureWorkflow.jsx | 5 - frontend/src/components/EditWorkflow.jsx | 185 +- frontend/src/components/NewHeader.jsx | 23 +- frontend/src/components/OrgHeaderexpanded.jsx | 1856 +++++++++-------- frontend/src/components/ParsedAction.jsx | 106 +- .../src/components/ShuffleCodeEditor1.jsx | 4 +- frontend/src/defaultCytoscapeStyle.jsx | 5 + frontend/src/views/Admin.jsx | 459 ++-- frontend/src/views/AngularWorkflow.jsx | 1102 ++++++++-- frontend/src/views/Docs.jsx | 1 + frontend/src/views/Workflows.jsx | 224 +- functions/onprem/worker/worker.go | 158 +- 13 files changed, 2759 insertions(+), 1372 deletions(-) diff --git a/frontend/src/components/BillingStats.jsx b/frontend/src/components/BillingStats.jsx index ac18d39b..519c7470 100644 --- a/frontend/src/components/BillingStats.jsx +++ b/frontend/src/components/BillingStats.jsx @@ -134,6 +134,9 @@ const AppStats = (defaultprops) => { Accept: "application/json", }, credentials: "include", + }).catch((error) => { + console.log("Error getting workflow stats: " + error); + return workflow }) if (response.status !== 200) { diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 6a7b55b7..6c8ae0ee 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -428,7 +428,6 @@ const ConfigureWorkflow = (props) => { console.log("Found webhook: ", trigger) if (trigger.app_association !== undefined && trigger.app_association.name !== null && trigger.app_association.name !== "") { - console.log("Actions: ", newactions) const findapp = trigger.app_association.name.toLowerCase() const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp) @@ -449,9 +448,6 @@ const ConfigureWorkflow = (props) => { newactions[foundindex].show_steps = true - console.log("CHANGED ACTION: ", newactions[foundindex]) - //console.log("Index: ", newactions[foundindex]) - continue } } @@ -1321,7 +1317,6 @@ const ConfigureWorkflow = (props) => { } if (step.type === "authenticate") { - console.log("AUTH STEP: ", step) if (data.must_authenticate === true ) { filled = false } else { diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index 9ac7fabe..4b4aa3d5 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -78,7 +78,6 @@ const EditWorkflow = (props) => { const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "") const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day')) - console.log("WORKFLOW: ", workflow) const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : []) const classes = useStyles(); @@ -235,7 +234,7 @@ const EditWorkflow = (props) => { -
+
{/*
@@ -565,14 +564,22 @@ const EditWorkflow = (props) => { fullWidth /> - - MSSP Suborg Distribution (beta - contact support@shuffler.io) + + MSSP Suborg Distribution (beta - contact support@shuffler.io for more info) {userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ? userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ? - - You can only distribute to suborgs from a parent org. - + userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ? + + Your organization does not have any suborgs yet. Please make one, then try again. + + : + + {innerWorkflow.parentorg_workflow !== undefined && innerWorkflow.parentorg_workflow !== null && innerWorkflow.parentorg_workflow.length > 0 ? This workflow is distributed from your parent workflow (you may not have access). : null} +
+
+ You can only distribute to suborgs from a parent org. +
: {/* diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index a23de3e5..a6058704 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -627,8 +627,8 @@ const CodeEditor = (props) => { newMarkers.push({ startRow: i, startCol: startCh, - endRow: i+1, - endCol: endCh+1, + endRow: i, + endCol: endCh, className: correctVariable ? "good-marker" : "bad-marker", type: "text", }) diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx index d36c8e8d..6f2455b3 100644 --- a/frontend/src/defaultCytoscapeStyle.jsx +++ b/frontend/src/defaultCytoscapeStyle.jsx @@ -133,6 +133,11 @@ const data = [ "background-gradient-stop-colors": "data(fillGradient)", }, }, + { + selector: `node[?parent_controlled]`, + css: { + }, + }, { selector: `node[app_name="Testing"]`, css: { diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index b074e404..f0f2dfd5 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -166,7 +166,9 @@ const Admin = (props) => { //console.log("Selected: ", selectedOrganization) const [appAuthenticationGroupModalOpen , setAppAuthenticationGroupModalOpen] = React.useState(false); const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]); + const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState(""); const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState(""); + const [appAuthenticationGroupEnvironment, setAppAuthenticationGroupEnvironment] = React.useState(""); const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState(""); const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]); const [organizationFeatures, setOrganizationFeatures] = React.useState({}); @@ -431,23 +433,62 @@ const Admin = (props) => { }); }; - const createAppAuthenticationGroup = (name, description, appAuthIds) => { + const deleteAppAuthenticationGroup = (appAuthGroupId) => { + const url = `${globalUrl}/api/v1/authentication/group/${appAuthGroupId}` + fetch(url, { + method: "DELETE", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for deleting app auth group"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + toast("Failed to delete app authentication group"); + } else { + toast("App authentication group deleted") + getAppAuthenticationGroups() + } + }) + .catch((error) => { + toast(error.toString()) + }) + } + + const createAppAuthenticationGroup = (name, environment, description, appAuthIds) => { + // Makes list of ids into a full-on list of auth, but just with the ID + // The backend fills in the rest + console.log("INput auth: ", appAuthIds) let app_auths = appAuthIds.map((appAuthId) => { return { id: appAuthId }; }) - fetch(globalUrl + "/api/v1/apps/authentication/group", { + var parsedAppGroup = { + label: name, + environment: environment, + description: description, + app_auths: app_auths + } + + if (appAuthenticationGroupId !== undefined && appAuthenticationGroupId !== null && appAuthenticationGroupId !== "") { + parsedAppGroup.id = appAuthenticationGroupId + } + + fetch(globalUrl + "/api/v1/authentication/group", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", - body: JSON.stringify({ - label: name, - description: description, - app_auths: app_auths - }), + body: JSON.stringify(parsedAppGroup), }) .then((response) => { if (response.status !== 200) { @@ -457,8 +498,15 @@ const Admin = (props) => { return response.json(); }) .then((responseJson) => { - // getAppAuthenticationGroups(); - toast("App authentication group created"); + if (responseJson.success === false) { + toast("Failed to create. Please try again, or contact support@shuffler.io") + } else { + // Close the modal + setAppAuthenticationGroupModalOpen(false) + + toast("App authentication group created") + getAppAuthenticationGroups() + } }) .catch((error) => { toast(error.toString()); @@ -805,7 +853,7 @@ If you're interested, please let me know a time that works for you, or set up a .then((responseJson) => { setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined setAllSchedules(responseJson.schedules || []); - setPipelines(responseJson.pipelines || []); + // setPipelines(responseJson.pipelines || []); }) .catch((error) => { // toast(error.toString()); @@ -990,13 +1038,11 @@ If you're interested, please let me know a time that works for you, or set up a } const data = { - command: pipeline.command, name: pipeline.name, type: state, environment: pipeline.environment, workflow_id: pipeline.workflow_id, trigger_id: pipeline.trigger_id, - start_node: pipeline.start_node, }; if (state === "start") toast("starting the pipeline"); @@ -1027,7 +1073,6 @@ If you're interested, please let me know a time that works for you, or set up a if (state === "start") toast("Successfully created pipeline"); else toast("Sucessfully stopped the pipeline"); } - setTimeout(handleGetAllTriggers, 1000); }) .catch((error) => { //toast(error.toString()); @@ -2093,10 +2138,10 @@ If you're interested, please let me know a time that works for you, or set up a }; const getAppAuthenticationGroups = () => { - console.log("DEBUG: Skipping app auth group loading") - return + //console.log("DEBUG: Skipping app auth group loading") + //return - fetch(globalUrl + "/api/v1/apps/authentication/group", { + fetch(globalUrl + "/api/v1/authentication/group", { method: "GET", headers: { "Content-Type": "application/json", @@ -4941,7 +4986,7 @@ If you're interested, please let me know a time that works for you, or set up a
+ + + + +
+ {/* Show a check box list of all app authentications to add to the auth group */} +
+ {authentication.map((data, index) => { + var checked = data.checked + if (checked === undefined || checked === null) { + checked = false + } + + if (appsForAppAuthGroup.includes(data.id)) { + checked = true + } + + return ( +
+ +
+ + { + handleAppAuthGroupCheckbox(data) + }} + name={data.label} + disabled={data.app.id in appsForAppAuthGroup} + /> +
+ + } + label={data.label} + /> +
+ ) + })} +
+
+ + )} @@ -5311,7 +5412,7 @@ If you're interested, please let me know a time that works for you, or set up a
- {/*
+ + +
-

App Authentication Groups

- +

App Authentication Groups

+ Groups of authentication options for subflows.{" "}
- + + + + - - {data.app_auths.map((appAuth, index) => ( - - {appAuth.app.name} - - ))} + {data.app_auths.map((appAuth, index) => { + if (appAuth.app.large_image === undefined || appAuth.app.large_image === null || appAuth.app.large_image === "") { + const foundImage = authentication.find((auth) => auth.app.id === appAuth.app.id) + if (foundImage !== undefined) { + appAuth.app.large_image = foundImage.app.large_image + + appAuth.app.name = foundImage.app.name + } + } + + const tooltip = `${appAuth.app.name.replaceAll("_", " ")} (authname: ${appAuth.label})` + + return ( + + {appAuth.app.name} + + ) + })}
} style={{ minWidth: 250, maxWidth: 250 }} @@ -5676,16 +5813,22 @@ If you're interested, please let me know a time that works for you, or set up a
{ + setAppAuthenticationGroupId(data.id) + + setAppAuthenticationGroupName(data.label) + setAppAuthenticationGroupDescription(data.description) + + setAppsForAppAuthGroup(data.app_auths.map((appAuth) => appAuth.id)) + setAppAuthenticationGroupEnvironment(data.environment) + setAppAuthenticationGroupModalOpen(true) }} - disabled={true} > { - // deleteAppAuthenticationGroup(data); + deleteAppAuthenticationGroup(data.id) }} - disabled={true} > @@ -5700,20 +5843,10 @@ If you're interested, please let me know a time that works for you, or set up a )} -
-
*/} - +
+ ) : null; const getLogs = async (ip, userId) => { diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 492e8ad9..f10ffae8 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -451,6 +451,9 @@ const AngularWorkflow = (defaultprops) => { const [lastExecution, setLastExecution] = React.useState(""); const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = React.useState(false); + const [authgroupModalOpen, setAuthgroupModalOpen] = React.useState(false); + const [authGroups, setAuthGroups] = React.useState([]) + const curpath = typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname; @@ -524,12 +527,13 @@ const AngularWorkflow = (defaultprops) => { const [workflowRecommendations, setWorkflowRecommendations] = React.useState(undefined); const [showErrors, setShowErrors] = React.useState(true); const [highlightedApp, setHighlightedApp] = React.useState("") - const [listCache, setListCache] = React.useState([]); - const [selectedOption, setSelectedOption] = React.useState(""); const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false); + const [distributedFromParent, setDistributedFromParent] = React.useState("") + const [suborgWorkflows, setSuborgWorkflows] = React.useState([]) + const [suggestionBox, setSuggestionBox] = React.useState({ "position": { "top": 500, @@ -539,6 +543,12 @@ const AngularWorkflow = (defaultprops) => { "attachedTo": "", }) + useEffect(() => { + if (!firstrequest && isLoaded && isLoggedIn && editWorkflowModalOpen === false) { + saveWorkflow(workflow) + } + }, [editWorkflowModalOpen]) + // New for generated stuff const releaseToConnectLabel = "Release to Connect" const integrationApps = [{ @@ -828,6 +838,75 @@ const AngularWorkflow = (defaultprops) => { loopRunning2 = true } + useEffect(() => { + if (workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0 && workflow.id === originalWorkflow.id) { + setOriginalWorkflow(workflow) + } + + // Special multi-workflow edgecase handler for events + if (distributedFromParent === "" && suborgWorkflows === []) { + } else { + if (cy !== undefined) { + cy.removeListener("select"); + cy.removeListener("unselect"); + cy.removeListener("add"); + cy.removeListener("remove"); + cy.removeListener("mouseover"); + cy.removeListener("mouseout"); + cy.removeListener("drag"); + cy.removeListener("free"); + cy.removeListener("cxttap"); + + setTimeout(() => { + setupGraph(workflow) + + 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)); + + cy.edgehandles({ + handleNodes: (el) => { + if (el.isNode() && + el.data("buttonType") != "ACTIONSUGGESTION" && + !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; + }, + }) + }, 50) + } + } + }, [workflow]) + useEffect(() => { // Current variable + future state controlled // This is so that the loop can stop itself as well @@ -958,7 +1037,7 @@ const AngularWorkflow = (defaultprops) => { }; const getAvailableWorkflows = (trigger_index) => { - fetch(globalUrl + "/api/v1/workflows", { + fetch(globalUrl + "/api/v1/workflows?subflow=true", { method: "GET", headers: { "Content-Type": "application/json", @@ -1307,7 +1386,7 @@ const AngularWorkflow = (defaultprops) => { if (response.status !== 200) { stop(); setExecutionModalView(0); - toast("Failed loading the workflow run") + //toast("Failed loading the workflow run") console.log("Status not 200 for stream results :O!"); //const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; @@ -1675,12 +1754,18 @@ const AngularWorkflow = (defaultprops) => { }) } - const saveWorkflow = (curworkflow, executionArgument, startNode) => { + const saveWorkflow = (curworkflow, executionArgument, startNode, duplicationOrg) => { 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` + + setTimeout(() => { + toast("You may not have access to this workflow.") + //window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle` + window.location.href = `/workflows` + }, 2500) + return } @@ -1910,13 +1995,19 @@ const AngularWorkflow = (defaultprops) => { useworkflow.id = props.match.params.key } + var headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + + if (useworkflow.org_id !== undefined && useworkflow.org_id !== null && useworkflow.org_id.length > 0) { + headers["Org-Id"] = useworkflow.org_id + } + setLastSaved(true); fetch(`${globalUrl}/api/v1/workflows/${useworkflow.id}`, { method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + headers: headers, body: JSON.stringify(useworkflow), credentials: "include", }) @@ -1924,11 +2015,20 @@ const AngularWorkflow = (defaultprops) => { setSavingState(0); if (response.status !== 200) { console.log("Status not 200 for setting workflows :O!"); - } + } else { + if (distributedFromParent === "" && suborgWorkflows === []) { + } else { + getChildWorkflows(useworkflow.id) + } + } return response.json(); }) .then((responseJson) => { + if (duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) { + duplicateParentWorkflow(useworkflow, duplicationOrg, true) + } + if (executionArgument !== undefined && startNode !== undefined) { //console.log("Running execution AFTER saving"); executeWorkflow(executionArgument, startNode, true); @@ -1993,8 +2093,11 @@ const AngularWorkflow = (defaultprops) => { console.log("Save workflow error: ", error.toString()); }); - setOriginalWorkflow(useworkflow) - return success; + if (originalWorkflow.id === undefined || originalWorkflow.id === null || originalWorkflow.id.length === 0 || useworkflow.id === originalWorkflow.id) { + setOriginalWorkflow(useworkflow) + } + + return success }; const monitorUpdates = () => { @@ -2087,14 +2190,20 @@ const AngularWorkflow = (defaultprops) => { curelements[i].addClass("not-executing-highlight"); } + var headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + + if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) { + headers["Org-Id"] = workflow.org_id + } + 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", - }, + headers: headers, credentials: "include", body: JSON.stringify(data), } @@ -2167,6 +2276,37 @@ const AngularWorkflow = (defaultprops) => { // This can be used to only show prioritzed ones later // Right now, it can prioritize authenticated ones //"Testing", + // + // + + const getAuthGroups = () => { + fetch(globalUrl + "/api/v1/authentication/groups", { + 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 === true) { + setAuthGroups(responseJson.data) + } else { + console.log("AppAuth group loading error: " + responseJson.reason); + } + }) + .catch((error) => { + setAuthGroups([]); + console.log("AppAuth group loading error: " + error.toString()); + }) + } const getAppAuthentication = (reset, updateAction, closeMenu) => { fetch(globalUrl + "/api/v1/apps/authentication", { @@ -2177,127 +2317,130 @@ const AngularWorkflow = (defaultprops) => { }, credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for app auth :O!"); + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for app auth :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + var shouldClose = false + if (responseJson.success) { + getAuthGroups() + + 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]; + + //console.log("Got auth: ", item); + + const newfields = {}; + for (let filterkey in item.fields) { + newfields[item.fields[filterkey].key] = item.fields[filterkey].value; } - return response.json(); - }) - .then((responseJson) => { - var shouldClose = false - if (responseJson.success) { - var newauth = []; - for (let authkey in responseJson.data) { - if (responseJson.data[authkey].defined === false) { - continue; - } + item.fields = newfields; - newauth.push(responseJson.data[authkey]); - } + const appname = selectedApp.name.toLowerCase().replaceAll(" ", "_", -1) + const itemname = item.app.name.toLowerCase().replaceAll(" ", "_", -1) + if (itemname === appname) { + authenticationOptions.push(item); - setAppAuthentication(newauth); + // Always becoming the last one + if (item.edited > latest) { + latest = item.edited; + selectedAction.selectedAuthentication = item; - 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]; - - //console.log("Got auth: ", item); - - const newfields = {}; - for (let filterkey in item.fields) { - newfields[item.fields[filterkey].key] = item.fields[filterkey].value; - } - - item.fields = newfields; - - const appname = selectedApp.name.toLowerCase().replaceAll(" ", "_", -1) - const itemname = item.app.name.toLowerCase().replaceAll(" ", "_", -1) - if (itemname === appname) { - authenticationOptions.push(item); - - // Always becoming the last one - if (item.edited > latest) { - latest = item.edited; - selectedAction.selectedAuthentication = item; - - for (let actionkey in workflow.actions) { - const actionAppname = workflow.actions[actionkey].app_name.toLowerCase().replaceAll(" ", "_", -1) - if (actionAppname === appname) { - workflow.actions[actionkey].selectedAuthentication = item; - workflow.actions[actionkey].authentication_id = item.id; - appUpdates = true; - } - } - } else { - //console.log("Not newer: ", item.edited, " vs ", latest) - } - } else { - //console.log("Appname is wrong: ", appname, " vs ", itemname) - } - } - - 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); - - toast("Added and updated authentication!"); - shouldClose = true - } else { - console.log("Closing auth modal? FAIL") - - toast("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); - shouldClose = false - } - } else { - toast("No authentication to update"); - } - } else { - shouldClose = true - } - } else { - setAppAuthentication([]); - shouldClose = true - } - - // Auto-closing if changes were made - if (closeMenu === true && shouldClose === true) { - setAuthenticationModalOpen(false); + for (let actionkey in workflow.actions) { + const actionAppname = workflow.actions[actionkey].app_name.toLowerCase().replaceAll(" ", "_", -1) + if (actionAppname === appname) { + workflow.actions[actionkey].selectedAuthentication = item; + workflow.actions[actionkey].authentication_id = item.id; + appUpdates = true; + } } - }) - .catch((error) => { - setAppAuthentication([]); - //toast("Auth loading error: " + error.toString()); - console.log("AppAuth error: " + error.toString()); - }); + } else { + //console.log("Not newer: ", item.edited, " vs ", latest) + } + } else { + //console.log("Appname is wrong: ", appname, " vs ", itemname) + } + } + + 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); + + toast("Added and updated authentication!"); + shouldClose = true + } else { + console.log("Closing auth modal? FAIL") + + toast("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); + shouldClose = false + } + } else { + toast("No authentication to update"); + } + } else { + shouldClose = true + } + + } else { + setAppAuthentication([]); + shouldClose = true + } + + // Auto-closing if changes were made + if (closeMenu === true && shouldClose === true) { + setAuthenticationModalOpen(false); + } + }) + .catch((error) => { + setAppAuthentication([]); + //toast("Auth loading error: " + error.toString()); + console.log("AppAuth error: " + error.toString()); + }); }; const getApps = () => { @@ -3082,6 +3225,36 @@ const AngularWorkflow = (defaultprops) => { return apps }; + const getChildWorkflows = (parentWorkflowId) => { + if (workflow.suborg_distribution === undefined || workflow.suborg_distribution === null || workflow.suborg_distribution.length === 0) { + return + } + + fetch(`${globalUrl}/api/v1/workflows/${parentWorkflowId}/child_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 response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + setSuborgWorkflows(responseJson) + } + }) + .catch((error) => { + console.log("Get child workflows error: ", error); + }) + } + const getWorkflow = (workflow_id, sourcenode) => { fetch(`${globalUrl}/api/v1/workflows/${workflow_id}`, { method: "GET", @@ -3125,9 +3298,24 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { // Load as JSON + // + // //console.log("Got workflow TXT: ", responseText) //const responseJson = JSON.parse(responseText) //console.log("Got workflow JSON: ", responseJson) + if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id.length > 0 && responseJson.id !== workflow_id) { + toast("Workflow ID mismatch. Redirecting to your workflow") + navigate(`/workflows/${responseJson.id}`) + } + + if (responseJson.parentorg_workflow !== undefined && responseJson.parentorg_workflow !== null && responseJson.parentorg_workflow !== "") { + setDistributedFromParent(responseJson.parentorg_workflow) + } + + + if (responseJson.childorg_workflow_ids !== undefined && responseJson.childorg_workflow_ids !== null && responseJson.childorg_workflow_ids.length > 0) { + getChildWorkflows(responseJson.id) + } // Not sure why this is necessary. if (responseJson.isValid === undefined) { @@ -3350,7 +3538,7 @@ const AngularWorkflow = (defaultprops) => { cy.on("add", "node", (e) => onNodeAdded(e)); cy.on("add", "edge", (e) => onEdgeAdded(e)); } else { - setOriginalWorkflow(responseJson); + setOriginalWorkflow(responseJson) setWorkflow(responseJson); setWorkflowDone(true); @@ -3704,14 +3892,11 @@ const AngularWorkflow = (defaultprops) => { } if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") { - console.log("Found triggers. Add!") - if (!found) { console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions) // Find how many executions it has var executions = 0 const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase())) - console.log("Matches: ", matchingExecutions.length) const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436" const decoratorNode = { position: { @@ -4220,7 +4405,7 @@ const AngularWorkflow = (defaultprops) => { return } - // Inject HTML at a fixed location + // Inject HTML at a fixed location? //const newHtml = "

Do you want to add this suggestion?

" // Find mouse cursor position on screen @@ -4415,13 +4600,24 @@ const AngularWorkflow = (defaultprops) => { return; } else if (data.isDescriptor) { - console.log("Can't select descriptor"); + // Find parent + event.target.unselect(); + + if (data.attachedTo !== undefined && data.attachedTo !== null && data.attachedTo.length > 0) { + const parentNode = cy.getElementById(data.attachedTo) + if (parentNode !== null && parentNode !== undefined) { + setTimeout(() => { + parentNode.select() + }, 100) + } + } + + //console.log("Can't select descriptor"); if (data.isTrigger) { console.log("But maybe we can select trigger descriptor? Maybe open execution tab?") setExecutionModalOpen(true) } - event.target.unselect(); return; } @@ -5701,9 +5897,6 @@ const AngularWorkflow = (defaultprops) => { } setWorkflow(workflow); - //if (data.type === "TRIGGER") { - // saveWorkflow(workflow); - //} } //var previouskey = 0 @@ -5983,7 +6176,15 @@ const AngularWorkflow = (defaultprops) => { workflow.id !== null && workflow.id.length > 0 ) { - window.location.pathname = "/workflows/" + props.match.params.key; + + // Check if + if (distributedFromParent === "" && suborgWorkflows === []) { + toast.info("Redirecting as the workflow ID does not match the URL") + + setTimeout(() => { + window.location.pathname = "/workflows/" + props.match.params.key; + }, 2500) + } } const animationDuration = 150; @@ -6171,7 +6372,6 @@ const AngularWorkflow = (defaultprops) => { }; const addActionSuggestions = (nodedata, event) => { - console.log("App Action suggestions being added") if (nodedata.type !== "ACTION") { return } @@ -6187,10 +6387,37 @@ const AngularWorkflow = (defaultprops) => { const parentlabel = parentNode.data("label").toLowerCase().replace(" ", "_") const parentname = parentNode.data("app_name").toLowerCase().replace(" ", "_") if (!parentlabel.startsWith(parentname)) { - console.log("Bad startname to start with: ", parentname, parentlabel) return } + // Check if action has changed + const parentAppId = parentNode.data("app_id") + const parentActionname = parentNode.data("name") + for (var appkey in apps) { + const curapp = apps[appkey] + + if (curapp.id !== parentAppId) { + continue + } + + if (curapp.actions === undefined || curapp.actions === null || curapp.actions.length === 0) { + continue + } + + var startIndex = curapp.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0) + if (startIndex === -1) { + startIndex = 0 + } + + if (curapp.actions[startIndex].name !== parentActionname) { + return + } + + break + } + + //const parentAction = parentNode.data("name") + 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, @@ -6204,8 +6431,7 @@ const AngularWorkflow = (defaultprops) => { // 2. Loop the apps' actions // 3. Find actions based on category label IF it exists - console.log("Fidning app match for: ", parentname) - var added = 0 + var addedLabels = [] for (let appKey in apps) { const curapp = apps[appKey] if (curapp.name.toLowerCase().replace(" ", "_") !== parentname) { @@ -6216,7 +6442,6 @@ const AngularWorkflow = (defaultprops) => { continue } - console.log("Found matching: ", curapp.name, parentname, curapp.actions.length) for (let actionKey in curapp.actions) { const curaction = curapp.actions[actionKey] @@ -6226,7 +6451,13 @@ const AngularWorkflow = (defaultprops) => { } if (curaction.category_label !== undefined && curaction.category_label !== null && curaction.category_label.length > 0) { - console.log("IN NODE ADD") + if (addedLabels.includes(curaction.category_label[0])) { + continue + } + + if (curaction.category_label[0].replaceAll("_", " ").toLowerCase() === "no label") { + continue + } cy.add({ group: "nodes", @@ -6240,12 +6471,13 @@ const AngularWorkflow = (defaultprops) => { }, position: { x: px, - y: py + (added * 50), + y: py + (addedLabels.length * 50), }, + locked: true, }) - added += 1 - if (added >= 3) { + addedLabels.push(curaction.category_label[0]) + if (addedLabels.length >= 2) { break } } @@ -6555,7 +6787,6 @@ const AngularWorkflow = (defaultprops) => { } if (!found) { - console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions) // Find how many executions it has var executions = 0 const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase())) @@ -7087,6 +7318,11 @@ const AngularWorkflow = (defaultprops) => { } */ + var parentcontrolled = false + if (branch.parent_controlled !== undefined && branch.parent_controlled !== null && branch.parent_controlled === true) { + parentcontrolled = true + } + edge.data = { id: branch.id, _id: branch.id, @@ -7096,6 +7332,7 @@ const AngularWorkflow = (defaultprops) => { conditions: conditions, hasErrors: branch.has_errors, decorator: false, + parent_controlled: parentcontrolled, }; // This is an attempt at prettier edges. The numbers are weird to work with. @@ -7125,7 +7362,6 @@ const AngularWorkflow = (defaultprops) => { return edge; }); - console.log("VISUAL BRANCHES: ", inputworkflow.visual_branches) if (inputworkflow.visual_branches !== undefined && inputworkflow.visual_branches !== null && inputworkflow.visual_branches.length > 0) { const visualedges = inputworkflow.visual_branches.map((branch, index) => { const edge = {}; @@ -7187,8 +7423,6 @@ const AngularWorkflow = (defaultprops) => { } else { setElements(insertedNodes); } - - console.log("Setupgraph done 2!") } const removeNode = (nodeId) => { @@ -7350,7 +7584,6 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (responseJson.success !== false) { - console.log("Usecases: ", usecases) setUsecases(responseJson) } else { } @@ -7402,6 +7635,7 @@ const AngularWorkflow = (defaultprops) => { if (firstrequest) { setFirstrequest(false); getWorkflow(props.match.params.key, {}); + getChildWorkflows(props.match.params.key) getRevisionHistory(props.match.params.key) getApps() fetchUsecases() @@ -7502,7 +7736,7 @@ const AngularWorkflow = (defaultprops) => { loopAllowed: function (node) { return false; }, - }); + }) //cy.edgehandles({ // preview: false, @@ -7606,7 +7840,7 @@ const AngularWorkflow = (defaultprops) => { trigger.status = "stopped"; setSelectedTrigger(trigger); setWorkflow(workflow); - saveWorkflow(workflow); + saveWorkflow(workflow) }) .catch((error) => { console.log("Stop schedule error: ", error.toString()) @@ -7690,8 +7924,8 @@ const AngularWorkflow = (defaultprops) => { return; } - var mappedStartnode = "" - const alledges = cy.edges().jsons() + var mappedStartnode = "" + const alledges = cy.edges().jsons() if (alledges !== undefined && alledges !== null && alledges.length > 0) { for (let edgekey in alledges) { const tmp = alledges[edgekey] @@ -10153,6 +10387,142 @@ const AngularWorkflow = (defaultprops) => { } } + const handleAppAuthGroupCheckbox = (data) => { + console.log("CHECKED: ", data) + + if (workflow.auth_groups === undefined || workflow.auth_groups === null) { + workflow.auth_groups = [] + } + + const foundIndex = workflow.auth_groups.findIndex(auth => auth === data.id) + if (foundIndex >= 0) { + workflow.auth_groups.splice(foundIndex, 1) + } else { + workflow.auth_groups.push(data.id) + } + + setWorkflow(workflow) + console.log("WF: ", workflow) + setUpdate(Math.random()) + } + + const authgroupModal = + { + }} + > + + { + e.preventDefault(); + setAuthgroupModalOpen(false) + }} + > + + + + + Authgroup Selection + + + + Authgroups are a way to control how a workflow runs. If chosen, the workflow will use the groups on the nodes that have them selected. If three are chosen, the workflows runs three times. This is an experimental MSSP feature to handle multiple environments. + + + + + {authGroups.map((data, index) => { + var checked = false + if (workflow.auth_groups !== undefined && workflow.auth_groups !== null) { + checked = workflow.auth_groups.includes(data.id) + } + + return ( +
{ + handleAppAuthGroupCheckbox(data) + }} + > + + + + {data.label} + + + + {data.environment} + + + {data.app_auths.map((appAuth, index) => { + if (appAuth.app.large_image === undefined || appAuth.app.large_image === null || appAuth.app.large_image === "") { + const foundImage = appAuthentication.find((auth) => auth.app.id === appAuth.app.id) + if (foundImage !== undefined) { + appAuth.app.large_image = foundImage.app.large_image + + appAuth.app.name = foundImage.app.name + } + } + + const tooltip = `${appAuth.app.name.replaceAll("_", " ")} (authname: ${appAuth.label})` + + return ( + + {appAuth.app.name} + + ) + })} + +
+ ) + })} + + + +
+
+ const executionArgumentModal = { // value: auth.id, // }); setSelectedAuth(auth.id); - }; - - console.log("TRANSFORMED AUTH DATA: ", transformedAuthData); + } return (
@@ -12643,9 +13011,11 @@ const AngularWorkflow = (defaultprops) => {
)} + {workflows === undefined || workflows === null || workflows.length === 0 ? null : ( + { - const cytoscapeViewWidths = isMobile ? 50 : 850; + const cytoscapeViewWidths = isMobile ? 50 : 950; const bottomBarStyle = { position: "fixed", right: isMobile ? 20 : 20, @@ -14980,7 +15350,8 @@ const AngularWorkflow = (defaultprops) => { }}>{workflow.name} - {isCorrectOrg ? null : + {!distributedFromParent ? + isCorrectOrg ? null : Warning: Change { }} >Active Organization to edit this Workflow. + : + + Warning: This workflow is controlled by your parent org and may not be editable. + } + + {originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0 || originalWorkflow.suborg_distribution.includes("none") ? null : + + + + View Suborg workflow + + + + } + + + {authGroups !== undefined && authGroups !== null && authGroups.length > 0 ? + + + + + + : null} +
{parentWorkflows.slice(0,5).map((wf, index) => { @@ -15346,7 +15940,7 @@ const AngularWorkflow = (defaultprops) => { ) } - const shownErrors = !isMobile && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 && showErrors && (!workflow.public || userdata.support === true) ? + const shownErrors = !distributedFromParent && !isMobile && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 && showErrors && (!workflow.public || userdata.support === true) ?
{ } } + /* if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { console.log("Shift key pressed") if (!workflow.public && executionModalOpen) { @@ -15544,6 +16139,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionModalView(0); } } + */ }; document.addEventListener('keydown', handleKeyDown); @@ -15594,6 +16190,106 @@ const AngularWorkflow = (defaultprops) => { ) } + // Used for handling suborg workflow distribution management + const updateCurrentWorkflow = (inputworkflow) => { + setLastSaved(false) + setSelectedAction({}); + setSelectedApp({}) + setWorkflow(inputworkflow) + + // Update props match key + if (inputworkflow.parentorg_workflow !== undefined && inputworkflow.parentorg_workflow !== null && inputworkflow.parentorg_workflow !== "") { + setDistributedFromParent(inputworkflow.parentorg_workflow) + } else { + setDistributedFromParent("") + } + + if (cy !== undefined) { + cy.removeListener("select"); + cy.removeListener("unselect"); + + cy.removeListener("add"); + cy.removeListener("remove"); + + cy.removeListener("mouseover"); + cy.removeListener("mouseout"); + + cy.removeListener("drag"); + cy.removeListener("free"); + cy.removeListener("cxttap"); + + setElements([]) + + // Remove all edges + cy.edges().remove() + cy.nodes().remove() + } + + // Remove all cytoscape triggers first? + /* + setTimeout(() => { + setupGraph(inputworkflow) + + 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)); + }, 25) + */ + + } + + // Uses Org-Id referencing header to create a workflow while getting it in realtime + // This further ensures the user needs access to GET the workflow properly + const duplicateParentWorkflow = (inputWorkflow, org_id, setWorkflow) => { + fetch(`${globalUrl}/api/v1/workflows/${inputWorkflow.id}`, { + method: "GET", + headers: { + "Org-Id": org_id, + "Content-Type": "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + getChildWorkflows(inputWorkflow.id) + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + //toast("Failed to duplicate workflow") + } else { + //toast("Successfully duplicated workflow. Reloading child workflows.") + if (setWorkflow === true) { + updateCurrentWorkflow(responseJson) + } + } + }) + .catch((error) => { + console.log("Dupe workflow for suborg error: ", error.toString()) + }) + } + const BottomCytoscapeBar = () => { if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) { return null; @@ -15616,11 +16312,11 @@ const AngularWorkflow = (defaultprops) => { ) : ( - - ); return ( @@ -15669,6 +16364,7 @@ const AngularWorkflow = (defaultprops) => { /> )} + {/*userdata.avatar === creatorProfile.github_avatar ? null :*/} @@ -15903,6 +16599,8 @@ const AngularWorkflow = (defaultprops) => { + +
); @@ -16021,6 +16719,7 @@ const AngularWorkflow = (defaultprops) => { aiSubmit={aiSubmit} listCache={listCache} + authGroups={authGroups} apps={apps} expansionModalOpen={codeEditorModalOpen} setExpansionModalOpen={setCodeEditorModalOpen} @@ -16600,7 +17299,8 @@ const AngularWorkflow = (defaultprops) => { // This is the playbutton at 150x150 const defaultImage = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg=="; + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg==" + const size = 40; const borderRadius = 5 if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) { @@ -16617,7 +17317,27 @@ const AngularWorkflow = (defaultprops) => { ) } - if (execution.execution_source === "webhook") { + if (execution.execution_source === "authgroups") { + const iconMargin = 7 + return ( +
+ +
+ ) + + } else if (execution.execution_source === "webhook") { return ( {"webhook"} { /> 0 ? ` Authgroup: ${data.authgroup}` : '')} + placement="left" >
{ {data.workflow.actions !== null ? (
{ marginBottom: "auto", }} > - {successActions} / {skippedActions > 0 ? skippedActions : {skippedActions}} / {calculatedResult} + {successActions} + {skippedActions > 0 ? skippedActions : {skippedActions}} = {calculatedResult}
) : null} @@ -17453,7 +18173,7 @@ const AngularWorkflow = (defaultprops) => { ) : null}
- {executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 && executionData.workflow.actions[0].environment !== "Cloud" ? + {executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
Env      @@ -17481,16 +18201,29 @@ const AngularWorkflow = (defaultprops) => { {executionData.execution_source !== undefined && executionData.execution_source !== null && executionData.execution_source.length > 0 && - executionData.execution_source !== "default" ? ( + executionData.execution_source !== "default" || + (executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0) ? (
Source    - {executionData.execution_parent !== null && + + + {executionData.execution_source === "authgroups" || (executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0) ? + + Auth Group '{executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0 ? `${executionData.authgroup}` : null}' + + : + executionData.execution_parent !== null && executionData.execution_parent !== undefined && executionData.execution_parent.length > 0 ? ( - executionData.execution_source === props.match.params.key ? ( + executionData.execution_source === props.match.params.key ? { @@ -17502,7 +18235,7 @@ const AngularWorkflow = (defaultprops) => { > Parent Execution - ) : ( + : { Parent Workflow ) - ) : ( + : executionData.execution_source === "questions" || executionData.execution_source === "web" ? { : executionData.execution_source - )} + }
) : null} @@ -20657,6 +21390,7 @@ const AngularWorkflow = (defaultprops) => { {authenticationModal} {tenzirConfigModal} {/*editWorkflowModal*/} + {authgroupModal} {executionArgumentModal} {configureWorkflowModal} {/*usecaseSlidein*/} @@ -20768,7 +21502,7 @@ const AngularWorkflow = (defaultprops) => {
) : (
- + Loading Workflow & Apps... diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 565f051f..3ba402cb 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -46,6 +46,7 @@ const Body = { height: "100%", color: "white", position: "relative", + paddingTop: 40, //textAlign: "center", }; diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 5c47c76b..99d4fca1 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -74,6 +74,7 @@ import { ArrowLeft as ArrowLeftIcon, ArrowRight as ArrowRightIcon, QueryStats as QueryStatsIcon, + Visibility as VisibilityIcon, } from "@mui/icons-material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; @@ -614,6 +615,7 @@ const Workflows = (props) => { const [videoViewOpen, setVideoViewOpen] = React.useState(false) const [gettingStartedItems, setGettingStartedItems] = React.useState([]) const [selectedWorkflowIndexes, setSelectedWorkflowIndexes] = React.useState([]) + const [highlightIds, setHighlightIds] = React.useState([]) const [apps, setApps] = React.useState([]); @@ -1213,7 +1215,24 @@ const Workflows = (props) => { } setFirstLoad(false) - }, 100) + }, 250) + + /* + setTimeout(() => { + var timeout = 0 + for (var key in newarray) { + const wf = newarray[key] + if (wf.actions === undefined || wf.actions === null || wf.actions.length === 0) { + setTimeout(() => { + sideloadWorkflow(wf.id, false) + }, timeout) + + timeout += 1000 + } + + } + }, 1000) + */ } else { if (isLoggedIn) { @@ -1637,20 +1656,24 @@ const Workflows = (props) => { }); }; - const copyWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)); - toast("Copying workflow " + data.name); - data.id = ""; - data.name = data.name + "_copy"; - data = deduplicateIds(data, true); + const duplicateWorkflow = (data) => { + //data = JSON.parse(JSON.stringify(data)); + toast("Copying workflow '" + data.name + "'. The new workflow will load in and be highlighted."); + //data.id = ""; + //data.name = data.name + "_copy"; + //data = deduplicateIds(data, true); - fetch(globalUrl + "/api/v1/workflows", { + const duplicateData = { + name: data.name + "_copy", + } + + fetch(`${globalUrl}/api/v1/workflows/${data.id}/duplicate`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, - body: JSON.stringify(data), + body: JSON.stringify(duplicateData), credentials: "include", }) .then((response) => { @@ -1660,7 +1683,20 @@ const Workflows = (props) => { } return response.json(); }) - .then(() => { + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Failed copying workflow: " + responseJson.reason) + } else { + toast("Failed copying workflow") + } + + return + } + + if (responseJson.id !== undefined) { + setHighlightIds([responseJson.id]) + } setTimeout(() => { getAvailableWorkflows(); }, 1000); @@ -1689,15 +1725,68 @@ const Workflows = (props) => { }) } - const sideloadWorkflow = (id, openEdit) => { + const exportSingleWorkflow = (data, setOpen) => { + setExportModalOpen(true) + + if (data.triggers !== null && data.triggers !== undefined) { + var newSubflows = []; + for (var key in data.triggers) { + const trigger = data.triggers[key]; + + if ( + trigger.parameters !== null && + trigger.parameters !== undefined + ) { + for (var subkey in trigger.parameters) { + const param = trigger.parameters[subkey]; + if ( + param.name === "workflow" && + param.value !== data.id && + !newSubflows.includes(param.value) + ) { + newSubflows.push(param.value); + } + } + } + } + + var parsedworkflows = []; + for (var key in newSubflows) { + const foundWorkflow = workflows.find( + (workflow) => workflow.id === newSubflows[key] + ); + if (foundWorkflow !== undefined && foundWorkflow !== null) { + parsedworkflows.push(foundWorkflow); + } + } + + if (parsedworkflows.length > 0) { + console.log( + "Appending subflows during export: ", + parsedworkflows.length + ); + data.subflows = parsedworkflows; + } + } + + setExportData(data) + setOpen(false) + } + + const sideloadWorkflow = (id, action, setOpen) => { + const storagewf = localStorage.getItem("workflows") const storageWorkflows = JSON.parse(storagewf) if (storageWorkflows === null || storageWorkflows === undefined || storageWorkflows.length === 0) { } else { for (var i = 0; i < storageWorkflows.length; i++) { if (storageWorkflows[i].id === id) { - if (storageWorkflows[i].image !== "") { - return + if (storageWorkflows[i].image !== "" && storageWorkflows[i].image !== undefined && storageWorkflows[i].image !== null) { + + if (action === undefined || action === null || action === "") { + console.log("RETURNING") + return + } } } } @@ -1718,8 +1807,16 @@ const Workflows = (props) => { return response.json() }) .then((responseJson) => { - if (openEdit) { - setEditing(responseJson) + if (responseJson.success !== false && responseJson.id !== undefined) { + if (action === "edit") { + setEditing(responseJson) + } else if (action === "publish") { + setPublishModalOpen(true) + setSelectedWorkflow(responseJson) + } else if (action === "export") { + exportSingleWorkflow(responseJson, setOpen) + } + } for (var i = 0; i < storageWorkflows.length; i++) { @@ -1730,8 +1827,9 @@ const Workflows = (props) => { } } - setWorkflows(storageWorkflows) - //setFilteredWorkflows(storageWorkflows) + //setWorkflows(storageWorkflows) + setFilteredWorkflows(storageWorkflows) + //setUpdate(Math.random()) }) .catch((error) => { console.log(error.toString()) @@ -1878,7 +1976,9 @@ const Workflows = (props) => { const appGroup = getWorkflowAppgroup(data) const [triggers, subflows] = getWorkflowMeta(data) - const isDistributed = data.suborg_distribution !== undefined && data.suborg_distribution !== null && data.suborg_distribution.includes(userdata.active_org.id) + const hasSuborgs = data.suborg_distribution !== undefined && data.suborg_distribution !== null && data.suborg_distribution.length > 0 + const isDistributed = (data.parentorg_workflow !== undefined && data.parentorg_workflow !== null && data.parentorg_workflow.length > 0) //|| (data.org_id !== userdata.active_org.id && data.org_id !== undefined && data.org_id !== null && data.org_id.length > 0) + const workflowMenuButtons = ( { setAnchorEl(null); }} > + {isDistributed ? + { + navigate(`/workflows/${data.id}`) + }} + > + + Explore Workflow + + : null} { event.stopPropagation() if (data.actions !== undefined && data.actions !== null && data.actions.length > 0 && data.image !== "") { @@ -1899,7 +2011,9 @@ const Workflows = (props) => { } else { //toast("Need to side-load workflow to be edited properly") - sideloadWorkflow(data.id, true) + sideloadWorkflow(data.id, "edit") + + toast.info("Loading full workflow for editing. Please wait...") } }} key={"change"} @@ -1909,9 +2023,11 @@ const Workflows = (props) => { { - setSelectedWorkflow(data); - setPublishModalOpen(true); + sideloadWorkflow(data.id, "publish") + + toast.info("Loading full workflow for publishing. Please wait...") }} key={"publish"} > @@ -1920,9 +2036,10 @@ const Workflows = (props) => { { - copyWorkflow(data); - setOpen(false); + duplicateWorkflow(data) + setOpen(false) }} key={"duplicate"} > @@ -1931,52 +2048,11 @@ const Workflows = (props) => { { - setExportModalOpen(true); + sideloadWorkflow(data.id, "export", setOpen) - if (data.triggers !== null && data.triggers !== undefined) { - var newSubflows = []; - for (var key in data.triggers) { - const trigger = data.triggers[key]; - - if ( - trigger.parameters !== null && - trigger.parameters !== undefined - ) { - for (var subkey in trigger.parameters) { - const param = trigger.parameters[subkey]; - if ( - param.name === "workflow" && - param.value !== data.id && - !newSubflows.includes(param.value) - ) { - newSubflows.push(param.value); - } - } - } - } - - var parsedworkflows = []; - for (var key in newSubflows) { - const foundWorkflow = workflows.find( - (workflow) => workflow.id === newSubflows[key] - ); - if (foundWorkflow !== undefined && foundWorkflow !== null) { - parsedworkflows.push(foundWorkflow); - } - } - - if (parsedworkflows.length > 0) { - console.log( - "Appending subflows during export: ", - parsedworkflows.length - ); - data.subflows = parsedworkflows; - } - } - - setExportData(data); - setOpen(false); + toast.info("Loading full workflow to be exported. Please wait...") }} key={"export"} > @@ -1985,6 +2061,7 @@ const Workflows = (props) => { { setDeleteModalOpen(true); setSelectedWorkflowId(data.id); @@ -2076,7 +2153,7 @@ const Workflows = (props) => { } return ( -
+
{selectedCategory !== "" ? @@ -2462,6 +2539,8 @@ const Workflows = (props) => { } const reader = new FileReader(); + var workflowids = [] + // Waits for the read reader.addEventListener("load", (event) => { var data = reader.result; @@ -2497,7 +2576,8 @@ const Workflows = (props) => { data.org_id = userdata.active_org.id data.org = [] data.execution_org = {} - + + workflowids.push(data.id) // Actually create it setNewWorkflow( @@ -2528,6 +2608,10 @@ const Workflows = (props) => { } setLoadWorkflowsModalOpen(false); + + if (workflowids.length > 0) { + setHighlightIds(workflowids) + } }; const getWorkflowMeta = (data) => { @@ -3682,7 +3766,7 @@ const Workflows = (props) => { workflowDelay += 75 } else { return ( - + ) diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index ace7c908..1aa83b26 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -1897,87 +1897,87 @@ func buildEnvVars(envMap map[string]string) []corev1.EnvVar { } func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { -if request.Body == nil { - log.Printf("[WARNING] (2) No body in request for workflowqueue") - resp.WriteHeader(http.StatusBadRequest) - return -} - -defer request.Body.Close() -body, err := ioutil.ReadAll(request.Body) -if err != nil { - log.Printf("[WARNING] (3) Failed reading body for workflowqueue") - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return -} - -var actionResult shuffle.ActionResult -err = json.Unmarshal(body, &actionResult) -if err != nil { - log.Printf("[ERROR] Failed shuffle.ActionResult unmarshaling (2): %s", err) - //resp.WriteHeader(401) - //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - //return -} - -if len(actionResult.ExecutionId) == 0 { - log.Printf("[ERROR] No workflow execution id in action result. Data: %s", string(body)) - resp.WriteHeader(400) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflow execution id in action result"}`))) - return -} - -// 1. Get the shuffle.WorkflowExecution(ExecutionId) from the database -// 2. if shuffle.ActionResult.Authentication != shuffle.WorkflowExecution.Authentication -> exit -// 3. Add to and update actionResult in workflowExecution -// 4. Push to db -// IF FAIL: Set executionstatus: abort or cancel -ctx := context.Background() -workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId) -if err != nil { - log.Printf("[ERROR][%s] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, actionResult.ExecutionId, err) - resp.WriteHeader(500) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist locally."}`, actionResult.ExecutionId))) - return -} - -if workflowExecution.Authorization != actionResult.Authorization { - log.Printf("[ERROR][%s] Bad authorization key when updating node (workflowQueue). Want: %s, Have: %s", actionResult.ExecutionId, workflowExecution.Authorization, actionResult.Authorization) - resp.WriteHeader(403) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key"}`))) - return -} - -if workflowExecution.Status == "FINISHED" { - log.Printf("[DEBUG][%s] Workflowexecution is already FINISHED. No further action can be taken", workflowExecution.ExecutionId) - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because it has status %s. Lastnode: %s"}`, workflowExecution.Status, workflowExecution.LastNode))) - return -} - -if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { - log.Printf("[WARNING][%s] Workflowexecution already has status %s. No further action can be taken", workflowExecution.ExecutionId, workflowExecution.Status) - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status))) - return -} - -retries := 0 -retry, retriesok := request.URL.Query()["retries"] -if retriesok && len(retry) > 0 { - val, err := strconv.Atoi(retry[0]) - if err == nil { - retries = val + if request.Body == nil { + log.Printf("[WARNING] (2) No body in request for workflowqueue") + resp.WriteHeader(http.StatusBadRequest) + return } -} -log.Printf("[DEBUG][%s] Action: Received, Label: '%s', Action: '%s', Status: %s, Run status: %s, Extra=Retry:%d", workflowExecution.ExecutionId, actionResult.Action.Label, actionResult.Action.AppName, actionResult.Status, workflowExecution.Status, retries) + defer request.Body.Close() + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("[WARNING] (3) Failed reading body for workflowqueue") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } -//results = append(results, actionResult) -//log.Printf("[INFO][%s] Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", workflowExecution.ExecutionId, action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters)) -//log.Printf("[DEBUG][%s] In workflowQueue with transaction", workflowExecution.ExecutionId) -runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp) + var actionResult shuffle.ActionResult + err = json.Unmarshal(body, &actionResult) + if err != nil { + log.Printf("[ERROR] Failed shuffle.ActionResult unmarshaling (2): %s", err) + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + //return + } + + if len(actionResult.ExecutionId) == 0 { + log.Printf("[ERROR] No workflow execution id in action result. Data: %s", string(body)) + resp.WriteHeader(400) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflow execution id in action result"}`))) + return + } + + // 1. Get the shuffle.WorkflowExecution(ExecutionId) from the database + // 2. if shuffle.ActionResult.Authentication != shuffle.WorkflowExecution.Authentication -> exit + // 3. Add to and update actionResult in workflowExecution + // 4. Push to db + // IF FAIL: Set executionstatus: abort or cancel + ctx := context.Background() + workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId) + if err != nil { + log.Printf("[ERROR][%s] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, actionResult.ExecutionId, err) + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist locally."}`, actionResult.ExecutionId))) + return + } + + if workflowExecution.Authorization != actionResult.Authorization { + log.Printf("[ERROR][%s] Bad authorization key when updating node (workflowQueue). Want: %s, Have: %s", actionResult.ExecutionId, workflowExecution.Authorization, actionResult.Authorization) + resp.WriteHeader(403) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key"}`))) + return + } + + if workflowExecution.Status == "FINISHED" { + log.Printf("[DEBUG][%s] Workflowexecution is already FINISHED. No further action can be taken", workflowExecution.ExecutionId) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because it has status %s. Lastnode: %s"}`, workflowExecution.Status, workflowExecution.LastNode))) + return + } + + if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { + log.Printf("[WARNING][%s] Workflowexecution already has status %s. No further action can be taken", workflowExecution.ExecutionId, workflowExecution.Status) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status))) + return + } + + retries := 0 + retry, retriesok := request.URL.Query()["retries"] + if retriesok && len(retry) > 0 { + val, err := strconv.Atoi(retry[0]) + if err == nil { + retries = val + } + } + + log.Printf("[DEBUG][%s] Action: Received, Label: '%s', Action: '%s', Status: %s, Run status: %s, Extra=Retry:%d", workflowExecution.ExecutionId, actionResult.Action.Label, actionResult.Action.AppName, actionResult.Status, workflowExecution.Status, retries) + + //results = append(results, actionResult) + //log.Printf("[INFO][%s] Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", workflowExecution.ExecutionId, action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters)) + //log.Printf("[DEBUG][%s] In workflowQueue with transaction", workflowExecution.ExecutionId) + runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp) } // Will make sure transactions are always ran for an execution. This is recursive if it fails. Allowed to fail up to 5 times @@ -2090,7 +2090,7 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl */ } } - + if setExecution || workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { log.Printf("[DEBUG][%s] Running setexec with status %s and %d/%d results", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) //result(s)", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results)) From 824756a6efb69354ea8b3c0520f5477e45d1d068 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 14 Jun 2024 05:52:58 +0530 Subject: [PATCH 011/336] fix: k8s support enhancements --- functions/onprem/orborus/orborus.go | 126 +++++++++++++++++++++------- 1 file changed, 98 insertions(+), 28 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 0660dfd2..a7e1031b 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -50,6 +50,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) // Starts jobs in bulk, so this could be increased @@ -659,7 +660,7 @@ func handleBackendImageDownload(ctx context.Context, images string) error { return nil } -func deployK8sWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) error { +func deployK8sWorker(image string, identifier string, env []string) error { env = append(env, fmt.Sprintf("IS_KUBERNETES=true")) env = append(env, fmt.Sprintf("KUBERNETES_NAMESPACE=%s", os.Getenv("KUBERNETES_NAMESPACE"))) @@ -677,7 +678,6 @@ func deployK8sWorker(image string, identifier string, env []string, executionReq return err } - //env = append(env, fmt.Sprintf("KUBERNETES_CONFIG=%s", config.String())) // FIXME: When a service account is used, the account is also mounted in the pod @@ -716,6 +716,9 @@ func deployK8sWorker(image string, identifier string, env []string, executionReq } } + env = append(env, fmt.Sprintf("BASE_URL=%s", baseUrl)) + env = append(env, fmt.Sprintf("SHUFFLE_SWARM_CONFIG=%s", swarmConfig)) + if len(kubernetesNamespace) == 0 { foundNamespace, err := shuffle.GetKubernetesNamespace() if err != nil { @@ -811,6 +814,33 @@ func deployK8sWorker(image string, identifier string, env []string, executionReq } log.Printf("[INFO] Created pod %q in namespace %q\n", createdPod.Name, createdPod.Namespace) + + // kubectl expose pod shuffle-workers --type=LoadBalancer --port=33333 + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: identifier, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "container": "shuffle-worker", + }, + Ports: []corev1.ServicePort{ + { + Protocol: "TCP", + Port: 33333, + TargetPort: intstr.FromInt(33333), + }, + }, + Type: corev1.ServiceTypeLoadBalancer, + }, + } + + _, err = clientset.CoreV1().Services(kubernetesNamespace).Create(context.TODO(), service, metav1.CreateOptions{}) + if err != nil { + log.Printf("[ERROR] Failed creating service: %v", err) + return err + } + return nil } @@ -820,14 +850,14 @@ func deployWorker(image string, identifier string, env []string, executionReques env = append(env, fmt.Sprintf("REGISTRY_URL=%s", os.Getenv("REGISTRY_URL"))) } - if isKubernetes == "true" { - err := deployK8sWorker(image, identifier, env, executionRequest) - if err != nil { - log.Printf("[ERROR] Failed deploying Kubernetes worker: %s", err) - } + // if isKubernetes == "true" { + // err := deployK8sWorker(image, identifier, env, executionRequest) + // if err != nil { + // log.Printf("[ERROR] Failed deploying Kubernetes worker: %s", err) + // } - return err - } + // return err + // } // Binds is the actual "-v" volume. // Max 20% CPU every second @@ -853,23 +883,25 @@ func deployWorker(image string, identifier string, env []string, executionReques } } - hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) - if strings.ToLower(cleanupEnv) != "false" { - hostConfig.AutoRemove = true - } - config := &container.Config{ Image: image, Env: env, } + if isKubernetes != "true" { + hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) + if strings.ToLower(cleanupEnv) != "false" { + hostConfig.AutoRemove = true + } + } + //var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG") parsedUuid := uuid.NewV4() - if swarmConfig == "run" || swarmConfig == "swarm" { + if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" { // FIXME: Should we handle replies properly? // In certain cases, a workflow may e.g. be aborted already. If it's aborted, that returns // a 401 from the worker, which returns an error here - go sendWorkerRequest(executionRequest) + go sendWorkerRequest(executionRequest, image, env) return nil } @@ -1510,16 +1542,26 @@ func main() { workerImage = newWorkerImage } - if swarmConfig == "run" || swarmConfig == "swarm" { - checkSwarmService(ctx) + if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" { - log.Printf("[DEBUG] Cleaning up containers from previous run") - cleanupExistingNodes(ctx) - time.Sleep(time.Duration(5) * time.Second) + if isKubernetes != "true" { + checkSwarmService(ctx) + log.Printf("[DEBUG] Cleaning up containers from previous run") + cleanupExistingNodes(ctx) + time.Sleep(time.Duration(5) * time.Second) + } log.Printf("[DEBUG] Deploying worker image %s to swarm", workerImage) - deployServiceWorkers(workerImage) - log.Printf("[DEBUG] Waiting 45 seconds to ensure workers are deployed. Run: \"docker service ls\" for more info") + + runString := "Run: \"docker service ls\" for more info" + + if isKubernetes != "true" { + deployServiceWorkers(workerImage) + } else { + deployK8sWorker(workerImage, "shuffle-workers", []string{}) + runString = "Run: \"kubectl get pods\" for more info" + } + log.Printf("[DEBUG] Waiting 45 seconds to ensure workers are deployed. %s", runString) time.Sleep(time.Duration(45) * time.Second) //deployServiceWorkers(workerImage) @@ -1529,7 +1571,12 @@ func main() { client := shuffle.GetExternalClient(baseUrl) fullUrl := fmt.Sprintf("%s/api/v1/workflows/queue", baseUrl) - log.Printf("[INFO] Finished configuring docker environment. Connecting to %s", fullUrl) + + if isKubernetes == "true" { + log.Printf("[INFO] Finished configuring kubernetes environment. Connecting to %s", fullUrl) + } else { + log.Printf("[INFO] Finished configuring docker environment. Connecting to %s", fullUrl) + } forwardData := bytes.NewBuffer([]byte{}) forwardMethod := "POST" @@ -2794,7 +2841,7 @@ func zombiecheck(ctx context.Context, workerTimeout int) error { return nil } -func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { +func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest, image string, env []string) error { parsedRequest := shuffle.OrborusExecutionRequest{ ExecutionId: workflowExecution.ExecutionId, Authorization: workflowExecution.Authorization, @@ -2827,6 +2874,15 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { streamUrl = fmt.Sprintf("%s:33333/api/v1/execute", parsedBaseurl) } + identifier := "shuffle-workers" + + if isKubernetes == "true" { + if shuffle.IsRunningInCluster() { + log.Printf("[INFO] Running in Kubernetes cluster") + // try getting the k8s worker server url + } + } + if len(workerServerUrl) > 0 { streamUrl = fmt.Sprintf("%s:33333/api/v1/execute", workerServerUrl) } @@ -2851,7 +2907,12 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { if len(newWorkerImage) > 0 { workerImage = newWorkerImage } - deployServiceWorkers(workerImage) + + if isKubernetes == "true" { + deployK8sWorker(workerImage, identifier, env) + } else { + deployServiceWorkers(workerImage) + } time.Sleep(time.Duration(10) * time.Second) //err = sendWorkerRequest(executionRequest) @@ -2870,7 +2931,11 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { workerImage = newWorkerImage } - deployServiceWorkers(workerImage) + if isKubernetes == "true" { + deployK8sWorker(workerImage, identifier, env) + } else { + deployServiceWorkers(workerImage) + } time.Sleep(time.Duration(10) * time.Second) //err = sendWorkerRequest(executionRequest) @@ -2899,6 +2964,11 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { _ = body - log.Printf("[DEBUG] Ran worker from request with execution ID: %s. Worker URL: %s. DEBUGGING:\ndocker service logs shuffle-workers 2>&1 -f | grep %s", workflowExecution.ExecutionId, streamUrl, workflowExecution.ExecutionId) + debugCommand := fmt.Sprintf("docker service logs shuffle-workers 2>&1 -f | grep %s", workflowExecution.ExecutionId) + if isKubernetes == "true" { + debugCommand = fmt.Sprintf("kubectl logs -n %s %s | grep %s", kubernetesNamespace, identifier, workflowExecution.ExecutionId) + } + + log.Printf("[DEBUG] Ran worker from request with execution ID: %s. Worker URL: %s. DEBUGGING:\n%s", workflowExecution.ExecutionId, streamUrl, debugCommand) return nil } From 3f0ed868cca0ac5c9470d76edc2729cdfa012705 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 17 Jun 2024 01:39:52 +0200 Subject: [PATCH 012/336] MSSP multi tenant workflow features ayy --- backend/go-app/go.mod | 2 +- backend/go-app/go.sum | 4 + backend/go-app/walkoff.go | 2 + frontend/src/components/Files.jsx | 150 ++-- frontend/src/components/NewHeader.jsx | 8 +- frontend/src/components/ParsedAction.jsx | 16 +- frontend/src/components/Priorities.jsx | 20 +- .../src/components/ShuffleCodeEditor1.jsx | 14 +- frontend/src/views/Admin.jsx | 654 +++++++++++------- frontend/src/views/AngularWorkflow.jsx | 430 ++++++++++-- frontend/src/views/Apps.jsx | 21 +- frontend/src/views/Docs.jsx | 8 +- frontend/src/views/SettingsPage.jsx | 88 ++- frontend/src/views/Workflows.jsx | 11 +- 14 files changed, 1064 insertions(+), 364 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 659f6364..5191a1d0 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -20,7 +20,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.6.40 + github.com/shuffle/shuffle-shared v0.6.46 golang.org/x/crypto v0.22.0 google.golang.org/api v0.176.1 google.golang.org/grpc v1.63.2 diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index 4c0d4206..13000a5c 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -303,6 +303,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -443,6 +445,8 @@ github.com/shuffle/shuffle-shared v0.6.27 h1:q4qZD6bGZFIvZ5Y10unGr3N3rZ7OryWyvva github.com/shuffle/shuffle-shared v0.6.27/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/shuffle/shuffle-shared v0.6.31 h1:MK1SW1pwjIP7hznq+mMlTPM1R3LIOfi1/bUL5xFSg/8= github.com/shuffle/shuffle-shared v0.6.31/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= +github.com/shuffle/shuffle-shared v0.6.46 h1:v/IXc+4V8DCWflGKGgaI37uuGnsylMjqKRylwNoXVrA= +github.com/shuffle/shuffle-shared v0.6.46/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 3d417472..8ea1550a 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -3382,6 +3382,8 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) { return } + debugUrl := fmt.Sprintf("/workflows/%s?execution_id=%s", workflowExecution.Workflow.ID, workflowExecution.ExecutionId) + resp.Header().Add("X-Debug-Url", debugUrl) workflowExecution.Priority = 11 environments, err := shuffle.GetEnvironments(ctx, user.ActiveOrg.Id) diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index 692d2e62..4ad5292a 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -44,9 +44,9 @@ const Files = (props) => { const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props; const [files, setFiles] = React.useState([]); - const [selectedNamespace, setSelectedNamespace] = React.useState("default"); + const [selectedCategory, setSelectedCategory] = React.useState("default"); const [openFileId, setOpenFileId] = React.useState(false); - const [fileNamespaces, setFileNamespaces] = React.useState([]); + const [fileCategories, setFileCategories] = React.useState([]); const [fileContent, setFileContent] = React.useState(""); const [openEditor, setOpenEditor] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false); @@ -57,6 +57,7 @@ const Files = (props) => { const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/standards") const [downloadBranch, setDownloadBranch] = React.useState("main"); const [downloadFolder, setDownloadFolder] = React.useState("translation_standards"); + const [contentLoading, setContentLoading] = React.useState(false) //const alert = useAlert(); const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log", "eml", "msg", "md", "xml", "sh", "bat", "ps1", "psm1", "psd1", "ps1xml", "pssc", "psc1", "response"] @@ -67,8 +68,8 @@ const Files = (props) => { console.log('do validate') console.log("new namespace name->",event.target.value); - fileNamespaces.push(event.target.value); - setSelectedNamespace(event.target.value); + fileCategories.push(event.target.value); + setSelectedCategory(event.target.value); setRenderTextBox(false); } @@ -107,12 +108,13 @@ const Files = (props) => { const getFiles = (namespace) => { var parsedurl = `${globalUrl}/api/v1/files` - if (namespace === undefined || namespace === "default") { + + if (namespace === undefined || namespace === null || namespace === "default") { } else if (namespace !== undefined && namespace !== null && namespace !== "") { parsedurl = `${globalUrl}/api/v1/files/namespaces/${namespace}?ids=true` - } else if (selectedNamespace !== undefined && selectedNamespace !== null && selectedNamespace !== "default" && selectedNamespace !== "") { - parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedNamespace}?ids=true` + } else if (selectedCategory !== undefined && selectedCategory !== null && selectedCategory !== "default" && selectedCategory !== "") { + parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedCategory}?ids=true` } fetch(parsedurl, { @@ -149,9 +151,11 @@ const Files = (props) => { setFiles([]); } - if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileNamespaces.length === 0 || responseJson.namespaces.length > fileNamespaces.length)) { - setFileNamespaces(responseJson.namespaces); - } + if (namespace === undefined || namespace === null || namespace === "default") { + if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileCategories.length === 0 || responseJson.namespaces.length > fileCategories.length)) { + setFileCategories(responseJson.namespaces) + } + } }) .catch((error) => { toast(error.toString()); @@ -159,7 +163,21 @@ const Files = (props) => { }; useEffect(() => { - getFiles(selectedNamespace) + getFiles("default") + + setTimeout(() => { + var category = selectedCategory + if (window.location.search.includes("category=")) { + const urlParams = new URLSearchParams(window.location.search) + category = urlParams.get("category") + } + + if (category !== undefined && category !== null && category.length > 0 && category !== "default") { + setSelectedCategory(category) + } + + getFiles(category) + }, 1000) }, []); const importStandardsFromUrl = (url, folder) => { @@ -393,7 +411,7 @@ const Files = (props) => { toast("Failed to delete file: " + responseJson.reason); } setTimeout(() => { - getFiles(); + getFiles(selectedCategory) }, 1500); }) .catch((error) => { @@ -402,6 +420,8 @@ const Files = (props) => { }; const readFileData = (file) => { + setContentLoading(true) + fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { method: "GET", headers: { @@ -411,6 +431,7 @@ const Files = (props) => { credentials: "include", }) .then((response) => { + setContentLoading(false) if (response.status !== 200) { console.log("Status not 200 for file :O!"); return ""; @@ -428,12 +449,12 @@ const Files = (props) => { return respdata }) .then((responseData) => { - - setFileContent(responseData); - //console.log("filecontent state ",fileContent); + setFileContent(responseData); + //console.log("filecontent state ",fileContent); }) .catch((error) => { - toast(error.toString()); + setContentLoading(false) + toast(error.toString()) }); }; @@ -505,12 +526,12 @@ const Files = (props) => { }; if ( - selectedNamespace !== undefined && - selectedNamespace !== null && - selectedNamespace.length > 0 && - selectedNamespace !== "default" + selectedCategory !== undefined && + selectedCategory !== null && + selectedCategory.length > 0 && + selectedCategory !== "default" ) { - data.namespace = selectedNamespace; + data.namespace = selectedCategory; } fetch(globalUrl + "/api/v1/files/create", { @@ -689,9 +710,9 @@ const Files = (props) => { - {fileNamespaces !== undefined && - fileNamespaces !== null && - fileNamespaces.length > 1 ? ( + {fileCategories !== undefined && + fileCategories !== null && + fileCategories.length > 1 ? ( File Category { + if (e.target.value === "") { + return + } + + if (e.target.value === selectedEnvironment) { + return + } + + toast.info("Updating environment KMS runs on to " + e.target.value) + data.environment = e.target.value + const envIndex = environments.findIndex((env) => env.Name === e.target.value) + if (envIndex === -1) { + toast.error("Environment not found") + return + } + + environments[envIndex].environment = e.target.value + setEnvironments(environments) + setShowEnvironmentDropdown(false) + + saveAuthentication(data) + }} + > + {environments.map((env, index) => { + if (env.archived === true) { + return null + } + + return ( + + {env.default === true ? "Default - " : ""}{env.Name} + + ) + })} + + + : null} +
+ } + style={{ + minWidth: 225, + maxWidth: 225, + overflow: "hidden", + }} + /> + + {/* + + */} + + {/* + + */} + { + return data.key; + }) + .join(", ") + } + style={{ + minWidth: 125, + maxWidth: 125, + overflow: "auto", + marginRight: 10, + }} + /> + + + { + updateAppAuthentication(data); + }} + disabled={ + data.org_id !== selectedOrganization.id ? true : false + } + > + + + {data.defined ? ( + + { + editAuthenticationConfig(data.id); + }} + > + + + + ) : ( + + {}} + disabled={ + data.org_id !== selectedOrganization.id + ? true + : false + } + > + + + + )} + { + deleteAuthentication(data); + }} + > + + + + + {selectedOrganization.id !== undefined && + data.org_id !== selectedOrganization.id ? ( + + + + ) : ( + + { + changeDistribution(data, !isDistributed); + }} + /> + + )} + + + ) + } + const authenticationView = curTab === 2 ? ( @@ -5361,6 +5721,10 @@ If you're interested, please let me know a time that works for you, or set up a
{authentication.map((data, index) => { var checked = data.checked + if (data.label !== undefined && data.label !== null && data.label.toLowerCase() === "kms shuffle storage") { + return null + } + if (checked === undefined || checked === null) { checked = false } @@ -5476,11 +5840,6 @@ If you're interested, please let me know a time that works for you, or set up a {authentication === undefined || authentication === null ? null : authentication.map((data, index) => { - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } - //console.log("Auth data: ", data) if (data.type === "oauth2") { data.fields = [ @@ -5503,187 +5862,14 @@ If you're interested, please let me know a time that works for you, or set up a ]; } - const isDistributed = - data.suborg_distributed === true ? true : false; - return ( - - - style={{ minWidth: 75, maxWidth: 75 }} - /> - - - {/* - - */} - - {/* - - */} - { - return data.key; - }) - .join(", ") - } - style={{ - minWidth: 125, - maxWidth: 125, - overflow: "auto", - marginRight: 10, - }} - /> - - - { - updateAppAuthentication(data); - }} - disabled={ - data.org_id !== selectedOrganization.id ? true : false - } - > - - - {data.defined ? ( - - { - editAuthenticationConfig(data.id); - }} - > - - - - ) : ( - - {}} - disabled={ - data.org_id !== selectedOrganization.id - ? true - : false - } - > - - - - )} - { - deleteAuthentication(data); - }} - > - - - - - {selectedOrganization.id !== undefined && - data.org_id !== selectedOrganization.id ? ( - - - - ) : ( - - { - changeDistribution(data, !isDistributed); - }} - /> - - )} - - - ); + + ) + + })}
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index f10ffae8..021cc0d6 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -88,6 +88,7 @@ import { Error as ErrorIcon, Warning as WarningIcon, ArrowLeft as ArrowLeftIcon, + ArrowRight as ArrowRightIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, FormatListNumbered as FormatListNumberedIcon, @@ -123,6 +124,8 @@ import { Add as AddIcon, ErrorOutline as ErrorOutlineIcon, + ArrowForward as ArrowForwardIcon, + } from "@mui/icons-material"; //import * as cytoscape from "cytoscape"; @@ -378,7 +381,7 @@ const svgSize = 24; const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const AngularWorkflow = (defaultprops) => { - const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id } = defaultprops; + const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id, ReactGA, } = defaultprops; const referenceUrl = globalUrl + "/api/v1/hooks/"; //const alert = useAlert() let navigate = useNavigate(); @@ -499,6 +502,7 @@ const AngularWorkflow = (defaultprops) => { const [selectedApp, setSelectedApp] = React.useState({}); const [selectedAction, setSelectedAction] = React.useState({}); const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); + const [selectedMeta, setSelectedMeta] = React.useState(undefined); // Disabled streaming for now const [streamDisabled, setStreamDisabled] = React.useState(true) @@ -950,6 +954,10 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (responseJson.success === true) { + if (responseJson.meta !== undefined && responseJson.meta !== null && Object.getOwnPropertyNames(responseJson.meta).length > 0) { + setSelectedMeta(responseJson.meta) + } + if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) { if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) { // Translate into markdown ![]() @@ -8018,10 +8026,10 @@ const AngularWorkflow = (defaultprops) => { backgroundColor: theme.palette.surfaceColor, cursor: "pointer", display: "flex", - }; + } - const VariableItem = (props) => { - const { variable, index, type } = props; + const VariableItem = (props) => { + const { variable, index, type } = props; const [open, setOpen] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState(null); @@ -8792,7 +8800,25 @@ const AngularWorkflow = (defaultprops) => { ? "cloud" : environments[defaultEnvironmentIndex] === undefined ? "cloud" - : environments[defaultEnvironmentIndex].Name; + : environments[defaultEnvironmentIndex].Name + + // Basic automatic auth mapping + var authId = "" + if (appAuthentication !== undefined && appAuthentication !== null && appAuthentication.length > 0) { + const appname = app.name.toLowerCase().replace(" ", "_") + for (var key in appAuthentication) { + const authKey = appAuthentication[key] + if (authKey.app.id === app.id) { + authId = authKey.id + break + } + + const appauthname = authKey.app.name.toLowerCase().replace(" ", "_") + if (appauthname === appname) { + authId = authKey.id + } + } + } // List other nodes in the workflow and see if they have an environment set. If they do, use that as the default if (cy !== undefined && cy !== null) { @@ -8840,7 +8866,7 @@ const AngularWorkflow = (defaultprops) => { app.categories.length > 0 ? app.categories[0] : "", - authentication_id: "", + authentication_id: authId, finished: false, template: app.template === true ? true : false, }; @@ -9722,30 +9748,49 @@ const AngularWorkflow = (defaultprops) => { } } - setSelectedAction(newSelectedAction); - setUpdate(Math.random()); + // Last fix for params + if (newSelectedAction.parameters !== undefined && newSelectedAction.parameters !== null && newSelectedAction.parameters.length > 0) { + for (let paramkey in newSelectedAction.parameters) { + const param = newSelectedAction.parameters[paramkey] + if (param.name !== "body") { + continue + } - const allNodes = cy.nodes().jsons(); - if (allNodes !== undefined && allNodes !== null) { - for (let nodekey in allNodes) { - const currentNode = allNodes[nodekey]; - if ( - currentNode.data.attachedTo === oldaction.id && - currentNode.data.isDescriptor - ) { - const foundnode = cy.getElementById(currentNode.data.id); - if (foundnode !== null && foundnode !== undefined) { - const iconInfo = GetIconInfo(newaction); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - foundnode.data("image", svgpin_Url); - foundnode.data("imageColor", iconInfo.iconBackgroundColor); - } - - break; + if (param.example !== undefined && param.example !== null && param.example.length > 0) { + if (param.value === undefined || param.value === null || param.value.length === 0) { + param.value = param.example } } + + newSelectedAction.parameters[paramkey] = param } + } + + console.log("New selected action: ", newSelectedAction) + setSelectedAction(newSelectedAction) + setUpdate(Math.random()) + + const allNodes = cy.nodes().jsons() + if (allNodes !== undefined && allNodes !== null) { + for (let nodekey in allNodes) { + const currentNode = allNodes[nodekey]; + if ( + currentNode.data.attachedTo === oldaction.id && + currentNode.data.isDescriptor + ) { + const foundnode = cy.getElementById(currentNode.data.id); + if (foundnode !== null && foundnode !== undefined) { + const iconInfo = GetIconInfo(newaction); + const svg_pin = ``; + const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); + foundnode.data("image", svgpin_Url); + foundnode.data("imageColor", iconInfo.iconBackgroundColor); + } + + break; + } + } + } // Send it in here, after all fields are filled // Disabled for now :( @@ -17673,6 +17718,71 @@ const AngularWorkflow = (defaultprops) => { ) } + const changeExecution = (data) => { + if ((data.result === undefined || data.result === null || data.result.length === 0) && data.status !== "FINISHED" && data.status !== "ABORTED") { + start() + setExecutionRunning(true) + setExecutionRequestStarted(false) + } + + var checkStarted = false + if (data.results !== undefined && data.results !== null && data.results.length > 0) { + if (data.execution_argument !== undefined && data.execution_argument !== null && data.execution_argument.includes("too large")) { + setExecutionData({}); + checkStarted = true + start(); + setExecutionRunning(true); + setExecutionRequestStarted(false); + } else { + if (data.results !== undefined && data.results !== null) { + for (let resultkey in data.results) { + if (data.results[resultkey].status !== "SUCCESS") { + continue + } + + if (data.results[resultkey].result.includes("too large")) { + setExecutionData({}); + checkStarted = true + start(); + setExecutionRunning(true); + setExecutionRequestStarted(false); + break + } + } + } + } + } + + const cur_execution = { + execution_id: data.execution_id, + authorization: data.authorization, + } + + setExecutionRequest(cur_execution) + setExecutionModalView(1) + + if (!checkStarted) { + handleUpdateResults(data, cur_execution) + + if (cy !== undefined && cy !== null) { + cy.elements().removeClass("success-highlight failure-highlight executing-highlight"); + for (let actionKey in data.workflow.actions) { + var actionitem = data.workflow.actions[actionKey] + + handleColoring(actionitem.id, "", actionitem.label) + } + + for (let resultKey in data.results) { + var item = data.results[resultKey] + + handleColoring(item.action.id, item.status, item.action.label) + } + } + + setExecutionData(data) + } + } + const ShowCopyingTooltip = () => { const [showCopying, setShowCopying] = React.useState(true) @@ -18101,7 +18211,7 @@ const AngularWorkflow = (defaultprops) => {

Details

@@ -18147,7 +18257,80 @@ const AngularWorkflow = (defaultprops) => { ) : null} - {isCloud ? ( + + + + + + + + + + + + + + {isCloud ? { - ) : null} + : null} +
{executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
@@ -18801,21 +18985,49 @@ const AngularWorkflow = (defaultprops) => { validate.result = JSON.parse(validate.result) } - const AppResultVariable = ({ data }) => { + const AppResultVariable = ({ data, action }) => { const [open, setOpen] = React.useState(false) const showVariable = data.value.length < 60 // Check if it's valid JSON const checked = validateJson(data.value.trim()) + if (data.name === "shuffle_action_logs" && data.value !== undefined && data.value !== null && data.value.length > 0 && data.value.includes("add env SHUFFLE_LOGS_DISABLED")) { + return ( +
+ + Action Logs + + + Logs for an action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode. + +
+ ) + } + + var showlink = false + if (data.name.endsWith("-Url")) { + //data.name = data.name.toLowerCase().replaceAll("-", "_") + if (data.value.startsWith(", ")) { + data.value = data.value.substring(2) + } + + if (!data.value.startsWith("http") || (data.value.startsWith("/") && data.value.includes("?"))) { + showlink = true + } + } + return (
{data.value.length > 60 || checked.valid ? { variant="body2" style={{ whiteSpace: 'pre-line', + color: showlink ? "#f85a3e" : "white", + cursor: showlink ? "pointer" : "default", }} - color="textSecondary" + onClick={(e) => { + if (showlink) { + e.preventDefault() + e.stopPropagation() + window.open(data.value, "_blank") + } + }} + color={showlink ? "inherit" : "textSecondary"} > {data.value} @@ -18981,6 +19202,10 @@ const AngularWorkflow = (defaultprops) => { return "Consider whether your Orborus environment can connect to a local IP or not." } + if (stringjson.includes("kms/")) { + return "KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=priorities. If you need help with KMS, please contact support@shuffler.io" + } + if (stringjson.includes("invalidurl")) { // IF count of "http" is more than one, 1, it's prolly invalid var additionalinfo = "" @@ -19006,7 +19231,7 @@ const AngularWorkflow = (defaultprops) => { if (stringjson.includes("connectionerror")) { if (stringjson.includes("kms")) { - return "KMS authentication failed. Check your notifications for more details." + return "KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=priorities&kms=true. If you need help with KMS, please contact support@shuffler.io" } return "The URL is incorrect, or Shuffle can't reach it. Set up a Shuffle Environment in the same VLAN, or whitelist Shuffle's IPs." @@ -19352,7 +19577,7 @@ const AngularWorkflow = (defaultprops) => { } return ( - + ); })}
@@ -20184,6 +20409,8 @@ const AngularWorkflow = (defaultprops) => { //if (configureWorkflowModalOpen) { // setSelectedAction({}); //} + // + setSelectedMeta(undefined) }} PaperProps={{ style: { @@ -20315,11 +20542,27 @@ const AngularWorkflow = (defaultprops) => { overflowY: "auto", overflowX: "hidden", }} + onLoad={() => { + /* + if (isCloud && ReactGA !== undefined) { + toast("Sending GA info") + // Google analytics info about what app people are looking at + ReactGA.event({ + category: "workflow", + action: `documentation_load`, + label: selectedApp.name, + }) + + } + */ + }} > {selectedApp.documentation === undefined || selectedApp.documentation === null || selectedApp.documentation.length === 0 ? ( - + { style={{ marginTop: 25, marginBottom: 25, - backgroundColor: theme.palette.inputColor, + backgroundColor: "rgba(255,255,255,0.6)", }} /> - - There is currently no extended documentation available for this - app. - + +
+ + There is no Shuffle-specific documentation for this app yet. Documentation is written custom for each app, and is a community effort. Hope to see your contribution + + +
+ - Want to help the making of, or imrpvoe this app?{" "} + Want to help the making of, or improve this app?{" "} +
{ )}
) : ( +
+ {selectedMeta !== undefined && selectedMeta !== null && Object.getOwnPropertyNames(selectedMeta).length > 0 && selectedMeta.name !== undefined && selectedMeta.name !== null ? +
+
+ {isMobile ? null : ( + + + + + + )} + {isMobile ? null : ( +
+ )} + + {selectedMeta.read_time} minute + {selectedMeta.read_time === 1 ? "" : "s"} to read + +
+
+ {isMobile || + selectedMeta.contributors === undefined || + selectedMeta.contributors === null ? ( + "" + ) : ( +
+ {selectedMeta.contributors.slice(0, 7).map((data, index) => { + return ( + + + {data.url} + + + ); + })} +
+ )} +
+
+ : null} { > {selectedApp.documentation} +
)}
diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 1000f7a0..4c816623 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -955,7 +955,6 @@ const Apps = (props) => { : null - console.log("Sharing config: ", sharingConfiguration); const activateButton = selectedApp.generated && !selectedApp.activated ? (
@@ -1281,10 +1280,15 @@ const Apps = (props) => { {isCloud && !internalIds.includes(selectedApp.name.toLowerCase()) ? -

{passwordFormMessage}

- +

{passwordFormMessage}

{isCloud && ( <> diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 99d4fca1..b742157a 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -2153,7 +2153,7 @@ const Workflows = (props) => { } return ( -
+
{selectedCategory !== "" ? @@ -2206,8 +2206,17 @@ const Workflows = (props) => { Edit '{data.name}' + +
+ + {isDistributed || hasSuborgs ? + + This is a parentorg-controlled workflow. + + : null}
} placement="right"> + Date: Mon, 17 Jun 2024 01:41:39 +0200 Subject: [PATCH 013/336] Fixed backend refs --- backend/go-app/go.mod | 10 +++++----- backend/go-app/go.sum | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 5191a1d0..ebe85b01 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -20,15 +20,15 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.6.46 + github.com/shuffle/shuffle-shared v0.6.47 golang.org/x/crypto v0.22.0 google.golang.org/api v0.176.1 google.golang.org/grpc v1.63.2 gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.30.0 - k8s.io/apimachinery v0.30.0 - k8s.io/client-go v0.30.0 + k8s.io/api v0.30.2 + k8s.io/apimachinery v0.30.2 + k8s.io/client-go v0.30.2 ) require ( @@ -58,7 +58,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/frikky/schemaless v0.0.11 // indirect + github.com/frikky/schemaless v0.0.13 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index 13000a5c..2023c819 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -160,6 +160,8 @@ github.com/frikky/schemaless v0.0.9 h1:RzNLPkJq5c4nlm5iLiTndFcbeQxdMGJIj266wSGt2 github.com/frikky/schemaless v0.0.9/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= github.com/frikky/schemaless v0.0.11 h1:c4r6CJX30XI+SoJdT9RlUd9qYSQlx6hvwGRtsypu+uM= github.com/frikky/schemaless v0.0.11/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= +github.com/frikky/schemaless v0.0.13 h1:ARiN9V7wr2VZXAr9JK5wvTbyPgpGrgeiL1VhR5MlgaQ= +github.com/frikky/schemaless v0.0.13/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsouza/go-dockerclient v1.11.0 h1:4ZAk6W7rPAtPXm7198EFqA5S68rwnNQORxlOA5OurCA= @@ -447,6 +449,8 @@ github.com/shuffle/shuffle-shared v0.6.31 h1:MK1SW1pwjIP7hznq+mMlTPM1R3LIOfi1/bU github.com/shuffle/shuffle-shared v0.6.31/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/shuffle/shuffle-shared v0.6.46 h1:v/IXc+4V8DCWflGKGgaI37uuGnsylMjqKRylwNoXVrA= github.com/shuffle/shuffle-shared v0.6.46/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= +github.com/shuffle/shuffle-shared v0.6.47 h1:EOalfIIBX97uGkgRRPsdUApts6yCCcXf1iSphm6UoE0= +github.com/shuffle/shuffle-shared v0.6.47/go.mod h1:XVIcR2/GyIk+6qmlpOG3NvIao6kCOma36EdMym4AeSY= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -981,10 +985,16 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= +k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= +k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= +k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= From 1296af8786d139e980f66bd2612f585685cb4066 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 17 Jun 2024 12:24:32 +0530 Subject: [PATCH 014/336] Fixed page reload issue on EnterKey press --- frontend/src/components/AppGrid.jsx | 10 ++++++++++ frontend/src/components/CreatorGrid.jsx | 5 +++++ frontend/src/components/DiscordChat.jsx | 5 +++++ frontend/src/components/DocsGrid.jsx | 5 +++++ frontend/src/components/SearchData.jsx | 4 ++-- frontend/src/components/WorkflowGrid.jsx | 5 +++++ 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 0f3fedef..11e25f0b 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -232,6 +232,11 @@ const AppGrid = (props) => { removeQuery("q"); refine(event.currentTarget.value); }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} limit={5} /> {/*isSearchStalled ? 'My search is stalled' : ''*/} @@ -994,6 +999,11 @@ const AppGrid = (props) => { onChange={(event) => { setSearchQuery(event.currentTarget.value); }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} limit={5} /> {/*isSearchStalled ? 'My search is stalled' : ''*/} diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx index ce6a197f..7010f483 100644 --- a/frontend/src/components/CreatorGrid.jsx +++ b/frontend/src/components/CreatorGrid.jsx @@ -136,6 +136,11 @@ const CreatorGrid = props => { removeQuery("q") refine(event.currentTarget.value) }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} /> {/*isSearchStalled ? 'My search is stalled' : ''*/} diff --git a/frontend/src/components/DiscordChat.jsx b/frontend/src/components/DiscordChat.jsx index e1d226b8..d06711c7 100644 --- a/frontend/src/components/DiscordChat.jsx +++ b/frontend/src/components/DiscordChat.jsx @@ -75,6 +75,11 @@ const DiscordChat = props => { fullWidth value={currentRefinement} onChange={(event) => refine(event.currentTarget.value)} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} placeholder="Search Discord Chats" style={{ backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%", }} InputProps={{ diff --git a/frontend/src/components/DocsGrid.jsx b/frontend/src/components/DocsGrid.jsx index 06469251..bc17543f 100644 --- a/frontend/src/components/DocsGrid.jsx +++ b/frontend/src/components/DocsGrid.jsx @@ -124,6 +124,11 @@ const DocsGrid = props => { removeQuery("q") refine(event.currentTarget.value) }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} limit={5} /> {/*isSearchStalled ? 'My search is stalled' : ''*/} diff --git a/frontend/src/components/SearchData.jsx b/frontend/src/components/SearchData.jsx index 35e7b9e3..c390666a 100644 --- a/frontend/src/components/SearchData.jsx +++ b/frontend/src/components/SearchData.jsx @@ -88,12 +88,12 @@ const SearchData = props => { const textFieldRef = useRef(null); const keyPressHandler = (e) => { - if (e.which === 13) { + if (e.key === "Enter") { + e.preventDefault(); // navigate(`/search?q=${currentRefinement}`, { state: value, replace: true }); // setModalOpen(false); const trimmedValue = inputValue.trim(); if (trimmedValue !== '') { - e.preventDefault(); navigate(`/search?q=${trimmedValue}`, { state: trimmedValue, replace: true }); setModalOpen(false); } diff --git a/frontend/src/components/WorkflowGrid.jsx b/frontend/src/components/WorkflowGrid.jsx index 589e4f7d..e10d2131 100644 --- a/frontend/src/components/WorkflowGrid.jsx +++ b/frontend/src/components/WorkflowGrid.jsx @@ -221,6 +221,11 @@ const AppGrid = props => { removeQuery("q") refine(event.currentTarget.value) }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} limit={5} /> : null} From de2a8ba8c8f8cf29b0eabe8ded259a811cfc829c Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 17 Jun 2024 14:32:45 +0200 Subject: [PATCH 015/336] Added random_element filter to properly work --- backend/app_sdk/app_base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index a7e0d4f8..415f1e94 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -97,6 +97,7 @@ def md5_base64(a): a = str(a) foundhash = hashlib.md5(a.encode('utf-8')).hexdigest() return base64.b64encode(foundhash.encode('utf-8')) + @shuffle_filters.register def base64_encode(a): @@ -107,6 +108,17 @@ def base64_encode(a): except: return base64.b64encode(a).decode() +@shuffle_filters.register +def random_element(a): + # Choose a random item from an array + a = list(a) + + if len(a) == 0: + return "" + + return random.choice(a) + + @shuffle_filters.register def base64_decode(a): a = str(a) From 5964f914b962ae41a629dea5ce016106ad2c1cbe Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 29 May 2024 17:05:23 +0530 Subject: [PATCH 016/336] Fixed the Re-rendering issue of Apps/Actions & Triggers --- frontend/src/components/ParsedAction.jsx | 2455 +++++++++++----------- frontend/src/views/AngularWorkflow.jsx | 811 ++++--- 2 files changed, 1586 insertions(+), 1680 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 0190a6bd..c90e7747 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -182,7 +182,14 @@ const ParsedAction = (props) => { const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); - + const [selectedActionParameters, setSelectedActionParameters] = React.useState([]); + const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); + const [actionlist, setActionlist] = React.useState([]); + const [jsonList, setJsonList] = React.useState([]); + const [showDropdown, setShowDropdown] = React.useState(false); + const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); + const [showAutocomplete, setShowAutocomplete] = React.useState(false); + const [menuPosition, setMenuPosition] = useState(null); const isIntegration = selectedAction.app_id === "integration" useEffect(() => { @@ -372,15 +379,6 @@ const ParsedAction = (props) => { //setStartNode(selectedAction.id) }; - const AppActionArguments = (props) => { - const [selectedActionParameters, setSelectedActionParameters] = React.useState([]); - const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); - const [actionlist, setActionlist] = React.useState([]); - const [jsonList, setJsonList] = React.useState([]); - const [showDropdown, setShowDropdown] = React.useState(false); - const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); - const [showAutocomplete, setShowAutocomplete] = React.useState(false); - const [menuPosition, setMenuPosition] = useState(null); useEffect(() => { if (selectedActionParameters !== undefined && selectedActionParameters !== null && selectedActionParameters.length === 0 @@ -1185,7 +1183,6 @@ const ParsedAction = (props) => { } // FIXME: Issue #40 - selectedActionParameters not reset - if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { var wrapperapp = { "id": "", @@ -1217,8 +1214,1198 @@ const ParsedAction = (props) => { // Check the actual value and if it's the same noAppSelected = true } - return ( -
+ + + const ActionSelectOption = (actionprops) => { + const { data, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; + const [hover, setHover] = React.useState(false); + + return ( + +
setHover(true)} onMouseLeave={() => setHover(false)} + onClick={() => { + //setSelectedAction(actionprops) + //setShowActionList(false) + //setUpdate(Math.random()) + // + if (data !== undefined && data !== null) { + setNewSelectedAction({ + target: { + value: data.name + } + }); + } + }} + > +
+ + {useIcon} + + {newActionname} +
+ {extraDescription.length > 0 ? + + {extraDescription} + + : null} +
+
+ ) + } + + const sortByCategoryLabel = (a, b) => { + const aHasCategoryLabel = a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0 + const bHasCategoryLabel = b.category_label !== undefined && b.category_label !== null && b.category_label.length > 0 + + // Sort by existence and length of "category_label" + if (aHasCategoryLabel && !bHasCategoryLabel) { + return -1 + } else if (!aHasCategoryLabel && bHasCategoryLabel) { + return 1 + } else { + return 0 + } + } + + // Function to deduplicate based on the "name" field + const deduplicateByName = (array) => { + const uniqueNames = {}; + return array.filter(item => { + if (!item.hasOwnProperty('name') || !item.name.length) { + return true + } + if (!uniqueNames[item.name]) { + uniqueNames[item.name] = true + return true + } + return false + }) + } + + // Gets the most important actions first + const renderedActionOptions = deduplicateByName(( + selectedApp.actions === undefined || selectedApp.actions === null ? [] : + selectedApp.actions.filter((a) => + a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label")) + ).sort(sortByCategoryLabel)) + + + const selectedAppIcon = selectedAction.large_image + var baselabel = selectedAction.label + return ( +
+ + {hideExtraTypes === true ? null : ( + +
+
+
{ + //window.open("/apps/${selectedAction.app_id}", "_blank") + }} + > + + + +

+ {( + selectedAction.app_name.charAt(0).toUpperCase() + + selectedAction.app_name.substring(1) + ).replaceAll("_", " ")} +

+
+
+ { + if (workflowExecutions.length > 0) { + // Look for the ID + var found = false; + for (let [key,keyval] in Object.entries(workflowExecutions)) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue; + } + + var foundResult = workflowExecutions[key].results.find( + (result) => result.action.id === selectedAction.id + ) + + if (foundResult === undefined || foundResult === null) { + continue; + } + + const oldstartnode = cy.getElementById(selectedAction.id); + if (oldstartnode !== undefined && oldstartnode !== null) { + const foundname = oldstartnode.data("label") + if (foundname !== undefined && foundname !== null) { + foundResult.action.label = foundname + } + } + + setSelectedResult(foundResult); + if (setCodeModalOpen !== undefined) { + setCodeModalOpen(true); + + found = true + } + + break; + } + + if (!found) { + toast("No result for this action yet. Please run the workflow first.") + } + } + }} + > + + + + + { + setAuthenticationModalOpen(true) + }} + > + + + + + {/* + {}} + > + + + + + + + */} + {/* + { + //setAuthenticationModalOpen(true); + console.log("Should enable/disable magic!") + console.log("Action: ", selectedAction) + if (selectedAction.run_magic_output === undefined) { + selectedAction.run_magic_output = true + } else { + if (selectedAction.run_magic_output === true) { + selectedAction.run_magic_output = false + } else { + selectedAction.run_magic_output = true + } + } + + setSelectedAction(selectedAction) + setUpdate(Math.random()); + }} + > + + + + + */} + {/* + { + }} + > + + + + + + + */} + { + //if (setAiQueryModalOpen !== undefined) { + // setAiQueryModalOpen(true) + //} else { + aiSubmit("Fill based on previous values", undefined, undefined, selectedAction) + //} + setAutocompleting(true) + }} + > + + {autoCompleting ? + + : + + } + + +
+
+
+ {/*selectedAction.id === workflow.start ? null : + + + + + */} + {selectedApp.versions !== null && + selectedApp.versions !== undefined && + selectedApp.versions.length > 1 ? ( + + ) : null} +
+
+
+
+ Name + { + // Copy the name value + const name = e.target.value + const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") + const newname = "$"+name.toLowerCase().replaceAll(" ", "_") + + // Check if it's the same as the current name in use + //if (name === selectedAction.label) { + // console.log("Returning from name thing") + // return + //} + + // Change in actions, triggers & conditions + // Highlight the changes somehow with a glow? + if (workflow.branches !== undefined && workflow.branches !== null) { + for (let [key,keyval] in Object.entries(workflow.branches)) { + if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { + for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { + const condition = workflow.branches[key].conditions[subkey] + const sourceparam = condition.source + const destinationparam = condition.destination + + // Should have a smarter way of discovering node names + // Finding index(es) and replacing at the location + if (sourceparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) + const extralength = newname.length-parsedBaseLabel.length + sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].source.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + + if (destinationparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) + const extralength = newname.length-parsedBaseLabel.length + destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + } + } + } + + for (let [key,keyval] in Object.entries(workflow.actions)) { + if (workflow.actions[key].id === selectedAction.id) { + continue + } + + const params = workflow.actions[key].parameters + console.log(params) + if (params === null || params === undefined) { + continue + } + + for (let [subkey, subkeyval] in Object.entries(params)) { + const param = workflow.actions[key].parameters[subkey]; + if (!param.value.includes("$")) { + continue + } + + // Should have a smarter way of discovering node names + // Do regex? + // Finding index(es) and replacing at the location + // + + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (param.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = param.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.actions[key].parameters[subkey].value) + const extralength = newname.length-parsedBaseLabel.length + param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) + + console.log("New: ", workflow.actions[key].parameters[subkey].value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + + setWorkflow(workflow); + setUpdate(Math.random()); + baselabel = name + }} + /> +
+ {/*!isCloud ? null :*/} +
+ + + Delay + { + if (actionDelayChange !== undefined) { + actionDelayChange(event) + } + }} + /> + + +
+ {/**/} +
+
+ )} + {selectedApp.name !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication !== undefined && + selectedAction.authentication.length === 0 && + requiresAuthentication ? ( +
+ + + + + +
+ ) : null} + + {selectedAction.authentication !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication.length > 0 ? ( +
+ Authentication +
+ + + {/* + + + curaction.authentication = authenticationOptions + if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") + */} + + { + setAuthenticationModalOpen(true); + }} + > + + + +
+
+ ) : null} + + {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( +
+ Environment + +
+ ) : null} + + {workflow.execution_variables !== undefined && + workflow.execution_variables !== null && + workflow.execution_variables.length > 0 ? ( +
+ Execution variable (optional) + +
+ ) : null} + + +
+ {/*hideExtraTypes ? null : +
+ Actions +
+ */} + + {setNewSelectedAction !== undefined ? ( + { + // Most popular + // Is categorized + // Uncategorized + return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions"; + }} + renderGroup={(params) => { + + return ( +
  • + {params.group} + {params.children} +
  • + ) + }} + options={renderedActionOptions} + ListboxProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + }, + }} + filterOptions={(options, { inputValue }) => { + //console.log("Option contains?: ", inputValue, options) + const lowercaseValue = inputValue.toLowerCase() + options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) + + return options + }} + getOptionLabel={(option) => { + if (option === undefined || option === null || option.name === undefined || option.name === null ) { + return null; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + + return newname; + }} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + // Workaround with event lol + if (newValue !== undefined && newValue !== null) { + setNewSelectedAction({ + target: { + value: newValue.name + } + }); + } + }} + renderOption={(props, data, state) => { + var newActionname = data.name; + if (data.label !== undefined && data.label !== null && data.label.length > 0) { + newActionname = data.label; + } + + var newActiondescription = data.description; + //console.log("DESC: ", newActiondescription) + if (data.description === undefined || data.description === null) { + newActiondescription = "Description: No description defined for this action" + } else { + newActiondescription = "Description: "+newActiondescription + } + + const iconInfo = GetIconInfo({ name: data.name }); + const useIcon = iconInfo.originalIcon; + + if (newActionname === undefined || newActionname === null) { + newActionname = "No name" + data.name = "No name" + data.label = "No name" + } + + newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); + + var method = "" + var extraDescription = "" + if (data.name.includes("get_")) { + method = "GET" + } else if (data.name.includes("post_")) { + method = "POST" + } else if (data.name.includes("put_")) { + method = "PUT" + } else if (data.name.includes("patch_")) { + method = "PATCH" + } else if (data.name.includes("delete_")) { + method = "DELETE" + } else if (data.name.includes("options_")) { + method = "OPTIONS" + } else if (data.name.includes("connect_")) { + method = "CONNECT" + } + + // FIXME: Should it require a base URL? + if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { + var extraUrl = "" + const descSplit = data.description.split("\n") + // Last line of descSplit + if (descSplit.length > 0) { + extraUrl = descSplit[descSplit.length-1] + } + + //for (let [line,lineval] in Object.entries(descSplit)) { + // if (descSplit[line].includes("http") && descSplit[line].includes("://")) { + // const urlsplit = descSplit[line].split("/") + // try { + // extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") + // } catch (e) { + // //console.log("Failed - running with -1") + // extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") + // } + + + // //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) + // //break + // } + //} + + if (extraUrl.length > 0) { + if (extraUrl.includes(" ")) { + extraUrl = extraUrl.split(" ")[0] + } + + if (extraUrl.includes("#")) { + extraUrl = extraUrl.split("#")[0] + } + + extraDescription = `${method} ${extraUrl}` + } else { + //console.log("No url found. Check again :)") + } + } + + return ( + + ); + }} + renderInput={(params) => { + if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { + const prefixes = ["Post", "Put", "Patch"] + for (let [key,keyval] in Object.entries(prefixes)) { + if (params.inputProps.value.startsWith(prefixes[key])) { + params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) + if (params.inputProps.value.length > 1) { + params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) + } + break + } + } + + // Check if it starts with "Get List" and method is "Get" + if (params.inputProps.value.startsWith("Get List")) { + console.log("Get List") + } + } + + return ( + + ); + }} + /> + ) : null} + + {/*setNewSelectedAction !== undefined ? + + : null*/} + +
    { + Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0 ? +
    {isIntegration ? apps !== undefined && apps !== null && apps.length > 0 ?
    @@ -2984,1247 +4171,9 @@ const ParsedAction = (props) => { ); })}
    - ); - } - return null; - }; - - - const ActionSelectOption = (actionprops) => { - const { data, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; - const [hover, setHover] = React.useState(false); - - return ( - -
    setHover(true)} onMouseLeave={() => setHover(false)} - onClick={() => { - //setSelectedAction(actionprops) - //setShowActionList(false) - //setUpdate(Math.random()) - // - if (data !== undefined && data !== null) { - setNewSelectedAction({ - target: { - value: data.name - } - }); - } - }} - > -
    - - {useIcon} - - {newActionname} -
    - {extraDescription.length > 0 ? - - {extraDescription} - - : null} -
    -
    - ) - } - - const sortByCategoryLabel = (a, b) => { - const aHasCategoryLabel = a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0 - const bHasCategoryLabel = b.category_label !== undefined && b.category_label !== null && b.category_label.length > 0 - - // Sort by existence and length of "category_label" - if (aHasCategoryLabel && !bHasCategoryLabel) { - return -1 - } else if (!aHasCategoryLabel && bHasCategoryLabel) { - return 1 - } else { - return 0 - } - } - - // Function to deduplicate based on the "name" field - const deduplicateByName = (array) => { - const uniqueNames = {}; - return array.filter(item => { - if (!item.hasOwnProperty('name') || !item.name.length) { - return true + : null } - if (!uniqueNames[item.name]) { - uniqueNames[item.name] = true - return true - } - return false - }) - } - - // Gets the most important actions first - const renderedActionOptions = deduplicateByName(( - selectedApp.actions === undefined || selectedApp.actions === null ? [] : - selectedApp.actions.filter((a) => - a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label")) - ).sort(sortByCategoryLabel)) - - const selectedAppIcon = selectedAction.large_image - var baselabel = selectedAction.label - return ( -
    - - {hideExtraTypes === true ? null : ( - -
    -
    -
    { - //window.open("/apps/${selectedAction.app_id}", "_blank") - }} - > - - - -

    - {( - selectedAction.app_name.charAt(0).toUpperCase() + - selectedAction.app_name.substring(1) - ).replaceAll("_", " ")} -

    -
    -
    - { - if (workflowExecutions.length > 0) { - // Look for the ID - var found = false; - for (let [key,keyval] in Object.entries(workflowExecutions)) { - if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { - continue; - } - - var foundResult = workflowExecutions[key].results.find( - (result) => result.action.id === selectedAction.id - ) - - if (foundResult === undefined || foundResult === null) { - continue; - } - - const oldstartnode = cy.getElementById(selectedAction.id); - if (oldstartnode !== undefined && oldstartnode !== null) { - const foundname = oldstartnode.data("label") - if (foundname !== undefined && foundname !== null) { - foundResult.action.label = foundname - } - } - - setSelectedResult(foundResult); - if (setCodeModalOpen !== undefined) { - setCodeModalOpen(true); - - found = true - } - - break; - } - - if (!found) { - toast.info("No result for this action yet. Please run the workflow first.") - } - } else { - toast.info("No workflow runs to search through. Run the workflow first.") - } - }} - > - - - - - { - setAuthenticationModalOpen(true) - }} - > - - - - - {/* - {}} - > - - - - - - - */} - {/* - { - //setAuthenticationModalOpen(true); - console.log("Should enable/disable magic!") - console.log("Action: ", selectedAction) - if (selectedAction.run_magic_output === undefined) { - selectedAction.run_magic_output = true - } else { - if (selectedAction.run_magic_output === true) { - selectedAction.run_magic_output = false - } else { - selectedAction.run_magic_output = true - } - } - - setSelectedAction(selectedAction) - setUpdate(Math.random()); - }} - > - - - - - */} - {/* - { - }} - > - - - - - - - */} - { - //if (setAiQueryModalOpen !== undefined) { - // setAiQueryModalOpen(true) - //} else { - aiSubmit("Fill based on previous values", undefined, undefined, selectedAction) - //} - setAutocompleting(true) - }} - > - - {autoCompleting ? - - : - - } - - -
    -
    -
    - {/*selectedAction.id === workflow.start ? null : - - - - - */} - {selectedApp.versions !== null && - selectedApp.versions !== undefined && - selectedApp.versions.length > 1 ? ( - - ) : null} -
    -
    -
    -
    - Name - { - // Copy the name value - const name = e.target.value - const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") - const newname = "$"+name.toLowerCase().replaceAll(" ", "_") - - // Check if it's the same as the current name in use - //if (name === selectedAction.label) { - // console.log("Returning from name thing") - // return - //} - - // Change in actions, triggers & conditions - // Highlight the changes somehow with a glow? - if (workflow.branches !== undefined && workflow.branches !== null) { - for (let [key,keyval] in Object.entries(workflow.branches)) { - if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { - for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { - const condition = workflow.branches[key].conditions[subkey] - const sourceparam = condition.source - const destinationparam = condition.destination - - // Should have a smarter way of discovering node names - // Finding index(es) and replacing at the location - if (sourceparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) - const extralength = newname.length-parsedBaseLabel.length - sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) - - console.log("New: ", workflow.branches[key].conditions[subkey].source.value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - - if (destinationparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) - const extralength = newname.length-parsedBaseLabel.length - destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) - - console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - } - } - } - } - - for (let [key,keyval] in Object.entries(workflow.actions)) { - if (workflow.actions[key].id === selectedAction.id) { - continue - } - - const params = workflow.actions[key].parameters - console.log(params) - if (params === null || params === undefined) { - continue - } - - for (let [subkey, subkeyval] in Object.entries(params)) { - const param = workflow.actions[key].parameters[subkey]; - if (!param.value.includes("$")) { - continue - } - - // Should have a smarter way of discovering node names - // Do regex? - // Finding index(es) and replacing at the location - // - - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (param.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = param.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.actions[key].parameters[subkey].value) - const extralength = newname.length-parsedBaseLabel.length - param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) - - console.log("New: ", workflow.actions[key].parameters[subkey].value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - } - - setWorkflow(workflow); - setUpdate(Math.random()); - baselabel = name - }} - /> -
    - {/*!isCloud ? null :*/} -
    - - - Delay - { - if (actionDelayChange !== undefined) { - actionDelayChange(event) - } - }} - /> - - -
    - {/**/} -
    -
    - )} - {selectedApp.name !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication !== undefined && - selectedAction.authentication.length === 0 && - requiresAuthentication ? ( -
    - - - - - -
    - ) : null} - - {selectedAction.authentication !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication.length > 0 ? ( -
    - Authentication -
    - - - {/* - - - curaction.authentication = authenticationOptions - if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") - */} - - { - setAuthenticationModalOpen(true); - }} - > - - - -
    -
    - ) : null} - - - {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( -
    - Environment - -
    - ) : null} - - {workflow.execution_variables !== undefined && - workflow.execution_variables !== null && - workflow.execution_variables.length > 0 ? ( -
    - Execution variable (optional) - -
    - ) : null} - - -
    - {/*hideExtraTypes ? null : -
    - Actions -
    - */} - - {setNewSelectedAction !== undefined ? ( - { - // Most popular - // Is categorized - // Uncategorized - return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions"; - }} - renderGroup={(params) => { - - return ( -
  • - {params.group} - {params.children} -
  • - ) - }} - options={renderedActionOptions} - ListboxProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - }, - }} - filterOptions={(options, { inputValue }) => { - //console.log("Option contains?: ", inputValue, options) - const lowercaseValue = inputValue.toLowerCase() - options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) - - return options - }} - getOptionLabel={(option) => { - if (option === undefined || option === null || option.name === undefined || option.name === null ) { - return null; - } - - const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - - return newname; - }} - fullWidth - style={{ - backgroundColor: theme.palette.inputColor, - height: 50, - borderRadius: theme.palette.borderRadius, - }} - onChange={(event, newValue) => { - // Workaround with event lol - if (newValue !== undefined && newValue !== null) { - setNewSelectedAction({ - target: { - value: newValue.name - } - }); - } - }} - renderOption={(props, data, state) => { - var newActionname = data.name; - if (data.label !== undefined && data.label !== null && data.label.length > 0) { - newActionname = data.label; - } - - var newActiondescription = data.description; - //console.log("DESC: ", newActiondescription) - if (data.description === undefined || data.description === null) { - newActiondescription = "Description: No description defined for this action" - } else { - newActiondescription = "Description: "+newActiondescription - } - - const iconInfo = GetIconInfo({ name: data.name }); - const useIcon = iconInfo.originalIcon; - - if (newActionname === undefined || newActionname === null) { - newActionname = "No name" - data.name = "No name" - data.label = "No name" - } - - newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); - - var method = "" - var extraDescription = "" - if (data.name.includes("get_")) { - method = "GET" - } else if (data.name.includes("post_")) { - method = "POST" - } else if (data.name.includes("put_")) { - method = "PUT" - } else if (data.name.includes("patch_")) { - method = "PATCH" - } else if (data.name.includes("delete_")) { - method = "DELETE" - } else if (data.name.includes("options_")) { - method = "OPTIONS" - } else if (data.name.includes("connect_")) { - method = "CONNECT" - } - - // FIXME: Should it require a base URL? - if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { - var extraUrl = "" - const descSplit = data.description.split("\n") - // Last line of descSplit - if (descSplit.length > 0) { - extraUrl = descSplit[descSplit.length-1] - } - - //for (let [line,lineval] in Object.entries(descSplit)) { - // if (descSplit[line].includes("http") && descSplit[line].includes("://")) { - // const urlsplit = descSplit[line].split("/") - // try { - // extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") - // } catch (e) { - // //console.log("Failed - running with -1") - // extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") - // } - - - // //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) - // //break - // } - //} - - if (extraUrl.length > 0) { - if (extraUrl.includes(" ")) { - extraUrl = extraUrl.split(" ")[0] - } - - if (extraUrl.includes("#")) { - extraUrl = extraUrl.split("#")[0] - } - - extraDescription = `${method} ${extraUrl}` - } else { - //console.log("No url found. Check again :)") - } - } - - return ( - - ); - }} - renderInput={(params) => { - if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { - const prefixes = ["Post", "Put", "Patch"] - for (let [key,keyval] in Object.entries(prefixes)) { - if (params.inputProps.value.startsWith(prefixes[key])) { - params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) - if (params.inputProps.value.length > 1) { - params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) - } - break - } - } - - // Check if it starts with "Get List" and method is "Get" - if (params.inputProps.value.startsWith("Get List")) { - console.log("Get List") - } - } - - return ( - - ); - }} - /> - ) : null} - - {/*setNewSelectedAction !== undefined ? - - : null*/} - -
    - +
    diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 021cc0d6..cfd65edd 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -4225,6 +4225,283 @@ const AngularWorkflow = (defaultprops) => { } }) + // Should get AI autocompletes + const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => { + if (setResponseMsg !== undefined) { + setResponseMsg("") + } + + if (value === undefined || value === "") { + console.log("No value input!") + return + } + + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(true) + } + + console.log("Submit conversation with value: ", value); + + // This is to find sample response and parse it as string + + var AppContext = [] + if (inputAction !== undefined && inputAction !== null) { + const parents = getParents(inputAction) + + console.log("Parents: ", parents) + var actionlist = [] + if (parents.length > 1) { + for (let [key,keyval] in Object.entries(parents)) { + const item = parents[key]; + if (item.label === "Execution Argument") { + continue; + } + + var exampledata = item.example === undefined || item.example === null ? "" : item.example; + // Find previous execution and their variables + //exampledata === "" && + if (workflowExecutions.length > 0) { + // Look for the ID + const found = false; + for (let [key,keyval] in Object.entries(workflowExecutions)) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue; + } + + var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id); + if (foundResult === undefined || foundResult === null) { + continue; + } + + if (foundResult.result !== undefined && foundResult.result !== null) { + foundResult = foundResult.result + } + + const valid = validateJson(foundResult, true) + if (valid.valid) { + if (valid.result.success === false) { + //console.log("Skipping success false autocomplete") + } else { + exampledata = valid.result; + break; + } + } else { + exampledata = foundResult; + } + } + } + + // 1. Take + const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_"); + + const actionvalue = { + app_name: item.app_name, + action_name: item.name, + label: item.label, + + type: "action", + id: item.id, + name: item.label, + autocomplete: itemlabelComplete, + example: exampledata, + }; + + actionlist.push(actionvalue); + } + } + + var fixedResults = [] + for (var i = 0; i < actionlist.length; i++) { + const item = actionlist[i]; + const responseFix = SetJsonDotnotation(item.example, "") + + // Check if json + const validated = validateJson(responseFix) + var exampledata = responseFix; + if (validated.valid) { + exampledata = JSON.stringify(validated.result) + } + + AppContext.push({ + "app_name": item.app_name, + "action_name": item.action_name, + "label": item.label, + "example": exampledata, + "example_response": exampledata, + }) + } + } + + var conversationData = { + "query": value, + "output_format": "action", + "app_context": AppContext, + + "workflow_id": workflow.id, + } + + if (inputAction !== undefined) { + console.log("Add app context! This should them get parameters directly") + conversationData.output_format = "action_parameters" + + conversationData.app_id = inputAction.app_id + conversationData.app_name = inputAction.app_name + conversationData.action_name = inputAction.name + conversationData.parameters = inputAction.parameters + + if (!value.includes(inputAction.label)) { + conversationData.query = inputAction.label.replaceAll("_", " ") + } + } + + // Onprem not available yet (April 2023) + // Should: Make OpenAI work for them with their own key + //fetch("https://shuffler.io/api/v1/conversation", { + fetch(`${globalUrl}/api/v1/conversation`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(conversationData), + credentials: "include", + }) + .then((response) => { + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(false) + } + + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + console.log("Conversation response: ", responseJson) + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + if (setResponseMsg !== undefined) { + setResponseMsg(responseJson.reason) + } + } + + return + } + + if (inputAction !== undefined) { + console.log("In input action! Should check params if they match, and add suggestions") + + if (responseJson.parameters === undefined || responseJson.parameters.length === 0) { + return + } + + var changed = false + + for (let paramkey in inputAction.parameters) { + const actionParam = inputAction.parameters[paramkey] + + if (actionParam.autocompleted === true) { + continue + } + + if (actionParam.configuration === true && actionParam.name !== "url") { + continue + } + + if (actionParam.value !== "" && actionParam.value !== actionParam.example) { + console.log("Skipping: ", actionParam) + continue + } + + for (let respParam of responseJson.parameters) { + if (respParam.name === actionParam.name) { + console.log("Found match for param: ", respParam) + + if (respParam.value === "") { + break + } + + changed = true + + inputAction.parameters[paramkey].autocompleted = true + inputAction.parameters[paramkey].value = respParam.value + break + } + } + } + + if (changed === true) { + console.log("Setting action! Force update pls :)") + setUpdate(Math.random()) + setSelectedAction(inputAction) + } + + return + } + + console.log("Suggestionbox location: ", suggestionBox) + + // Add action + if (responseJson.app_name !== undefined && responseJson.app_name !== null) { + // Always added to 0, 0 + // Should use suggestionBox.position.x, suggestionBox.position.y + var newitem = { + "data": responseJson, + "position": { + "x": suggestionBox.node_position.x !== undefined ? suggestionBox.node_position.x : 0, + "y": suggestionBox.node_position.y !== undefined ? suggestionBox.node_position.y + 100 : 0, + }, + "group": "nodes", + } + + newitem.type = "ACTION" + newitem.isStartNode = false + newitem.data.id = uuidv4() + newitem.data.type = "ACTION" + newitem.data.isStartNode = false + + newitem.data.is_valid = true + newitem.data.isValid = true + + cy.add({ + group: newitem.group, + data: newitem.data, + position: newitem.position, + }); + + // Add edge + const newId = uuidv4() + cy.add({ + group: "edges", + data: { + id: newId, + _id: newId, + source: suggestionBox.attachedTo, + target: newitem.data.id, + } + }) + //label: "Generated", + + setSuggestionBox({ + "position": { + "top": 500, + "left": 500, + }, + "open": false, + "attachedTo": "", + }); + } + }) + .catch((error) => { + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(false) + } + + console.log("Conv response error: ", error); + }); + } + // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once // onNodeClick @@ -13647,7 +13924,48 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : ""; - } + }else if( + selectedTrigger.trigger_type === "USERINPUT" + ){ + if ( + workflow.triggers[selectedTriggerIndex].parameters === undefined || + workflow.triggers[selectedTriggerIndex].parameters === null || + workflow.triggers[selectedTriggerIndex].parameters.length === 0 + ) { + workflow.triggers[selectedTriggerIndex].parameters = []; + workflow.triggers[selectedTriggerIndex].parameters[0] = { + name: "alertinfo", + value: "Do you want to continue the workflow? Start parameters: $exec", + }; + + // boolean, + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "options", + value: "boolean", + }; + + // email,sms,app ... + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "type", + value: "subflow", + }; + + workflow.triggers[selectedTriggerIndex].parameters[3] = { + name: "email", + value: "test@test.com", + }; + workflow.triggers[selectedTriggerIndex].parameters[4] = { + name: "sms", + value: "0000000", + }; + workflow.triggers[selectedTriggerIndex].parameters[5] = { + name: "subflow", + value: "", + }; + + setWorkflow(workflow); + } + } } const WebhookSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "WEBHOOK" ? null : @@ -14442,48 +14760,7 @@ const AngularWorkflow = (defaultprops) => { }) } - const UserinputSidebar = () => { - if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) { - if ( - workflow.triggers[selectedTriggerIndex].parameters === undefined || - workflow.triggers[selectedTriggerIndex].parameters === null || - workflow.triggers[selectedTriggerIndex].parameters.length === 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = []; - workflow.triggers[selectedTriggerIndex].parameters[0] = { - name: "alertinfo", - value: "Do you want to continue the workflow? Start parameters: $exec", - }; - - // boolean, - workflow.triggers[selectedTriggerIndex].parameters[1] = { - name: "options", - value: "boolean", - }; - - // email,sms,app ... - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "type", - value: "subflow", - }; - - workflow.triggers[selectedTriggerIndex].parameters[3] = { - name: "email", - value: "test@test.com", - }; - workflow.triggers[selectedTriggerIndex].parameters[4] = { - name: "sms", - value: "0000000", - }; - workflow.triggers[selectedTriggerIndex].parameters[5] = { - name: "subflow", - value: "", - }; - - setWorkflow(workflow); - } - - return ( + const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

    {selectedTrigger.app_name} @@ -14820,11 +15097,6 @@ const AngularWorkflow = (defaultprops) => {

    - ) - } - - return null - } const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null :
    @@ -16677,115 +16949,17 @@ const AngularWorkflow = (defaultprops) => { }; const RightSideBar = (props) => { - const { - //workflow, - //setWorkflow, - //setSelectedAction, - //setUpdate, - //selectedApp, - //workflowExecutions, - //setSelectedResult, - //selectedAction, - //setSelectedApp, - //setSelectedTrigger, - //setSelectedEdge, - //setCurrentView, - //cy, - //setAuthenticationModalOpen, - //setVariablesModalOpen, - //setCodeModalOpen, - //selectedNameChange, - //rightsidebarStyle, - //showEnvironment, - //selectedActionEnvironment, - //environments, - //setNewSelectedAction, - //appApiViewStyle, - //globalUrl, - //setSelectedActionEnvironment, - //requiresAuthentication, - //scrollConfig, - //setScrollConfig, - } = props; - if (!rightSideBarOpen) { - return null; - } var defaultReturn = null - if (Object.getOwnPropertyNames(selectedAction).length > 0) { - if (Object.getOwnPropertyNames(selectedAction).length === 0) { - return null; - } - - defaultReturn = - - } else if (Object.getOwnPropertyNames(selectedComment).length > 0) { + + if (Object.getOwnPropertyNames(selectedComment).length > 0) { defaultReturn = } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (selectedTrigger.trigger_type === "SCHEDULE") { - // Handled elsewhere as an experiment - defaultReturn = null - } else if (selectedTrigger.trigger_type === "WEBHOOK") { - defaultReturn = null - } else if (selectedTrigger.trigger_type === "SUBFLOW") { + if (selectedTrigger.trigger_type === "SUBFLOW") { defaultReturn = } else if (selectedTrigger.trigger_type === "EMAIL") { defaultReturn = - } else if (selectedTrigger.trigger_type === "USERINPUT") { - defaultReturn = } else if (selectedTrigger.trigger_type === undefined) { //defaultReturn = return null; @@ -19646,12 +19820,68 @@ const AngularWorkflow = (defaultprops) => {
    {executionModal} - - + + { + rightSideBarOpen && Object.getOwnPropertyNames(selectedAction).length > 0 ? +
    + +
    : null + } {/* Looks for triggers" */} {/* Only fixed the ones that require scrolling on a small screen */} {/* Most important: Actions. But these are a lot more complex */} - {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE") ? + {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE" || selectedTrigger.trigger_type === "USERINPUT") ?
    {Object.getOwnPropertyNames(selectedTrigger).length > 0 ? selectedTrigger.trigger_type === "SCHEDULE" ? @@ -19660,6 +19890,8 @@ const AngularWorkflow = (defaultprops) => { PipelineSidebar : selectedTrigger.trigger_type === "WEBHOOK" ? WebhookSidebar + : selectedTrigger.trigger_type === "USERINPUT" ? + UserinputSidebar : null : null}
    @@ -20914,282 +21146,7 @@ const AngularWorkflow = (defaultprops) => { ) : null; - // Should get AI autocompletes - const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => { - if (setResponseMsg !== undefined) { - setResponseMsg("") - } - if (value === undefined || value === "") { - console.log("No value input!") - return - } - - if (setSuggestionLoading !== undefined) { - setSuggestionLoading(true) - } - - console.log("Submit conversation with value: ", value); - - // This is to find sample response and parse it as string - - var AppContext = [] - if (inputAction !== undefined && inputAction !== null) { - const parents = getParents(inputAction) - - console.log("Parents: ", parents) - var actionlist = [] - if (parents.length > 1) { - for (let [key,keyval] in Object.entries(parents)) { - const item = parents[key]; - if (item.label === "Execution Argument") { - continue; - } - - var exampledata = item.example === undefined || item.example === null ? "" : item.example; - // Find previous execution and their variables - //exampledata === "" && - if (workflowExecutions.length > 0) { - // Look for the ID - const found = false; - for (let [key,keyval] in Object.entries(workflowExecutions)) { - if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { - continue; - } - - var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id); - if (foundResult === undefined || foundResult === null) { - continue; - } - - if (foundResult.result !== undefined && foundResult.result !== null) { - foundResult = foundResult.result - } - - const valid = validateJson(foundResult, true) - if (valid.valid) { - if (valid.result.success === false) { - //console.log("Skipping success false autocomplete") - } else { - exampledata = valid.result; - break; - } - } else { - exampledata = foundResult; - } - } - } - - // 1. Take - const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_"); - - const actionvalue = { - app_name: item.app_name, - action_name: item.name, - label: item.label, - - type: "action", - id: item.id, - name: item.label, - autocomplete: itemlabelComplete, - example: exampledata, - }; - - actionlist.push(actionvalue); - } - } - - var fixedResults = [] - for (var i = 0; i < actionlist.length; i++) { - const item = actionlist[i]; - const responseFix = SetJsonDotnotation(item.example, "") - - // Check if json - const validated = validateJson(responseFix) - var exampledata = responseFix; - if (validated.valid) { - exampledata = JSON.stringify(validated.result) - } - - AppContext.push({ - "app_name": item.app_name, - "action_name": item.action_name, - "label": item.label, - "example": exampledata, - "example_response": exampledata, - }) - } - } - - var conversationData = { - "query": value, - "output_format": "action", - "app_context": AppContext, - - "workflow_id": workflow.id, - } - - if (inputAction !== undefined) { - console.log("Add app context! This should them get parameters directly") - conversationData.output_format = "action_parameters" - - conversationData.app_id = inputAction.app_id - conversationData.app_name = inputAction.app_name - conversationData.action_name = inputAction.name - conversationData.parameters = inputAction.parameters - - if (!value.includes(inputAction.label)) { - conversationData.query = inputAction.label.replaceAll("_", " ") - } - } - - // Onprem not available yet (April 2023) - // Should: Make OpenAI work for them with their own key - //fetch("https://shuffler.io/api/v1/conversation", { - fetch(`${globalUrl}/api/v1/conversation`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(conversationData), - credentials: "include", - }) - .then((response) => { - if (setSuggestionLoading !== undefined) { - setSuggestionLoading(false) - } - - if (response.status !== 200) { - console.log("Status not 200 for stream results :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - console.log("Conversation response: ", responseJson) - if (responseJson.success === false) { - if (responseJson.reason !== undefined) { - if (setResponseMsg !== undefined) { - setResponseMsg(responseJson.reason) - } - } - - return - } - - if (inputAction !== undefined) { - console.log("In input action! Should check params if they match, and add suggestions") - - if (responseJson.parameters === undefined || responseJson.parameters.length === 0) { - return - } - - var changed = false - - for (let paramkey in inputAction.parameters) { - const actionParam = inputAction.parameters[paramkey] - - if (actionParam.autocompleted === true) { - continue - } - - if (actionParam.configuration === true && actionParam.name !== "url") { - continue - } - - if (actionParam.value !== "" && actionParam.value !== actionParam.example) { - console.log("Skipping: ", actionParam) - continue - } - - for (let respParam of responseJson.parameters) { - if (respParam.name === actionParam.name) { - console.log("Found match for param: ", respParam) - - if (respParam.value === "") { - break - } - - changed = true - - inputAction.parameters[paramkey].autocompleted = true - inputAction.parameters[paramkey].value = respParam.value - break - } - } - } - - if (changed === true) { - console.log("Setting action! Force update pls :)") - setUpdate(Math.random()) - setSelectedAction(inputAction) - } - - return - } - - console.log("Suggestionbox location: ", suggestionBox) - - // Add action - if (responseJson.app_name !== undefined && responseJson.app_name !== null) { - // Always added to 0, 0 - // Should use suggestionBox.position.x, suggestionBox.position.y - var newitem = { - "data": responseJson, - "position": { - "x": suggestionBox.node_position.x !== undefined ? suggestionBox.node_position.x : 0, - "y": suggestionBox.node_position.y !== undefined ? suggestionBox.node_position.y + 100 : 0, - }, - "group": "nodes", - } - - newitem.type = "ACTION" - newitem.isStartNode = false - newitem.data.id = uuidv4() - newitem.data.type = "ACTION" - newitem.data.isStartNode = false - - newitem.data.is_valid = true - newitem.data.isValid = true - - cy.add({ - group: newitem.group, - data: newitem.data, - position: newitem.position, - }); - - // Add edge - const newId = uuidv4() - cy.add({ - group: "edges", - data: { - id: newId, - _id: newId, - source: suggestionBox.attachedTo, - target: newitem.data.id, - } - }) - //label: "Generated", - - setSuggestionBox({ - "position": { - "top": 500, - "left": 500, - }, - "open": false, - "attachedTo": "", - }); - } - }) - .catch((error) => { - if (setSuggestionLoading !== undefined) { - setSuggestionLoading(false) - } - - console.log("Conv response error: ", error); - }); - } const SuggestionBoxUi = () => { const [suggestionValue, setSuggestionValue] = useState(""); From 3600502f94197afe3b3a828f9d81715a44f55039 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 30 May 2024 00:46:58 +0530 Subject: [PATCH 017/336] Fixed the action parameter bug --- frontend/src/components/ParsedAction.jsx | 12 ++++++------ frontend/src/views/AngularWorkflow.jsx | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index c90e7747..937b0557 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -381,13 +381,12 @@ const ParsedAction = (props) => { useEffect(() => { - if (selectedActionParameters !== undefined && selectedActionParameters !== null && selectedActionParameters.length === 0 + if (selectedActionParameters !== undefined && selectedActionParameters !== null ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { setSelectedActionParameters(selectedAction.parameters); } } - if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { // FIXME - this is the bad thing @@ -613,7 +612,8 @@ const ParsedAction = (props) => { setActionlist(actionlist); } } - }); + }, + [selectedAction, selectedVariableParameter, workflowExecutions, listCache]); const calculateHelpertext = (input_data) => { @@ -1307,6 +1307,8 @@ const ParsedAction = (props) => { const selectedAppIcon = selectedAction.large_image + console.log("Selected action: ", selectedAction) + console.log("Selected action paramaters: ", selectedAction.parameters) var baselabel = selectedAction.label return (
    @@ -2075,7 +2077,6 @@ const ParsedAction = (props) => {
    ) : null} - {workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > 0 ? ( @@ -2395,7 +2396,6 @@ const ParsedAction = (props) => { })} : null*/} -
    { placeholder = data.example; - if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length === 0) { + if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length > 0) { data.value = data.example; } // In case of data.example diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index cfd65edd..32054a69 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -4584,6 +4584,7 @@ const AngularWorkflow = (defaultprops) => { workflow.actions[foundindex].name = curaction.name setWorkflow(workflow) + console.log(workflow) } break } From 73b5d1a30d54daefc069579766144135d36f7116 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 31 May 2024 12:58:56 +0530 Subject: [PATCH 018/336] Bug fixes --- frontend/src/components/ParsedAction.jsx | 16 ++++++++-------- frontend/src/views/AngularWorkflow.jsx | 3 +-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 937b0557..3ce989fe 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -380,12 +380,13 @@ const ParsedAction = (props) => { }; - useEffect(() => { - if (selectedActionParameters !== undefined && selectedActionParameters !== null - ) { + useEffect( + () => { + // if (selectedActionParameters !== undefined && selectedActionParameters !== null + // ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { setSelectedActionParameters(selectedAction.parameters); - } + // } } if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { @@ -2054,7 +2055,6 @@ const ParsedAction = (props) => { return ( { placeholder = data.example; - if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length > 0) { + if (data.name === "url") { data.value = data.example; } // In case of data.example @@ -3149,8 +3149,8 @@ const ParsedAction = (props) => { id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" - defaultValue={data.value} - //value={data.value} + // defaultValue={data.value} + value={data.value} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 32054a69..b3711b81 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -16953,7 +16953,6 @@ const AngularWorkflow = (defaultprops) => { var defaultReturn = null - if (Object.getOwnPropertyNames(selectedComment).length > 0) { defaultReturn = } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { @@ -17811,7 +17810,7 @@ const AngularWorkflow = (defaultprops) => { console.log("IN useeffectt (2)" + collapsed) return; } - }) + },[]) /* componentWillUpdate = (nextProps, nextState) => { console.log(nextProps, nextState) From af734f7fb407db628d085ff695834b78bf0a9f6e Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sun, 2 Jun 2024 15:30:10 +0530 Subject: [PATCH 019/336] Bug fix --- frontend/src/components/ParsedAction.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 3ce989fe..1db53973 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -3149,8 +3149,8 @@ const ParsedAction = (props) => { id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" - // defaultValue={data.value} - value={data.value} + defaultValue={data.value} + // value={data.value} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', From 4f1bfc2313b002887993b4560db1d38bb9bb8beb Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 6 Jun 2024 12:56:01 +0530 Subject: [PATCH 020/336] Done with find action dropdown --- frontend/src/components/ParsedAction.jsx | 75 +- frontend/src/views/AngularWorkflow.jsx | 1027 +++++++++++----------- 2 files changed, 550 insertions(+), 552 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 1db53973..95a9ad4e 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -180,9 +180,8 @@ const ParsedAction = (props) => { const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); - const [autoCompleting, setAutocompleting] = React.useState(false); - const [selectedActionParameters, setSelectedActionParameters] = React.useState([]); + const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction.parameters); const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); const [actionlist, setActionlist] = React.useState([]); const [jsonList, setJsonList] = React.useState([]); @@ -382,9 +381,12 @@ const ParsedAction = (props) => { useEffect( () => { + console.log("UseEffect Rendered!") + console.log("Workflow", workflow) // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { + console.log("Setting action parameters!!") setSelectedActionParameters(selectedAction.parameters); // } } @@ -614,9 +616,13 @@ const ParsedAction = (props) => { } } }, - [selectedAction, selectedVariableParameter, workflowExecutions, listCache]); - - + [selectedAction,selectedApp,setNewSelectedAction] + ); + console.log("selectedActionParameters: ", selectedActionParameters) + console.log("selectedApp:", selectedApp) + console.log("selectedAction: ", selectedAction) + console.log("ACTIONLIST: ", actionlist) + console.log("selectedVariableParameter", selectedVariableParameter) const calculateHelpertext = (input_data) => { var helperText = "" var looperText = "" @@ -1184,7 +1190,7 @@ const ParsedAction = (props) => { } // FIXME: Issue #40 - selectedActionParameters not reset - + if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { var wrapperapp = { "id": "", "name": "noapp", @@ -1210,15 +1216,16 @@ const ParsedAction = (props) => { var authWritten = false; var noAppSelected = false - const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") + var paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") if (paramIndex === -1 || selectedAction.parameters[paramIndex].value === "" || selectedAction.parameters[paramIndex].value === "noapp") { // Check the actual value and if it's the same noAppSelected = true } + } const ActionSelectOption = (actionprops) => { - const { data, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; + const { option, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; const [hover, setHover] = React.useState(false); return ( @@ -1234,18 +1241,20 @@ const ParsedAction = (props) => { paddingBottom: 4, backgroundColor: hover ? theme.palette.surfaceColor : theme.palette.inputColor, }} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} - onClick={() => { + onClick={(event) => { + // event.preventDefault() //setSelectedAction(actionprops) //setShowActionList(false) //setUpdate(Math.random()) // - if (data !== undefined && data !== null) { + if (option !== undefined && option !== null) { setNewSelectedAction({ target: { - value: data.name + value: option.name } }); } + document.activeElement.blur(); }} >
    @@ -1308,7 +1317,7 @@ const ParsedAction = (props) => { const selectedAppIcon = selectedAction.large_image - console.log("Selected action: ", selectedAction) +// console.log("Selected action: ", selectedAction) console.log("Selected action paramaters: ", selectedAction.parameters) var baselabel = selectedAction.label return ( @@ -2220,54 +2229,55 @@ const ParsedAction = (props) => { } }); } - }} - renderOption={(props, data, state) => { - var newActionname = data.name; - if (data.label !== undefined && data.label !== null && data.label.length > 0) { - newActionname = data.label; + event.target.blur(); + }} + renderOption={(props, option, state) => { + var newActionname = option.name; + if (option.label !== undefined && option.label !== null && option.label.length > 0) { + newActionname = option.label; } - var newActiondescription = data.description; + var newActiondescription = option.description; //console.log("DESC: ", newActiondescription) - if (data.description === undefined || data.description === null) { + if (option.description === undefined || option.description === null) { newActiondescription = "Description: No description defined for this action" } else { newActiondescription = "Description: "+newActiondescription } - const iconInfo = GetIconInfo({ name: data.name }); + const iconInfo = GetIconInfo({ name: option.name }); const useIcon = iconInfo.originalIcon; if (newActionname === undefined || newActionname === null) { newActionname = "No name" - data.name = "No name" - data.label = "No name" + option.name = "No name" + option.label = "No name" } newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); var method = "" var extraDescription = "" - if (data.name.includes("get_")) { + if (option.name.includes("get_")) { method = "GET" - } else if (data.name.includes("post_")) { + } else if (option.name.includes("post_")) { method = "POST" - } else if (data.name.includes("put_")) { + } else if (option.name.includes("put_")) { method = "PUT" - } else if (data.name.includes("patch_")) { + } else if (option.name.includes("patch_")) { method = "PATCH" - } else if (data.name.includes("delete_")) { + } else if (option.name.includes("delete_")) { method = "DELETE" - } else if (data.name.includes("options_")) { + } else if (option.name.includes("options_")) { method = "OPTIONS" - } else if (data.name.includes("connect_")) { + } else if (option.name.includes("connect_")) { method = "CONNECT" } // FIXME: Should it require a base URL? - if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { + if (method.length > 0 && option.description !== undefined && option.description !== null && option.description.includes("http")) { var extraUrl = "" - const descSplit = data.description.split("\n") + const descSplit = option.description.split("\n") // Last line of descSplit if (descSplit.length > 0) { extraUrl = descSplit[descSplit.length-1] @@ -2306,7 +2316,8 @@ const ParsedAction = (props) => { return ( { //const data = JSON.parse(JSON.stringify(event.target.data())) const data = event.target.data() - + console.log("===============================Node selected=============================") console.log("NODE SELECT: ", data) if (data.app_name === "Shuffle Workflow") { @@ -8567,11 +8567,15 @@ const AngularWorkflow = (defaultprops) => { ); }; - const handleSetTab = (event, newValue) => { - setCurrentView(newValue); - }; + const HandleLeftView = () => { + // console.log("HandleLeftView Rendered!") + + const handleSetTab = (event, newValue) => { + setCurrentView(newValue); + }; + // Defaults to apps. var thisview = ( { ) } - - - const TriggersView = () => { - const triggersViewStyle = { - marginLeft: 10, - marginRight: 10, - display: "flex", - flexDirection: "column", - } - - // Predefined hurr - return ( -
    -
    - {triggers.map((trigger, index) => { - - /* - if (trigger.trigger_type === "PIPELINE") { - if (userdata.support !== true) { - return null - } - } - */ - - // Hiding since March 2024 - if (trigger.trigger_type === "EMAIL") { - return null - } - - const imagesize = isMobile ? 40 : trigger.large_image.includes("svg") ? 50 : 50 - var imageline = trigger.large_image.length === 0 ? - : - - - const title = trigger.trigger_type === "WEBHOOK" ? "Workflow starters" : trigger.trigger_type === "SUBFLOW" ? "Mid-Workflow" : "" - - const color = trigger.is_valid ? green : yellow; - return ( - - {title.length > 0 ? - - {title} - - : null} - - { - 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) { - toast("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 barHeight = bodyHeight - appBarSize - 50; - const appScrollStyle = { - overflow: "scroll", - maxHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight, - minHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight, - marginTop: 1, - overflowY: "auto", - overflowX: "hidden", - } - - const handleAppDrag = (e, app) => { - const cycontainer = cy.container(); - - - if (app.type === "TRIGGER") { - handleTriggerDrag(e, app) - return - } - - //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 = [] - } - - /* - if (app.actions === undefined || app.actions === null || app.actions.length === 0) { - toast("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 = "" - - const startIndex = app.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0) - const actionIndex = startIndex < 0 ? 0 : startIndex - - // Make the first action the most relevant one for them based on previous use - if ( - app.actions[actionIndex].parameters !== undefined && - app.actions[actionIndex].parameters !== null && - app.actions[actionIndex].parameters.length > 0 - ) { - parameters = app.actions[actionIndex].parameters; - for (let paramkey in parameters) { - // Check if parameter.name == "headers" and if it includes "=undefined". If it does, set the value to example if it exists, otherwise empty - if (parameters[paramkey].name === "headers" && parameters[paramkey].value.includes("=undefined")) { - if (parameters[paramkey].example !== undefined && parameters[paramkey].example !== null && parameters[paramkey].example.length > 0) { - parameters[paramkey].value = parameters[paramkey].example - } else { - parameters[paramkey].value = "" - } - } - } - - //parameters = app.actions[0].parameters; - } - - if ( - app.actions[actionIndex].returns !== undefined && - app.actions[actionIndex].returns !== null && - app.actions[actionIndex].returns.example !== undefined && - app.actions[actionIndex].returns.example !== null && - app.actions[actionIndex].returns.example.length > 0 - ) { - example = app.actions[actionIndex].returns.example; - } - - if ( - app.actions[actionIndex].description !== undefined && - app.actions[actionIndex].description !== null && - app.actions[actionIndex].description.length > 0 - ) { - description = app.actions[actionIndex].description - } - - var parsedEnvironments = - environments === null || environments === [] - ? "cloud" - : environments[defaultEnvironmentIndex] === undefined - ? "cloud" - : environments[defaultEnvironmentIndex].Name - - // Basic automatic auth mapping - var authId = "" - if (appAuthentication !== undefined && appAuthentication !== null && appAuthentication.length > 0) { - const appname = app.name.toLowerCase().replace(" ", "_") - for (var key in appAuthentication) { - const authKey = appAuthentication[key] - if (authKey.app.id === app.id) { - authId = authKey.id - break - } - - const appauthname = authKey.app.name.toLowerCase().replace(" ", "_") - if (appauthname === appname) { - authId = authKey.id - } - } - } - - // List other nodes in the workflow and see if they have an environment set. If they do, use that as the default - if (cy !== undefined && cy !== null) { - const foundnodes = cy.nodes().jsons() - if (foundnodes !== undefined && foundnodes !== null && foundnodes.length > 0) { - // As they should all be the same, this is just an override - for (let nodekey in foundnodes) { - const curnode = foundnodes[nodekey] - if (curnode.data.environment !== undefined && curnode.data.environment !== null && curnode.data.environment.length > 0) { - parsedEnvironments = curnode.data.environment - break - } - } - } - } - - const newAppData = { - name: app.actions[actionIndex].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, - required_body_fields: app.actions[actionIndex].required_body_fields, - category: - app.categories !== null && - app.categories !== undefined && - app.categories.length > 0 - ? app.categories[0] - : "", - authentication_id: authId, - finished: false, - template: app.template === true ? true : false, - }; - - // FIXME: overwrite category if the ACTION chosen has a different category - // - - if (!isCloud && (!app.is_valid || (!app.activated && app.generated))) { - console.log("NOT VALID: Activate!") - - activateApp(app.id, false) - } - - // 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; - + // console.log("AppView Rendered!") //extraApps, const [visibleApps, setVisibleApps] = React.useState( Array.prototype.concat.apply( @@ -9833,8 +9323,505 @@ const AngularWorkflow = (defaultprops) => {
    ); +} + + const TriggersView = () => { + console.log("TriggerView Rendered!") + const triggersViewStyle = { + marginLeft: 10, + marginRight: 10, + display: "flex", + flexDirection: "column", + } + + // Predefined hurr + return ( +
    +
    + {triggers.map((trigger, index) => { + + /* + if (trigger.trigger_type === "PIPELINE") { + if (userdata.support !== true) { + return null + } + } + */ + + // Hiding since March 2024 + if (trigger.trigger_type === "EMAIL") { + return null + } + + const imagesize = isMobile ? 40 : trigger.large_image.includes("svg") ? 50 : 50 + var imageline = trigger.large_image.length === 0 ? + : + + + const title = trigger.trigger_type === "WEBHOOK" ? "Workflow starters" : trigger.trigger_type === "SUBFLOW" ? "Mid-Workflow" : "" + + const color = trigger.is_valid ? green : yellow; + return ( + + {title.length > 0 ? + + {title} + + : null} + + { + 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) { + toast("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 barHeight = bodyHeight - appBarSize - 50; + const appScrollStyle = { + overflow: "scroll", + maxHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight, + minHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight, + marginTop: 1, + overflowY: "auto", + overflowX: "hidden", + } + + const handleAppDrag = (e, app) => { + const cycontainer = cy.container(); + + + if (app.type === "TRIGGER") { + handleTriggerDrag(e, app) + return + } + + //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 = [] + } + + /* + if (app.actions === undefined || app.actions === null || app.actions.length === 0) { + toast("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 = "" + + const startIndex = app.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0) + const actionIndex = startIndex < 0 ? 0 : startIndex + + // Make the first action the most relevant one for them based on previous use + if ( + app.actions[actionIndex].parameters !== undefined && + app.actions[actionIndex].parameters !== null && + app.actions[actionIndex].parameters.length > 0 + ) { + parameters = app.actions[actionIndex].parameters; + for (let paramkey in parameters) { + // Check if parameter.name == "headers" and if it includes "=undefined". If it does, set the value to example if it exists, otherwise empty + if (parameters[paramkey].name === "headers" && parameters[paramkey].value.includes("=undefined")) { + if (parameters[paramkey].example !== undefined && parameters[paramkey].example !== null && parameters[paramkey].example.length > 0) { + parameters[paramkey].value = parameters[paramkey].example + } else { + parameters[paramkey].value = "" + } + } + } + + //parameters = app.actions[0].parameters; + } + + if ( + app.actions[actionIndex].returns !== undefined && + app.actions[actionIndex].returns !== null && + app.actions[actionIndex].returns.example !== undefined && + app.actions[actionIndex].returns.example !== null && + app.actions[actionIndex].returns.example.length > 0 + ) { + example = app.actions[actionIndex].returns.example; + } + + if ( + app.actions[actionIndex].description !== undefined && + app.actions[actionIndex].description !== null && + app.actions[actionIndex].description.length > 0 + ) { + description = app.actions[actionIndex].description + } + + var parsedEnvironments = + environments === null || environments === [] + ? "cloud" + : environments[defaultEnvironmentIndex] === undefined + ? "cloud" + : environments[defaultEnvironmentIndex].Name; + + // List other nodes in the workflow and see if they have an environment set. If they do, use that as the default + if (cy !== undefined && cy !== null) { + const foundnodes = cy.nodes().jsons() + if (foundnodes !== undefined && foundnodes !== null && foundnodes.length > 0) { + // As they should all be the same, this is just an override + for (let nodekey in foundnodes) { + const curnode = foundnodes[nodekey] + if (curnode.data.environment !== undefined && curnode.data.environment !== null && curnode.data.environment.length > 0) { + parsedEnvironments = curnode.data.environment + break + } + } + } + } + + const newAppData = { + name: app.actions[actionIndex].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, + required_body_fields: app.actions[actionIndex].required_body_fields, + 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 + // + + if (!isCloud && (!app.is_valid || (!app.activated && app.generated))) { + console.log("NOT VALID: Activate!") + + activateApp(app.id, false) + } + + // 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 getNextActionName = (appName) => { var highest = ""; From 355466a9a28ae03f8255dec188bf102b93804ea6 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 6 Jun 2024 17:44:15 +0530 Subject: [PATCH 021/336] Done with delay --- frontend/src/components/ParsedAction.jsx | 31 +++++++++--- frontend/src/views/AngularWorkflow.jsx | 64 ++++++++++++------------ 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 95a9ad4e..7b308d71 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -177,7 +177,8 @@ const ParsedAction = (props) => { const classes = useStyles(); const [hideBody, setHideBody] = React.useState(true); const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false); - + const [appActionName, setAppActionName] = React.useState(selectedAction.label); + const [delay, setDelay] = React.useState(selectedAction?.execution_delay); const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); @@ -197,6 +198,7 @@ const ParsedAction = (props) => { } }, [expansionModalOpen]) + useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { return @@ -383,6 +385,9 @@ const ParsedAction = (props) => { () => { console.log("UseEffect Rendered!") console.log("Workflow", workflow) + setAppActionName(selectedAction.label) + setDelay(selectedAction.execution_delay) + // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { @@ -618,6 +623,12 @@ const ParsedAction = (props) => { }, [selectedAction,selectedApp,setNewSelectedAction] ); + + useEffect(() => { + selectedNameChange(appActionName) + actionDelayChange(delay) + },[appActionName,delay]) + console.log("selectedActionParameters: ", selectedActionParameters) console.log("selectedApp:", selectedApp) console.log("selectedAction: ", selectedAction) @@ -1628,11 +1639,17 @@ const ParsedAction = (props) => { fullWidth color="primary" placeholder={selectedAction.label} - defaultValue={selectedAction.label} - onChange={selectedNameChange} + value={appActionName} + onChange={ + (event) => { + let newValue = event.target.value + newValue = newValue.replaceAll(" ", "_") + setAppActionName(newValue) + } + } onBlur={(e) => { // Copy the name value - const name = e.target.value + const name = appActionName const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") const newname = "$"+name.toLowerCase().replaceAll(" ", "_") @@ -1848,11 +1865,9 @@ const ParsedAction = (props) => { disableUnderline: true, }} placeholder={selectedAction.execution_delay} - defaultValue={selectedAction.execution_delay} + value={delay} onChange={(event) => { - if (actionDelayChange !== undefined) { - actionDelayChange(event) - } + setDelay(event.target.value) }} /> diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 7e173716..bece66c2 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -22,7 +22,6 @@ import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx"; import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; import algoliasearch from 'algoliasearch/lite'; - import { Zoom, Fade, @@ -10067,44 +10066,43 @@ const AngularWorkflow = (defaultprops) => { // appname & version // description // ACTION select - const selectedNameChange = (event) => { - event.target.value = event.target.value.replaceAll("(", ""); - event.target.value = event.target.value.replaceAll(")", ""); - event.target.value = event.target.value.replaceAll("]", ""); - event.target.value = event.target.value.replaceAll("[", ""); - event.target.value = event.target.value.replaceAll("{", ""); - event.target.value = event.target.value.replaceAll("}", ""); - event.target.value = event.target.value.replaceAll("*", ""); - event.target.value = event.target.value.replaceAll("!", ""); - event.target.value = event.target.value.replaceAll("@", ""); - event.target.value = event.target.value.replaceAll("#", ""); - event.target.value = event.target.value.replaceAll("$", ""); - event.target.value = event.target.value.replaceAll("%", ""); - event.target.value = event.target.value.replaceAll("&", ""); - event.target.value = event.target.value.replaceAll("#", ""); - event.target.value = event.target.value.replaceAll(".", ""); - event.target.value = event.target.value.replaceAll(",", ""); - event.target.value = event.target.value.replaceAll(" ", "_"); - event.target.value = event.target.value.replaceAll("^", "_"); - event.target.value = event.target.value.replaceAll("'", "_"); - event.target.value = event.target.value.replaceAll("\"", "_"); - event.target.value = event.target.value.replaceAll("\"", "_"); - event.target.value = event.target.value.replaceAll(":", "_"); - event.target.value = event.target.value.replaceAll(";", "_"); - event.target.value = event.target.value.replaceAll("=", "_"); - event.target.value = event.target.value.replaceAll("+", "_"); - - selectedAction.label = event.target.value; + const selectedNameChange = (appActionName) => { + appActionName = appActionName.replaceAll("(", ""); + appActionName = appActionName.replaceAll(")", ""); + appActionName = appActionName.replaceAll("]", ""); + appActionName = appActionName.replaceAll("[", ""); + appActionName = appActionName.replaceAll("{", ""); + appActionName = appActionName.replaceAll("}", ""); + appActionName = appActionName.replaceAll("*", ""); + appActionName = appActionName.replaceAll("!", ""); + appActionName = appActionName.replaceAll("@", ""); + appActionName = appActionName.replaceAll("#", ""); + appActionName = appActionName.replaceAll("$", ""); + appActionName = appActionName.replaceAll("%", ""); + appActionName = appActionName.replaceAll("&", ""); + appActionName = appActionName.replaceAll("#", ""); + appActionName = appActionName.replaceAll(".", ""); + appActionName = appActionName.replaceAll(",", ""); + appActionName = appActionName.replaceAll(" ", "_"); + appActionName = appActionName.replaceAll("^", "_"); + appActionName = appActionName.replaceAll("'", "_"); + appActionName = appActionName.replaceAll("\"", "_"); + appActionName = appActionName.replaceAll("\"", "_"); + appActionName = appActionName.replaceAll(":", "_"); + appActionName = appActionName.replaceAll(";", "_"); + appActionName = appActionName.replaceAll("=", "_"); + appActionName = appActionName.replaceAll("+", "_"); + selectedAction.label = appActionName; setSelectedAction(selectedAction); }; - const actionDelayChange = (event) => { - if (isNaN(event.target.value)) { - console.log("NAN: ", event.target.value) + const actionDelayChange = (delay) => { + if (isNaN(delay)) { + console.log("NAN: ", delay) return } - const parsedNumber = parseInt(event.target.value) + const parsedNumber = parseInt(delay) if (parsedNumber > 86400) { console.log("Max number is 1 day (86400)") return From 03a1e62e4ede84a7becf60b7cc2a349635d24cf5 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 7 Jun 2024 17:10:54 +0530 Subject: [PATCH 022/336] Fixed the parameter value change dynamically --- frontend/src/components/ParsedAction.jsx | 58 +++++++++++++++--------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 7b308d71..2d428ca8 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -178,12 +178,16 @@ const ParsedAction = (props) => { const [hideBody, setHideBody] = React.useState(true); const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false); const [appActionName, setAppActionName] = React.useState(selectedAction.label); - const [delay, setDelay] = React.useState(selectedAction?.execution_delay); + const [delay, setDelay] = React.useState(selectedAction?.execution_delay || 0); + const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction.parameters); const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); + const [paramValues, setParamValues] = React.useState( + selectedAction.parameters.map((param) => param.value) || [] + ); const [actionlist, setActionlist] = React.useState([]); const [jsonList, setJsonList] = React.useState([]); const [showDropdown, setShowDropdown] = React.useState(false); @@ -386,8 +390,9 @@ const ParsedAction = (props) => { console.log("UseEffect Rendered!") console.log("Workflow", workflow) setAppActionName(selectedAction.label) - setDelay(selectedAction.execution_delay) + setDelay(selectedAction?.execution_delay) + setParamValues(selectedAction.parameters.map((param) => param.value) || []) // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { @@ -634,6 +639,16 @@ const ParsedAction = (props) => { console.log("selectedAction: ", selectedAction) console.log("ACTIONLIST: ", actionlist) console.log("selectedVariableParameter", selectedVariableParameter) + + const handleParamChange = (event, count,data) => { + setParamValues((prev) => { + const newValues = [...prev]; + newValues[count] = event.target.value; + return newValues; + } + ); + changeActionParameter(event, count, data) + } const calculateHelpertext = (input_data) => { var helperText = "" var looperText = "" @@ -1574,7 +1589,7 @@ const ParsedAction = (props) => { MenuProps={{ disableScrollLock: true, }} - defaultValue={selectedAction.app_version} + value={selectedAction.app_version} onChange={(event) => { const newversion = selectedApp.versions.find( (tmpApp) => tmpApp.version == event.target.value @@ -2340,22 +2355,23 @@ const ParsedAction = (props) => { /> ); }} - renderInput={(params) => { - if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { - const prefixes = ["Post", "Put", "Patch"] - for (let [key,keyval] in Object.entries(prefixes)) { - if (params.inputProps.value.startsWith(prefixes[key])) { - params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) - if (params.inputProps.value.length > 1) { - params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) + renderInput={(params) => { + if (params.inputProps?.value) { + const prefixes = ["Post", "Put", "Patch"]; + for (let prefix of prefixes) { + if (params.inputProps.value.startsWith(prefix)) { + let newValue = params.inputProps.value.replace(prefix + " ", ""); + if (newValue.length > 1) { + newValue = newValue.charAt(0).toUpperCase() + newValue.substring(1); } - break + // Set the new value without mutating inputProps + params = { ...params, inputProps: { ...params.inputProps, value: newValue } }; + break; } } - // Check if it starts with "Get List" and method is "Get" if (params.inputProps.value.startsWith("Get List")) { - console.log("Get List") + console.log("Get List"); } } @@ -2802,9 +2818,9 @@ const ParsedAction = (props) => { placeholder = data.example; - if (data.name === "url") { - data.value = data.example; - } + // if (data.name === "url") { + // data.value = data.example; + // } // In case of data.example if (data.value === undefined || data.value === null) { data.value = "" @@ -3046,7 +3062,6 @@ const ParsedAction = (props) => { } } //selectedActionParameters - if (changed) { // Sort selectedActionParameters based on selectedActionParameters.required //selectedActionParameters.sort((a, b) => (a.required < b.required) ? 1 : -1) @@ -3175,8 +3190,8 @@ const ParsedAction = (props) => { id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" - defaultValue={data.value} - // value={data.value} + // defaultValue={data.value} + value={paramValues[count] !== undefined ? paramValues[count] : data.value} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', @@ -3196,7 +3211,8 @@ const ParsedAction = (props) => { placeholder={placeholder} onChange={(event) => { //changeActionParameterCodemirror(event, count, data) - changeActionParameter(event, count, data); + // changeActionParameter(event, count, data); + handleParamChange(event, count, data) }} helperText={returnHelperText(data.name, data.value)} onBlur={(event) => { From a56b5ed8bf35fca0469368eb2eebbd4fca26b8ae Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 10 Jun 2024 15:18:48 +0530 Subject: [PATCH 023/336] Fixed the delay issue --- frontend/src/components/ParsedAction.jsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 2d428ca8..70a5970a 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -390,16 +390,15 @@ const ParsedAction = (props) => { console.log("UseEffect Rendered!") console.log("Workflow", workflow) setAppActionName(selectedAction.label) - setDelay(selectedAction?.execution_delay) - + setDelay(selectedAction?.execution_delay || 0) + if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { + console.log("Setting action parameters!!") + setSelectedActionParameters(selectedAction.parameters); + // } + } setParamValues(selectedAction.parameters.map((param) => param.value) || []) // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { - if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { - console.log("Setting action parameters!!") - setSelectedActionParameters(selectedAction.parameters); - // } - } if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { // FIXME - this is the bad thing From 5b48e290d723eaf9221602b4c02954a272b1e9c3 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 11 Jun 2024 16:09:01 +0530 Subject: [PATCH 024/336] Fixed the bug with param value change --- frontend/src/components/ParsedAction.jsx | 39 ++++++++++++++++-------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 70a5970a..af509c51 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -183,11 +183,16 @@ const ParsedAction = (props) => { const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); - const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction.parameters); + const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []); const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); const [paramValues, setParamValues] = React.useState( - selectedAction.parameters.map((param) => param.value) || [] - ); + selectedAction?.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + }) + ); const [actionlist, setActionlist] = React.useState([]); const [jsonList, setJsonList] = React.useState([]); const [showDropdown, setShowDropdown] = React.useState(false); @@ -201,8 +206,17 @@ const ParsedAction = (props) => { setLastSaved(false) } }, [expansionModalOpen]) + useEffect(() => { + setParamValues(selectedAction.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + })) + },[ + selectedAction, selectedApp,setNewSelectedAction + ]) - useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { return @@ -396,7 +410,6 @@ const ParsedAction = (props) => { setSelectedActionParameters(selectedAction.parameters); // } } - setParamValues(selectedAction.parameters.map((param) => param.value) || []) // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { @@ -638,14 +651,14 @@ const ParsedAction = (props) => { console.log("selectedAction: ", selectedAction) console.log("ACTIONLIST: ", actionlist) console.log("selectedVariableParameter", selectedVariableParameter) - const handleParamChange = (event, count,data) => { - setParamValues((prev) => { - const newValues = [...prev]; - newValues[count] = event.target.value; - return newValues; - } - ); + const newParams = [...paramValues]; + newParams.map((param) => { + if (param.name === data.name) { + param.value = event.target.value; + } + }) + setParamValues(newParams); changeActionParameter(event, count, data) } const calculateHelpertext = (input_data) => { @@ -3190,7 +3203,7 @@ const ParsedAction = (props) => { rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" // defaultValue={data.value} - value={paramValues[count] !== undefined ? paramValues[count] : data.value} + value={data.value} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', From 70a313d40a11cc567f2ef8f0c865ef31fe7fde3b Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 11 Jun 2024 16:23:31 +0530 Subject: [PATCH 025/336] Fixed the autoComplete issue:Closecase in Hive --- frontend/src/components/ParsedAction.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index af509c51..12f81ad8 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -885,10 +885,12 @@ const ParsedAction = (props) => { } //console.log("CHANGING ACTION COUNT !") - selectedActionParameters[count].autocompleted = false - selectedAction.parameters[count].autocompleted = false - selectedActionParameters[count].value = event.target.value; - selectedAction.parameters[count].value = event.target.value; + setTimeout(() => { + // selectedActionParameters[count].autocompleted = false + selectedAction.parameters[count].autocompleted = false + // selectedActionParameters[count].value = event.target.value; + selectedAction.parameters[count].value = event.target.value; + },100) var forceUpdate = false if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { @@ -3994,7 +3996,7 @@ const ParsedAction = (props) => { } const buttonTitle = `Authenticate ${selectedApp.name.replaceAll("_", " ")}` - const hasAutocomplete = data.autocompleted === true + const hasAutocomplete = data?.autocompleted === true return (
    {hideBodyButton} From df8493ae69eb2cc9722cd6c5b4f884381d0d6e45 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 11 Jun 2024 16:32:01 +0530 Subject: [PATCH 026/336] Solved the param change issue --- frontend/src/components/ParsedAction.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 12f81ad8..5f7edc20 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -3205,7 +3205,11 @@ const ParsedAction = (props) => { rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" // defaultValue={data.value} - value={data.value} + value={ + paramValues.find((param) => param.name === data.name) !== undefined + ? paramValues.find((param) => param.name === data.name).value + : data.value + } //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', From bcda8c6143d26ead65960486a7dee7c760cf1631 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 11 Jun 2024 17:11:55 +0530 Subject: [PATCH 027/336] Fixed the dropdown bug in UserInputSidebar --- frontend/src/views/AngularWorkflow.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index bece66c2..be234c12 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -14944,6 +14944,7 @@ const AngularWorkflow = (defaultprops) => { onChange={(event, newValue) => { console.log("Changed autocomplete!") handleWorkflowSelectionUpdate({ target: { value: newValue } }, true) + event.target.blur(); }} renderOption={(props, data, state) => { if (data.id === workflow.id) { @@ -14972,6 +14973,7 @@ const AngularWorkflow = (defaultprops) => { value: data, }}, true) + document.activeElement.blur(); }} > From e87b112ff6fc521e78477d6a1bee71d41ce5b936 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 12 Jun 2024 12:59:09 +0530 Subject: [PATCH 028/336] Fixed the bug --- frontend/src/components/ParsedAction.jsx | 390 +++++++++-------------- 1 file changed, 154 insertions(+), 236 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 5f7edc20..c02afb09 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -401,244 +401,160 @@ const ParsedAction = (props) => { useEffect( () => { - console.log("UseEffect Rendered!") - console.log("Workflow", workflow) - setAppActionName(selectedAction.label) - setDelay(selectedAction?.execution_delay || 0) - if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { - console.log("Setting action parameters!!") - setSelectedActionParameters(selectedAction.parameters); - // } - } - // if (selectedActionParameters !== undefined && selectedActionParameters !== null - // ) { - if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { - - // FIXME - this is the bad thing - setSelectedVariableParameter(workflow.workflow_variables[0].name); - } - - if (actionlist.length === 0) { - // FIXME: Have previous execution values in here - if (workflowExecutions.length > 0) { - for (let [key,keyval] in Object.entries(workflowExecutions)) { - if ( - workflowExecutions[key].execution_argument === undefined || - workflowExecutions[key].execution_argument === null || - workflowExecutions[key].execution_argument.length === 0 - ) { - continue; - } - - const valid = validateJson(workflowExecutions[key].execution_argument) - if (valid.valid) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: valid.result, - }) - break - } - } - + console.log("UseEffect Rendered!"); + console.log("Workflow", workflow); + + // Only set app action name if it has changed + if (selectedAction.label !== appActionName) { + setAppActionName(selectedAction.label); } - + + // Only set delay if it has changed + const newDelay = selectedAction?.execution_delay || 0; + if (newDelay !== delay) { + setDelay(newDelay); + } + + // Only set selected action parameters if they have changed + if (selectedAction.parameters && selectedAction.parameters.length > 0) { + console.log("Setting action parameters!!"); + setSelectedActionParameters(selectedAction.parameters); + } + + // Only set selected variable parameter if it is null or undefined + if (!selectedVariableParameter && workflow.workflow_variables?.length > 0) { + setSelectedVariableParameter(workflow.workflow_variables[0].name); + } + + // Initialize action list if it is empty if (actionlist.length === 0) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: "", - }) - } - - /* - actionlist.push({ - type: "Shuffle DB", - name: "Shuffle DB", - value: "$shuffle_cache", - highlight: "shuffle_cache", - autocomplete: "shuffle_cache", - example: { - "what": "", - "unique gmail ids new": "", - }, - }) - */ - - var cachekey = { - type: "Shuffle DB", - name: "Shuffle DB", - value: "$shuffle_cache", - highlight: "shuffle_cache", - autocomplete: "shuffle_cache", - example: "", - } - - if (listCache !== undefined && listCache !== null && listCache.keys !== undefined && listCache.keys !== null && listCache.keys.length > 0) { - cachekey.example = {} - - for (var i in listCache.keys) { - const item = listCache.keys[i] - if (item.key === undefined || item.key === null || item.key.length === 0) { - continue - } - - var itemvalue = item.value === undefined || item.value === null ? "" : item.value - try{ - if (itemvalue.length > 10000) { - itemvalue = "" - } - - } catch (e) { - itemvalue = "" - } - - var itemkey = item.key.split(" ").join("_") - cachekey.example[itemkey] = { - "value": itemvalue, - } - } - } else { - } - - actionlist.push(cachekey) - - if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) { - for (let [key,keyval] in Object.entries(workflow.workflow_variables)) { - const item = workflow.workflow_variables[key]; - actionlist.push({ - type: "workflow_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: item.value, - }); - } - } - - if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) { - for (let [key,keyval] in Object.entries(workflow.execution_variables)) { - const item = workflow.execution_variables[key] - - var exampleoutput = "" - for (let execkey in workflowExecutions) { - const exec = workflowExecutions[execkey] - if (exec["execution_variables"] === undefined || exec["execution_variables"] === null) { - continue - } - - const foundExec = exec.execution_variables.find((exvar) => exvar.name === item.name) - if (!foundExec) { - continue - } - - if (foundExec.value !== undefined && foundExec.value !== null && foundExec.value.length > 0) { - exampleoutput = foundExec.value - break - } - } - - actionlist.push({ - type: "execution_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: exampleoutput, - }); - } - } - - // Loops parent nodes' old results to fix autocomplete - if (getParents !== undefined) { - var parents = getParents(selectedAction) - - if (parents.length > 1) { - var labels = [] - //for (let [parentkey, parentkeyval] in Object.entries(parents)) { - for (let parentkey in parents) { - const parentNode = parents[parentkey] - if (parentNode.label === "Execution Argument") { - continue - } - - //if (labels.includes(item.label)) { - // continue - //} - - labels.push(parentNode.label) - - var exampledata = parentNode.example === undefined || parentNode.example === null ? "" : parentNode.example - // Find previous execution and their variables - //exampledata === "" && - if (workflowExecutions.length > 0) { - // Look for the ID - const found = false; - for (let wfkey in workflowExecutions) { - if (workflowExecutions[wfkey].results === undefined || workflowExecutions[wfkey].results === null) { - - continue; - } - - var foundResult = workflowExecutions[wfkey].results.find((result) => result.action.id === parentNode.id) - - if (foundResult === undefined || foundResult === null) { - continue - } - - if (foundResult.result !== undefined && foundResult.result !== null) { - foundResult = foundResult.result - } - - const valid = validateJson(foundResult) - if (valid.valid) { - if (valid.result.success === false) { - //console.log("Skipping success false autocomplete") - } else { - - // FIXME: Have a merge system to allow to use kind of any key from that node in the last 10-20 execs - //if (exampledata.length > 0) { - // exampledata = valid.result - //} else { - // exampledata = valid.result - //} - - exampledata = valid.result - break + if (workflowExecutions.length > 0) { + for (let [key, keyval] of Object.entries(workflowExecutions)) { + const execArg = workflowExecutions[key].execution_argument; + if (execArg && execArg.length > 0) { + const valid = validateJson(execArg); + if (valid.valid) { + actionlist.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: valid.result, + }); + break; + } } - } else { - exampledata = foundResult } - } - } - - // 1. Take - const itemlabelComplete = parentNode.label === null || parentNode.label === undefined ? "" : parentNode.label.split(" ").join("_"); - - const actionvalue = { - type: "action", - id: parentNode.id, - name: parentNode.label, - autocomplete: itemlabelComplete, - example: exampledata, - } - - actionlist.push(actionvalue) - } - } - - setActionlist(actionlist); - } - } - }, - [selectedAction,selectedApp,setNewSelectedAction] + } + + if (actionlist.length === 0) { + actionlist.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: "", + }); + } + + let cacheKey = { + type: "Shuffle DB", + name: "Shuffle DB", + value: "$shuffle_cache", + highlight: "shuffle_cache", + autocomplete: "shuffle_cache", + example: "", + }; + + if (listCache?.keys?.length > 0) { + cacheKey.example = {}; + for (let item of listCache.keys) { + if (item.key) { + let itemValue = item.value ?? ""; + if (itemValue.length > 10000) { + itemValue = ""; + } + cacheKey.example[item.key.split(" ").join("_")] = { value: itemValue }; + } + } + } + + actionlist.push(cacheKey); + + if (workflow.workflow_variables?.length > 0) { + for (let [key, keyval] of Object.entries(workflow.workflow_variables)) { + const item = workflow.workflow_variables[key]; + actionlist.push({ + type: "workflow_variable", + name: item.name, + value: item.value, + id: item.id, + autocomplete: item.name.split(" ").join("_"), + example: item.value, + }); + } + } + + if (workflow.execution_variables?.length > 0) { + for (let [key, keyval] of Object.entries(workflow.execution_variables)) { + const item = workflow.execution_variables[key]; + let exampleOutput = ""; + for (let exec of workflowExecutions) { + const foundExec = exec.execution_variables?.find(exvar => exvar.name === item.name); + if (foundExec?.value) { + exampleOutput = foundExec.value; + break; + } + } + actionlist.push({ + type: "execution_variable", + name: item.name, + value: item.value, + id: item.id, + autocomplete: item.name.split(" ").join("_"), + example: exampleOutput, + }); + } + } + + if (getParents) { + const parents = getParents(selectedAction); + if (parents.length > 1) { + const labels = []; + for (let parentNode of parents) { + if (parentNode.label !== "Execution Argument" && !labels.includes(parentNode.label)) { + labels.push(parentNode.label); + let exampleData = parentNode.example ?? ""; + if (!exampleData && workflowExecutions.length > 0) { + for (let exec of workflowExecutions) { + const foundResult = exec.results?.find(result => result.action.id === parentNode.id); + if (foundResult) { + const valid = validateJson(foundResult.result); + if (valid.valid && valid.result.success !== false) { + exampleData = valid.result; + break; + } + } + } + } + actionlist.push({ + type: "action", + id: parentNode.id, + name: parentNode.label, + autocomplete: parentNode.label.split(" ").join("_"), + example: exampleData, + }); + } + } + } + } + + setActionlist(actionlist); + } + }, + [selectedAction,selectedApp,setNewSelectedAction,workflow, workflowExecutions, listCache, getParents, actionlist] ); useEffect(() => { @@ -886,9 +802,9 @@ const ParsedAction = (props) => { //console.log("CHANGING ACTION COUNT !") setTimeout(() => { - // selectedActionParameters[count].autocompleted = false + selectedActionParameters[count].autocompleted = false selectedAction.parameters[count].autocompleted = false - // selectedActionParameters[count].value = event.target.value; + selectedActionParameters[count].value = event.target.value; selectedAction.parameters[count].value = event.target.value; },100) @@ -1060,10 +976,12 @@ const ParsedAction = (props) => { } } + setTimeout(() => { selectedActionParameters[count].autocompleted = false selectedAction.parameters[count].autocompleted = false selectedActionParameters[count].value = data selectedAction.parameters[count].value = data + }, 100); setSelectedAction(selectedAction) //setUpdate(Math.random()) //setUpdate(event.target.value) From da4e25f6c9c97c1058a6dad089ab5e10c6999b74 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 12 Jun 2024 23:38:52 +0530 Subject: [PATCH 029/336] Fixes in ParsedAction --- frontend/src/components/ParsedAction.jsx | 33 ++++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index c02afb09..60e87f22 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -206,16 +206,16 @@ const ParsedAction = (props) => { setLastSaved(false) } }, [expansionModalOpen]) - useEffect(() => { - setParamValues(selectedAction.parameters.map((param) => { - return { - name: param.name, - value: param.value, - } - })) - },[ - selectedAction, selectedApp,setNewSelectedAction - ]) +// useEffect(() => { +// setParamValues(selectedAction.parameters.map((param) => { +// return { +// name: param.name, +// value: param.value, +// } +// })) +// },[ +// selectedAction, selectedApp,setNewSelectedAction +// ]) useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { @@ -404,6 +404,12 @@ const ParsedAction = (props) => { console.log("UseEffect Rendered!"); console.log("Workflow", workflow); + setParamValues(selectedAction.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + })) // Only set app action name if it has changed if (selectedAction.label !== appActionName) { setAppActionName(selectedAction.label); @@ -554,14 +560,13 @@ const ParsedAction = (props) => { setActionlist(actionlist); } }, - [selectedAction,selectedApp,setNewSelectedAction,workflow, workflowExecutions, listCache, getParents, actionlist] + [selectedAction,selectedApp,setNewSelectedAction,workflow, workflowExecutions, getParents] ); - useEffect(() => { selectedNameChange(appActionName) actionDelayChange(delay) },[appActionName,delay]) - + console.log("selectedActionParameters: ", selectedActionParameters) console.log("selectedApp:", selectedApp) console.log("selectedAction: ", selectedAction) @@ -3126,7 +3131,7 @@ const ParsedAction = (props) => { value={ paramValues.find((param) => param.name === data.name) !== undefined ? paramValues.find((param) => param.name === data.name).value - : data.value + : "" } //options={{ // theme: 'gruvbox-dark', From 50dcf618a0f1fcc668a1e914b2c9ca02066d5f11 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 13 Jun 2024 00:04:33 +0530 Subject: [PATCH 030/336] Changes --- frontend/src/views/AngularWorkflow.jsx | 546 +++++++------------------ 1 file changed, 144 insertions(+), 402 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index be234c12..2f9a123d 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -553,7 +553,7 @@ const AngularWorkflow = (defaultprops) => { }, [editWorkflowModalOpen]) // New for generated stuff - const releaseToConnectLabel = "Release to Connect" +const releaseToConnectLabel = "Release to Connect" const integrationApps = [{ "id": "integration", "name": "Integration Framework", @@ -1446,18 +1446,18 @@ const AngularWorkflow = (defaultprops) => { trigger.parameters = [] - const topic = document.getElementById('topic')?.value - const bootstrapServers = document.getElementById('bootstrap_servers')?.value - const groupId = document.getElementById('group_id')?.value + const topic = document.getElementById('topic')?.value; + const bootstrapServers = document.getElementById('bootstrap_servers')?.value; + const groupId = document.getElementById('group_id')?.value; //const autoOffsetReset = document.getElementById('auto_offset_reset')?.value; if(topic) { trigger.parameters.push({ name: "topic", value: topic - }) + }); } else { - toast("Please enter the topic name"); + toast("please enter the topic name"); return; } @@ -3283,19 +3283,12 @@ const AngularWorkflow = (defaultprops) => { // don't redirect if it exists const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; var execFound = new URLSearchParams(cursearch).get("execution_id"); - var sessionToken = new URLSearchParams(cursearch).get("session_token"); - if (execFound === null && sessionToken === null) { - toast(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) - setTimeout(() => { - window.location.pathname = "/workflows"; - }, 2000); - } else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") { - toast(`Injecting session token and reloading workflow..`) - setTimeout(() => { - setCookie("session_token", sessionToken, { path: "/" }); - window.location.href = "https://shuffler.io/workflows/3abdfb21-b40f-4e50-b855-ac0d62f83cbe"; - }, 2000); - } + if (execFound === null) { + toast(`You don't access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) + setTimeout(() => { + window.location.pathname = "/workflows"; + }, 2000); + } } } @@ -3776,28 +3769,20 @@ const AngularWorkflow = (defaultprops) => { const onNodeDragStop = (event, selectedAction) => { const nodedata = event.target.data(); if (nodedata.id === selectedAction.id) { - //console.log("Same node, return") - return + return; } if (nodedata.finished === false) { - //console.log("Node is not finished, return") - return + return; } const connected = event.target.connectedEdges().jsons() if (connected.length > 0 && connected !== undefined) { for (let connectkey in connected) { const edge = connected[connectkey] - if (edge.data.decorator && edge.data.label === releaseToConnectLabel) { - // Transform to normal edge - const currentedge = cy.getElementById(edge.data.id) - if (currentedge !== undefined && currentedge !== null) { - currentedge.data("decorator", false) - currentedge.data("label", "") - } - continue - } + //console.log("EDGE:", edge) + + //const edge = edgeBase.json() const sourcenode = cy.getElementById(edge.data.source) const destinationnode = cy.getElementById(edge.data.target) @@ -4012,141 +3997,26 @@ const AngularWorkflow = (defaultprops) => { } if (nodedata.id === selectedAction.id) { - return + return; } - - if ((nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT" || nodedata.type === "ACTION") && !nodedata.isStartNode) { - // Check if it already has any non-decorator branches attached to it - const branches = cy.elements('edge').jsons() - var branchFound = false - var decoratorIds = [] - for (var branchkey in branches) { - if (branches[branchkey].data.source === nodedata.id || branches[branchkey].data.target === nodedata.id) { - - if (branches[branchkey].data.decorator === true) { - - // Add the source/destination - if (branches[branchkey].data.source === nodedata.id) { - decoratorIds.push(branches[branchkey].data.target) - } else { - decoratorIds.push(branches[branchkey].data.source) - } - - continue - } - - branchFound = true - break - } - } - - if (!branchFound) { - //console.log("Found action during drag. Checking closest nodes as it doesn't have a valid branch") - var closestNode = null - var minDistance = 300 - - const draggedNode = event.target - const allnodes = cy.nodes().jsons() - for (var nodekey in allnodes) { - const node = allnodes[nodekey] - if (node.data.id === nodedata.id) { - continue - } - - // Decorators - if (node.data.attachedTo !== undefined) { - continue - } - - if (node.position === undefined || node.position === null || node.position.x === undefined || node.position.y === undefined) { - continue - } - - if (node.data.type !== "ACTION" && node.data.type !== "TRIGGER") { - continue - } - - const distance = Math.sqrt( - Math.pow(draggedNode.position('x') - node.position.x, 2) + - Math.pow(draggedNode.position('y') - node.position.y, 2) - ) - - if (decoratorIds.includes(node.data.id)) { - //console.log("Found existing decorator for: ", node.data.app_name, "Distance: ", distance) - - if (distance > 300) { - // Remove the branch - const edgeToRemove = cy.getElementById(branches[branchkey].data.id) - if (edgeToRemove !== null && edgeToRemove !== undefined) { - //console.log("Removing edge: ", edgeToRemove) - edgeToRemove.remove() - //decoratorIds.splice(decoratorIds.indexOf(node.data.id), 1) - break - } - } - } - if (distance < minDistance) { - minDistance = distance - closestNode = node - } - } + /* + // 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 !== null && closestNode !== undefined) { - //console.log("Closest node app: ", closestNode.data.app_name, "Distance: ", minDistance) - - /* - if (decoratorIds.length > 0) { - console.log("Decorators already exists. If within distance of 15 add to existing, otherwise remove old and add new: ", decoratorIds) - for (var decoratorkey in decoratorIds) { - const decoratorEdge = cy.getElementById(decoratorIds[decoratorkey]) - if (decoratorEdge === null || decoratorEdge === undefined) { - continue - } - - const sourceNode = cy.getElementById(decoratorEdge.data.source) - const targetNode = cy.getElementById(decoratorEdge.data.target) - - const distance = Math.sqrt( - Math.pow(draggedNode.position('x') - sourceNode.position('x'), 2) + - Math.pow(draggedNode.position('y') - sourceNode.position('y'), 2) - ) - - // Check plus minus 15 in distance from mindistance - if (distance > minDistance - 15 && distance < minDistance + 15) { - console.log("Within distance of 15, add to existing edge") - } else { - console.log("Outside distance of 15, remove old edge and add new") - } - - } - } - */ - - if (decoratorIds.length === 0) { - //const edgeCurve = calculateEdgeCurve(draggedNode.position(), closestNode.position) - //currentedge.style('control-point-distance', edgeCurve.distance) - //currentedge.style('control-point-weight', edgeCurve.weight) - - const newId = uuidv4() - cy.add({ - group: "edges", - data: { - decorator: true, - id: newId, - _id: newId, - source: closestNode.data.id, - target: nodedata.id, - label: releaseToConnectLabel, - conditions: [], - } - }) - } - } - } - } + if (closestNode.found) { + console.log("No closest node found for: ", nodedata.id) + } else { + console.log("Closest: ", closestNode) + } + */ if ( originalLocation.x === 0 && @@ -4205,11 +4075,11 @@ const AngularWorkflow = (defaultprops) => { } // Ensure it only happens once - document.removeEventListener("mousemove", onMouseUpdate, false) - } + document.removeEventListener("mousemove", onMouseUpdate, false); + }; - document.addEventListener("mousemove", onMouseUpdate, false) - } + document.addEventListener("mousemove", onMouseUpdate, false); + }; useBeforeunload(() => { @@ -4222,7 +4092,7 @@ const AngularWorkflow = (defaultprops) => { document.removeEventListener("paste", handlePaste, true); } } - }) + }); // Should get AI autocompletes const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => { @@ -5624,10 +5494,11 @@ const AngularWorkflow = (defaultprops) => { // Checks for errors in edges when they're added const onEdgeAdded = (event) => { - const edge = event.target.data() - //console.log("EDGE ADDED!: ", edge) + setLastSaved(false); + const edge = event.target.data(); + + //console.log("edge added: ", edge) if (edge.source === undefined && edge.target === undefined) { - console.log("Edge source and target is undefined") return } @@ -5641,7 +5512,6 @@ const AngularWorkflow = (defaultprops) => { const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { - console.log("Source or destination node is undefined") } 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") { @@ -5654,10 +5524,10 @@ const AngularWorkflow = (defaultprops) => { console.log("Node: ", targetedge) if (targetedge !== -1) { + event.target.remove() //console.log("Found branch already!") toast.error("Triggers can have exactly one target node") - event.target.remove() return @@ -5678,10 +5548,6 @@ const AngularWorkflow = (defaultprops) => { } } - if (edge.decorator === true) { - console.log("Doing nothing to branch because decorator") - return - } var targetnode = workflow.triggers.findIndex( (data) => data.id === edge.target @@ -5721,14 +5587,15 @@ const AngularWorkflow = (defaultprops) => { } } - if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") { + if ( + eventTarget.data("isDescriptor") === true || + eventTarget.data("type") === "COMMENT" + ) { console.log("Removing because of descriptor or comment") - event.target.remove() - return + event.target.remove(); + return; } - - setLastSaved(false) targetnode = -1; // Check if: @@ -5736,51 +5603,38 @@ const AngularWorkflow = (defaultprops) => { // dest == dest && source == source // backend: check all children? to stop recursion var found = false; - const branches = cy.edges().jsons() - - const startNode = cy.nodes().jsons().find((node) => node.data.isStartNode === true) - var startnodeId = workflow.start - if (startNode !== undefined && startNode !== null) { - startnodeId = startNode.data.id - } - - //for (let branchkey in workflow.branches) { - for (let branchkey in branches) { - const branch = branches[branchkey].data - - //if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { - if (branch.target === edge.source && branch.source === edge.target) { - toast("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) { - } else if (branch.target === edge.target && branch.source === edge.source) { - - if (branch.conditions === undefined) { - // Edgehandles - } else { - console.log("Removing because the same branch already exists") - event.target.remove() - - found = true - break - } - } else if (edge.target === startnodeId) { - targetnode = workflow.triggers.findIndex((data) => data.id === edge.source) + for (let branchkey in workflow.branches) { + if ( + workflow.branches[branchkey].destination_id === edge.source && + workflow.branches[branchkey].source_id === edge.target + ) { + toast("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 + ) { + //toast("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") { - toast("Can't make arrow to starting node") - event.target.remove() - break + toast("Can't make arrow to starting node"); + event.target.remove(); + break; } found = true; } - //} else if (edge.source === workflow.branches[branchkey].source_id) { - } else if (edge.source === branch.source) { + } 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 @@ -5824,6 +5678,7 @@ const AngularWorkflow = (defaultprops) => { newdst !== null ) { const dstdata = RunAutocompleter(newdst.data()); + //console.log("DST Autocompleter: ", dstdata); } var newbranch = { @@ -11538,101 +11393,11 @@ const AngularWorkflow = (defaultprops) => { {/* Check if dest is the same as start */} - {conditionsDisabled ? - - Conditions are unavailable between triggers and the startnode. - - : null} - -
    - {/* - - - - */} - -
    + {conditionsDisabled ? + + Conditions are unavailable between triggers and the startnode. + + : null}
    ); }; @@ -12396,7 +12161,7 @@ const AngularWorkflow = (defaultprops) => { return transformedData; - } + }; const AppAuthSelector = ({ appAuthData }) => { const [selectedAuth, setSelectedAuth] = useState(""); @@ -12409,7 +12174,6 @@ const AngularWorkflow = (defaultprops) => { const handleShowingValue = (appName) => { let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) - console.log("LIST WITH VALUES: ", listWithValues) for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] @@ -13092,7 +12856,7 @@ const AngularWorkflow = (defaultprops) => { data: newbranch, }; - cy.add(cybranch) + cy.add(cybranch); } console.log("Value to be set: ", e.target.value); @@ -13636,27 +13400,25 @@ const AngularWorkflow = (defaultprops) => {
    - {/*
    -
    -
    -
    - Auth Override -
    -
    +
    +
    +
    + Auth Override +
    +
    -
    -
    - -
    -
    -
    +
    +
    + +
    +
    +
    - */}
    - ) + ); } return null; @@ -15610,8 +15372,12 @@ const AngularWorkflow = (defaultprops) => { right: 0, left: isMobile ? 20 : leftBarSize + 20, top: isMobile ? 30 : appBarSize + 20, + pointerEvents: "none", } + + + const TopCytoscapeBar = (props) => { if (workflow.public === true) { return null @@ -15627,7 +15393,7 @@ const AngularWorkflow = (defaultprops) => {
    {

    {workflow.name}

    @@ -16376,8 +16143,6 @@ const AngularWorkflow = (defaultprops) => { } } - /* - // Infinitely annoying. Need a new bind if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { console.log("Shift key pressed") if (!workflow.public && executionModalOpen) { @@ -16389,8 +16154,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionModalView(0); } } - */ - } + }; document.addEventListener('keydown', handleKeyDown); @@ -18260,7 +18024,7 @@ const AngularWorkflow = (defaultprops) => {
    {foundnotifications > 0 ? - + { @@ -19319,11 +19083,10 @@ const AngularWorkflow = (defaultprops) => { return "The queries or data sent to the API is most likely wrong (400). Check the body of the result for more information." } - /* if (result.status === 200 || result.status === 201 || result.status === 204) { return "It looks like the result was successful! If it didn't work, make sure to check if the body you are sending was correct." } - */ + // Validate and check for newlines if (result.success !== false) { @@ -21012,9 +20775,8 @@ const AngularWorkflow = (defaultprops) => {
    Configuration options for {selectedOption}
    - - {selectedOption === "Kafka Queue" ? -
    + {selectedOption === "Kafka Queue" && ( + <> Topic { placeholder={"earliest"} defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''} /> */} -
    - : null} - - param.name === "bootstrap_servers")?.value) || ''} - /> - + + )}
    - ); - }; - + if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { if (workflow.triggers[selectedTriggerIndex] === undefined) { return null; @@ -13360,10 +13062,298 @@ const releaseToConnectLabel = "Release to Connect" }} /> {!showDropdown ? null : - + { + handleMenuClose(); + }} + open={!!menuPosition} + style={{ + border: `2px solid #f85a3e`, + color: "white", + marginTop: 2, + }} + > + {actionlist.map((innerdata) => { + const icon = + innerdata.type === "action" ? ( + + ) : innerdata.type === "workflow_variable" || + innerdata.type === "execution_variable" ? ( + + ) : ( + + ); + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById( + "execution_argument_input_field" + ); + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e"; + } else { + exec_text_field.style.border = ""; + } + } + + // Also doing arguments + if ( + workflow.triggers !== undefined && + workflow.triggers !== null && + workflow.triggers.length > 0 + ) { + for (let triggerkey in workflow.triggers) { + const item = workflow.triggers[triggerkey]; + + if (cy !== undefined) { + var node = cy.getElementById(item.id); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + } + } + } + + const handleActionHover = (inside, actionId) => { + if (cy !== undefined) { + var node = cy.getElementById(actionId); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + }; + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true); + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id); + } + }; + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false); + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id); + } + }; + + var parsedPaths = []; + console.log("Found example data: ", innerdata.example) + if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } + + const coverColor = "#82ccc3" + + return parsedPaths.length > 0 ? ( + + {/* + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + backgroundColor: theme.palette.inputColor, + color: "white", + minWidth: 250, + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ) + + return ( + { }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {icon} {pathdata.name} +
    +
    +
    + ); + })} + + */} + + + {icon} {innerdata.name} + + } + parentMenuOpen={!!menuPosition} + style={{ + color: "white", + minWidth: 250, + maxWidth: 250, + maxHeight: 50, + overflow: "hidden", + }} + onClick={() => { + console.log("CLICKED: ", innerdata); + console.log(innerdata.example) + handleItemClick([innerdata]); + }} + > + + + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + + {innerdata.name} + + + + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + // + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ); + // + + const indentation_count = (pathdata.name.match(/\./g) || []).length+1 + const baseIndent =
    + //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 + const boxPadding = 0 + const namesplit = pathdata.name.split(".") + const newname = namesplit[namesplit.length-1] + return ( + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {Array(indentation_count).fill().map((subdata, subindex) => { + return ( + baseIndent + ) + })} + {icon} {newname} + {pathdata.type === "list" ? { + + }} /> : null} +
    +
    +
    + ); + })} + + + + ) : ( + handleMouseover()} + onMouseOut={() => { + handleMouseOut(); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + +
    + {icon} {innerdata.name} +
    +
    +
    + ); + })} + } {/*
    0) { defaultReturn = } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (selectedTrigger.trigger_type === "SUBFLOW") { - defaultReturn = - } else if (selectedTrigger.trigger_type === "EMAIL") { + if (selectedTrigger.trigger_type === "EMAIL") { defaultReturn = } else if (selectedTrigger.trigger_type === undefined) { //defaultReturn = @@ -19648,6 +19636,13 @@ const releaseToConnectLabel = "Release to Connect" : null}
    : null} + + { + rightSideBarOpen && selectedTrigger.trigger_type === "SUBFLOW"&& Object.getOwnPropertyNames(selectedTrigger).length > 0 ? +
    + +
    : null + } {/* Date: Mon, 17 Jun 2024 11:42:15 +0530 Subject: [PATCH 034/336] Made paramValues separate --- frontend/src/components/ParsedAction.jsx | 27 ++++++++++-------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 9bf06710..a9b0c955 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -206,16 +206,16 @@ const ParsedAction = (props) => { setLastSaved(false) } }, [expansionModalOpen]) -// useEffect(() => { -// setParamValues(selectedAction.parameters.map((param) => { -// return { -// name: param.name, -// value: param.value, -// } -// })) -// },[ -// selectedAction, selectedApp,setNewSelectedAction -// ]) + useEffect(() => { + setParamValues(selectedAction.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + })) + },[ + selectedAction, selectedApp,setNewSelectedAction, setUpdate, showDropdown, showAutocomplete, selectedActionParameters + ]) // useEffect(() => { // if (selectedAction.parameters === null || selectedAction.parameters === undefined) { @@ -404,12 +404,7 @@ const ParsedAction = (props) => { console.log("UseEffect Rendered!"); console.log("Workflow", workflow); - setParamValues(selectedAction.parameters.map((param) => { - return { - name: param.name, - value: param.value, - } - })) + // Only set app action name if it has changed if (selectedAction.label !== appActionName) { setAppActionName(selectedAction.label); From 1570f852b43fb1bc1ce98d03adb7cff70defa9c1 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 17 Jun 2024 16:52:43 +0530 Subject: [PATCH 035/336] Fixed the exec args issue --- frontend/src/components/ParsedAction.jsx | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index a9b0c955..5761da74 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -214,9 +214,24 @@ const ParsedAction = (props) => { } })) },[ - selectedAction, selectedApp,setNewSelectedAction, setUpdate, showDropdown, showAutocomplete, selectedActionParameters + selectedAction, selectedApp,setNewSelectedAction, workflow, ]) +// useEffect(() => { +// if(!showAutocomplete){ +// paramValueChange(); +// } +// },[menuPosition,showAutocomplete]) + +// const paramValueChange = () => { +// setParamValues(selectedAction.parameters.map((param) => { +// return { +// name: param.name, +// value: param.value, +// } +// })) +// } + // useEffect(() => { // if (selectedAction.parameters === null || selectedAction.parameters === undefined) { // return @@ -801,12 +816,10 @@ const ParsedAction = (props) => { } //console.log("CHANGING ACTION COUNT !") - setTimeout(() => { selectedActionParameters[count].autocompleted = false selectedAction.parameters[count].autocompleted = false selectedActionParameters[count].value = event.target.value; selectedAction.parameters[count].value = event.target.value; - },100) var forceUpdate = false if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { @@ -3608,7 +3621,7 @@ const ParsedAction = (props) => { //selectedAction.parameters[count].value = selectedActionParameters[count].value; //setSelectedAction(selectedAction); //setUpdate(Math.random()); - + setShowDropdown(false); setMenuPosition(null); }; From 1a4b673bc8208baa6ee7e3bf2d6496b0bb8c2726 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 17 Jun 2024 17:00:23 +0530 Subject: [PATCH 036/336] Fixed the webhook associated app dropdown issue --- frontend/src/components/ParsedAction.jsx | 1 - frontend/src/views/AngularWorkflow.jsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 5761da74..9d8a4988 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -2204,7 +2204,6 @@ const ParsedAction = (props) => { } }); } - event.target.blur(); }} renderOption={(props, option, state) => { var newActionname = option.name; diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 65e7abff..f7d6227d 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -13829,6 +13829,7 @@ const releaseToConnectLabel = "Release to Connect" setUpdate(Math.random()); } + document.activeElement.blur(); }} >
    From db1138019f72ab0665cb232ac84062e9658fa670 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 17 Jun 2024 16:39:50 +0200 Subject: [PATCH 037/336] Minor angularworkflow fix --- frontend/src/views/AngularWorkflow.jsx | 47 +++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 021cc0d6..a2ef33d2 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2012,6 +2012,13 @@ const AngularWorkflow = (defaultprops) => { headers["Org-Id"] = useworkflow.org_id } + // Realtime makes the workflow if it doesn't exist + /* + if (duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) { + headers["Org-Id"] = duplicationOrg + } + */ + setLastSaved(true); fetch(`${globalUrl}/api/v1/workflows/${useworkflow.id}`, { method: "PUT", @@ -2033,7 +2040,8 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .then((responseJson) => { - if (duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) { + if (useworkflow.id === originalWorkflow.id && duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) { + //duplicateParentWorkflow(useworkflow, duplicationOrg, true) duplicateParentWorkflow(useworkflow, duplicationOrg, true) } @@ -3234,15 +3242,19 @@ const AngularWorkflow = (defaultprops) => { }; const getChildWorkflows = (parentWorkflowId) => { - if (workflow.suborg_distribution === undefined || workflow.suborg_distribution === null || workflow.suborg_distribution.length === 0) { + if (originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0) { + console.log("No suborg distribution") return } + const orgId = originalWorkflow.org_id === undefined || originalWorkflow.org_id === null || originalWorkflow.org_id === "" ? "" : originalWorkflow.org_id + fetch(`${globalUrl}/api/v1/workflows/${parentWorkflowId}/child_workflows`, { method: "GET", headers: { "Content-Type": "application/json", - Accept: "application/json", + "Accept": "application/json", + "Org-Id": orgId, }, credentials: "include", }) @@ -3325,6 +3337,10 @@ const AngularWorkflow = (defaultprops) => { getChildWorkflows(responseJson.id) } + if (responseJson.parentorg_workflow !== undefined && responseJson.parentorg_workflow !== null && responseJson.parentorg_workflow.length > 0) { + getChildWorkflows(responseJson.parentorg_workflow) + } + // Not sure why this is necessary. if (responseJson.isValid === undefined) { responseJson.isValid = true; @@ -8584,6 +8600,11 @@ const AngularWorkflow = (defaultprops) => { return; } + if (parsedApp === undefined || parsedApp === null || parsedApp.data === undefined || parsedApp.data === null) { + toast("Failed to add trigger. Please try again.") + return + } + // Using remove & replace, as this triggers the function // onNodeAdded() with this node after it's added @@ -13584,7 +13605,7 @@ const AngularWorkflow = (defaultprops) => { // Special SCHEDULE handler var trigger_header_auth = "" - if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined ) { + if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers !== null && workflow.triggers !== undefined && workflow.triggers.length >= selectedTriggerIndex && workflow.triggers[selectedTriggerIndex] !== undefined ) { if (selectedTrigger.trigger_type === "SCHEDULE" && workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null) { console.log("Autofixing schedule") @@ -15475,34 +15496,49 @@ const AngularWorkflow = (defaultprops) => { View Suborg workflow { borderRadius: theme.palette.borderRadius, }} onChange={(e) => { + console.log("SELECT ONCHANGE DONE") + if (selectedActionParameters[count].value[selectedActionParameters[count].value.length - 1] === ".") { e.target.value.autocomplete = e.target.value.autocomplete.slice(1,e.target.value.autocomplete.length); } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 75613a2b..bc863f68 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -4398,15 +4398,12 @@ const releaseToConnectLabel = "Release to Connect" //const data = JSON.parse(JSON.stringify(event.target.data())) const data = event.target.data() - console.log("===============================Node selected=============================") - console.log("NODE SELECT: ", data) - if (data.app_name === "Shuffle Workflow") { - console.log("Shuffle Workflow selected") - if ((data.parameters !== undefined) && (data.parameters.length > 0)) { - getWorkflowApps(data.parameters[0].value) + if (data.app_name === "Shuffle Workflow") { + if ((data.parameters !== undefined) && (data.parameters.length > 0)) { + getWorkflowApps(data.parameters[0].value) + } } - } if (data.buttonType == "ACTIONSUGGESTION") { const attachedToId = data.attachedTo @@ -6930,8 +6927,6 @@ const releaseToConnectLabel = "Release to Connect" const allNodes = cy.nodes().jsons(); if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") { - console.log("In this :)") - var found = false; for (let nodekey in allNodes) { const currentNode = allNodes[nodekey]; @@ -9928,7 +9923,6 @@ const releaseToConnectLabel = "Release to Connect" } } - console.log("New selected action: ", newSelectedAction) setSelectedAction(newSelectedAction) setUpdate(Math.random()) @@ -21328,15 +21322,12 @@ const releaseToConnectLabel = "Release to Connect" if (paramcheck !== undefined) { // Escapes all double quotes const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\""); - console.log("REPLACE WITH: ", toReplace) if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { paramcheck["value_replace"] = [{ "key": data.name, "value": toReplace, }] - console.log("IN IF: ", paramcheck) - } else { const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name) if (subparamindex === -1) { @@ -21347,8 +21338,6 @@ const releaseToConnectLabel = "Release to Connect" } else { paramcheck["value_replace"][subparamindex]["value"] = toReplace } - - console.log("IN ELSE: ", paramcheck) } if (paramcheck["value_replace"] === undefined) { From ed6ecc0bf4cab6f22c43155a115378dfa5315db7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 20 Jun 2024 14:57:40 +0200 Subject: [PATCH 041/336] Fixed a lot of billing-ui problems, and added rate limiting alerts --- frontend/src/components/Billing.jsx | 1919 +++++++++++++++++---------- frontend/src/views/Workflows.jsx | 2 +- 2 files changed, 1190 insertions(+), 731 deletions(-) diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index e52bdce7..e567a5c7 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -7,7 +7,7 @@ import countries from "../components/Countries.jsx"; import { Box, Paper, - Typography, + Typography, Divider, Button, Grid, @@ -19,94 +19,150 @@ import { DialogTitle, DialogContent, TextField, - InputAdornment, + InputAdornment, IconButton, - Chip, + Chip, Checkbox, - Tooltip, + Tooltip, + DialogContentText, + DialogActions, + LinearProgress } from "@mui/material"; -import { useNavigate, Link } from "react-router-dom"; +import { useNavigate, Link, json } from "react-router-dom"; import { Autocomplete } from "@mui/material"; -import { toast } from "react-toastify" +import { toast } from "react-toastify" import { - Cached as CachedIcon, - ContentCopy as ContentCopyIcon, - Draw as DrawIcon, - Close as CloseIcon, + Cached as CachedIcon, + ContentCopy as ContentCopyIcon, + Draw as DrawIcon, + Close as CloseIcon, + Delete, + RestaurantRounded, } from "@mui/icons-material"; //import { useAlert import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; import BillingStats from "../components/BillingStats.jsx"; import { handlePayasyougo } from "../views/HandlePaymentNew.jsx" +import DeleteIcon from '@mui/icons-material/Delete'; const Billing = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props; - //const alert = useAlert(); - let navigate = useNavigate(); + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props; + //const alert = useAlert(); + let navigate = useNavigate(); - const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false); - const [dealList, setDealList] = React.useState([]); - const [dealName, setDealName] = React.useState(""); - const [dealAddress, setDealAddress] = React.useState(""); - const [dealType, setDealType] = React.useState("MSSP"); - const [dealCountry, setDealCountry] = React.useState("United States"); - const [dealCurrency, setDealCurrency] = React.useState("USD"); - const [dealStatus, setDealStatus] = React.useState("initiated"); - const [dealValue, setDealValue] = React.useState(""); - const [dealDiscount, setDealDiscount] = React.useState(""); - const [dealerror, setDealerror] = React.useState(""); + const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false); + const [dealList, setDealList] = React.useState([]); + const [dealName, setDealName] = React.useState(""); + const [dealAddress, setDealAddress] = React.useState(""); + const [dealType, setDealType] = React.useState("MSSP"); + const [dealCountry, setDealCountry] = React.useState("United States"); + const [dealCurrency, setDealCurrency] = React.useState("USD"); + const [dealStatus, setDealStatus] = React.useState("initiated"); + const [dealValue, setDealValue] = React.useState(""); + const [dealDiscount, setDealDiscount] = React.useState(""); + const [dealerror, setDealerror] = React.useState(""); + const [openChangeEmailBox, setOpenChangeEmailBox] = useState(false); + const [isMouseOverOnChangeEmail, setIsMouseOverOnChangeEmail] = useState(false); + const [currentAppRunsInPercentage, setCurrentAppRunsInPercentage] = useState(0); + const [currentAppRunsInNumber, setCurrentAppRunsInNumber] = useState(0); + const [alertThresholds, setAlertThresholds] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.AlertThreshold !== undefined && selectedOrganization.Billing.AlertThreshold !== null ? selectedOrganization.Billing.AlertThreshold : [{ percentage: '', count: '', Email_send: false }]); + const [currentIndex, setCurrentIndex] = useState(0); + + useEffect(() => { + if (userdata.app_execution_limit !== undefined && userdata.app_execution_usage !== undefined) { + const percentage = (userdata.app_execution_usage / userdata.app_execution_limit) * 100; + setCurrentAppRunsInPercentage(Math.round(percentage)); + setCurrentAppRunsInNumber(userdata.app_execution_limit - userdata.app_execution_usage); + } + }, [userdata]); + + + const [BillingEmail, setBillingEmail] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.Email !== undefined && selectedOrganization.Billing.Email != null && selectedOrganization.Billing.Email.length > 0 ? selectedOrganization.Billing.Email : selectedOrganization.org); + + useState(() => { + // Set the billing email + setBillingEmail( + selectedOrganization.Billing !== undefined && + selectedOrganization.Billing.Email !== undefined && + selectedOrganization.Billing.Email.length > 0 + ? selectedOrganization.Billing.Email + : selectedOrganization.org + ); + + // Set and sort the alert thresholds + const alertThresholds = selectedOrganization.Billing !== undefined && + selectedOrganization.Billing.AlertThreshold !== undefined && + selectedOrganization.Billing.AlertThreshold !== null + ? selectedOrganization.Billing.AlertThreshold + : [{ percentage: '', count: '', Email_send: false }]; + + const sortedAlertThresholds = alertThresholds.sort((a, b) => { + const countA = parseFloat(a.count); + const countB = parseFloat(b.count); + if (isNaN(countA)) return 1; + if (isNaN(countB)) return -1; + + return countA - countB; + }); + + setAlertThresholds(sortedAlertThresholds); + + const findCurrentIndex = sortedAlertThresholds.some(threshold => threshold.Email_send === false); + setCurrentIndex(findCurrentIndex ? sortedAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1); + + }, [selectedOrganization]); const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : "" const products = [ - { code: "", label: "MSSP", phone: "" }, - { code: "", label: "Enterprise", phone: "" }, - { code: "", label: "Consultancy", phone: "" }, - { code: "", label: "Support", phone: "" }, - ]; + { code: "", label: "MSSP", phone: "" }, + { code: "", label: "Enterprise", phone: "" }, + { code: "", label: "Consultancy", phone: "" }, + { code: "", label: "Support", phone: "" }, + ]; const handleGetDeals = (orgId) => { - console.log("Get deals!"); + console.log("Get deals!"); - if (orgId.length === 0) { - toast( - "Organization ID not defined (get deals). Please contact us on https://shuffler.io if this persists logout." - ); - return; - } + if (orgId.length === 0) { + toast( + "Organization ID not defined (get deals). Please contact us on https://shuffler.io if this persists logout." + ); + return; + } - const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; - fetch(url, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => { - if (response.status !== 200) { - console.log("Bad status code in get deals: ", response.status); - } + const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Bad status code in get deals: ", response.status); + } - return response.json(); - }) - .then((responseJson) => { - console.log("Got deals: ", responseJson); - if (responseJson.success === false) { - toast("Failed loading deals. Contact support if this persists"); - } else { - setDealList(responseJson); - } - }) - .catch((error) => { - console.log("Error getting org deals: ", error); - toast( - "Failed getting deals for your org. Contact support if this persists." - ); - }); - }; + return response.json(); + }) + .then((responseJson) => { + console.log("Got deals: ", responseJson); + if (responseJson.success === false) { + toast("Failed loading deals. Contact support if this persists"); + } else { + setDealList(responseJson); + } + }) + .catch((error) => { + console.log("Error getting org deals: ", error); + toast( + "Failed getting deals for your org. Contact support if this persists." + ); + }); + }; useEffect(() => { if (isCloud && selectedOrganization.partner_info !== undefined && selectedOrganization.partner_info.reseller === true) { @@ -121,15 +177,15 @@ const Billing = (props) => { maxWidth: 400, width: "100%", backgroundColor: theme.palette.platformColor, - borderRadius: theme.palette.borderRadius*2, + borderRadius: theme.palette.borderRadius * 2, border: "1px solid rgba(255,255,255,0.3)", - marginRight: 10, + marginRight: 10, marginTop: 15, } - const isCloud = - window.location.host === "localhost:3002" || - window.location.host === "shuffler.io"; + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; billingInfo.subscription = { "active": true, @@ -161,82 +217,83 @@ const Billing = (props) => { var checkoutObject = { lineItems: [ { - price: priceItem, + price: priceItem, quantity: 1 }, ], mode: "subscription", billingAddressCollection: "auto", - successUrl: successUrl, - cancelUrl: failUrl, + successUrl: successUrl, + cancelUrl: failUrl, clientReferenceId: props.userdata.active_org.id, } //submitType: "donate", stripe.redirectToCheckout(checkoutObject) - .then(function (result) { - console.log("SUCCESS STRIPE?: ", result) + .then(function (result) { + console.log("SUCCESS STRIPE?: ", result) - ReactGA.event({ - category: "pricing", - action: "add_card_success", - label: "", + ReactGA.event({ + category: "pricing", + action: "add_card_success", + label: "", + }) }) - }) - .catch(function(error) { - console.error("STRIPE ERROR: ", error) + .catch(function (error) { + console.error("STRIPE ERROR: ", error) - ReactGA.event({ - category: "pricing", - action: "add_card_error", - label: "", - }) - }); + ReactGA.event({ + category: "pricing", + action: "add_card_error", + label: "", + }) + }); } const cancelSubscriptions = (subscription_id) => { - const orgId = selectedOrganization.id; - const data = { - subscription_id: subscription_id, - action: "cancel", - org_id: selectedOrganization.id, - }; + const orgId = selectedOrganization.id; + const data = { + subscription_id: subscription_id, + action: "cancel", + org_id: selectedOrganization.id, + }; - const url = globalUrl + `/api/v1/orgs/${orgId}/cancel`; - fetch(url, { - mode: "cors", - method: "POST", - body: JSON.stringify(data), - credentials: "include", - crossDomain: true, - withCredentials: true, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } - - if (handleGetOrg != undefined) { - handleGetOrg(selectedOrganization.id); + const url = globalUrl + `/api/v1/orgs/${orgId}/cancel`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); } - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success !== undefined && responseJson.success) { - toast("Successfully stopped subscription!"); - } else { - toast("Failed stopping subscription. Please contact us."); - } - }) - .catch(function (error) { - console.log("Error: ", error); - toast("Failed stopping subscription. Please contact us."); - }); - }; + if (handleGetOrg != undefined) { + handleGetOrg(selectedOrganization.id); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success !== undefined && responseJson.success) { + toast("Successfully stopped subscription!"); + } else { + toast("Failed stopping subscription. Please contact us."); + } + }) + .catch(function (error) { + console.log("Error: ", error); + toast("Failed stopping subscription. Please contact us."); + }); + }; + const sendSignatureRequest = (subscription) => { const url = `${globalUrl}/api/v1/orgs/${selectedOrganization.id}`; @@ -246,46 +303,47 @@ const Billing = (props) => { org_id: selectedOrganization.id, subscription: subscription, }), - mode: "cors", - method: "POST", - credentials: "include", - crossDomain: true, - withCredentials: true, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then((response) => { - if (response.status !== 200) { - console.log("Error in response"); - } - return response.json(); - }) - .then((responseJson) => { - console.log("Response from signature request: ", responseJson); - }) - .catch((error) => { - console.log("Error: ", error); + mode: "cors", + method: "POST", + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, }) + .then((response) => { + if (response.status !== 200) { + console.log("Error in response"); + } + return response.json(); + }) + .then((responseJson) => { + console.log("Response from signature request: ", responseJson); + }) + .catch((error) => { + console.log("Error: ", error); + }) } const SubscriptionObject = (props) => { - const { globalUrl, index, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, highlight, } = props; + const { globalUrl, index, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, highlight, } = props; - const [signatureOpen, setSignatureOpen] = React.useState(false); - const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed) + const [signatureOpen, setSignatureOpen] = React.useState(false); + const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed) const [hovered, setHovered] = React.useState(false) + const [newBillingEmail, setNewBillingEmail] = useState(''); var top_text = "Base Cloud Access" if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) { subscription.name = "Enterprise" subscription.currency_text = "$" - subscription.price = subscription.level*180 - subscription.limit = subscription.level*100000 + subscription.price = subscription.level * 180 + subscription.limit = subscription.level * 100000 subscription.interval = subscription.recurrence subscription.features = [ "Includes " + subscription.limit + " app runs/month. ", - "Multi-Tenancy and Region-Selection", + "Multi-Tenancy and Region-Selection", "And all other features from /pricing", ] } @@ -301,17 +359,17 @@ const Billing = (props) => { if (subscription.name.includes("default")) { top_text = "Custom Contract" newPaperstyle.border = "1px solid #f85a3e" - showSupport = true + showSupport = true } if (subscription.name.includes("App Run Units")) { top_text = "Cloud Access" - showSupport = true + showSupport = true } if (subscription.name.includes("Open Source")) { top_text = "Open Source" - showSupport = true + showSupport = true } if (subscription.name.includes("Scale")) { @@ -327,8 +385,80 @@ const Billing = (props) => { newPaperstyle.backgroundColor = theme.palette.surfaceColor } + const handleClickOpen = () => { + setOpenChangeEmailBox(true); + }; + + const handleCloseChangeEmailBox = () => { + setOpenChangeEmailBox(false); + }; + + + const getCircularReplacer = () => { + const seen = new WeakSet(); + return (key, value) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) { + return; + } + seen.add(value); + } + return value; + }; + }; + + const HandleChangeBillingEmail = (orgId) => { + const email = newBillingEmail; + const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/; + console.log("Pattern matches: ", emailPattern.test(email)); + if (!emailPattern.test(email)) { + toast("Please enter a valid email address"); + return; + } else { + setNewBillingEmail(email); + } + + toast("Updating billing email. Please Wait") + + const data = { + org_id: orgId, + email: newBillingEmail, + billing: { + email: newBillingEmail, + }, + }; + + const url = `${globalUrl}/api/v1/orgs/${orgId}/billing`; + fetch(url, { + method: "POST", + body: JSON.stringify(data), + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Bad status code in get org:", response.status); + } + return response.json(); + }).then((responseJson) => { + console.log("Got org:", responseJson); + if (responseJson.success === true) { + toast.success("Successfully updated billing email"); + setBillingEmail(newBillingEmail); + setOpenChangeEmailBox(false); + } else { + toast.error("Failed to update billing email. Please try again."); + } + }) + .catch((error) => { + console.log("Error getting org:", error); + }); + } + return ( - setHovered(true)} onMouseLeave={() => setHovered(false)} @@ -336,34 +466,34 @@ const Billing = (props) => { - { - e.preventDefault(); - setSignatureOpen(false); - setTosChecked(false) - }} - > - - + { + e.preventDefault(); + setSignatureOpen(false); + setTosChecked(false) + }} + > + + Read and Accept the EULA @@ -371,13 +501,13 @@ const Billing = (props) => { rows={17} multiline fullWidth - InputProps={{ - readOnly: true, - style: { - fontSize: 14, - color: "rgba(255, 255, 255, 0.6)", - } - }} + InputProps={{ + readOnly: true, + style: { + fontSize: 14, + color: "rgba(255, 255, 255, 0.6)", + } + }} value={subscription.eula} /> { }} inputProps={{ 'aria-label': 'primary checkbox' }} /> - { + { setTosChecked(!tosChecked) }}> Accept - + By clicking the “accept” button, you are signing the document, electronically agreeing that it has the same legal validity and effects as a handwritten signature, and that you have the competent authority to represent and sign on behalf an entity. Need support or have questions? Contact us at support@shuffler.io. -
    +
    -
    +
    {top_text === "Base Cloud Access" && userdata.has_card_available === true ? { @@ -436,80 +566,80 @@ const Billing = (props) => { }} variant="outlined" color="primary" - /> + /> : null} {top_text} {top_text === "Base Cloud Access" && userdata.has_card_available === false ? - - : null} + : null} {isCloud && highlight === true && top_text !== "Base Cloud Access" ? - { setSignatureOpen(true) }} > - + - : null} + : null}
    - -
    - - {subscription.name} - + +
    + + {subscription.name} + - {subscription.currency_text !== undefined ? -
    - - {subscription.currency_text}{subscription.price} - - - / {subscription.interval} - -
    + {subscription.currency_text !== undefined ? +
    + + {subscription.currency_text}{subscription.price} + + + / {subscription.interval} + +
    : null} - - Features - -
      + + Features + +
        {subscription.features !== undefined && subscription.features !== null ? subscription.features.map((feature, index) => { var parsedFeature = feature if (feature.includes("Documentation: ")) { - parsedFeature = - Documentation to get started } if (feature.includes("Worker License: ")) { - const fieldId = "webhook_uri_field_"+index + const fieldId = "webhook_uri_field_" + index parsedFeature = - + @@ -517,47 +647,47 @@ const Billing = (props) => { {}} - InputProps={{ - endAdornment: - - { - var copyText = document.getElementById(fieldId); - if (copyText !== undefined && copyText !== null) { - console.log("NAVIGATOR: ", navigator); - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast("Can only copy over HTTPS (port 3443)"); - return; - } + style={{ + backgroundColor: theme.palette.inputColor, + borderRadius: theme.palette.borderRadius, + }} + id={fieldId} + onClick={() => { }} + InputProps={{ + endAdornment: + + { + var copyText = document.getElementById(fieldId); + if (copyText !== undefined && copyText !== null) { + console.log("NAVIGATOR: ", navigator); + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } - navigator.clipboard.writeText(copyText.value); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999 - ); /* For mobile devices */ + navigator.clipboard.writeText(copyText.value); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ - /* Copy the text inside the text field */ - document.execCommand("copy"); - toast("Copied Webhook URL"); - } else { - console.log("Couldn't find webhook URI field: ", copyText); - } - }} - edge="end" - > - - - - }} + /* Copy the text inside the text field */ + document.execCommand("copy"); + toast("Copied Webhook URL"); + } else { + console.log("Couldn't find webhook URI field: ", copyText); + } + }} + edge="end" + > + + + + }} fullWidth /> @@ -565,30 +695,102 @@ const Billing = (props) => { return (
      • - + {parsedFeature}
      • ) }) : null} -
      -
    - {isCloud && (highlight === true && (subscription.name === "Pay as you go" && subscription.limit <= 10000) || subscription.name === "Open Source") ? - - - {subscription.name.includes("Scale") ? - "" - : + +
    + {isCloud && (highlight === true && (subscription.name === "Pay as you go" && subscription.limit <= 10000) || subscription.name === "Open Source") ? + + + {subscription.name.includes("Scale") ? + "" + : - userdata.has_card_available === true ? - "While you have a card attached to your account, Shuffle will no longer prevent workflows from running. Billing will occur at the start of each month." - : - `You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.` - } + userdata.has_card_available === true ? + "While you have a card attached to your account, Shuffle will no longer prevent workflows from running. Billing will occur at the start of each month." + : + `You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.` + } + +
    + + Billing email: {BillingEmail} - Billing email: {selectedOrganization.org} - {/*isCloud ? + {userdata.has_card_available === true && ( + + )} + + Change Billing Email + + + Enter the new billing email address. + + { if (event.key === 'Enter') HandleChangeBillingEmail(selectedOrganization.id) }} + onChange={(e) => setNewBillingEmail(e.target.value)} + /> + + + + + + +
    + {/*isCloud ? : null*/} - + {userdata.has_card_available === true ? + - {userdata.has_card_available === true ? - : null} - -
    + + : null} - {showSupport ? + {showSupport ? - : null } - + : null} + ) } const addDealModal = ( - { - setSelectedDealModalOpen(false); - }} - PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: "800px", - minHeight: "320px", - }, - }} - > - - Register new deal - - -
    - { - setDealName(e.target.value); - }} - /> - { - setDealAddress(e.target.value); - }} - /> -
    -
    - { - setDealValue(e.target.value); - }} - /> - option.label} - onChange={(event, newValue) => { - setDealCountry(newValue.label); - }} - renderOption={(props, option) => ( - img": { mr: 2, flexShrink: 0 } }} - {...props} - > - - {option.label} ({option.code}) +{option.phone} - - )} - renderInput={(params) => ( - - )} - /> - { - setDealType(newValue); - }} - getOptionLabel={(option) => option.label} - renderOption={(props, option) => ( - img": { mr: 2, flexShrink: 0 } }} - {...props} - > - {option.label} - - )} - renderInput={(params) => ( - - )} - /> -
    - {dealerror.length > 0 ? ( - - error registering: {dealerror} - - ) : null} -
    - - -
    -
    -
    - ); + //setDealName("") + //setDealAddress("") + //setDealCountry("") + //setDealValue("") + }} + > + Cancel + + +
    + + + ); - const submitDeal = (dealName, dealAddress, dealCountry, dealValue) => { - if (dealerror.length > 0) { - setDealerror(""); - } + const submitDeal = (dealName, dealAddress, dealCountry, dealValue) => { + if (dealerror.length > 0) { + setDealerror(""); + } - const orgId = selectedOrganization.id; - const data = { - reseller_org: orgId, - name: dealName, - address: dealAddress, - country: dealCountry, - value: dealValue, - }; + const orgId = selectedOrganization.id; + const data = { + reseller_org: orgId, + name: dealName, + address: dealAddress, + country: dealCountry, + value: dealValue, + }; - const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; - fetch(url, { - mode: "cors", - method: "POST", - body: JSON.stringify(data), - credentials: "include", - crossDomain: true, - withCredentials: true, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } + const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success === true) { - setSelectedDealModalOpen(false); - toast( - "Added new deal! We will be in touch shortly with an update." - ); + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + setSelectedDealModalOpen(false); + toast( + "Added new deal! We will be in touch shortly with an update." + ); + + setDealName(""); + setDealAddress(""); + setDealValue(""); + setDealCountry("United States"); + setDealType("MSSP"); + } else { + setDealerror(responseJson.reason); + } + }) + .catch(function (error) { + //console.log("Error: ", error); + setDealerror(error.toString()); + toast("Failed adding deal reg: ", error); + }); + }; + const addAlertThreshold = () => { + setAlertThresholds([...alertThresholds, { percentage: '', count: '', Email_send: false }]); + }; + + const updateAlertThreshold = (index, field, value) => { + + if (field === 'percentage') { + if (value > 100 || value < 0) { + value = 0 + toast("The percentage value should be between 0 and 100") + } + } else if (field === 'count') { + if (value < 0 || value >= userdata.app_execution_limit) { + value = 0 + toast("The count value should be greater than 0 and less than the total app execution limit") + } + } + + + const totalValue = userdata.app_execution_limit; + const newAlertThresholds = alertThresholds.map((threshold, i) => { + if (i === index) { + const newValue = parseFloat(value); + if (field === 'percentage') { + const newCount = (newValue / 100) * totalValue; + return { + ...threshold, + percentage: isNaN(newValue) ? '' : Math.round(newValue), + count: isNaN(newCount) ? '' : Math.round(newCount), + Email_send: false + }; + } else if (field === 'count') { + const newPercentage = (newValue / totalValue) * 100; + return { + ...threshold, + count: newValue, + percentage: isNaN(newPercentage) ? '' : Math.round(newPercentage), + Email_send: false + }; + } + } + return threshold; + }); + setAlertThresholds(newAlertThresholds); + }; + + + const handleDeleteAlertThreshold = (index) => { + const newAlertThresholds = alertThresholds.filter((_, i) => i !== index); + setAlertThresholds(newAlertThresholds); + + // Update currentIndex based on remaining elements + const findCurrentIndex = newAlertThresholds.some(threshold => threshold.Email_send === false); + setCurrentIndex(findCurrentIndex ? newAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1); + }; + + const HandleEditOrgForAlertThreshold = (orgId) => { + + // Use the `some` method to check for invalid counts + const invalidCount = alertThresholds.some((threshold) => { + if (threshold.count === '') { + toast("Please enter a valid Count or Percentage value"); + return true; // Stop checking further and return true if invalid + } + return false; + }); + + // If any invalid count is found, return early + if (invalidCount) { + return; + } + + toast("Updating Email Alert Threshold. Please wait..."); + + const data = { + org_id: orgId, + billing: { + email: BillingEmail, + AlertThreshold: alertThresholds.map(threshold => ({ + ...threshold, + percentage: parseInt(threshold.percentage, 10), + count: parseInt(threshold.count, 10), + })), + }, + }; + + const url = `${globalUrl}/api/v1/orgs/${orgId}`; + fetch(url, { + method: "POST", + body: JSON.stringify(data), + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Bad status code in get org:", response.status); + } + return response.json(); + }).then((responseJson) => { + console.log("Got org:", responseJson); + if (responseJson.success === true) { + toast.success("Successfully updated Email Alert Thresholds"); + const findCurrentIndex = alertThresholds.some(threshold => threshold.Email_send === false); + setCurrentIndex(findCurrentIndex ? alertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1); + } else { + toast.error("Failed to update Email Alert Thresholds. Please try again."); + } + }) + .catch((error) => { + console.log("Error getting org:", error); + }); + }; + + const getSafeValue = (value) => { + + if (value === undefined || value === null || isNaN(value)) { + return 0; + } else { + return value; + } + }; - setDealName(""); - setDealAddress(""); - setDealValue(""); - setDealCountry("United States"); - setDealType("MSSP"); - } else { - setDealerror(responseJson.reason); - } - }) - .catch(function (error) { - //console.log("Error: ", error); - setDealerror(error.toString()); - toast("Failed adding deal reg: ", error); - }); - }; const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null return ( -
    - {addDealModal} - {clickedFromOrgTab? -

    Billing & Licensing

    : - - Billing & Licensing - } - {clickedFromOrgTab? - {isCloud ? - "Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below." - : - "Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update." - }: - - {isCloud ? +
    + {addDealModal} + {clickedFromOrgTab ? +

    Billing & Licensing

    : + + Billing & Licensing + } + {clickedFromOrgTab ? + {isCloud ? "Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below." : "Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update." - } - } + } : + + {isCloud ? + "Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below." + : + "Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update." + } + } - {userdata.support === true ? -
    + {userdata.support === true ? +
    For sales: Create  New Cloud Contract @@ -1001,7 +1327,7 @@ const Billing = (props) => { New Onprem Contract -   -   +   -   Google Drive Link @@ -1009,22 +1335,20 @@ const Billing = (props) => { Sales Process - -
    : null } - {isChildOrg ? - - Billing is handled by your parent organisation. Reach out to support@shuffler.io if you have questions about this. - + {isChildOrg ? + + Billing is handled by your parent organisation. Reach out to support@shuffler.io if you have questions about this. + : null} -
    - {isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null : +
    + {isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null : { subscription={billingInfo.subscription} highlight={selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0} /> - : !isCloud ? - - - - - : null} + : !isCloud ? + + + + + : null} {isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 && - !isChildOrg ? - selectedOrganization.subscriptions - .reverse() - .map((sub, index) => { - return ( - - ) - }) - : null} - {/* + !isChildOrg ? + selectedOrganization.subscriptions + .reverse() + .map((sub, index) => { + return ( + + ) + }) + : null} + {/* { */} -
    +
    - {/*isCloud && + {/*isCloud && selectedOrganization.partner_info !== undefined && selectedOrganization.partner_info.reseller === true ? (
    @@ -1333,21 +1657,156 @@ const Billing = (props) => {
    ) : null*/} -
    +
    - Utilization & Stats + Manage Billing -
    - + Manage your billing and licensing information below. When you reach the certain thresholds of your subscription limit, you will be notified by email. + + Current Usage: + + + You have used {currentAppRunsInPercentage}% of total app execution limit or {userdata.app_execution_usage} app runs out of {userdata.app_execution_limit} app runs. + + +
    + + Set email alert thresholds for app runs + + + You will be notified by email when you reach the + {currentIndex !== -1 + ? " " + getSafeValue(alertThresholds[currentIndex].percentage) + '%' + " " + : " " + '0%' + " " + } + of your total app execution limit or + {currentIndex !== -1 + ? " " + getSafeValue(alertThresholds[currentIndex].count) + " " + : " " + 0 + " "} + app runs. + + +
    + {alertThresholds.map((threshold, index) => ( +
    + updateAlertThreshold(index, 'percentage', e.target.value)} + margin="normal" + variant="outlined" + inputProps={{ + max: 100, + }} + /> + updateAlertThreshold(index, 'count', e.target.value)} + margin="normal" + variant="outlined" + /> + { + alertThresholds.length > 1 && + ( + + ) + } +
    + ))} +
    +
    + + + +
    +
    + + Utilization & Stats + +
    + + />
    ) } diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 299e0c2d..ffdf0ec4 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -2153,7 +2153,7 @@ const Workflows = (props) => { } return ( -
    +
    {selectedCategory !== "" ? From e395832fc479e05875a4edf32ece78dae3249818 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 20 Jun 2024 20:44:50 +0530 Subject: [PATCH 042/336] Fixed the issue of merging the renderingIssue branch --- README.md | 2 + frontend/src/components/AppGrid.jsx | 4 - frontend/src/components/ParsedAction.jsx | 3283 +++++++++++----------- frontend/src/components/WorkflowGrid.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 1821 ++++++------ 5 files changed, 2468 insertions(+), 2644 deletions(-) diff --git a/README.md b/README.md index 3e22c1d4..c1a214e2 100755 --- a/README.md +++ b/README.md @@ -11,11 +11,13 @@ Shuffle Automation [Shuffle](https://shuffler.io) is an automation platform for and by the community, focusing on accessibility for anyone to automate. Security operations is complex, but it doesn't have to be. +[ Get training ](https://shuffler.io/training) [_Key Features_](https://shuffler.io/docs/features) — [_Community & Support_](https://discord.gg/B2CBzUm) — [_Documentation_](https://shuffler.io/docs) — [_Getting Started_](https://shuffler.io/docs/getting_started) — [_Development_](https://github.com/shuffle/Shuffle/blob/master/.github/CONTRIBUTING.md) +[ Set up a demo call ](https://shuffler.io/contact) Follow us on Twitter at [@shuffleio](https://twitter.com/shuffleio). diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 11e25f0b..b6133cbf 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -5,7 +5,6 @@ import ReactGA from "react-ga4"; import { Link } from "react-router-dom"; import { removeQuery } from "../components/ScrollToTop.jsx"; import { useMemo } from "react"; - import { Tabs, Tab, Collapse } from "@mui/material"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; @@ -33,10 +32,8 @@ import { ClearRefinements, connectStateResults } from "react-instantsearch-dom"; - import aa from "search-insights"; import { useLocation } from 'react-router-dom'; - import "./FilterCSS.css"; import { @@ -158,7 +155,6 @@ const AppGrid = (props) => { const handleSearch = () => { refine(searchQuery.trim()); }; - return (
    { } = props; const classes = useStyles(); - const [hideBody, setHideBody] = React.useState(true); - const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false); - + const [hideBody, setHideBody] = React.useState(true) + const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false) + const [appActionName, setAppActionName] = React.useState(selectedAction.label); + const [delay, setDelay] = React.useState(selectedAction?.execution_delay || 0); + const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); - const [autoCompleting, setAutocompleting] = React.useState(false); - + const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []); + const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); + const [paramValues, setParamValues] = React.useState( + selectedAction?.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + }) + ); + const [actionlist, setActionlist] = React.useState([]); + const [jsonList, setJsonList] = React.useState([]); + const [showDropdown, setShowDropdown] = React.useState(false); + const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); + const [showAutocomplete, setShowAutocomplete] = React.useState(false); + const [menuPosition, setMenuPosition] = useState(null); const isIntegration = selectedAction.app_id === "integration" useEffect(() => { @@ -190,12 +206,22 @@ const ParsedAction = (props) => { setLastSaved(false) } }, [expansionModalOpen]) + useEffect(() => { + setParamValues(selectedAction.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + })) + },[ + selectedAction, selectedApp,setNewSelectedAction, workflow, + ]) useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { return } - + const paramcheck = selectedAction.parameters.find(param => param.name === "body") if (paramcheck === undefined || paramcheck === null) { return @@ -206,12 +232,11 @@ const ParsedAction = (props) => { setHideBody(true) } else { setHideBody(false) - + if (paramcheck.id === "UNTOGGLED") { setActivateHidingBodyButton(false) } } - }, []) const keywords = [ @@ -372,252 +397,184 @@ const ParsedAction = (props) => { //setStartNode(selectedAction.id) }; - const AppActionArguments = (props) => { - const [selectedActionParameters, setSelectedActionParameters] = React.useState([]); - const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); - const [actionlist, setActionlist] = React.useState([]); - const [jsonList, setJsonList] = React.useState([]); - const [showDropdown, setShowDropdown] = React.useState(false); - const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); - const [showAutocomplete, setShowAutocomplete] = React.useState(false); - const [menuPosition, setMenuPosition] = useState(null); - - useEffect(() => { - if (selectedActionParameters !== undefined && selectedActionParameters !== null && selectedActionParameters.length === 0 - ) { - if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { - setSelectedActionParameters(selectedAction.parameters); - } - } - - if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { - - // FIXME - this is the bad thing - setSelectedVariableParameter(workflow.workflow_variables[0].name); - } - - if (actionlist.length === 0) { - // FIXME: Have previous execution values in here - if (workflowExecutions.length > 0) { - for (let [key,keyval] in Object.entries(workflowExecutions)) { - if ( - workflowExecutions[key].execution_argument === undefined || - workflowExecutions[key].execution_argument === null || - workflowExecutions[key].execution_argument.length === 0 - ) { - continue; - } - - const valid = validateJson(workflowExecutions[key].execution_argument) - if (valid.valid) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: valid.result, - }) - break - } - } + useEffect( + () => { + + // Only set app action name if it has changed + if (selectedAction.label !== appActionName) { + setAppActionName(selectedAction.label); } - - if (actionlist.length === 0) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: "", - }) + + // Only set delay if it has changed + const newDelay = selectedAction?.execution_delay || 0; + if (newDelay !== delay) { + setDelay(newDelay); } + + // Only set selected action parameters if they have changed + if (selectedAction.parameters && selectedAction.parameters.length > 0) { + setSelectedActionParameters(selectedAction.parameters); + } + + // Only set selected variable parameter if it is null or undefined + if (!selectedVariableParameter && workflow.workflow_variables?.length > 0) { + setSelectedVariableParameter(workflow.workflow_variables[0].name); + } + + + }, + [selectedAction,selectedApp,setNewSelectedAction,workflow, workflowExecutions, getParents] + ); - /* - actionlist.push({ - type: "Shuffle DB", - name: "Shuffle DB", - value: "$shuffle_cache", - highlight: "shuffle_cache", - autocomplete: "shuffle_cache", - example: { - "what": "", - "unique gmail ids new": "", - }, - }) - */ + useEffect(() => { + const newActionList = []; - var cachekey = { - type: "Shuffle DB", - name: "Shuffle DB", - value: "$shuffle_cache", - highlight: "shuffle_cache", - autocomplete: "shuffle_cache", - example: "", + // Process workflowExecutions + if (workflowExecutions.length > 0) { + for (let execution of workflowExecutions) { + const execArg = execution.execution_argument; + if (execArg && execArg.length > 0) { + const valid = validateJson(execArg); + if (valid.valid) { + newActionList.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: valid.result, + }); + break; + } + } + } } - if (listCache !== undefined && listCache !== null && listCache.keys !== undefined && listCache.keys !== null && listCache.keys.length > 0) { - cachekey.example = {} - - for (var i in listCache.keys) { - const item = listCache.keys[i] - if (item.key === undefined || item.key === null || item.key.length === 0) { - continue - } - - var itemvalue = item.value === undefined || item.value === null ? "" : item.value - try{ - if (itemvalue.length > 10000) { - itemvalue = "" - } - - } catch (e) { - itemvalue = "" - } - - var itemkey = item.key.split(" ").join("_") - cachekey.example[itemkey] = { - "value": itemvalue, - } - } - } else { - } - - actionlist.push(cachekey) - - if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) { - for (let [key,keyval] in Object.entries(workflow.workflow_variables)) { - const item = workflow.workflow_variables[key]; - actionlist.push({ - type: "workflow_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: item.value, + // Add default Execution Argument if none were added + if (newActionList.length === 0) { + newActionList.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: "", }); - } + + let cacheKey = { + type: "Shuffle DB", + name: "Shuffle DB", + value: "$shuffle_cache", + highlight: "shuffle_cache", + autocomplete: "shuffle_cache", + example: "", + }; + + if (listCache?.keys?.length > 0) { + cacheKey.example = {}; + for (let item of listCache.keys) { + if (item.key) { + let itemValue = item.value ?? ""; + if (itemValue.length > 10000) { + itemValue = ""; + } + cacheKey.example[item.key.split(" ").join("_")] = { value: itemValue }; + } + } + } + + newActionList.push(cacheKey); } - if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) { - for (let [key,keyval] in Object.entries(workflow.execution_variables)) { - const item = workflow.execution_variables[key] - - var exampleoutput = "" - for (let execkey in workflowExecutions) { - const exec = workflowExecutions[execkey] - if (exec["execution_variables"] === undefined || exec["execution_variables"] === null) { - continue - } - - const foundExec = exec.execution_variables.find((exvar) => exvar.name === item.name) - if (!foundExec) { - continue - } - - if (foundExec.value !== undefined && foundExec.value !== null && foundExec.value.length > 0) { - exampleoutput = foundExec.value - break - } - } - - actionlist.push({ - type: "execution_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: exampleoutput, - }); - } + // Process workflow variables + if (workflow.workflow_variables?.length > 0) { + for (let variable of workflow.workflow_variables) { + newActionList.push({ + type: "workflow_variable", + name: variable.name, + value: variable.value, + id: variable.id, + autocomplete: variable.name.split(" ").join("_"), + example: variable.value, + }); + } } - // Loops parent nodes' old results to fix autocomplete - if (getParents !== undefined) { - var parents = getParents(selectedAction) + // Process execution variables + if (workflow.execution_variables?.length > 0) { + for (let variable of workflow.execution_variables) { + let exampleOutput = ""; + for (let exec of workflowExecutions) { + const foundExec = exec.execution_variables?.find(exvar => exvar.name === variable.name); + if (foundExec?.value) { + exampleOutput = foundExec.value; + break; + } + } + newActionList.push({ + type: "execution_variable", + name: variable.name, + value: variable.value, + id: variable.id, + autocomplete: variable.name.split(" ").join("_"), + example: exampleOutput, + }); + } + } - if (parents.length > 1) { - var labels = [] - //for (let [parentkey, parentkeyval] in Object.entries(parents)) { - for (let parentkey in parents) { - const parentNode = parents[parentkey] - if (parentNode.label === "Execution Argument") { - continue - } + // Process parent actions if getParents is provided + if (getParents) { + const parents = getParents(selectedAction); + if (parents.length > 1) { + const labels = []; + for (let parentNode of parents) { + if (parentNode.label !== "Execution Argument" && !labels.includes(parentNode.label)) { + labels.push(parentNode.label); + let exampleData = parentNode.example ?? ""; + if (!exampleData && workflowExecutions.length > 0) { + for (let exec of workflowExecutions) { + const foundResult = exec.results?.find(result => result.action.id === parentNode.id); + if (foundResult) { + const valid = validateJson(foundResult.result); + if (valid.valid && valid.result.success !== false) { + exampleData = valid.result; + break; + } + } + } + } + newActionList.push({ + type: "action", + id: parentNode.id, + name: parentNode.label, + autocomplete: parentNode.label.split(" ").join("_"), + example: exampleData, + }); + } + } + } + } - //if (labels.includes(item.label)) { - // continue - //} - - labels.push(parentNode.label) - - var exampledata = parentNode.example === undefined || parentNode.example === null ? "" : parentNode.example - // Find previous execution and their variables - //exampledata === "" && - if (workflowExecutions.length > 0) { - // Look for the ID - const found = false; - for (let wfkey in workflowExecutions) { - if (workflowExecutions[wfkey].results === undefined || workflowExecutions[wfkey].results === null) { - - continue; - } - - var foundResult = workflowExecutions[wfkey].results.find((result) => result.action.id === parentNode.id) - - if (foundResult === undefined || foundResult === null) { - continue - } - - if (foundResult.result !== undefined && foundResult.result !== null) { - foundResult = foundResult.result - } - - const valid = validateJson(foundResult) - if (valid.valid) { - if (valid.result.success === false) { - //console.log("Skipping success false autocomplete") - } else { - - // FIXME: Have a merge system to allow to use kind of any key from that node in the last 10-20 execs - //if (exampledata.length > 0) { - // exampledata = valid.result - //} else { - // exampledata = valid.result - //} - - exampledata = valid.result - break - } - } else { - exampledata = foundResult - } - } - } - - // 1. Take - const itemlabelComplete = parentNode.label === null || parentNode.label === undefined ? "" : parentNode.label.split(" ").join("_"); - - const actionvalue = { - type: "action", - id: parentNode.id, - name: parentNode.label, - autocomplete: itemlabelComplete, - example: exampledata, - } - - actionlist.push(actionvalue) - } - } - - setActionlist(actionlist); + // Update the actionlist state + setActionlist(newActionList); + }, [workflow.execution_variables, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents]); + + useEffect(() => { + selectedNameChange(appActionName) + actionDelayChange(delay) + },[appActionName,delay]) + + const handleParamChange = (event, count,data) => { + const newParams = [...paramValues]; + newParams.map((param) => { + if (param.name === data.name) { + param.value = event.target.value; + } + }) + setParamValues(newParams); + changeActionParameter(event, count, data) } - } - }); - - const calculateHelpertext = (input_data) => { var helperText = "" var looperText = "" @@ -843,9 +800,9 @@ const ParsedAction = (props) => { //console.log("CHANGING ACTION COUNT !") selectedActionParameters[count].autocompleted = false - selectedAction.parameters[count].autocompleted = false - selectedActionParameters[count].value = event.target.value; - selectedAction.parameters[count].value = event.target.value; + selectedAction.parameters[count].autocompleted = false + selectedActionParameters[count].value = event.target.value; + selectedAction.parameters[count].value = event.target.value; var forceUpdate = false if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { @@ -1185,7 +1142,7 @@ const ParsedAction = (props) => { } // FIXME: Issue #40 - selectedActionParameters not reset - if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { + if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { var wrapperapp = { "id": "", @@ -1212,13 +1169,1209 @@ const ParsedAction = (props) => { var authWritten = false; var noAppSelected = false - const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") + var paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") if (paramIndex === -1 || selectedAction.parameters[paramIndex].value === "" || selectedAction.parameters[paramIndex].value === "noapp") { // Check the actual value and if it's the same noAppSelected = true } - return ( -
    + } + + + const ActionSelectOption = (actionprops) => { + const { option, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; + const [hover, setHover] = React.useState(false); + + return ( + +
    setHover(true)} onMouseLeave={() => setHover(false)} + onClick={(event) => { + // event.preventDefault() + //setSelectedAction(actionprops) + //setShowActionList(false) + //setUpdate(Math.random()) + // + if (option !== undefined && option !== null) { + setNewSelectedAction({ + target: { + value: option.name + } + }); + } + document.activeElement.blur(); + }} + > +
    + + {useIcon} + + {newActionname} +
    + {extraDescription.length > 0 ? + + {extraDescription} + + : null} +
    +
    + ) + } + + const sortByCategoryLabel = (a, b) => { + const aHasCategoryLabel = a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0 + const bHasCategoryLabel = b.category_label !== undefined && b.category_label !== null && b.category_label.length > 0 + + // Sort by existence and length of "category_label" + if (aHasCategoryLabel && !bHasCategoryLabel) { + return -1 + } else if (!aHasCategoryLabel && bHasCategoryLabel) { + return 1 + } else { + return 0 + } + } + + // Function to deduplicate based on the "name" field + const deduplicateByName = (array) => { + const uniqueNames = {}; + return array.filter(item => { + if (!item.hasOwnProperty('name') || !item.name.length) { + return true + } + if (!uniqueNames[item.name]) { + uniqueNames[item.name] = true + return true + } + return false + }) + } + + // Gets the most important actions first + const renderedActionOptions = deduplicateByName(( + selectedApp.actions === undefined || selectedApp.actions === null ? [] : + selectedApp.actions.filter((a) => + a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label")) + ).sort(sortByCategoryLabel)) + + + const selectedAppIcon = selectedAction.large_image + var baselabel = selectedAction.label + return ( +
    + + {hideExtraTypes === true ? null : ( + +
    +
    +
    { + //window.open("/apps/${selectedAction.app_id}", "_blank") + }} + > + + + +

    + {( + selectedAction.app_name.charAt(0).toUpperCase() + + selectedAction.app_name.substring(1) + ).replaceAll("_", " ")} +

    +
    +
    + { + if (workflowExecutions.length > 0) { + // Look for the ID + var found = false; + for (let [key,keyval] in Object.entries(workflowExecutions)) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue; + } + + var foundResult = workflowExecutions[key].results.find( + (result) => result.action.id === selectedAction.id + ) + + if (foundResult === undefined || foundResult === null) { + continue; + } + + const oldstartnode = cy.getElementById(selectedAction.id); + if (oldstartnode !== undefined && oldstartnode !== null) { + const foundname = oldstartnode.data("label") + if (foundname !== undefined && foundname !== null) { + foundResult.action.label = foundname + } + } + + setSelectedResult(foundResult); + if (setCodeModalOpen !== undefined) { + setCodeModalOpen(true); + + found = true + } + + break; + } + + if (!found) { + toast("No result for this action yet. Please run the workflow first.") + } + } + }} + > + + + + + { + setAuthenticationModalOpen(true) + }} + > + + + + + {/* + {}} + > + + + + + + + */} + {/* + { + //setAuthenticationModalOpen(true); + console.log("Should enable/disable magic!") + console.log("Action: ", selectedAction) + if (selectedAction.run_magic_output === undefined) { + selectedAction.run_magic_output = true + } else { + if (selectedAction.run_magic_output === true) { + selectedAction.run_magic_output = false + } else { + selectedAction.run_magic_output = true + } + } + + setSelectedAction(selectedAction) + setUpdate(Math.random()); + }} + > + + + + + */} + {/* + { + }} + > + + + + + + + */} + { + //if (setAiQueryModalOpen !== undefined) { + // setAiQueryModalOpen(true) + //} else { + aiSubmit("Fill based on previous values", undefined, undefined, selectedAction) + //} + setAutocompleting(true) + }} + > + + {autoCompleting ? + + : + + } + + +
    +
    +
    + {/*selectedAction.id === workflow.start ? null : + + + + + */} + {selectedApp.versions !== null && + selectedApp.versions !== undefined && + selectedApp.versions.length > 1 ? ( + + ) : null} +
    +
    +
    +
    + Name + { + let newValue = event.target.value + newValue = newValue.replaceAll(" ", "_") + setAppActionName(newValue) + } + } + onBlur={(e) => { + // Copy the name value + const name = appActionName + const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") + const newname = "$"+name.toLowerCase().replaceAll(" ", "_") + + // Check if it's the same as the current name in use + //if (name === selectedAction.label) { + // console.log("Returning from name thing") + // return + //} + + // Change in actions, triggers & conditions + // Highlight the changes somehow with a glow? + if (workflow.branches !== undefined && workflow.branches !== null) { + for (let [key,keyval] in Object.entries(workflow.branches)) { + if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { + for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { + const condition = workflow.branches[key].conditions[subkey] + const sourceparam = condition.source + const destinationparam = condition.destination + + // Should have a smarter way of discovering node names + // Finding index(es) and replacing at the location + if (sourceparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) + const extralength = newname.length-parsedBaseLabel.length + sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].source.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + + if (destinationparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) + const extralength = newname.length-parsedBaseLabel.length + destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + } + } + } + + for (let [key,keyval] in Object.entries(workflow.actions)) { + if (workflow.actions[key].id === selectedAction.id) { + continue + } + + const params = workflow.actions[key].parameters + console.log(params) + if (params === null || params === undefined) { + continue + } + + for (let [subkey, subkeyval] in Object.entries(params)) { + const param = workflow.actions[key].parameters[subkey]; + if (!param.value.includes("$")) { + continue + } + + // Should have a smarter way of discovering node names + // Do regex? + // Finding index(es) and replacing at the location + // + + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (param.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = param.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.actions[key].parameters[subkey].value) + const extralength = newname.length-parsedBaseLabel.length + param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) + + console.log("New: ", workflow.actions[key].parameters[subkey].value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + + setWorkflow(workflow); + setUpdate(Math.random()); + baselabel = name + }} + /> +
    + {/*!isCloud ? null :*/} +
    + + + Delay + { + setDelay(event.target.value) + }} + /> + + +
    + {/**/} +
    +
    + )} + {selectedApp.name !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication !== undefined && + selectedAction.authentication.length === 0 && + requiresAuthentication ? ( +
    + + + + + +
    + ) : null} + + {selectedAction.authentication !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication.length > 0 ? ( +
    + Authentication +
    + + + {/* + + + curaction.authentication = authenticationOptions + if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") + */} + + { + setAuthenticationModalOpen(true); + }} + > + + + +
    +
    + ) : null} + + {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( +
    + Environment + +
    + ) : null} + {workflow.execution_variables !== undefined && + workflow.execution_variables !== null && + workflow.execution_variables.length > 0 ? ( +
    + Execution variable (optional) + +
    + ) : null} + + +
    + {/*hideExtraTypes ? null : +
    + Actions +
    + */} + + {setNewSelectedAction !== undefined ? ( + { + // Most popular + // Is categorized + // Uncategorized + return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions"; + }} + renderGroup={(params) => { + + return ( +
  • + {params.group} + {params.children} +
  • + ) + }} + options={renderedActionOptions} + ListboxProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + }, + }} + filterOptions={(options, { inputValue }) => { + //console.log("Option contains?: ", inputValue, options) + const lowercaseValue = inputValue.toLowerCase() + options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) + + return options + }} + getOptionLabel={(option) => { + if (option === undefined || option === null || option.name === undefined || option.name === null ) { + return null; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + + return newname; + }} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + // Workaround with event lol + if (newValue !== undefined && newValue !== null) { + setNewSelectedAction({ + target: { + value: newValue.name + } + }); + } + }} + renderOption={(props, option, state) => { + var newActionname = option.name; + if (option.label !== undefined && option.label !== null && option.label.length > 0) { + newActionname = option.label; + } + + var newActiondescription = option.description; + //console.log("DESC: ", newActiondescription) + if (option.description === undefined || option.description === null) { + newActiondescription = "Description: No description defined for this action" + } else { + newActiondescription = "Description: "+newActiondescription + } + + const iconInfo = GetIconInfo({ name: option.name }); + const useIcon = iconInfo.originalIcon; + + if (newActionname === undefined || newActionname === null) { + newActionname = "No name" + option.name = "No name" + option.label = "No name" + } + + newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); + + var method = "" + var extraDescription = "" + if (option.name.includes("get_")) { + method = "GET" + } else if (option.name.includes("post_")) { + method = "POST" + } else if (option.name.includes("put_")) { + method = "PUT" + } else if (option.name.includes("patch_")) { + method = "PATCH" + } else if (option.name.includes("delete_")) { + method = "DELETE" + } else if (option.name.includes("options_")) { + method = "OPTIONS" + } else if (option.name.includes("connect_")) { + method = "CONNECT" + } + + // FIXME: Should it require a base URL? + if (method.length > 0 && option.description !== undefined && option.description !== null && option.description.includes("http")) { + var extraUrl = "" + const descSplit = option.description.split("\n") + // Last line of descSplit + if (descSplit.length > 0) { + extraUrl = descSplit[descSplit.length-1] + } + + //for (let [line,lineval] in Object.entries(descSplit)) { + // if (descSplit[line].includes("http") && descSplit[line].includes("://")) { + // const urlsplit = descSplit[line].split("/") + // try { + // extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") + // } catch (e) { + // //console.log("Failed - running with -1") + // extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") + // } + + + // //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) + // //break + // } + //} + + if (extraUrl.length > 0) { + if (extraUrl.includes(" ")) { + extraUrl = extraUrl.split(" ")[0] + } + + if (extraUrl.includes("#")) { + extraUrl = extraUrl.split("#")[0] + } + + extraDescription = `${method} ${extraUrl}` + } else { + //console.log("No url found. Check again :)") + } + } + + return ( + + ); + }} + renderInput={(params) => { + if (params.inputProps?.value) { + const prefixes = ["Post", "Put", "Patch"]; + for (let prefix of prefixes) { + if (params.inputProps.value.startsWith(prefix)) { + let newValue = params.inputProps.value.replace(prefix + " ", ""); + if (newValue.length > 1) { + newValue = newValue.charAt(0).toUpperCase() + newValue.substring(1); + } + // Set the new value without mutating inputProps + params = { ...params, inputProps: { ...params.inputProps, value: newValue } }; + break; + } + } + // Check if it starts with "Get List" and method is "Get" + if (params.inputProps.value.startsWith("Get List")) { + console.log("Get List"); + } + } + + return ( + + ); + }} + /> + ) : null} + + {/*setNewSelectedAction !== undefined ? + + : null*/} +
    { + Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0 ? +
    {isIntegration ? apps !== undefined && apps !== null && apps.length > 0 ?
    @@ -1589,9 +2742,9 @@ const ParsedAction = (props) => { placeholder = data.example; - if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length === 0) { - data.value = data.example; - } + // if (data.name === "url") { + // data.value = data.example; + // } // In case of data.example if (data.value === undefined || data.value === null) { data.value = "" @@ -1671,186 +2824,181 @@ const ParsedAction = (props) => { //setSelectedActionParameters(selectedActionParameters) } - var hideBodyButton = ""; - const hideBodyButtonValue = ( -
    - - { - var tag = "TOGGLED" - if (hideBody) { - tag = "UNTOGGLED" - } + var hideBodyButton = ""; + const hideBodyButtonValue = ( +
    + + { + const newHideBody = !hideBody; + setHideBody(newHideBody) + + const updatedParameters = selectedActionParameters.map((param) => { + if (param.name === "body") { + return { ...param, id: newHideBody ? "UNTOGGLED" : "TOGGLED" }; + } + if (param.description === openApiFieldDesc) { + return { ...param, field_active: newHideBody }; + } + return param; + }); + + setSelectedActionParameters(updatedParameters); + + /* + setTimeout(() => { + const element = document.getElementById("hide_body_button"); + if (element) { + element.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + }, 100); + */ + }} + name="requires_unique" + /> + } + label={hideBody ? "Show Body" : "Hide Body"} + /> + +
    + ); - setHideBody(!hideBody) - for (let paramkey in Object.entries(selectedActionParameters)) { - var currentItem = selectedActionParameters[paramkey]; - if (currentItem.name === "ssl_verify") { + if (selectedApp.generated && data.name === "body") { + const regex = /\${(\w+)}/g; + const found = placeholder.match(regex); - } + hideBodyButton = hideBodyButtonValue; + if (found === null || !hideBody) { + if (found === null) { - if (currentItem.name === "body") { - currentItem.id = tag - } + if (activateHidingBodyButton !== true) { + setActivateHidingBodyButton(true) + } - if (currentItem.description === openApiFieldDesc) { - currentItem.field_active = !hideBody - } - } - - - // Scroll to hide_body_button - setTimeout(() => { - var element = document.getElementById("hide_body_button") - if (element !== undefined && element !== null) { - // Keep the button a little below the top - element.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }, 100) - + } else { + //console.log("In found: ", found, hideBody) + } + } else { - }} - name="requires_unique" - /> - } - label={hideBody ? "Show Body" : "Hide Body"} - /> -
    -
    - ) + rows = "1"; + disabled = true; + openApiHelperText = "OpenAPI spec: fill the following fields."; - if (selectedApp.generated && data.name === "body") { - const regex = /\${(\w+)}/g; - const found = placeholder.match(regex); + var changed = false; + var tempArray = [] + for (let specKey in found) { + const tmpitem = found[specKey]; + var skip = false; - // setActivateHidingBodyButton(false) - // - hideBodyButton = hideBodyButtonValue; - if (found === null || !hideBody) { - if (found === null) { - setActivateHidingBodyButton(true); - } else { - //console.log("In found: ", found, hideBody) - } - } else { + for (let innerkey in selectedActionParameters) { + if (selectedActionParameters[innerkey].name === tmpitem) { + skip = true; + break; + } + } - rows = "1"; - disabled = true; - openApiHelperText = "OpenAPI spec: fill the following fields."; + if (skip) { + //console.log("SKIPPING ", tmpitem) + continue; + } - var changed = false; - var tempArray = [] - for (let specKey in found) { - const tmpitem = found[specKey]; - var skip = false; + changed = true; + var isRequired = false + // Check if original field name is in the selectedAction.required_body_fields + if (selectedAction.required_body_fields !== undefined && selectedAction.required_body_fields !== null) { + for (let innerkey in selectedAction.required_body_fields) { + if (selectedAction.required_body_fields[innerkey] === tmpitem) { + isRequired = true + break + } + } + } - for (let innerkey in selectedActionParameters) { - if (selectedActionParameters[innerkey].name === tmpitem) { - skip = true; - break; - } - } + tempArray.push({ + action_field: "", + configuration: false, + description: openApiFieldDesc, + example: "", + id: "", + multiline: true, + name: tmpitem, + options: null, + required: isRequired, + schema: { type: "string" }, + skip_multicheck: false, + tags: null, + value: "", + variant: "STATIC_VALUE", + field_active: true, + + autocompleted: true, + }); + } + + var required = selectedActionParameters.filter(item => item.required === true) + var notRequired = selectedActionParameters.filter(item => item.required === false) - if (skip) { - //console.log("SKIPPING ", tmpitem) - continue; - } + if (tempArray.length > 0) { + // Sort tempArray based on tempArray.required + tempArray.sort((a, b) => (a.required < b.required) ? 1 : -1) + // Add all items to the selectedActionParameters array + for (let innerkey in tempArray) { + tempArray[innerkey].id = "ADDED" - changed = true; - var isRequired = false - // Check if original field name is in the selectedAction.required_body_fields - if (selectedAction.required_body_fields !== undefined && selectedAction.required_body_fields !== null) { - for (let innerkey in selectedAction.required_body_fields) { - if (selectedAction.required_body_fields[innerkey] === tmpitem) { - isRequired = true - break - } - } - } + if (tempArray[innerkey].required === true) { + required.push(tempArray[innerkey]) + } else { + notRequired.push(tempArray[innerkey]) + } + } + } + //selectedActionParameters + if (changed) { + // Sort selectedActionParameters based on selectedActionParameters.required + //selectedActionParameters.sort((a, b) => (a.required < b.required) ? 1 : -1) + // Find the "headers" and "queries" field names and put them on the first indexes anyway + var newArray = required.concat(notRequired) + - tempArray.push({ - action_field: "", - configuration: false, - description: openApiFieldDesc, - example: "", - id: "", - multiline: true, - name: tmpitem, - options: null, - required: isRequired, - schema: { type: "string" }, - skip_multicheck: false, - tags: null, - value: "", - variant: "STATIC_VALUE", - field_active: true, - - autocompleted: true, - }); - } - - console.log("TEMP ARRAY: ", tempArray) - var required = selectedActionParameters.filter(item => item.required === true) - var notRequired = selectedActionParameters.filter(item => item.required === false) + setSelectedActionParameters(newArray) + } - if (tempArray.length > 0) { - // Sort tempArray based on tempArray.required - tempArray.sort((a, b) => (a.required < b.required) ? 1 : -1) - // Add all items to the selectedActionParameters array - for (let innerkey in tempArray) { - tempArray[innerkey].id = "ADDED" + return hideBodyButton; + } + } - if (tempArray[innerkey].required === true) { - required.push(tempArray[innerkey]) - } else { - notRequired.push(tempArray[innerkey]) - } - } - } - //selectedActionParameters - - if (changed) { - // Sort selectedActionParameters based on selectedActionParameters.required - //selectedActionParameters.sort((a, b) => (a.required < b.required) ? 1 : -1) - // Find the "headers" and "queries" field names and put them on the first indexes anyway - var newArray = required.concat(notRequired) - - - setSelectedActionParameters(newArray) - } - - return hideBodyButton; - } - } - - if (activateHidingBodyButton === true) { - hideBodyButton = ""; - } + if (activateHidingBodyButton === true) { + hideBodyButton = ""; + } const clickedFieldId = "rightside_field_" + count; @@ -1962,8 +3110,12 @@ const ParsedAction = (props) => { id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" - defaultValue={data.value} - //value={data.value} + // defaultValue={data.value} + value={ + paramValues.find((param) => param.name === data.name) !== undefined + ? paramValues.find((param) => param.name === data.name).value + : "" + } //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', @@ -1983,7 +3135,8 @@ const ParsedAction = (props) => { placeholder={placeholder} onChange={(event) => { //changeActionParameterCodemirror(event, count, data) - changeActionParameter(event, count, data); + // changeActionParameter(event, count, data); + handleParamChange(event, count, data) }} helperText={returnHelperText(data.name, data.value)} onBlur={(event) => { @@ -2378,7 +3531,6 @@ const ParsedAction = (props) => { }; const handleItemClick = (values) => { - console.log("In normal itemclick") if (values === undefined ||values === null ||values.length === 0) { return; } @@ -2401,31 +3553,28 @@ const ParsedAction = (props) => { // Handles the fields under OpenAPI body to be parsed. if (data.name.startsWith("${") && data.name.endsWith("}")) { - console.log("INSIDE VALUE REPLACE: ", data.name, toComplete); - // PARAM FIX - Gonna use the ID field, even though it's a hack const paramcheck = selectedAction.parameters.find( (param) => param.name === "body" - ); + ) + if (paramcheck !== undefined) { - if ( - paramcheck["value_replace"] === undefined || - paramcheck["value_replace"] === null - ) { + if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { paramcheck["value_replace"] = [ { key: data.name, value: toComplete, }, - ]; + ] } else { - const subparamindex = paramcheck[ - "value_replace" - ].findIndex((param) => param.key === data.name); + const subparamindex = paramcheck["value_replace"] + .findIndex((param) => param.key === data.name); + if (subparamindex === -1) { paramcheck["value_replace"].push({ key: data.name, value: toComplete, - }); + }) + } else { paramcheck["value_replace"][subparamindex]["value"] += toComplete; @@ -2433,7 +3582,9 @@ const ParsedAction = (props) => { } selectedActionParameters[count]["value_replace"] = paramcheck; - selectedAction.parameters[count]["value_replace"] = paramcheck; + + selectedAction.parameters = selectedActionParameters + //selectedAction.parameters[count]["value_replace"] = paramcheck; setSelectedAction(selectedAction); setUpdate(Math.random()); @@ -2450,7 +3601,7 @@ const ParsedAction = (props) => { //selectedAction.parameters[count].value = selectedActionParameters[count].value; //setSelectedAction(selectedAction); //setUpdate(Math.random()); - + setShowDropdown(false); setMenuPosition(null); }; @@ -2753,10 +3904,16 @@ const ParsedAction = (props) => { } const buttonTitle = `Authenticate ${selectedApp.name.replaceAll("_", " ")}` - const hasAutocomplete = data.autocompleted === true + const hasAutocomplete = data?.autocompleted === true + + + if (data.variant === undefined || data.variant === null) { + data.variant = "STATIC_VALUE" + } + return (
    - {hideBodyButton} + {/* {hideBodyButton} */}
    @@ -2890,9 +4047,9 @@ const ParsedAction = (props) => { Autocomplete { - const newversion = selectedApp.versions.find( - (tmpApp) => tmpApp.version == event.target.value - ) - - if (newversion !== undefined && newversion !== null) { - getApp(newversion.id, true) - } - - // Change in all actions in the workflow at the same time and add a toast.success() about it - for (var actionkey in workflow.actions) { - const action = workflow.actions[actionkey] - if (action.app_name === selectedAction.app_name) { - workflow.actions[actionkey].app_version = event.target.value - } - } - - toast.success("Changed version of all nodes to "+event.target.value) - }} - style={{ - marginTop: 10, - backgroundColor: theme.palette.surfaceColor, - backgroundColor: theme.palette.inputColor, - color: "white", - height: 35, - marginleft: 10, - borderRadius: theme.palette.borderRadius, - }} - SelectDisplayProps={{ - style: { - }, - }} - > - {selectedApp.versions.map((data, index) => { - return ( - - {data.version} - - ); - })} - - ) : null} -
    -
    -
    -
    - Name - { - // Copy the name value - const name = e.target.value - const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") - const newname = "$"+name.toLowerCase().replaceAll(" ", "_") - - // Check if it's the same as the current name in use - //if (name === selectedAction.label) { - // console.log("Returning from name thing") - // return - //} - - // Change in actions, triggers & conditions - // Highlight the changes somehow with a glow? - if (workflow.branches !== undefined && workflow.branches !== null) { - for (let [key,keyval] in Object.entries(workflow.branches)) { - if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { - for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { - const condition = workflow.branches[key].conditions[subkey] - const sourceparam = condition.source - const destinationparam = condition.destination - - // Should have a smarter way of discovering node names - // Finding index(es) and replacing at the location - if (sourceparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) - const extralength = newname.length-parsedBaseLabel.length - sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) - - console.log("New: ", workflow.branches[key].conditions[subkey].source.value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - - if (destinationparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) - const extralength = newname.length-parsedBaseLabel.length - destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) - - console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - } - } - } - } - - for (let [key,keyval] in Object.entries(workflow.actions)) { - if (workflow.actions[key].id === selectedAction.id) { - continue - } - - const params = workflow.actions[key].parameters - console.log(params) - if (params === null || params === undefined) { - continue - } - - for (let [subkey, subkeyval] in Object.entries(params)) { - const param = workflow.actions[key].parameters[subkey]; - if (!param.value.includes("$")) { - continue - } - - // Should have a smarter way of discovering node names - // Do regex? - // Finding index(es) and replacing at the location - // - - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (param.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = param.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.actions[key].parameters[subkey].value) - const extralength = newname.length-parsedBaseLabel.length - param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) - - console.log("New: ", workflow.actions[key].parameters[subkey].value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - } - - setWorkflow(workflow); - setUpdate(Math.random()); - baselabel = name - }} - /> -
    - {/*!isCloud ? null :*/} -
    - - - Delay - { - if (actionDelayChange !== undefined) { - actionDelayChange(event) - } - }} - /> - - -
    - {/**/} -
    - - )} - {selectedApp.name !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication !== undefined && - selectedAction.authentication.length === 0 && - requiresAuthentication ? ( -
    - - - - - -
    - ) : null} - - {selectedAction.authentication !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication.length > 0 ? ( -
    - Authentication -
    - - - {/* - - - curaction.authentication = authenticationOptions - if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") - */} - - { - setAuthenticationModalOpen(true); - }} - > - - - -
    -
    - ) : null} - - - {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( -
    - Environment - -
    - ) : null} - - {workflow.execution_variables !== undefined && - workflow.execution_variables !== null && - workflow.execution_variables.length > 0 ? ( -
    - Execution variable (optional) - -
    - ) : null} - - -
    - {/*hideExtraTypes ? null : -
    - Actions -
    - */} - - {setNewSelectedAction !== undefined ? ( - { - // Most popular - // Is categorized - // Uncategorized - return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions"; - }} - renderGroup={(params) => { - - return ( -
  • - {params.group} - {params.children} -
  • - ) - }} - options={renderedActionOptions} - ListboxProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - }, - }} - filterOptions={(options, { inputValue }) => { - //console.log("Option contains?: ", inputValue, options) - const lowercaseValue = inputValue.toLowerCase() - options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) - - return options - }} - getOptionLabel={(option) => { - if (option === undefined || option === null || option.name === undefined || option.name === null ) { - return null; - } - - const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - - return newname; - }} - fullWidth - style={{ - backgroundColor: theme.palette.inputColor, - height: 50, - borderRadius: theme.palette.borderRadius, - }} - onChange={(event, newValue) => { - // Workaround with event lol - if (newValue !== undefined && newValue !== null) { - setNewSelectedAction({ - target: { - value: newValue.name - } - }); - } - }} - renderOption={(props, data, state) => { - var newActionname = data.name; - if (data.label !== undefined && data.label !== null && data.label.length > 0) { - newActionname = data.label; - } - - var newActiondescription = data.description; - //console.log("DESC: ", newActiondescription) - if (data.description === undefined || data.description === null) { - newActiondescription = "Description: No description defined for this action" - } else { - newActiondescription = "Description: "+newActiondescription - } - - const iconInfo = GetIconInfo({ name: data.name }); - const useIcon = iconInfo.originalIcon; - - if (newActionname === undefined || newActionname === null) { - newActionname = "No name" - data.name = "No name" - data.label = "No name" - } - - newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); - - var method = "" - var extraDescription = "" - if (data.name.includes("get_")) { - method = "GET" - } else if (data.name.includes("post_")) { - method = "POST" - } else if (data.name.includes("put_")) { - method = "PUT" - } else if (data.name.includes("patch_")) { - method = "PATCH" - } else if (data.name.includes("delete_")) { - method = "DELETE" - } else if (data.name.includes("options_")) { - method = "OPTIONS" - } else if (data.name.includes("connect_")) { - method = "CONNECT" - } - - // FIXME: Should it require a base URL? - if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { - var extraUrl = "" - const descSplit = data.description.split("\n") - // Last line of descSplit - if (descSplit.length > 0) { - extraUrl = descSplit[descSplit.length-1] - } - - //for (let [line,lineval] in Object.entries(descSplit)) { - // if (descSplit[line].includes("http") && descSplit[line].includes("://")) { - // const urlsplit = descSplit[line].split("/") - // try { - // extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") - // } catch (e) { - // //console.log("Failed - running with -1") - // extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") - // } - - - // //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) - // //break - // } - //} - - if (extraUrl.length > 0) { - if (extraUrl.includes(" ")) { - extraUrl = extraUrl.split(" ")[0] - } - - if (extraUrl.includes("#")) { - extraUrl = extraUrl.split("#")[0] - } - - extraDescription = `${method} ${extraUrl}` - } else { - //console.log("No url found. Check again :)") - } - } - - return ( - - ); - }} - renderInput={(params) => { - if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { - const prefixes = ["Post", "Put", "Patch"] - for (let [key,keyval] in Object.entries(prefixes)) { - if (params.inputProps.value.startsWith(prefixes[key])) { - params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) - if (params.inputProps.value.length > 1) { - params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) - } - break - } - } - - // Check if it starts with "Get List" and method is "Get" - if (params.inputProps.value.startsWith("Get List")) { - console.log("Get List") - } - } - - return ( - - ); - }} - /> - ) : null} - - {/*setNewSelectedAction !== undefined ? - - : null*/} - -
    - +
    diff --git a/frontend/src/components/WorkflowGrid.jsx b/frontend/src/components/WorkflowGrid.jsx index e10d2131..4c4d112e 100644 --- a/frontend/src/components/WorkflowGrid.jsx +++ b/frontend/src/components/WorkflowGrid.jsx @@ -194,7 +194,7 @@ const AppGrid = props => { // Don't return anything unless refinement works return null } - + return ( {onlyResults !== true ? diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 7197f7d6..807cf2f9 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-multi-comp */ -import React, { useState, useEffect, useLayoutEffect } from "react"; +import React, { useState, useEffect, useLayoutEffect, memo, useMemo, useRef } from "react"; import ReactDOM from "react-dom" import theme from "../theme.jsx"; @@ -22,7 +22,6 @@ import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx"; import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; import algoliasearch from 'algoliasearch/lite'; - import { Zoom, Fade, @@ -554,7 +553,7 @@ const AngularWorkflow = (defaultprops) => { }, [editWorkflowModalOpen]) // New for generated stuff - const releaseToConnectLabel = "Release to Connect" +const releaseToConnectLabel = "Release to Connect" const integrationApps = [{ "id": "integration", "name": "Integration Framework", @@ -1447,18 +1446,18 @@ const AngularWorkflow = (defaultprops) => { trigger.parameters = [] - const topic = document.getElementById('topic')?.value - const bootstrapServers = document.getElementById('bootstrap_servers')?.value - const groupId = document.getElementById('group_id')?.value + const topic = document.getElementById('topic')?.value; + const bootstrapServers = document.getElementById('bootstrap_servers')?.value; + const groupId = document.getElementById('group_id')?.value; //const autoOffsetReset = document.getElementById('auto_offset_reset')?.value; if(topic) { trigger.parameters.push({ name: "topic", value: topic - }) + }); } else { - toast("Please enter the topic name"); + toast("please enter the topic name"); return; } @@ -3296,19 +3295,19 @@ const AngularWorkflow = (defaultprops) => { // don't redirect if it exists const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; var execFound = new URLSearchParams(cursearch).get("execution_id"); - var sessionToken = new URLSearchParams(cursearch).get("session_token"); + var sessionToken = new URLSearchParams(cursearch).get("session_token"); if (execFound === null && sessionToken === null) { - toast(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) - setTimeout(() => { - window.location.pathname = "/workflows"; - }, 2000); - } else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") { + toast(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) + setTimeout(() => { + window.location.pathname = "/workflows"; + }, 2000); +} else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") { toast(`Injecting session token and reloading workflow..`) setTimeout(() => { setCookie("session_token", sessionToken, { path: "/" }); window.location.href = "https://shuffler.io/workflows/3abdfb21-b40f-4e50-b855-ac0d62f83cbe"; }, 2000); - } + } } } @@ -3793,12 +3792,11 @@ const AngularWorkflow = (defaultprops) => { const onNodeDragStop = (event, selectedAction) => { const nodedata = event.target.data(); if (nodedata.id === selectedAction.id) { - //console.log("Same node, return") + //console.log("Same node, return") return } if (nodedata.finished === false) { - //console.log("Node is not finished, return") return } @@ -4134,8 +4132,8 @@ const AngularWorkflow = (defaultprops) => { // Check plus minus 15 in distance from mindistance if (distance > minDistance - 15 && distance < minDistance + 15) { console.log("Within distance of 15, add to existing edge") - } else { - console.log("Outside distance of 15, remove old edge and add new") + } else { + console.log("Outside distance of 15, remove old edge and add new") } } @@ -4160,8 +4158,8 @@ const AngularWorkflow = (defaultprops) => { conditions: [], } }) - } - } + } + } } } @@ -4239,7 +4237,286 @@ const AngularWorkflow = (defaultprops) => { document.removeEventListener("paste", handlePaste, true); } } - }) + }); + + // Should get AI autocompletes + const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => { + if (setResponseMsg !== undefined) { + setResponseMsg("") + } + + if (value === undefined || value === "") { + console.log("No value input!") + return + } + + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(true) + } + + console.log("Submit conversation with value: ", value); + + // This is to find sample response and parse it as string + + var AppContext = [] + if (inputAction !== undefined && inputAction !== null) { + const parents = getParents(inputAction) + + console.log("Parents: ", parents) + var actionlist = [] + if (parents.length > 1) { + for (let [key,keyval] in Object.entries(parents)) { + const item = parents[key]; + if (item.label === "Execution Argument") { + continue; + } + + var exampledata = item.example === undefined || item.example === null ? "" : item.example; + // Find previous execution and their variables + //exampledata === "" && + if (workflowExecutions.length > 0) { + // Look for the ID + const found = false; + for (let [key,keyval] in Object.entries(workflowExecutions)) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue; + } + + var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id); + if (foundResult === undefined || foundResult === null) { + continue; + } + + if (foundResult.result !== undefined && foundResult.result !== null) { + foundResult = foundResult.result + } + + const valid = validateJson(foundResult, true) + if (valid.valid) { + if (valid.result.success === false) { + //console.log("Skipping success false autocomplete") + } else { + exampledata = valid.result; + break; + } + } else { + exampledata = foundResult; + } + } + } + + // 1. Take + const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_"); + + const actionvalue = { + app_name: item.app_name, + action_name: item.name, + label: item.label, + + type: "action", + id: item.id, + name: item.label, + autocomplete: itemlabelComplete, + example: exampledata, + }; + + actionlist.push(actionvalue); + } + } + + var fixedResults = [] + for (var i = 0; i < actionlist.length; i++) { + const item = actionlist[i]; + const responseFix = SetJsonDotnotation(item.example, "") + + // Check if json + const validated = validateJson(responseFix) + var exampledata = responseFix; + if (validated.valid) { + exampledata = JSON.stringify(validated.result) + } + + AppContext.push({ + "app_name": item.app_name, + "action_name": item.action_name, + "label": item.label, + "example": exampledata, + "example_response": exampledata, + }) + } + } + + var conversationData = { + "query": value, + "output_format": "action", + "app_context": AppContext, + + "workflow_id": workflow.id, + } + + if (inputAction !== undefined) { + console.log("Add app context! This should them get parameters directly") + conversationData.output_format = "action_parameters" + + conversationData.app_id = inputAction.app_id + conversationData.app_name = inputAction.app_name + conversationData.action_name = inputAction.name + conversationData.parameters = inputAction.parameters + + if (!value.includes(inputAction.label)) { + conversationData.query = inputAction.label.replaceAll("_", " ") + } + } + + // Onprem not available yet (April 2023) + // Should: Make OpenAI work for them with their own key + //fetch("https://shuffler.io/api/v1/conversation", { + fetch(`${globalUrl}/api/v1/conversation`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(conversationData), + credentials: "include", + }) + .then((response) => { + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(false) + } + + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + console.log("Conversation response: ", responseJson) + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + if (setResponseMsg !== undefined) { + setResponseMsg(responseJson.reason) + } + } + + return + } + + if (inputAction !== undefined) { + console.log("In input action! Should check params if they match, and add suggestions") + + if (responseJson.parameters === undefined || responseJson.parameters.length === 0) { + return + } + + var changed = false + + for (let paramkey in inputAction.parameters) { + const actionParam = inputAction.parameters[paramkey] + + if (actionParam.autocompleted === true) { + continue + } + + if (actionParam.configuration === true && actionParam.name !== "url") { + continue + } + + if (actionParam.value !== "" && actionParam.value !== actionParam.example) { + console.log("Skipping: ", actionParam) + continue + } + + for (let respParam of responseJson.parameters) { + if (respParam.name === actionParam.name) { + console.log("Found match for param: ", respParam) + + if (respParam.value === "") { + break + } + + changed = true + + inputAction.parameters[paramkey].autocompleted = true + inputAction.parameters[paramkey].value = respParam.value + break + } + } + } + + if (changed === true) { + console.log("Setting action! Force update pls :)") + setUpdate(Math.random()) + setSelectedAction(inputAction) + } + + return + } + + console.log("Suggestionbox location: ", suggestionBox) + + // Add action + if (responseJson.app_name !== undefined && responseJson.app_name !== null) { + // Always added to 0, 0 + // Should use suggestionBox.position.x, suggestionBox.position.y + var newitem = { + "data": responseJson, + "position": { + "x": suggestionBox.node_position.x !== undefined ? suggestionBox.node_position.x : 0, + "y": suggestionBox.node_position.y !== undefined ? suggestionBox.node_position.y + 100 : 0, + }, + "group": "nodes", + } + + newitem.type = "ACTION" + newitem.isStartNode = false + newitem.data.id = uuidv4() + newitem.data.type = "ACTION" + newitem.data.isStartNode = false + + newitem.data.is_valid = true + newitem.data.isValid = true + + cy.add({ + group: newitem.group, + data: newitem.data, + position: newitem.position, + }); + + // Add edge + const newId = uuidv4() + cy.add({ + group: "edges", + data: { + id: newId, + _id: newId, + source: suggestionBox.attachedTo, + target: newitem.data.id, + } + }) + //label: "Generated", + + setSuggestionBox({ + "position": { + "top": 500, + "left": 500, + }, + "open": false, + "attachedTo": "", + }); + } + }) + .catch((error) => { + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(false) + } + + console.log("Conv response error: ", error); + }); + } + + // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once @@ -4250,15 +4527,12 @@ const AngularWorkflow = (defaultprops) => { //const data = JSON.parse(JSON.stringify(event.target.data())) const data = event.target.data() - - console.log("NODE SELECT: ", data) - if (data.app_name === "Shuffle Workflow") { - console.log("Shuffle Workflow selected") - if ((data.parameters !== undefined) && (data.parameters.length > 0)) { - getWorkflowApps(data.parameters[0].value) + if (data.app_name === "Shuffle Workflow") { + if ((data.parameters !== undefined) && (data.parameters.length > 0)) { + getWorkflowApps(data.parameters[0].value) + } } - } if (data.buttonType == "ACTIONSUGGESTION") { const attachedToId = data.attachedTo @@ -4323,6 +4597,7 @@ const AngularWorkflow = (defaultprops) => { workflow.actions[foundindex].name = curaction.name setWorkflow(workflow) + console.log(workflow) } break } @@ -5363,10 +5638,11 @@ const AngularWorkflow = (defaultprops) => { // Checks for errors in edges when they're added const onEdgeAdded = (event) => { - const edge = event.target.data() - //console.log("EDGE ADDED!: ", edge) + setLastSaved(false); + const edge = event.target.data(); + + //console.log("edge added: ", edge) if (edge.source === undefined && edge.target === undefined) { - console.log("Edge source and target is undefined") return } @@ -5380,7 +5656,6 @@ const AngularWorkflow = (defaultprops) => { const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { - console.log("Source or destination node is undefined") } 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") { @@ -5393,10 +5668,10 @@ const AngularWorkflow = (defaultprops) => { console.log("Node: ", targetedge) if (targetedge !== -1) { + event.target.remove() //console.log("Found branch already!") toast.error("Triggers can have exactly one target node") - event.target.remove() return @@ -5417,10 +5692,6 @@ const AngularWorkflow = (defaultprops) => { } } - if (edge.decorator === true) { - console.log("Doing nothing to branch because decorator") - return - } var targetnode = workflow.triggers.findIndex( (data) => data.id === edge.target @@ -5460,14 +5731,15 @@ const AngularWorkflow = (defaultprops) => { } } - if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") { + if ( + eventTarget.data("isDescriptor") === true || + eventTarget.data("type") === "COMMENT" + ) { console.log("Removing because of descriptor or comment") - event.target.remove() - return + event.target.remove(); + return; } - - setLastSaved(false) targetnode = -1; // Check if: @@ -5475,51 +5747,38 @@ const AngularWorkflow = (defaultprops) => { // dest == dest && source == source // backend: check all children? to stop recursion var found = false; - const branches = cy.edges().jsons() - - const startNode = cy.nodes().jsons().find((node) => node.data.isStartNode === true) - var startnodeId = workflow.start - if (startNode !== undefined && startNode !== null) { - startnodeId = startNode.data.id - } - - //for (let branchkey in workflow.branches) { - for (let branchkey in branches) { - const branch = branches[branchkey].data - - //if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { - if (branch.target === edge.source && branch.source === edge.target) { - toast("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) { - } else if (branch.target === edge.target && branch.source === edge.source) { - - if (branch.conditions === undefined) { - // Edgehandles - } else { - console.log("Removing because the same branch already exists") - event.target.remove() - - found = true - break - } - } else if (edge.target === startnodeId) { - targetnode = workflow.triggers.findIndex((data) => data.id === edge.source) + for (let branchkey in workflow.branches) { + if ( + workflow.branches[branchkey].destination_id === edge.source && + workflow.branches[branchkey].source_id === edge.target + ) { + toast("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 + ) { + //toast("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") { - toast("Can't make arrow to starting node") - event.target.remove() - break + toast("Can't make arrow to starting node"); + event.target.remove(); + break; } found = true; } - //} else if (edge.source === workflow.branches[branchkey].source_id) { - } else if (edge.source === branch.source) { + } 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 @@ -5563,6 +5822,7 @@ const AngularWorkflow = (defaultprops) => { newdst !== null ) { const dstdata = RunAutocompleter(newdst.data()); + //console.log("DST Autocompleter: ", dstdata); } var newbranch = { @@ -6796,8 +7056,6 @@ const AngularWorkflow = (defaultprops) => { const allNodes = cy.nodes().jsons(); if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") { - console.log("In this :)") - var found = false; for (let nodekey in allNodes) { const currentNode = allNodes[nodekey]; @@ -8305,11 +8563,15 @@ const AngularWorkflow = (defaultprops) => { ); }; - const handleSetTab = (event, newValue) => { - setCurrentView(newValue); - }; + const HandleLeftView = () => { + // console.log("HandleLeftView Rendered!") + + const handleSetTab = (event, newValue) => { + setCurrentView(newValue); + }; + // Defaults to apps. var thisview = ( { const AppView = (props) => { const { allApps, prioritizedApps, filteredApps, extraApps } = props; - + // console.log("AppView Rendered!") //extraApps, const [visibleApps, setVisibleApps] = React.useState( Array.prototype.concat.apply( @@ -9577,7 +9839,9 @@ const AngularWorkflow = (defaultprops) => {
    ); - }; +} + + const getNextActionName = (appName) => { var highest = ""; @@ -9788,7 +10052,6 @@ const AngularWorkflow = (defaultprops) => { } } - console.log("New selected action: ", newSelectedAction) setSelectedAction(newSelectedAction) setUpdate(Math.random()) @@ -9824,44 +10087,43 @@ const AngularWorkflow = (defaultprops) => { // appname & version // description // ACTION select - const selectedNameChange = (event) => { - event.target.value = event.target.value.replaceAll("(", ""); - event.target.value = event.target.value.replaceAll(")", ""); - event.target.value = event.target.value.replaceAll("]", ""); - event.target.value = event.target.value.replaceAll("[", ""); - event.target.value = event.target.value.replaceAll("{", ""); - event.target.value = event.target.value.replaceAll("}", ""); - event.target.value = event.target.value.replaceAll("*", ""); - event.target.value = event.target.value.replaceAll("!", ""); - event.target.value = event.target.value.replaceAll("@", ""); - event.target.value = event.target.value.replaceAll("#", ""); - event.target.value = event.target.value.replaceAll("$", ""); - event.target.value = event.target.value.replaceAll("%", ""); - event.target.value = event.target.value.replaceAll("&", ""); - event.target.value = event.target.value.replaceAll("#", ""); - event.target.value = event.target.value.replaceAll(".", ""); - event.target.value = event.target.value.replaceAll(",", ""); - event.target.value = event.target.value.replaceAll(" ", "_"); - event.target.value = event.target.value.replaceAll("^", "_"); - event.target.value = event.target.value.replaceAll("'", "_"); - event.target.value = event.target.value.replaceAll("\"", "_"); - event.target.value = event.target.value.replaceAll("\"", "_"); - event.target.value = event.target.value.replaceAll(":", "_"); - event.target.value = event.target.value.replaceAll(";", "_"); - event.target.value = event.target.value.replaceAll("=", "_"); - event.target.value = event.target.value.replaceAll("+", "_"); - - selectedAction.label = event.target.value; + const selectedNameChange = (appActionName) => { + appActionName = appActionName.replaceAll("(", ""); + appActionName = appActionName.replaceAll(")", ""); + appActionName = appActionName.replaceAll("]", ""); + appActionName = appActionName.replaceAll("[", ""); + appActionName = appActionName.replaceAll("{", ""); + appActionName = appActionName.replaceAll("}", ""); + appActionName = appActionName.replaceAll("*", ""); + appActionName = appActionName.replaceAll("!", ""); + appActionName = appActionName.replaceAll("@", ""); + appActionName = appActionName.replaceAll("#", ""); + appActionName = appActionName.replaceAll("$", ""); + appActionName = appActionName.replaceAll("%", ""); + appActionName = appActionName.replaceAll("&", ""); + appActionName = appActionName.replaceAll("#", ""); + appActionName = appActionName.replaceAll(".", ""); + appActionName = appActionName.replaceAll(",", ""); + appActionName = appActionName.replaceAll(" ", "_"); + appActionName = appActionName.replaceAll("^", "_"); + appActionName = appActionName.replaceAll("'", "_"); + appActionName = appActionName.replaceAll("\"", "_"); + appActionName = appActionName.replaceAll("\"", "_"); + appActionName = appActionName.replaceAll(":", "_"); + appActionName = appActionName.replaceAll(";", "_"); + appActionName = appActionName.replaceAll("=", "_"); + appActionName = appActionName.replaceAll("+", "_"); + selectedAction.label = appActionName; setSelectedAction(selectedAction); }; - const actionDelayChange = (event) => { - if (isNaN(event.target.value)) { - console.log("NAN: ", event.target.value) + const actionDelayChange = (delay) => { + if (isNaN(delay)) { + console.log("NAN: ", delay) return } - const parsedNumber = parseInt(event.target.value) + const parsedNumber = parseInt(delay) if (parsedNumber > 86400) { console.log("Max number is 1 day (86400)") return @@ -11297,11 +11559,11 @@ const AngularWorkflow = (defaultprops) => { {/* Check if dest is the same as start */} - {conditionsDisabled ? - - Conditions are unavailable between triggers and the startnode. - - : null} + {conditionsDisabled ? + + Conditions are unavailable between triggers and the startnode. + + : null}
    {/* @@ -12155,7 +12417,7 @@ const AngularWorkflow = (defaultprops) => { return transformedData; - } + }; const AppAuthSelector = ({ appAuthData }) => { const [selectedAuth, setSelectedAuth] = useState(""); @@ -12168,8 +12430,11 @@ const AngularWorkflow = (defaultprops) => { const handleShowingValue = (appName) => { let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) - console.log("LIST WITH VALUES: ", listWithValues) + if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) { + return "no-overrides"; + } + for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] } @@ -12397,9 +12662,6 @@ const AngularWorkflow = (defaultprops) => { setActionlist(actionlist); } - // Shows nested list of nodes > their JSON lists - const ActionlistWrapper = (props) => { - const { data } = props; const handleMenuClose = () => { setUpdate(Math.random()); @@ -12445,302 +12707,7 @@ const AngularWorkflow = (defaultprops) => { marginRight: 15, }; - return ( - { - handleMenuClose(); - }} - open={!!menuPosition} - style={{ - border: `2px solid #f85a3e`, - color: "white", - marginTop: 2, - }} - > - {actionlist.map((innerdata) => { - const icon = - innerdata.type === "action" ? ( - - ) : innerdata.type === "workflow_variable" || - innerdata.type === "execution_variable" ? ( - - ) : ( - - ); - - const handleExecArgumentHover = (inside) => { - var exec_text_field = document.getElementById( - "execution_argument_input_field" - ); - if (exec_text_field !== null) { - if (inside) { - exec_text_field.style.border = "2px solid #f85a3e"; - } else { - exec_text_field.style.border = ""; - } - } - - // Also doing arguments - if ( - workflow.triggers !== undefined && - workflow.triggers !== null && - workflow.triggers.length > 0 - ) { - for (let triggerkey in workflow.triggers) { - const item = workflow.triggers[triggerkey]; - - if (cy !== undefined) { - var node = cy.getElementById(item.id); - if (node.length > 0) { - if (inside) { - node.addClass("shuffle-hover-highlight"); - } else { - node.removeClass("shuffle-hover-highlight"); - } - } - } - } - } - } - - const handleActionHover = (inside, actionId) => { - if (cy !== undefined) { - var node = cy.getElementById(actionId); - if (node.length > 0) { - if (inside) { - node.addClass("shuffle-hover-highlight"); - } else { - node.removeClass("shuffle-hover-highlight"); - } - } - } - }; - - const handleMouseover = () => { - if (innerdata.type === "Execution Argument") { - handleExecArgumentHover(true); - } else if (innerdata.type === "action") { - handleActionHover(true, innerdata.id); - } - }; - - const handleMouseOut = () => { - if (innerdata.type === "Execution Argument") { - handleExecArgumentHover(false); - } else if (innerdata.type === "action") { - handleActionHover(false, innerdata.id); - } - }; - - var parsedPaths = []; - console.log("Found example data: ", innerdata.example) - if (typeof innerdata.example === "object") { - parsedPaths = GetParsedPaths(innerdata.example, ""); - } - - const coverColor = "#82ccc3" - - return parsedPaths.length > 0 ? ( - - {/* - - {icon} {innerdata.name} -
    - } - parentMenuOpen={!!menuPosition} - style={{ - backgroundColor: theme.palette.inputColor, - color: "white", - minWidth: 250, - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - {parsedPaths.map((pathdata, index) => { - // FIXME: Should be recursive in here - const icon = - pathdata.type === "value" ? ( - - ) : pathdata.type === "list" ? ( - - ) : ( - - ) - - return ( - { }} - onClick={() => { - handleItemClick([innerdata, pathdata]); - }} - > - -
    - {icon} {pathdata.name} -
    -
    -
    - ); - })} - - */} - - - {icon} {innerdata.name} -
    - } - parentMenuOpen={!!menuPosition} - style={{ - color: "white", - minWidth: 250, - maxWidth: 250, - maxHeight: 50, - overflow: "hidden", - }} - onClick={() => { - console.log("CLICKED: ", innerdata); - console.log(innerdata.example) - handleItemClick([innerdata]); - }} - > - - - { - //console.log("HOVER: ", pathdata); - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - - {innerdata.name} - - - - {parsedPaths.map((pathdata, index) => { - // FIXME: Should be recursive in here - // - const icon = - pathdata.type === "value" ? ( - - ) : pathdata.type === "list" ? ( - - ) : ( - - ); - // - - const indentation_count = (pathdata.name.match(/\./g) || []).length+1 - const baseIndent =
    - //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 - const boxPadding = 0 - const namesplit = pathdata.name.split(".") - const newname = namesplit[namesplit.length-1] - return ( - { - //console.log("HOVER: ", pathdata); - }} - onClick={() => { - handleItemClick([innerdata, pathdata]); - }} - > - -
    - {Array(indentation_count).fill().map((subdata, subindex) => { - return ( - baseIndent - ) - })} - {icon} {newname} - {pathdata.type === "list" ? { - - }} /> : null} -
    -
    -
    - ); - })} - - - - ) : ( - handleMouseover()} - onMouseOut={() => { - handleMouseOut(); - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - -
    - {icon} {innerdata.name} -
    -
    -
    - ); - })} - - ); - }; - + if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { if (workflow.triggers[selectedTriggerIndex] === undefined) { return null; @@ -12851,7 +12818,7 @@ const AngularWorkflow = (defaultprops) => { data: newbranch, }; - cy.add(cybranch) + cy.add(cybranch); } console.log("Value to be set: ", e.target.value); @@ -13353,10 +13320,298 @@ const AngularWorkflow = (defaultprops) => { }} /> {!showDropdown ? null : - + { + handleMenuClose(); + }} + open={!!menuPosition} + style={{ + border: `2px solid #f85a3e`, + color: "white", + marginTop: 2, + }} + > + {actionlist.map((innerdata) => { + const icon = + innerdata.type === "action" ? ( + + ) : innerdata.type === "workflow_variable" || + innerdata.type === "execution_variable" ? ( + + ) : ( + + ); + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById( + "execution_argument_input_field" + ); + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e"; + } else { + exec_text_field.style.border = ""; + } + } + + // Also doing arguments + if ( + workflow.triggers !== undefined && + workflow.triggers !== null && + workflow.triggers.length > 0 + ) { + for (let triggerkey in workflow.triggers) { + const item = workflow.triggers[triggerkey]; + + if (cy !== undefined) { + var node = cy.getElementById(item.id); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + } + } + } + + const handleActionHover = (inside, actionId) => { + if (cy !== undefined) { + var node = cy.getElementById(actionId); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + }; + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true); + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id); + } + }; + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false); + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id); + } + }; + + var parsedPaths = []; + console.log("Found example data: ", innerdata.example) + if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } + + const coverColor = "#82ccc3" + + return parsedPaths.length > 0 ? ( + + {/* + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + backgroundColor: theme.palette.inputColor, + color: "white", + minWidth: 250, + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ) + + return ( + { }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {icon} {pathdata.name} +
    +
    +
    + ); + })} + + */} + + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + color: "white", + minWidth: 250, + maxWidth: 250, + maxHeight: 50, + overflow: "hidden", + }} + onClick={() => { + console.log("CLICKED: ", innerdata); + console.log(innerdata.example) + handleItemClick([innerdata]); + }} + > + + + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + + {innerdata.name} + + + + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + // + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ); + // + + const indentation_count = (pathdata.name.match(/\./g) || []).length+1 + const baseIndent =
    + //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 + const boxPadding = 0 + const namesplit = pathdata.name.split(".") + const newname = namesplit[namesplit.length-1] + return ( + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {Array(indentation_count).fill().map((subdata, subindex) => { + return ( + baseIndent + ) + })} + {icon} {newname} + {pathdata.type === "list" ? { + + }} /> : null} +
    +
    +
    + ); + })} + + + + ) : ( + handleMouseover()} + onMouseOut={() => { + handleMouseOut(); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + +
    + {icon} {innerdata.name} +
    +
    +
    + ); + })} + } {/*
    {
    - {/*
    -
    -
    -
    - Auth Override -
    -
    +
    +
    +
    + Auth Override +
    +
    -
    -
    - -
    -
    -
    +
    +
    + +
    +
    +
    - */}
    - ) + ); } return null; @@ -13669,7 +13922,48 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : ""; - } + }else if( + selectedTrigger.trigger_type === "USERINPUT" + ){ + if ( + workflow.triggers[selectedTriggerIndex].parameters === undefined || + workflow.triggers[selectedTriggerIndex].parameters === null || + workflow.triggers[selectedTriggerIndex].parameters.length === 0 + ) { + workflow.triggers[selectedTriggerIndex].parameters = []; + workflow.triggers[selectedTriggerIndex].parameters[0] = { + name: "alertinfo", + value: "Do you want to continue the workflow? Start parameters: $exec", + }; + + // boolean, + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "options", + value: "boolean", + }; + + // email,sms,app ... + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "type", + value: "subflow", + }; + + workflow.triggers[selectedTriggerIndex].parameters[3] = { + name: "email", + value: "test@test.com", + }; + workflow.triggers[selectedTriggerIndex].parameters[4] = { + name: "sms", + value: "0000000", + }; + workflow.triggers[selectedTriggerIndex].parameters[5] = { + name: "subflow", + value: "", + }; + + setWorkflow(workflow); + } + } } const WebhookSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "WEBHOOK" ? null : @@ -13793,6 +14087,7 @@ const AngularWorkflow = (defaultprops) => { setUpdate(Math.random()); } + document.activeElement.blur(); }} >
    @@ -14464,48 +14759,7 @@ const AngularWorkflow = (defaultprops) => { }) } - const UserinputSidebar = () => { - if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) { - if ( - workflow.triggers[selectedTriggerIndex].parameters === undefined || - workflow.triggers[selectedTriggerIndex].parameters === null || - workflow.triggers[selectedTriggerIndex].parameters.length === 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = []; - workflow.triggers[selectedTriggerIndex].parameters[0] = { - name: "alertinfo", - value: "Do you want to continue the workflow? Start parameters: $exec", - }; - - // boolean, - workflow.triggers[selectedTriggerIndex].parameters[1] = { - name: "options", - value: "boolean", - }; - - // email,sms,app ... - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "type", - value: "subflow", - }; - - workflow.triggers[selectedTriggerIndex].parameters[3] = { - name: "email", - value: "test@test.com", - }; - workflow.triggers[selectedTriggerIndex].parameters[4] = { - name: "sms", - value: "0000000", - }; - workflow.triggers[selectedTriggerIndex].parameters[5] = { - name: "subflow", - value: "", - }; - - setWorkflow(workflow); - } - - return ( + const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

    {selectedTrigger.app_name} @@ -14703,6 +14957,7 @@ const AngularWorkflow = (defaultprops) => { onChange={(event, newValue) => { console.log("Changed autocomplete!") handleWorkflowSelectionUpdate({ target: { value: newValue } }, true) + event.target.blur(); }} renderOption={(props, data, state) => { if (data.id === workflow.id) { @@ -14731,6 +14986,7 @@ const AngularWorkflow = (defaultprops) => { value: data, }}, true) + document.activeElement.blur(); }} > @@ -14842,12 +15098,6 @@ const AngularWorkflow = (defaultprops) => {

    - ) - } - - return null - } - const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null :

    @@ -15372,8 +15622,12 @@ const AngularWorkflow = (defaultprops) => { right: 0, left: isMobile ? 20 : leftBarSize + 20, top: isMobile ? 30 : appBarSize + 20, + pointerEvents: "none", } + + + const TopCytoscapeBar = (props) => { if (workflow.public === true) { return null @@ -15389,7 +15643,7 @@ const AngularWorkflow = (defaultprops) => {
    {

    {workflow.name}

    @@ -16154,8 +16409,6 @@ const AngularWorkflow = (defaultprops) => { } } - /* - // Infinitely annoying. Need a new bind if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { console.log("Shift key pressed") if (!workflow.public && executionModalOpen) { @@ -16167,8 +16420,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionModalView(0); } } - */ - } + }; document.addEventListener('keydown', handleKeyDown); @@ -16715,115 +16967,13 @@ const AngularWorkflow = (defaultprops) => { }; const RightSideBar = (props) => { - const { - //workflow, - //setWorkflow, - //setSelectedAction, - //setUpdate, - //selectedApp, - //workflowExecutions, - //setSelectedResult, - //selectedAction, - //setSelectedApp, - //setSelectedTrigger, - //setSelectedEdge, - //setCurrentView, - //cy, - //setAuthenticationModalOpen, - //setVariablesModalOpen, - //setCodeModalOpen, - //selectedNameChange, - //rightsidebarStyle, - //showEnvironment, - //selectedActionEnvironment, - //environments, - //setNewSelectedAction, - //appApiViewStyle, - //globalUrl, - //setSelectedActionEnvironment, - //requiresAuthentication, - //scrollConfig, - //setScrollConfig, - } = props; - - if (!rightSideBarOpen) { - return null; - } var defaultReturn = null - if (Object.getOwnPropertyNames(selectedAction).length > 0) { - if (Object.getOwnPropertyNames(selectedAction).length === 0) { - return null; - } - - defaultReturn = - - } else if (Object.getOwnPropertyNames(selectedComment).length > 0) { + if (Object.getOwnPropertyNames(selectedComment).length > 0) { defaultReturn = } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (selectedTrigger.trigger_type === "SCHEDULE") { - // Handled elsewhere as an experiment - defaultReturn = null - } else if (selectedTrigger.trigger_type === "WEBHOOK") { - defaultReturn = null - } else if (selectedTrigger.trigger_type === "SUBFLOW") { - defaultReturn = - } else if (selectedTrigger.trigger_type === "EMAIL") { + if (selectedTrigger.trigger_type === "EMAIL") { defaultReturn = - } else if (selectedTrigger.trigger_type === "USERINPUT") { - defaultReturn = } else if (selectedTrigger.trigger_type === undefined) { //defaultReturn = return null; @@ -17674,7 +17824,7 @@ const AngularWorkflow = (defaultprops) => { console.log("IN useeffectt (2)" + collapsed) return; } - }) + },[]) /* componentWillUpdate = (nextProps, nextState) => { console.log(nextProps, nextState) @@ -18137,7 +18287,7 @@ const AngularWorkflow = (defaultprops) => {
    {foundnotifications > 0 ? - + { @@ -19196,11 +19346,10 @@ const AngularWorkflow = (defaultprops) => { return "The queries or data sent to the API is most likely wrong (400). Check the body of the result for more information." } - /* if (result.status === 200 || result.status === 201 || result.status === 204) { return "It looks like the result was successful! If it didn't work, make sure to check if the body you are sending was correct." } - */ + // Validate and check for newlines if (result.success !== false) { @@ -19684,12 +19833,68 @@ const AngularWorkflow = (defaultprops) => {
    {executionModal} - - + + { + rightSideBarOpen && Object.getOwnPropertyNames(selectedAction).length > 0 ? +
    + +
    : null + } {/* Looks for triggers" */} {/* Only fixed the ones that require scrolling on a small screen */} {/* Most important: Actions. But these are a lot more complex */} - {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE") ? + {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE" || selectedTrigger.trigger_type === "USERINPUT") ?
    {Object.getOwnPropertyNames(selectedTrigger).length > 0 ? selectedTrigger.trigger_type === "SCHEDULE" ? @@ -19698,10 +19903,19 @@ const AngularWorkflow = (defaultprops) => { PipelineSidebar : selectedTrigger.trigger_type === "WEBHOOK" ? WebhookSidebar + : selectedTrigger.trigger_type === "USERINPUT" ? + UserinputSidebar : null : null}
    : null} + + { + rightSideBarOpen && selectedTrigger.trigger_type === "SUBFLOW"&& Object.getOwnPropertyNames(selectedTrigger).length > 0 ? +
    + +
    : null + } {/* {
    Configuration options for {selectedOption}
    - - {selectedOption === "Kafka Queue" ? -
    + {selectedOption === "Kafka Queue" && ( + <> Topic { placeholder={"earliest"} defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''} /> */} -
    - : null} - - param.name === "bootstrap_servers")?.value) || ''} - /> - + + )}
    + + + */} + +

    ); }; @@ -12212,6 +12334,10 @@ const releaseToConnectLabel = "Release to Connect" let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) console.log("LIST WITH VALUES: ", listWithValues) + if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) { + return "no-overrides"; + } + for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] } @@ -14875,7 +15001,6 @@ const releaseToConnectLabel = "Release to Connect"
    - const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null :

    @@ -16746,7 +16871,6 @@ const releaseToConnectLabel = "Release to Connect" const RightSideBar = (props) => { - var defaultReturn = null if (Object.getOwnPropertyNames(selectedComment).length > 0) { defaultReturn = From fab2c9569661aa9369cbfa198e736b89271fbd9d Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 20 Jun 2024 23:59:15 +0200 Subject: [PATCH 044/336] Minor fixes for admin & oauth2 --- frontend/src/components/Oauth2Auth.jsx | 15 +- frontend/src/views/Admin.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 285 ++++++++++++++++--------- frontend/src/views/LoginPage.jsx | 2 +- frontend/src/views/Workflows.jsx | 45 +++- 5 files changed, 231 insertions(+), 118 deletions(-) diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 1ebf81dd..f0c20a64 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -476,15 +476,15 @@ const AuthenticationOauth2 = (props) => { var defaultPrompt = "login" if (prompt !== undefined && prompt !== null && prompt.length > 0) { defaultPrompt = prompt - } + } - var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`; + var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`; - if (admin_consent === true) { - console.log("Running Oauth2 WITH admin consent") - //url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`; - url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`; - } + if (admin_consent === true) { + console.log("Running Oauth2 WITH admin consent") + //url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`; + url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`; + } // Force new consent //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; @@ -997,7 +997,6 @@ const AuthenticationOauth2 = (props) => { color: "white", padding: 5, minWidth: 300, - maxWidth: 300, }} onChange={(e, value) => { //handleScopeChange(e) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 3fb82bc3..8546e887 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -3745,7 +3745,7 @@ If you're interested, please let me know a time that works for you, or set up a aria-label="disabled tabs example" > Edit Details /> - Cloud Synchronization /> + Org Limits & Cloud Sync /> Priorities /> Billing & Stats /> Branding (Beta) /> diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index bc863f68..f9218bc2 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2144,23 +2144,25 @@ const releaseToConnectLabel = "Release to Connect" const executeWorkflow = (executionArgument, startNode, hasSaved) => { + if (hasSaved === false) { + setExecutionRequestStarted(true) + saveWorkflow(workflow, executionArgument, startNode); + //console.log("FIXME: Might have forgotten to save before executing."); + return; + } + + if (workflow.public) { + toast("Save it to get a new version"); + } + + var returncheck = monitorUpdates(); + if (!returncheck) { + toast("No startnode set."); + return; + } + ReactDOM.unstable_batchedUpdates(() => { - if (hasSaved === false) { - setExecutionRequestStarted(true); - saveWorkflow(workflow, executionArgument, startNode); - //console.log("FIXME: Might have forgotten to save before executing."); - return; - } - if (workflow.public) { - toast("Save it to get a new version"); - } - - var returncheck = monitorUpdates(); - if (!returncheck) { - toast("No startnode set."); - return; - } setVisited([]) setExecutionRequest({}) @@ -2168,38 +2170,49 @@ const releaseToConnectLabel = "Release to Connect" // FIXME: Check if any node contains $exec in a param // If they do, show a popup asking if they want to execute it without an execution argument, or to use a previous one - if (executionArgument === undefined || executionArgument === null || executionArgument.length === 0 && workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { - var foundmissing = false - for (let actionkey in workflow.actions) { - if (workflow.actions[actionkey].parameters === undefined || workflow.actions[actionkey].parameters === null || workflow.actions[actionkey].parameters.length === 0) { - continue - } + if (executionArgument === undefined || executionArgument === null || executionArgument.length === 0) - for (let paramkey in workflow.actions[actionkey].parameters) { - const param = workflow.actions[actionkey].parameters[paramkey] - if (param.value === undefined || param.value === null || param.value.length === 0) { + if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { + var foundmissing = false + for (let actionkey in workflow.actions) { + if (workflow.actions[actionkey].parameters === undefined || workflow.actions[actionkey].parameters === null || workflow.actions[actionkey].parameters.length === 0) { continue } - if (param.value.indexOf("$exec") !== -1) { - foundmissing = true + for (let paramkey in workflow.actions[actionkey].parameters) { + const param = workflow.actions[actionkey].parameters[paramkey] + if (param.value === undefined || param.value === null || param.value.length === 0) { + continue + } + + if (param.value.indexOf("$exec") !== -1) { + foundmissing = true + break + } + } + + if (foundmissing) { break } } if (foundmissing) { - break + //toast("This workflow contains a node that requires an execution argument. Please provide one.") + if (workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) { + setExecutionRequestStarted(false) + setExecutionArgumentModalOpen(true) + return + } + + if (workflowExecutions.length > 0) { + setExecutionRequestStarted(false) + setExecutionArgumentModalOpen(true) + + return + } } } - if (foundmissing) { - //toast("This workflow contains a node that requires an execution argument. Please provide one.") - setExecutionRequestStarted(false) - setExecutionArgumentModalOpen(true) - return - } - } - var curelements = cy.elements(); for (let i = 0; i < curelements.length; i++) { curelements[i].addClass("not-executing-highlight"); @@ -10738,7 +10751,7 @@ const releaseToConnectLabel = "Release to Connect" padding: 30, pointerEvents: "auto", color: "white", - minWidth: isMobile ? "90%" : 800, + minWidth: isMobile ? "90%" : 650, border: theme.palette.defaultBorder, }, }} @@ -10754,77 +10767,120 @@ const releaseToConnectLabel = "Release to Connect" style={{ zIndex: 5000, position: "absolute", top: 34, right: 34 }} onClick={(e) => { e.preventDefault(); - setExecutionArgumentModalOpen(false); + setExecutionArgumentModalOpen(false) }} > - Provide a runtime argument + Provide an execution argument - - At least one node in this workflow requires a runtime argument ($exec). Please select one below, or provide a custom one in the text field next to the run button. - - {/* -
    - - { - setExecutionText(e.target.value); - }} - /> - -
    - */} - - {availableArguments.length > 0 ? -
    - - Previously used arguments: - - {availableArguments.map((data) => { - return ( - { - setExecutionText(data) - executeWorkflow(data, workflow.start, lastSaved); - setExecutionArgumentModalOpen(false) + {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ? +
    + {workflow.input_questions.map((question, index) => { + + return ( +
    + {question.name} + -
    - - {data} - - - ) - })} -
    - : null} + fullWidth={true} + placeholder="" + id="emailfield" + margin="normal" + variant="outlined" + onBlur={(e) => { + var newtext = {} + if (executionText.length > 0) { + try { + newtext = JSON.parse(executionText) + // Check if list or object, then make it object only + if (Array.isArray(newtext)) { + newtext = {} + } + } catch (e) { + console.log("Error parsing JSON: ", e) + } + } - + newtext[question.value] = e.target.value + setExecutionText(JSON.stringify(newtext)) + }} + /> +
    + ) + })} + + +
    + : +
    + + At least one node in this workflow requires an execution argument ($exec). Please select one below, or provide a custom one in the text field next to the run button. + + + + {availableArguments.length > 0 ? +
    + + Previously used arguments: + + {availableArguments.map((data) => { + return ( + { + setExecutionText(data) + executeWorkflow(data, workflow.start, lastSaved); + + setExecutionArgumentModalOpen(false) + }} + > +
    + + {data} + + + ) + })} +
    + : null} + + +
    + } @@ -14811,6 +14867,7 @@ const releaseToConnectLabel = "Release to Connect"
    ) : null} + {workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ? ( @@ -14842,6 +14899,7 @@ const releaseToConnectLabel = "Release to Connect" }} /> ) : null} + {workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[ @@ -14874,6 +14932,39 @@ const releaseToConnectLabel = "Release to Connect" ) : null}
    + +
    + Enabled Input-Questions + {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ? +
    + {workflow.input_questions.map((question, index) => { + const selectionClick = () => { + console.log("Clicked input question: ", question) + console.log("PARAMS: ", workflow.triggers[selectedTriggerIndex].parameters) + } + + return ( +
    { + selectionClick() + }}> + + + {question.name} + +
    + ) + })} +
    + : +
    { + setEditWorkflowModalOpen(true) + }}> + No Input-Questions found. Click to add them! +
    + } +
    const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null : @@ -16441,7 +16532,7 @@ const releaseToConnectLabel = "Release to Connect" - ); + ) return (
    diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index d3c529b3..52b9cd1d 100755 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -166,7 +166,7 @@ const LoginDialog = (props) => { } else { if (responseJson["reason"] === "MFA_REDIRECT") { setLoginInfo( - "MFA required. Please the 6-digit code from your authenticator" + "MFA required. Please enter the 6-digit code from your authenticator" ); setMFAField(true); return; diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index ffdf0ec4..0b642d56 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -976,15 +976,16 @@ const Workflows = (props) => { }, 1000); } else if (selectedWorkflowIndexes.length > 0) { // Do backwards so it doesn't change + toast("Starting deletion of workflows. This might take a while.") for (var i = selectedWorkflowIndexes.length - 1; i >= 0; i--) { const workflow = filteredWorkflows[selectedWorkflowIndexes[i]-1] if (workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null) { - deleteWorkflow(workflow.id); + deleteWorkflow(workflow.id, true) } } setTimeout(() => { - getAvailableWorkflows(); + getAvailableWorkflows() }, 1000); setSelectedWorkflowIndexes([]); @@ -1115,7 +1116,7 @@ const Workflows = (props) => { }) } - const getAvailableWorkflows = () => { + const getAvailableWorkflows = (amount) => { var storageWorkflows = [] try { const storagewf = localStorage.getItem("workflows") @@ -1132,7 +1133,12 @@ const Workflows = (props) => { //console.log("Failed to get workflows from localstorage: ", e) } - fetch(globalUrl + "/api/v1/workflows", { + var url = `${globalUrl}/api/v1/workflows` + if (amount !== undefined && amount !== null) { + url += `?top=${amount}` + } + + fetch(url, { method: "GET", headers: { "Content-Type": "application/json", @@ -1338,8 +1344,21 @@ const Workflows = (props) => { setView(tmpView); } + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundTab = params["top"]; + if (foundTab !== null && foundTab !== undefined) { + // Check if it's a number + if (isNaN(foundTab)) { + getAvailableWorkflows() + } else { + getAvailableWorkflows(foundTab) + } + } else { + getAvailableWorkflows() + } + getApps() - getAvailableWorkflows(); getFramework() } }, []) @@ -1836,7 +1855,7 @@ const Workflows = (props) => { }) } - const deleteWorkflow = (id) => { + const deleteWorkflow = (id, bulk) => { fetch(globalUrl + "/api/v1/workflows/" + id, { method: "DELETE", headers: { @@ -1850,15 +1869,19 @@ const Workflows = (props) => { console.log("Status not 200 for setting workflows :O!"); toast("Failed deleting workflow. Do you have access?"); } else { - toast("Deleted workflow " + id); + if (bulk !== true) { + toast("Deleted workflow " + id); + } } return response.json(); }) .then(() => { - setTimeout(() => { - getAvailableWorkflows(); - }, 1000); + if (bulk !== true) { + setTimeout(() => { + getAvailableWorkflows(); + }, 1000); + } }) .catch((error) => { toast(error.toString()); @@ -2962,7 +2985,7 @@ const Workflows = (props) => { className={classes.datagrid} rows={rows} columns={columns} - pageSize={25} + pageSize={100} checkboxSelection autoHeight density="standard" From 5322599c0ae5b56864e2eb2aac2102b9328077f3 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 21 Jun 2024 06:21:40 +0530 Subject: [PATCH 045/336] feat[k8s-keep-alive: making it work --- functions/onprem/orborus/orborus.go | 121 +++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 3 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index a7e1031b..e23a715b 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -50,7 +50,9 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/util/intstr" + ) // Starts jobs in bulk, so this could be increased @@ -666,7 +668,11 @@ func deployK8sWorker(image string, identifier string, env []string) error { if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 { env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_HOST=%s", os.Getenv("KUBERNETES_SERVICE_HOST"))) - } + } + + if len(os.Getenv("SHUFFLE_MEMCACHED")) > 0 { + env = append(env, fmt.Sprintf("SHUFFLE_MEMCACHED=%s", os.Getenv("SHUFFLE_MEMCACHED"))) + } if len(os.Getenv("KUBERNETES_SERVICE_PORT")) > 0 { env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_PORT=%s", os.Getenv("KUBERNETES_SERVICE_PORT"))) @@ -787,7 +793,8 @@ func deployK8sWorker(image string, identifier string, env []string) error { }, Spec: corev1.PodSpec{ RestartPolicy: "Never", - DNSPolicy: "Default", + // DNSPolicy: "Default", + DNSPolicy: corev1.DNSClusterFirst, // NodeSelector: map[string]string{ // "node": "master", // }, @@ -1442,6 +1449,115 @@ func main() { log.Printf("[INFO] Running inside k8s cluster") } + if isKubernetes == "true" { + clientset, _, err := shuffle.GetKubernetesClient() + if err != nil { + log.Printf("[ERROR] Error getting kubernetes client:", err) + os.Exit(1) + } + + kubernetesNamespace := "default" + + // Check if namespace exist as variable. If so, make it + if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 && !namespacemade { + kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") + } + + // fix roles + // check if "service-creator" role is assigned to the service account "default" + roleBindingName := "service-creator-binding" + serviceAccountName := "default" + // Check if the RoleBinding exists + roleBinding, err := clientset.RbacV1().RoleBindings(kubernetesNamespace).Get(context.TODO(), roleBindingName, metav1.GetOptions{}) + if err != nil { + log.Printf("[WARNING] Failed to get RoleBinding %s: %s", roleBindingName, err) + // create role and rolebinding + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleBindingName, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"services"}, + Verbs: []string{"create"}, + }, + }, + } + + ctx := context.TODO() + + _, err := clientset.RbacV1().Roles(kubernetesNamespace).Create(ctx, role, metav1.CreateOptions{}) + if err != nil { + log.Printf("[ERROR] Failed to create Role %s: %s", roleBindingName, err) + if !strings.Contains(fmt.Sprintf("%s", err), "already exists") { + log.Printf("[INFO] role %s already exists", roleBindingName) + } + } + + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleBindingName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: serviceAccountName, + Namespace: kubernetesNamespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "Role", + Name: roleBindingName, + }, + } + + + + _, err = clientset.RbacV1().RoleBindings(kubernetesNamespace).Create(ctx, roleBinding, metav1.CreateOptions{}) + if err != nil { + log.Printf("[ERROR] Failed to create RoleBinding %s: %s", roleBindingName, err) + if !strings.Contains(fmt.Sprintf("%s", err), "already exists") { + log.Printf("[INFO] rolebinding %s already exists", roleBindingName) + } + } + + + log.Printf("[INFO] Created Role %s and RoleBinding %s", roleBindingName, roleBindingName) + } else { + log.Printf("[INFO] RoleBinding %s exists", roleBindingName) + } + + // Check if the RoleBinding is assigned to the service account + var found bool + for _, subject := range roleBinding.Subjects { + if subject.Kind == "ServiceAccount" && subject.Name == serviceAccountName { + found = true + break + } + } + + if !found { + log.Printf("[WARNING] Service account %s is not assigned to RoleBinding %s\n", serviceAccountName, roleBindingName) + // assign the service account to the rolebinding + roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{ + Kind: "ServiceAccount", + Name: serviceAccountName, + Namespace: kubernetesNamespace, + }) + + ctx := context.TODO() + + _, err := clientset.RbacV1().RoleBindings(kubernetesNamespace).Update(ctx, roleBinding, metav1.UpdateOptions{}) + if err != nil { + log.Printf("[ERROR] Failed to update RoleBinding %s: %s", roleBindingName, err) + if !strings.Contains(fmt.Sprintf("%s", err), "already exists") { + log.Printf("[INFO] rolebinding %s already exists", roleBindingName) + } + } + } + } + startupDelay := os.Getenv("SHUFFLE_ORBORUS_STARTUP_DELAY") if len(startupDelay) > 0 { log.Printf("[DEBUG] Setting startup delay to %#v", startupDelay) @@ -1543,7 +1659,6 @@ func main() { } if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" { - if isKubernetes != "true" { checkSwarmService(ctx) log.Printf("[DEBUG] Cleaning up containers from previous run") From 77e7a4a371a063761004bef10b6039b61a85d746 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 21 Jun 2024 17:31:43 +0530 Subject: [PATCH 046/336] Node Drag edge connection issue --- frontend/src/views/AngularWorkflow.jsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 0a17d93d..cca097d8 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -3817,9 +3817,15 @@ const releaseToConnectLabel = "Release to Connect" if (connected.length > 0 && connected !== undefined) { for (let connectkey in connected) { const edge = connected[connectkey] - //console.log("EDGE:", edge) - - //const edge = edgeBase.json() + if (edge.data.decorator && edge.data.label === releaseToConnectLabel) { + // Transform to normal edge + const currentedge = cy.getElementById(edge.data.id) + if (currentedge !== undefined && currentedge !== null) { + currentedge.data("decorator", false) + currentedge.data("label", "") + } + continue + } const sourcenode = cy.getElementById(edge.data.source) const destinationnode = cy.getElementById(edge.data.target) From ad9743af10927e63866504c72bd307d4fae98a8c Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 21 Jun 2024 15:12:40 +0200 Subject: [PATCH 047/336] Fixed workflow editing things --- frontend/src/components/EditWorkflow.jsx | 75 ++++++++++------- frontend/src/components/Oauth2Auth.jsx | 18 ++-- frontend/src/views/Admin.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 102 +++++++++++++++++++++-- frontend/src/views/Workflows.jsx | 13 +-- 5 files changed, 153 insertions(+), 57 deletions(-) diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index 4b4aa3d5..cc4a33b4 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -338,26 +338,6 @@ const EditWorkflow = (props) => { autoFocus fullWidth /> -
    - { - setDescription(event.target.value) - }} - InputProps={{ - style: { - color: "white", - }, - }} - maxRows={4} - color="primary" - defaultValue={innerWorkflow.description} - placeholder="Description" - multiline - label="Description" - margin="dense" - fullWidth - /> -
    {usecases !== null && usecases !== undefined && usecases.length > 0 ? @@ -425,27 +405,56 @@ const EditWorkflow = (props) => { fullWidth value={newWorkflowTags} onChange={(chip) => { - console.log("Chip: ", chip) - //newWorkflowTags.push(chip); setNewWorkflowTags(chip); }} + onBlur={(event) => { + if (event.target.value.length === 0) { + return + } + + if (newWorkflowTags.includes(event.target.value)) { + return + } + + newWorkflowTags.push(event.target.value) + setNewWorkflowTags(newWorkflowTags) + + setUpdate(Math.random()) + }} onAdd={(chip) => { - newWorkflowTags.push(chip); - setNewWorkflowTags(newWorkflowTags); + newWorkflowTags.push(chip) + setNewWorkflowTags(newWorkflowTags) }} onDelete={(chip, index) => { console.log("Deleting: ", chip, index) - newWorkflowTags.splice(index, 1); - setNewWorkflowTags(newWorkflowTags); - setUpdate(Math.random()); + newWorkflowTags.splice(index, 1) + setNewWorkflowTags(newWorkflowTags) + setUpdate(Math.random()) }} />
    {showMoreClicked === true ? - - - +
    + { + setDescription(event.target.value) + }} + InputProps={{ + style: { + color: "white", + }, + }} + multiLine + rows={3} + color="primary" + defaultValue={innerWorkflow.description} + placeholder="Description" + multiline + label="Description" + margin="dense" + fullWidth + />
    @@ -564,6 +573,8 @@ const EditWorkflow = (props) => { fullWidth /> + + MSSP Suborg Distribution (beta - contact support@shuffler.io for more info) @@ -669,6 +680,7 @@ const EditWorkflow = (props) => { } + Input fields @@ -781,6 +793,7 @@ const EditWorkflow = (props) => { + Git Backup Repository @@ -931,7 +944,7 @@ const EditWorkflow = (props) => { - +
    : null} diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index f0c20a64..e9e794b2 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -471,32 +471,26 @@ const AuthenticationOauth2 = (props) => { state += `%26refresh_uri%3d${authentication_url}`; } - // No prompt forcing - //var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`; + // FIXME: Should this be =consent? var defaultPrompt = "login" if (prompt !== undefined && prompt !== null && prompt.length > 0) { - defaultPrompt = prompt + defaultPrompt = prompt } var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`; - if (admin_consent === true) { console.log("Running Oauth2 WITH admin consent") //url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`; url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`; } - // Force new consent + console.log("OAUTH2 URL: ", url) + + // Force new consent //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; - // Admin consent + // Admin consent //const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent` - - // &resource=https%3A%2F%2Fgraph.microsoft.com& - - // FIXME: Awful, but works for prototyping - // How can we get a callback properly realtime? - // How can we properly try-catch without breaks on error? try { var newwin = window.open(url, "", "width=582,height=700"); //console.log(newwin) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 8546e887..f9f85761 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -3745,7 +3745,7 @@ If you're interested, please let me know a time that works for you, or set up a aria-label="disabled tabs example" > Edit Details /> - Org Limits & Cloud Sync /> + Limits & Cloud Sync /> Priorities /> Billing & Stats /> Branding (Beta) /> diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 0a17d93d..5df415e8 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -5294,7 +5294,59 @@ const releaseToConnectLabel = "Release to Connect" } setTimeout(() => { - setSelectedTriggerIndex(trigger_index); + if (trigger_index !== -1) { + const trigger = workflow.triggers[trigger_index] + if (trigger !== undefined && trigger !== null) { + + // Autofixer + if (trigger.trigger_type === "USERINPUT") { + const relevantparams = [ + "alertinfo", + "options", + "type", + "email", + "sms", + "subflow", + ] + var foundparams = 0 + for (var paramkey in trigger.parameters) { + if (relevantparams.includes(trigger.parameters[paramkey].name)) { + foundparams++ + } + } + + if (foundparams < 6) { + trigger.parameters = [{ + name: "alertinfo", + value: "Do you want to continue the workflow? Start parameters: $exec", + },{ + name: "options", + value: "boolean", + }, + { + name: "type", + value: "subflow", + }, + { + name: "email", + value: "test@test.com", + }, + { + name: "sms", + value: "0000000", + }, + { + name: "subflow", + value: "", + }] + + workflow.triggers[trigger_index].parameters = trigger.parameters + } + } + } + } + + setSelectedTriggerIndex(trigger_index) setSelectedTrigger(data) setSelectedActionEnvironment(data.env) }, 25) @@ -12382,7 +12434,7 @@ const releaseToConnectLabel = "Release to Connect" setSubworkflow(e.target.value); // Sets the startnode - if (e.target.value.id !== workflow.id) { + if (e.target.value.id !== workflow.id && e.target.value.id.length > 0 ) { console.log("WORKFLOW: ", e.target.value); const startnode = e.target.value.actions.find((action) => action.id === e.target.value.start); @@ -14808,6 +14860,10 @@ const releaseToConnectLabel = "Release to Connect" }) } + if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0 && selectedTriggerIndex >= 0 && selectedTriggerIndex < workflow.triggers.length) { + console.log(workflow.triggers[selectedTriggerIndex]) + } + const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

    @@ -14994,7 +15050,12 @@ const releaseToConnectLabel = "Release to Connect" const newname = (option.name.charAt(0).toUpperCase() + option.name.substring(1)).replaceAll("_", " "); return newname; }} - options={workflows} + options={ + [{ + "id": "", + "name": "No Workflow Selected", + }].concat(workflows) + } fullWidth style={{ backgroundColor: theme.palette.inputColor, @@ -15149,14 +15210,40 @@ const releaseToConnectLabel = "Release to Connect"

    -
    +
    Enabled Input-Questions {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
    {workflow.input_questions.map((question, index) => { + var foundParamIndex = workflow.triggers[selectedTriggerIndex].parameters.findIndex((param) => param.name === "input_questions") + const selectionClick = () => { - console.log("Clicked input question: ", question) - console.log("PARAMS: ", workflow.triggers[selectedTriggerIndex].parameters) + if (foundParamIndex === -1) { + workflow.triggers[selectedTriggerIndex].parameters.push({ + "name": "input_questions", + "value": [], + }) + + foundParamIndex = workflow.triggers[selectedTriggerIndex].parameters.length - 1 + } else { + try { + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = JSON.parse(workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value) + } catch (e) { + console.log("Couldn't parse input questions: ", e) + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = [] + } + } + + if (workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.includes(question.name)) { + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.filter((item) => item !== question.name) + } else { + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.push(question.name) + } + + // Make it back to a string + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = JSON.stringify(workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value) + setWorkflow(workflow) + setUpdate(Math.random()) } return ( @@ -15164,7 +15251,7 @@ const releaseToConnectLabel = "Release to Connect" selectionClick() }}> {question.name} @@ -15176,6 +15263,7 @@ const releaseToConnectLabel = "Release to Connect" :
    { setEditWorkflowModalOpen(true) + toast.info("Expand and scroll down to add input-questions") }}> No Input-Questions found. Click to add them!
    diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 0b642d56..816f17c5 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1162,10 +1162,11 @@ const Workflows = (props) => { }) .then((responseJson) => { if (responseJson !== undefined) { + var newarray = [] - for (var key in responseJson) { - const wf = responseJson[key] - if (wf.public === true) { + for (var wfkey in responseJson) { + const wf = responseJson[wfkey] + if (wf.public === true || wf.hidden === true) { continue } @@ -1178,9 +1179,9 @@ const Workflows = (props) => { var parsedactionlist = []; for (var key in newarray) { const workflow = newarray[key] - if (workflow.status === "production") { - setProdFilter = true - } + //if (workflow.status === "production") { + // setProdFilter = true + //} for (var actionkey in newarray[key].actions) { const action = newarray[key].actions[actionkey]; From 3db9a21a5abc324dc6de4be87008e4a7a37195a8 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 21 Jun 2024 19:27:51 +0530 Subject: [PATCH 048/336] Fixed that pointer issue on TopCytoscapBar --- frontend/src/views/AngularWorkflow.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index cca097d8..35dd0525 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -15712,7 +15712,6 @@ const releaseToConnectLabel = "Release to Connect" right: 0, left: isMobile ? 20 : leftBarSize + 20, top: isMobile ? 30 : appBarSize + 20, - pointerEvents: "none", } @@ -15733,7 +15732,6 @@ const releaseToConnectLabel = "Release to Connect"

    {workflow.name}

    From ef46b5ab9952a43fd730c928a42fde633b9c9d63 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 21 Jun 2024 20:30:42 +0530 Subject: [PATCH 049/336] Fixed that release to connect branch issue --- frontend/src/views/AngularWorkflow.jsx | 97 +++++++++++++++----------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 35dd0525..ed846eec 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -5650,16 +5650,15 @@ const releaseToConnectLabel = "Release to Connect" // Checks for errors in edges when they're added const onEdgeAdded = (event) => { - setLastSaved(false); - const edge = event.target.data(); - - //console.log("edge added: ", edge) + const edge = event.target.data() + //console.log("EDGE ADDED!: ", edge) if (edge.source === undefined && edge.target === undefined) { + // console.log("Edge source and target is undefined") return } if (edge.readded === true) { - console.log("Readded edge - stopping") + // console.log("Readded edge - stopping") event.target.data("readded", false) return @@ -5668,6 +5667,7 @@ const releaseToConnectLabel = "Release to Connect" const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { + // console.log("Source or destination node is undefined") } 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") { @@ -5680,17 +5680,17 @@ const releaseToConnectLabel = "Release to Connect" console.log("Node: ", targetedge) if (targetedge !== -1) { - event.target.remove() //console.log("Found branch already!") toast.error("Triggers can have exactly one target node") + event.target.remove() return // name: "Shuffle Workflow", // name: "User Input", } else { - console.log("Node doesn't already have one") + // console.log("Node doesn't already have one") } }, 50) } @@ -5704,6 +5704,10 @@ const releaseToConnectLabel = "Release to Connect" } } + if (edge.decorator === true) { + // console.log("Doing nothing to branch because decorator") + return + } var targetnode = workflow.triggers.findIndex( (data) => data.id === edge.target @@ -5721,7 +5725,7 @@ const releaseToConnectLabel = "Release to Connect" if (eventTarget.data("isButton") === true) { const parentNode = cy.getElementById(eventTarget.data("attachedTo")) event.target.remove() - console.log("Setting it to parentnode: ", parentNode.data()) + // console.log("Setting it to parentnode: ", parentNode.data()) if (parentNode !== undefined && parentNode !== null) { //event.target.data("target", eventTarget.data("attachedTo")) @@ -5743,15 +5747,14 @@ const releaseToConnectLabel = "Release to Connect" } } - if ( - eventTarget.data("isDescriptor") === true || - eventTarget.data("type") === "COMMENT" - ) { - console.log("Removing because of descriptor or comment") - event.target.remove(); - return; + if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") { + // console.log("Removing because of descriptor or comment") + event.target.remove() + return } + + setLastSaved(false) targetnode = -1; // Check if: @@ -5759,38 +5762,51 @@ const releaseToConnectLabel = "Release to Connect" // 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 - ) { - toast("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 - ) { - //toast("That branch already exists"); - event.target.remove(); + const branches = cy.edges().jsons() + + const startNode = cy.nodes().jsons().find((node) => node.data.isStartNode === true) + var startnodeId = workflow.start + if (startNode !== undefined && startNode !== null) { + startnodeId = startNode.data.id + } + + //for (let branchkey in workflow.branches) { + for (let branchkey in branches) { + const branch = branches[branchkey].data + + //if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { + if (branch.target === edge.source && branch.source === edge.target) { + toast("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) { + } else if (branch.target === edge.target && branch.source === edge.source) { + + if (branch.conditions === undefined) { + // Edgehandles + } else { + // console.log("Removing because the same branch already exists") + event.target.remove() + + found = true + break + } + } else if (edge.target === startnodeId) { + targetnode = workflow.triggers.findIndex((data) => data.id === edge.source) - 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") { - toast("Can't make arrow to starting node"); - event.target.remove(); - break; + toast("Can't make arrow to starting node") + event.target.remove() + break } found = true; } - } else if (edge.source === workflow.branches[branchkey].source_id) { + //} else if (edge.source === workflow.branches[branchkey].source_id) { + } else if (edge.source === branch.source) { // FIXME: Verify multi-target for triggers // 1. Check if destination exists // 2. Check if source is a trigger @@ -5834,7 +5850,6 @@ const releaseToConnectLabel = "Release to Connect" newdst !== null ) { const dstdata = RunAutocompleter(newdst.data()); - //console.log("DST Autocompleter: ", dstdata); } var newbranch = { From 4276717ff6e86f8c1e8f6b307f1a234de1ff8133 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 21 Jun 2024 22:35:42 +0530 Subject: [PATCH 050/336] Other small fixes --- frontend/src/views/AngularWorkflow.jsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index ed846eec..81a76c41 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -12501,9 +12501,9 @@ const releaseToConnectLabel = "Release to Connect" let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) console.log("LIST WITH VALUES: ", listWithValues) - if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) { - return "no-overrides"; - } + // if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) { + // return "no-overrides"; + // } for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] @@ -18389,7 +18389,7 @@ const releaseToConnectLabel = "Release to Connect"
    {foundnotifications > 0 ? - + { @@ -21401,7 +21401,11 @@ const releaseToConnectLabel = "Release to Connect" //cy.remove('*') setElements([]) } - + + // // Remove all edges + // cy.edges().remove() + // cy.nodes().remove() + // Remove all cy nodes setTimeout(() => { setupGraph(newrevision) From 52227397a9d0118b4a574b77dfbe1bfe9b4780c7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 22 Jun 2024 23:44:01 +0200 Subject: [PATCH 051/336] Fixed user input to work better --- backend/go-app/walkoff.go | 2 +- frontend/src/components/EditWorkflow.jsx | 4 +- frontend/src/views/AngularWorkflow.jsx | 39 ++-- frontend/src/views/RunWorkflow.jsx | 245 ++++++++++++++++++----- 4 files changed, 211 insertions(+), 79 deletions(-) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 8ea1550a..7ff135e3 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1076,7 +1076,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request // return workflowExecution, fmt.Sprintf("%s", err), nil } else { log.Printf("[ERROR] Failed in prepareExecution: '%s'", err) - return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err + return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed running: %s", err), err } } diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index cc4a33b4..e2443664 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -685,8 +685,8 @@ const EditWorkflow = (props) => { Input fields - - Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the workflow run page. + + Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the workflow run page. If chosen in the User Input node, these will be required fields. diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index fb28083d..baab3dab 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1073,22 +1073,20 @@ const releaseToConnectLabel = "Release to Connect" // User Input & Subflow nodes if (param.name === "workflow" || param.name === "subflow") { - const paramIndex = param.name === "workflow" ? 0 : 5 - console.log("Current vs new: ", workflow.triggers[trigger_index].parameters[paramIndex].value, subworkflow.id) - - if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) { - 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); - } - } - } - } + const paramIndex = param.name === "workflow" ? 0 : 5 + if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) { + 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) { @@ -3255,7 +3253,6 @@ const releaseToConnectLabel = "Release to Connect" const getChildWorkflows = (parentWorkflowId) => { if (originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0) { - console.log("No suborg distribution") return } @@ -14866,10 +14863,6 @@ const releaseToConnectLabel = "Release to Connect" }) } - if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0 && selectedTriggerIndex >= 0 && selectedTriggerIndex < workflow.triggers.length) { - console.log(workflow.triggers[selectedTriggerIndex]) - } - const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

    @@ -15217,7 +15210,7 @@ const releaseToConnectLabel = "Release to Connect"

    - Enabled Input-Questions + Required Input-Questions {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
    {workflow.input_questions.map((question, index) => { @@ -17156,10 +17149,12 @@ const releaseToConnectLabel = "Release to Connect" //defaultReturn = return null; } else { + /* console.log( "Unable to handle invalid trigger type " + selectedTrigger.trigger_type ); + */ return null; } } else if (Object.getOwnPropertyNames(selectedEdge).length > 0) { diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx index ee1802d7..4d8dc1bf 100644 --- a/frontend/src/views/RunWorkflow.jsx +++ b/frontend/src/views/RunWorkflow.jsx @@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react'; import ReactDOM from "react-dom" +import { ToastContainer, toast } from "react-toastify" import { useInterval } from "react-powerhooks"; import { makeStyles } from '@mui/material/styles'; import { useNavigate, Link, useParams } from "react-router-dom"; @@ -43,7 +44,8 @@ const bodyDivStyle = { const RunWorkflow = (defaultprops) => { const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops; - let navigate = useNavigate(); + let navigate = useNavigate(); + const [_, setUpdate] = useState(""); // Used to force rendring, don't remove const [message, setMessage] = useState(""); const [workflow, setWorkflow] = React.useState({}); const [executionRequest, setExecutionRequest] = React.useState({}); @@ -55,6 +57,7 @@ const RunWorkflow = (defaultprops) => { const [selectedOrganization, setSelectedOrganization] = React.useState(undefined); const [apps, setApps] = React.useState([]); const [buttonClicked, setButtonClicked] = React.useState(""); + const [foundSourcenode, setFoundSourcenode] = React.useState(undefined); const boxStyle = { color: "white", @@ -340,6 +343,8 @@ const RunWorkflow = (defaultprops) => { event.preventDefault() } + console.log("ONSUBMIT: ", event, execution_id, authorization, answer) + stop() setMessage("") setExecutionLoading(true) @@ -389,12 +394,27 @@ const RunWorkflow = (defaultprops) => { url += `?reference_execution=${execution_id}&authorization=${authorization}&answer=${answer}` data = {} fetchBody.method = "GET" + + if (executionArgument !== undefined && executionArgument !== null) { + try { + if (typeof executionArgument === "string") { + url += "¬e=" + executionArgument + } else { + url += "¬e=" + JSON.stringify(executionArgument) + } + } catch (e) { + url += "¬e=" + executionArgument + } + } + } else { fetchBody.method = "POST" fetchBody.body = JSON.stringify(data) } - console.log("Pre request: ", url, fetchBody) + // IF there is an execution argument, we should use it + console.log("FULL URL: ", url) + fetch(url, fetchBody) .then((response) => { if (response.status !== 200 && response.status !== 201) { @@ -408,16 +428,26 @@ const RunWorkflow = (defaultprops) => { }) start(); - return + return response.json() } } - return response.json(); + return response.json() }) .then(responseJson => { setExecutionLoading(false) - if (responseJson["success"] === false) { + if (responseJson.success === false) { console.log("Failed sending execution request") + if (responseJson.reason !== undefined && responseJson.reason !== null) { + toast.warn(responseJson.reason) + } + + stop() + setMessage("") + setExecutionData({}) + setExecutionInfo("") + setExecutionRunning(false) + setExecutionRequest({}) } else { console.log("Started execution") @@ -426,17 +456,24 @@ const RunWorkflow = (defaultprops) => { } else { setExecutionRunning(true); setExecutionRequest(responseJson) - start(); + start() } } }) .catch(error => { //setExecutionInfo("Error in workflow startup: " + error) + toast.warn("Error in workflow startup: " + error) + + stop() + setMessage("") + setExecutionData({}) + setExecutionInfo("") + setExecutionLoading(false) }) } - const getWorkflow = (workflow_id) => { + const getWorkflow = (workflow_id, selectedNode) => { fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { method: "GET", headers: { @@ -480,6 +517,48 @@ const RunWorkflow = (defaultprops) => { setExecutionArgument(newexec) } + if (selectedNode !== undefined && selectedNode !== null && selectedNode.length > 0) { + var found = false + for (var actionkey in responseJson.actions) { + if (responseJson.actions[actionkey].id === selectedNode) { + found = true + setFoundSourcenode(responseJson.actions[actionkey]) + break + } + } + + if (!found) { + for (var triggerkey in responseJson.triggers) { + if (responseJson.triggers[triggerkey].id !== selectedNode) { + continue + } + + setFoundSourcenode(responseJson.triggers[triggerkey]) + + if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0 && responseJson.triggers[triggerkey].trigger_type === "USERINPUT") { + + // Look for input questions param + for (var paramkey in responseJson.triggers[triggerkey].parameters) { + if (responseJson.triggers[triggerkey].parameters[paramkey].name === "input_questions") { + + var relevantquestions = [] + for (var questionkey in responseJson.input_questions) { + if (responseJson.triggers[triggerkey].parameters[paramkey].value.includes(responseJson.input_questions[questionkey].name)) { + relevantquestions.push(responseJson.input_questions[questionkey]) + } + } + + responseJson.input_questions = relevantquestions + } + } + } + + + break + } + } + } + handleGetOrg(responseJson.org_id) setWorkflow(responseJson); }) @@ -489,7 +568,7 @@ const RunWorkflow = (defaultprops) => { }; const { start, stop } = useInterval({ - duration: 3000, + duration: 1500, startImmediate: true, callback: () => { fetchUpdates(executionRequest.execution_id, executionRequest.authorization) @@ -526,15 +605,11 @@ const RunWorkflow = (defaultprops) => { for (var key in responseJson.results) { if (responseJson.results[key].status === "WAITING") { - console.log("Found: ", responseJson.results[key]) - const validate = validateJson(responseJson.results[key].result) - console.log("Validate: ", validate) if (validate.valid && typeof validate.result === "string") { validate.result = JSON.parse(validate.result) } - console.log("Newresult: ", validate.result) if (validate.result["information"] !== undefined && validate.result["information"] !== null) { setWorkflowQuestion(validate.result["information"]) } @@ -623,15 +698,15 @@ const RunWorkflow = (defaultprops) => { return } - fetch(globalUrl + "/api/v1/streams/results", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(innerRequest), - credentials: "include", - }) + fetch(globalUrl + "/api/v1/streams/results", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(innerRequest), + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for stream results :O!"); @@ -651,11 +726,14 @@ const RunWorkflow = (defaultprops) => { }); }; - const answer = new URLSearchParams(window.location.search).get("answer") - const execution_id = new URLSearchParams(window.location.search).get("reference_execution") - const authorization = new URLSearchParams(window.location.search).get("authorization") + const searchParams = new URLSearchParams(window.location.search) + const answer = searchParams.get("answer") + const execution_id = searchParams.get("reference_execution") + const authorization = searchParams.get("authorization") + const sourceNode = searchParams.get("source_node") + useEffect(() => { - getWorkflow(props.match.params.key) + getWorkflow(props.match.params.key, sourceNode) if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) { console.log("Get execution: ", execution_id) fetchUpdates(execution_id, authorization, true) @@ -667,13 +745,60 @@ const RunWorkflow = (defaultprops) => { }, []) + useEffect(() => { + if (executionData === undefined || executionData === null || executionData === {}) { + return + } + + if (foundSourcenode === undefined || foundSourcenode === null || foundSourcenode === {}) { + return + } + + if (foundSourcenode.trigger_type !== "USERINPUT") { + return + } + + if (executionData.results === undefined || executionData.results === null || executionData.results.length === 0) { + return + } + + for (var resultkey in executionData.results) { + const result = executionData.results[resultkey] + if (result.action.id !== foundSourcenode.id) { + continue + } + + var parsedresult = result.result + try { + parsedresult = JSON.parse(parsedresult) + } catch (e) { + console.log("Error parsing result: ", e) + } + + if (result.status !== "WAITING") { + if (parsedresult.click_info !== undefined && parsedresult.click_info !== null) { + if (parsedresult.click_info.user !== undefined && parsedresult.click_info.user !== null && parsedresult.click_info.user.length > 0) { + setMessage("Already answered by " + parsedresult.click_info.user) + } + } else { + setMessage("Answered.") + } + + } + + break + } + + }, [executionData, foundSourcenode]) const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)" const buttonStyle = {borderRadius: 25, height: 50, fontSize: 18, backgroundImage: handleValidateForm(executionArgument) || executionLoading ? buttonBackground : "grey", color: "white"} - //console.log("execdata: ", executionData) const disabledButtons = message.length > 0 || executionData.status === "FINISHED" || executionData.status === "ABORTED" + //{disabledButtons ? null : + + const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : "Unknown" const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.org !== undefined && selectedOrganization.org !== null? selectedOrganization.org : "support@shuffler.io" //const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.contact !== undefined && selectedOrganization.contact !== null? selectedOrganization.contact : "support@shuffler.io" @@ -724,49 +849,54 @@ const RunWorkflow = (defaultprops) => { {organization} - - {contact} - - {message} + + {disabledButtons && message.length > 0 ? null : + + {message} + + } {answer !== undefined && answer !== null ? null : - {workflow.name} + {workflow.name} } {workflowQuestion.length > 0 ? - - {workflowQuestion} - +
    + + {workflowQuestion} + +
    : null} {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
    {workflow.input_questions.map((question, index) => { - return (
    {question.name} { + onBlur={(e) => { //setExecutionArgument(e.target.value) executionArgument[question.value] = e.target.value + setUpdate(Math.random()) }} />
    @@ -813,20 +943,27 @@ const RunWorkflow = (defaultprops) => { : null} - : - answer !== undefined && answer !== null ? + ((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ? - - {disabledButtons ? "Already answered. Nothing to do." : ""} - + + {disabledButtons && message.length > 0 ? + + {message}. You may close this window. + + : + + {disabledButtons ? "Answered. You may close this window." : ""} + + } + {disabledButtons ? null : What do you want to do? }
    -
    } - {buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ? + {/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ? finalize workflow animation { console.log("Img loaded.") @@ -868,7 +1005,7 @@ const RunWorkflow = (defaultprops) => { }} /> - : null} + : null*/}
    {executionInfo} From fcafc620ae9482450ce597e5f48384323aaf905c Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 24 Jun 2024 14:23:44 +0530 Subject: [PATCH 052/336] Changed the UI for the Workflow revisions --- frontend/src/views/AngularWorkflow.jsx | 106 ++++++++++++++++--------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 81a76c41..2517c26c 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2094,12 +2094,14 @@ const releaseToConnectLabel = "Release to Connect" } setWorkflow(workflow); + setSelectedRevision(workflow); } setSavingState(1); setTimeout(() => { setSavingState(0); }, 1500); + getRevisionHistory(useworkflow.id) } }) .catch((error) => { @@ -17027,6 +17029,10 @@ const releaseToConnectLabel = "Release to Connect" style={{ height: 50, marginLeft: 10 }} variant={"outlined"} onClick={() => { + if(!lastSaved){ + toast("Save the workflow first") + return + } setShowWorkflowRevisions(true) setSelectedRevision(workflow) //setOriginalWorkflow(workflow) @@ -21335,14 +21341,12 @@ const releaseToConnectLabel = "Release to Connect" }*/ const RevisionBox = (props) => { - const { revision, } = props - - if (revision === undefined || revision === null || revision === {}) { + const { revision, } = props + if (revision === undefined || revision === null) { return null } var newrevision = JSON.parse(JSON.stringify(revision)) - // Make unix timestamp into ISO timestamp in the format July 27th, 3:05 AM // Format: July 27th, 3:05 AM //console.log("Edited time: ", revision.edited) @@ -21402,9 +21406,9 @@ const releaseToConnectLabel = "Release to Connect" setElements([]) } - // // Remove all edges - // cy.edges().remove() - // cy.nodes().remove() + // Remove all edges + cy.edges().remove() + cy.nodes().remove() // Remove all cy nodes setTimeout(() => { @@ -21456,6 +21460,7 @@ const releaseToConnectLabel = "Release to Connect" {translatedDate} + {/* {newrevision.edited.toString().slice(6,10)} | {newrevision.revision_id.slice(0,5)} */} @@ -21538,39 +21543,65 @@ const releaseToConnectLabel = "Release to Connect" ) } - - const drawerData = originalWorkflow !== undefined && originalWorkflow !== null && originalWorkflow !== {} ? -
    - + //! Logs + console.log("Selected Revision", selectedRevision) + console.log("Workflow state", workflow) + console.log("All revision", allRevisions) + const drawerData = originalWorkflow !== undefined && originalWorkflow !== null ? +
    + Version History (Beta) +
    +
    +
    + + Current Version + + +
    - + +
    - {allRevisions.length > 0 ? - allRevisions.map((revision, index) => { - if (revision.edited === originalWorkflow.edited) { - return null - } - return ( - - ) - }) - : -
    - - No other revisions found. Save your workflow with changes to create a revision. - -
    - } -
    + {allRevisions.length > 0 ? +
    + { + allRevisions.map((revision, index) => { + if(revision.edited === selectedRevision.edited){ + return null + } + + return ( + + ) + }) + } +
    + + : +
    + + No other revisions found. Save your workflow with changes to create a revision. + +
    + } +
    +
    : null const workflowRevisions = !showWorkflowRevisions ? null : @@ -21581,7 +21612,7 @@ const releaseToConnectLabel = "Release to Connect" onClose={() => { //setShowWorkflowRevisions(false) }} - style={{ resize: "both", overflow: "auto", zIndex: 10005 }} + style={{ resize: "both", overflow: "hidden", zIndex: 10005 }} hideBackdrop={true} variant="persistent" BackdropProps={{ @@ -21592,7 +21623,7 @@ const releaseToConnectLabel = "Release to Connect" PaperProps={{ style: { resize: "both", - overflow: "auto", + overflow: "hidden", minWidth: isMobile ? "100%" : 360, maxWidth: isMobile ? "100%" : 360, backgroundColor: theme.palette.platformColor, @@ -21600,7 +21631,6 @@ const releaseToConnectLabel = "Release to Connect" fontSize: 18, zIndex: 15001, borderRight: theme.palette.defaultBorder, - paddingTop: 15, }, }} > From e1f7a1e9d35811da8de63b082a516eaf26a7a1ec Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 24 Jun 2024 15:28:21 +0200 Subject: [PATCH 053/336] Updated some workflow issues --- backend/go-app/go.mod | 2 +- frontend/src/components/Billing.jsx | 4 +- frontend/src/components/NewHeader.jsx | 3 +- frontend/src/components/ParsedAction.jsx | 30 ++++- frontend/src/views/Admin.jsx | 10 +- frontend/src/views/AngularWorkflow.jsx | 155 ++++++++++++----------- 6 files changed, 119 insertions(+), 85 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index ebe85b01..84fcf764 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -20,7 +20,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.6.47 + github.com/shuffle/shuffle-shared v0.6.50 golang.org/x/crypto v0.22.0 google.golang.org/api v0.176.1 google.golang.org/grpc v1.63.2 diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index e567a5c7..cb18d20d 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -1775,7 +1775,7 @@ const Billing = (props) => {
    - ))} + )})}
    ); }; @@ -13053,7 +13052,7 @@ const releaseToConnectLabel = "Release to Connect"
    @@ -13758,9 +13757,9 @@ const releaseToConnectLabel = "Release to Connect"
    -
    +
    - Auth Override + Authentication Override
    @@ -15815,7 +15814,7 @@ const releaseToConnectLabel = "Release to Connect"
    + - + + : null} +
    + {/* )} */}
    @@ -1946,6 +2009,7 @@ const AppGrid = (props) => { selectedOptionOfCreatedWith={selectedOptionOfCreatedWith} /> )} + { setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps} setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith} /> +
    diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index d5b33aca..f7598852 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -1623,12 +1623,12 @@ const Billing = (props) => { {userdata.support === true ? : diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index e2443664..46be4138 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -6,6 +6,7 @@ import UsecaseSearch from "../components/UsecaseSearch.jsx" import WorkflowGrid from "../components/WorkflowGrid.jsx" import dayjs from 'dayjs'; import WorkflowTemplatePopup from "./WorkflowTemplatePopup.jsx"; +import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx" import { Badge, @@ -206,6 +207,16 @@ const EditWorkflow = (props) => { Workflows can be built from scratch, or from templates. Usecases can help you discover next steps, and you can search for them directly. Learn more + +
    + +
    + {showUpload === true ?
    @@ -957,7 +968,7 @@ const EditWorkflow = (props) => { }} > {showMoreClicked ? : } - {showMoreClicked ? "Collapse": "Expand"} + {showMoreClicked ? "Less Options": "More Options"}
    diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx index 1c12d7fd..014fa46f 100644 --- a/frontend/src/components/LicencePopup.jsx +++ b/frontend/src/components/LicencePopup.jsx @@ -515,16 +515,16 @@ const LicencePopup = (props) => { } useEffect(() => { - console.log("New variant: ", shuffleVariant) + console.log("New variant: ", shuffleVariant) - if (shuffleVariant === 1) { - setCalculatedCost("$600") - setSelectedValue(8) - } else { - setCalculatedCost("$540") - setSelectedValue(300) - } - }, [shuffleVariant]) + if (shuffleVariant === 1) { + setCalculatedCost("$960") + setSelectedValue(8) + } else { + setCalculatedCost("$960") + setSelectedValue(300) + } + }, [shuffleVariant]) if (typeof window === 'undefined' || window.location === undefined) { return null @@ -680,7 +680,7 @@ const LicencePopup = (props) => { color: "white", } - + console.log("Priceitem: ", shuffleVariant) const isLoggedInHandler = () => { if (calculatedCost === payasyougo) { handlePayasyougo(props.userdata) @@ -690,7 +690,7 @@ const LicencePopup = (props) => { const priceItem = window.location.origin === "https://shuffler.io" ? shuffleVariant === 0 ? "app_executions" : "cores" : - shuffleVariant === 0 ? "price_1MROFrDzMUgUjxHShcSxgHO1" : "price_1NXjQqDzMUgUjxHSg690R4FP" + shuffleVariant === 0 ? "price_1PWI5zDzMUgUjxHSKkz0fGdN" : "price_1NXjQqDzMUgUjxHSg690R4FP" const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success` const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure` diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx index 12bc756a..fa626601 100644 --- a/frontend/src/components/NewHeader.jsx +++ b/frontend/src/components/NewHeader.jsx @@ -517,12 +517,11 @@ const Header = (props) => { window.location.href = responseJson["url"] return }, 2000) - } - + } else { setTimeout(() => { window.location.reload() }, 2000); - + } toast("Successfully changed active organization - refreshing!"); } else { if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) { @@ -1547,7 +1546,7 @@ const Header = (props) => {
    {/* Shuffle 1.4.0 is out! Read more about  */} - Shuffle now offers  + Early Success! More  {/* { ReactGA.event({ @@ -1596,10 +1595,11 @@ const Header = (props) => { navigate("/training") - }} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}> - Public Training! + }} style={{ cursor: "pointer", textDecoration: "none", fontWeight:600, color: "rgba(255,255,255,0.8)" }}> + Public Trainings +  Ahead! { setShowTopbar(false) diff --git a/frontend/src/components/OrgHeaderexpanded.jsx b/frontend/src/components/OrgHeaderexpanded.jsx index 4cfbd690..58d1a244 100644 --- a/frontend/src/components/OrgHeaderexpanded.jsx +++ b/frontend/src/components/OrgHeaderexpanded.jsx @@ -1,516 +1,517 @@ -import React, { useEffect } from "react"; - -import { makeStyles } from "@mui/styles"; -import theme from '../theme.jsx'; -import { toast } from "react-toastify" -import Chip from '@mui/material/Chip'; -import Stack from '@mui/material/Stack'; -import SubflowSuggestions from "../components/SubflowSuggestions.jsx"; - -import { - FormControl, - InputLabel, - Paper, - OutlinedInput, - Checkbox, - Card, - Tooltip, - FormControlLabel, - Typography, - Switch, - Select, - MenuItem, - Divider, - TextField, - Button, - Tabs, - Tab, - Grid, - IconButton, - Autocomplete, - Dialog, - DialogTitle, - DialogActions, - DialogContent, - Box -} from "@mui/material"; - -import { - ExpandLess as ExpandLessIcon, - ExpandMore as ExpandMoreIcon, - Save as SaveIcon, -} from "@mui/icons-material"; - -const useStyles = makeStyles({ - notchedOutline: { - borderColor: "#f85a3e !important", - }, -}) - -const OrgHeaderexpanded = (props) => { - const { - userdata, - selectedOrganization, - setSelectedOrganization, - globalUrl, - isCloud, - adminTab, +import React, { useEffect } from "react"; + +import { makeStyles } from "@mui/styles"; +import theme from '../theme.jsx'; +import { toast } from "react-toastify" +import Chip from '@mui/material/Chip'; +import Stack from '@mui/material/Stack'; +import SubflowSuggestions from "../components/SubflowSuggestions.jsx"; + +import { + FormControl, + InputLabel, + Paper, + OutlinedInput, + Checkbox, + Card, + Tooltip, + FormControlLabel, + Typography, + Switch, + Select, + MenuItem, + Divider, + TextField, + Button, + Tabs, + Tab, + Grid, + IconButton, + Autocomplete, + Dialog, + DialogTitle, + DialogActions, + DialogContent, + Box +} from "@mui/material"; + +import { + ExpandLess as ExpandLessIcon, + ExpandMore as ExpandMoreIcon, + Save as SaveIcon, +} from "@mui/icons-material"; + +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important", + }, +}) + +const OrgHeaderexpanded = (props) => { + const { + userdata, + selectedOrganization, + setSelectedOrganization, + globalUrl, + isCloud, + adminTab, selectedStatus, setSelectedStatus, isEditOrgTab - } = props; - - const classes = useStyles(); - const defaultBranch = "master"; - - const [orgName, setOrgName] = React.useState(selectedOrganization.name); - const [orgDescription, setOrgDescription] = React.useState( - selectedOrganization.description - ); - - const [appDownloadUrl, setAppDownloadUrl] = React.useState( - selectedOrganization.defaults === undefined - ? "https://github.com/frikky/shuffle-apps" - : selectedOrganization.defaults.app_download_repo === undefined || - selectedOrganization.defaults.app_download_repo.length === 0 - ? "https://github.com/frikky/shuffle-apps" - : selectedOrganization.defaults.app_download_repo - ); - const [appDownloadBranch, setAppDownloadBranch] = React.useState( - selectedOrganization.defaults === undefined - ? defaultBranch - : selectedOrganization.defaults.app_download_branch === undefined || - selectedOrganization.defaults.app_download_branch.length === 0 - ? defaultBranch - : selectedOrganization.defaults.app_download_branch - ); - const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState( - selectedOrganization.defaults === undefined - ? "https://github.com/frikky/shuffle-apps" - : selectedOrganization.defaults.workflow_download_repo === undefined || - selectedOrganization.defaults.workflow_download_repo.length === 0 - ? "https://github.com/frikky/shuffle-workflows" - : selectedOrganization.defaults.workflow_download_repo - ); - const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState( - selectedOrganization.defaults === undefined - ? defaultBranch - : selectedOrganization.defaults.workflow_download_branch === undefined || - selectedOrganization.defaults.workflow_download_branch.length === 0 - ? defaultBranch - : selectedOrganization.defaults.workflow_download_branch - ); - const [ssoEntrypoint, setSsoEntrypoint] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.sso_entrypoint === undefined || - selectedOrganization.sso_config.sso_entrypoint.length === 0 - ? "" - : selectedOrganization.sso_config.sso_entrypoint - ); - const [ssoCertificate, setSsoCertificate] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.sso_certificate === undefined || - selectedOrganization.sso_config.sso_certificate.length === 0 - ? "" - : selectedOrganization.sso_config.sso_certificate - ); - const [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined - ? false - : selectedOrganization.sso_config.SSORequired === undefined - ? false - : selectedOrganization.sso_config.SSORequired); - - const [notificationWorkflow, setNotificationWorkflow] = React.useState( - selectedOrganization.defaults === undefined - ? "" - : selectedOrganization.defaults.notification_workflow === undefined || - selectedOrganization.defaults.notification_workflow.length === 0 - ? "" - : selectedOrganization.defaults.notification_workflow - ); - - const [documentationReference, setDocumentationReference] = React.useState( - selectedOrganization.defaults === undefined - ? "" - : selectedOrganization.defaults.documentation_reference === undefined || - selectedOrganization.defaults.documentation_reference.length === 0 - ? "" - : selectedOrganization.defaults.documentation_reference - ); - const [openidClientId, setOpenidClientId] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.client_id === undefined || - selectedOrganization.sso_config.client_id.length === 0 - ? "" - : selectedOrganization.sso_config.client_id - ); - const [openidClientSecret, setOpenidClientSecret] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.client_secret === undefined || - selectedOrganization.sso_config.client_secret.length === 0 - ? "" - : selectedOrganization.sso_config.client_secret - ); - const [openidAuthorization, setOpenidAuthorization] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.openid_authorization === undefined || - selectedOrganization.sso_config.openid_authorization.length === 0 - ? "" - : selectedOrganization.sso_config.openid_authorization - ); - const [openidToken, setOpenidToken] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.openid_token === undefined || - selectedOrganization.sso_config.openid_token.length === 0 - ? "" - : selectedOrganization.sso_config.openid_token - ) + } = props; + + const classes = useStyles(); + const defaultBranch = "master"; + + const [orgName, setOrgName] = React.useState(selectedOrganization.name); + const [orgDescription, setOrgDescription] = React.useState( + selectedOrganization.description + ); + + const [appDownloadUrl, setAppDownloadUrl] = React.useState( + selectedOrganization.defaults === undefined + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.app_download_repo === undefined || + selectedOrganization.defaults.app_download_repo.length === 0 + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.app_download_repo + ); + const [appDownloadBranch, setAppDownloadBranch] = React.useState( + selectedOrganization.defaults === undefined + ? defaultBranch + : selectedOrganization.defaults.app_download_branch === undefined || + selectedOrganization.defaults.app_download_branch.length === 0 + ? defaultBranch + : selectedOrganization.defaults.app_download_branch + ); + const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState( + selectedOrganization.defaults === undefined + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.workflow_download_repo === undefined || + selectedOrganization.defaults.workflow_download_repo.length === 0 + ? "https://github.com/frikky/shuffle-workflows" + : selectedOrganization.defaults.workflow_download_repo + ); + const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState( + selectedOrganization.defaults === undefined + ? defaultBranch + : selectedOrganization.defaults.workflow_download_branch === undefined || + selectedOrganization.defaults.workflow_download_branch.length === 0 + ? defaultBranch + : selectedOrganization.defaults.workflow_download_branch + ); + const [ssoEntrypoint, setSsoEntrypoint] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.sso_entrypoint === undefined || + selectedOrganization.sso_config.sso_entrypoint.length === 0 + ? "" + : selectedOrganization.sso_config.sso_entrypoint + ); + const [ssoCertificate, setSsoCertificate] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.sso_certificate === undefined || + selectedOrganization.sso_config.sso_certificate.length === 0 + ? "" + : selectedOrganization.sso_config.sso_certificate + ); + const [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined + ? false + : selectedOrganization.sso_config.SSORequired === undefined + ? false + : selectedOrganization.sso_config.SSORequired); + + const [notificationWorkflow, setNotificationWorkflow] = React.useState( + selectedOrganization.defaults === undefined + ? "" + : selectedOrganization.defaults.notification_workflow === undefined || + selectedOrganization.defaults.notification_workflow.length === 0 + ? "" + : selectedOrganization.defaults.notification_workflow + ); + + const [documentationReference, setDocumentationReference] = React.useState( + selectedOrganization.defaults === undefined + ? "" + : selectedOrganization.defaults.documentation_reference === undefined || + selectedOrganization.defaults.documentation_reference.length === 0 + ? "" + : selectedOrganization.defaults.documentation_reference + ); + const [openidClientId, setOpenidClientId] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.client_id === undefined || + selectedOrganization.sso_config.client_id.length === 0 + ? "" + : selectedOrganization.sso_config.client_id + ); + const [openidClientSecret, setOpenidClientSecret] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.client_secret === undefined || + selectedOrganization.sso_config.client_secret.length === 0 + ? "" + : selectedOrganization.sso_config.client_secret + ); + const [openidAuthorization, setOpenidAuthorization] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.openid_authorization === undefined || + selectedOrganization.sso_config.openid_authorization.length === 0 + ? "" + : selectedOrganization.sso_config.openid_authorization + ); + const [openidToken, setOpenidToken] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.openid_token === undefined || + selectedOrganization.sso_config.openid_token.length === 0 + ? "" + : selectedOrganization.sso_config.openid_token + ) const [uploadRepo, setUploadRepo] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_repo === undefined || selectedOrganization.defaults.workflow_upload_repo.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_repo) const [uploadBranch, setUploadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch === undefined || selectedOrganization.defaults.workflow_upload_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch) const [uploadUsername, setUploadUsername] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_username === undefined || selectedOrganization.defaults.workflow_upload_username.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_username) const [uploadToken, setUploadToken] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_token === undefined || selectedOrganization.defaults.workflow_upload_token.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_token) - - const [workflows, setWorkflows] = React.useState([]) - const [workflow, setWorkflow] = React.useState({}) - - 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) - - if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) { - - const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow) - if (workflow !== undefined && workflow !== null) { - setWorkflow(workflow) - } - } - } - }) - .catch((error) => { - console.log("Error getting workflows: " + error); - }) - } - - useEffect(() => { - getAvailableWorkflows() - }, []) - - const handleEditOrg = ( - name, - description, - orgId, - image, - defaults, - sso_config - ) => { - - const data = { - name: name, - description: description, - org_id: orgId, - image: image, - defaults: defaults, - sso_config: sso_config, - }; - - const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; - fetch(url, { - mode: "cors", - method: "POST", - body: JSON.stringify(data), - credentials: "include", - crossDomain: true, - withCredentials: true, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - toast("Failed updating org: ", responseJson.reason); - } else { - toast("Successfully edited org!"); - } - }) - ) - .catch((error) => { - toast("Err: " + error.toString()); - }); - }; - - - const handleWorkflowSelectionUpdate = (e, isUserinput) => { - if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) { - console.log("Returning as there's no id") - return null - } - - setWorkflow(e.target.value) - setNotificationWorkflow(e.target.value.id) - toast("Updated notification workflow. Don't forget to save!") - } - - const orgSaveButton = ( - -
    - -
    -
    - ); - - const toggleBetweenRequiredOrOptional = (event) => { - setSSORequired(event.target.checked); - }; - - return ( -
    - - - - Notification Workflow - - {/* - - */} - - -
    - {workflows !== undefined && workflows !== null && workflows.length > 0 ? - { - if ( - option === undefined || - option === null || - option.name === undefined || - option.name === null - ) { - return "No Workflow Selected"; - } - - const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - return newname; - }} - options={workflows} - fullWidth - style={{ - backgroundColor: theme.palette.inputColor, - height: 50, - borderRadius: theme.palette.borderRadius, - }} - onChange={(event, newValue) => { - console.log("Found value: ", newValue) - - var parsedinput = { target: { value: newValue } } - - // For variables - if (typeof newValue === 'string' && newValue.startsWith("$")) { - parsedinput = { - target: { - value: { - "name": newValue, - "id": newValue, - "actions": [], - "triggers": [], - } - } - } - } - - handleWorkflowSelectionUpdate(parsedinput) - }} - renderOption={(props, data, state) => { - if (data.id === workflow.id) { - data = workflow; - } - - return ( - - {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} - : null} - - Choose {data.name} - - - } placement="bottom"> - { - var parsedinput = { target: { value: data } } - handleWorkflowSelectionUpdate(parsedinput) - }} - > - {data.name} - - - ) - }} - renderInput={(params) => { - return ( - - ); - }} - /> - : - { - setNotificationWorkflow(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - } -
    - {orgSaveButton} -
    -
    -
    -
    - - - Org Documentation reference - { - setDocumentationReference(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - + + const [workflows, setWorkflows] = React.useState([]) + const [workflow, setWorkflow] = React.useState({}) + + 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) + + if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) { + + const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow) + if (workflow !== undefined && workflow !== null) { + setWorkflow(workflow) + } + } + } + }) + .catch((error) => { + console.log("Error getting workflows: " + error); + }) + } + + useEffect(() => { + getAvailableWorkflows() + }, []) + + const handleEditOrg = ( + name, + description, + orgId, + image, + defaults, + sso_config + ) => { + + const data = { + name: name, + description: description, + org_id: orgId, + image: image, + defaults: defaults, + sso_config: sso_config, + }; + + const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed updating org: ", responseJson.reason); + } else { + toast("Successfully edited org!"); + } + }) + ) + .catch((error) => { + toast("Err: " + error.toString()); + }); + }; + + + const handleWorkflowSelectionUpdate = (e, isUserinput) => { + if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) { + console.log("Returning as there's no id") + return null + } + + setWorkflow(e.target.value) + setNotificationWorkflow(e.target.value.id) + toast("Updated notification workflow. Don't forget to save!") + } + + const orgSaveButton = ( + +
    + +
    +
    + ); + + const toggleBetweenRequiredOrOptional = (event) => { + setSSORequired(event.target.checked); + }; + + return ( +
    + + + + Notification Workflow + + {/* + + */} + + +
    + {workflows !== undefined && workflows !== null && workflows.length > 0 ? + { + if ( + option === undefined || + option === null || + option.name === undefined || + option.name === null + ) { + return "No Workflow Selected"; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + return newname; + }} + options={workflows} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + console.log("Found value: ", newValue) + + var parsedinput = { target: { value: newValue } } + + // For variables + if (typeof newValue === 'string' && newValue.startsWith("$")) { + parsedinput = { + target: { + value: { + "name": newValue, + "id": newValue, + "actions": [], + "triggers": [], + } + } + } + } + + handleWorkflowSelectionUpdate(parsedinput) + }} + renderOption={(props, data, state) => { + if (data.id === workflow.id) { + data = workflow; + } + + return ( + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.name} + : null} + + Choose {data.name} + + + } placement="bottom"> + { + var parsedinput = { target: { value: data } } + handleWorkflowSelectionUpdate(parsedinput) + }} + > + {data.name} + + + ) + }} + renderInput={(params) => { + return ( + + ); + }} + /> + : + { + setNotificationWorkflow(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + } +
    + {orgSaveButton} +
    +
    +
    +
    + + + Org Documentation reference + { + setDocumentationReference(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + Workflow Backup Repository @@ -663,409 +664,409 @@ const OrgHeaderexpanded = (props) => { SSO Configuration -
    - Make SAML SSO or OpenID Authentication Required or Optional for Your Organization. -
    - - {SSORequired ? 'Required' : 'Optional'} -
    -
    - - OpenID connect - - - - Client ID - 0 - } - id="outlined-with-placeholder" - margin="normal" - variant="outlined" - placeholder="The OpenID client ID from the identity provider" - value={openidClientId} - onChange={(e) => { - setOpenidClientId(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - Client Secret (optional) - 0 - } - id="outlined-with-placeholder" - margin="normal" - variant="outlined" - placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE" - value={openidClientSecret} - onChange={(e) => { - setOpenidClientSecret(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - - - Authorization URL - { - setOpenidAuthorization(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - Token URL - { - setOpenidToken(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - {/* } */} - {/*isCloud ? null : */} - - SAML SSO (v1.1) - - - - SSO Entrypoint (IdP) - 0 - } - id="outlined-with-placeholder" - margin="normal" - variant="outlined" - placeholder="The entrypoint URL from your provider" - value={ssoEntrypoint} - onChange={(e) => { - setSsoEntrypoint(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - SSO Certificate (X509) - { - setSsoCertificate(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - {isCloud ? - - IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso - - : null} - - {isCloud ? null : ( - - - App Download URL - { - setAppDownloadUrl(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - )} - {isCloud ? null : ( - - - App Download Branch - { - setAppDownloadBranch(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - )} - {isCloud ? null : ( - - - Workflow Download URL - { - setWorkflowDownloadUrl(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - )} - {isCloud ? null : ( - - - Workflow Download Branch - { - setWorkflowDownloadBranch(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - )} - -
    - {orgSaveButton} -
    - {/* - - {expanded ? - - : - - } - - */} -
    -
    - ) -} - +
    + Make SAML SSO or OpenID Authentication Required or Optional for Your Organization. +
    + + {SSORequired ? 'Required' : 'Optional'} +
    +
    + + OpenID connect + + + + Client ID + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The OpenID client ID from the identity provider" + value={openidClientId} + onChange={(e) => { + setOpenidClientId(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Client Secret (optional) + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE" + value={openidClientSecret} + onChange={(e) => { + setOpenidClientSecret(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + + + Authorization URL + { + setOpenidAuthorization(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Token URL + { + setOpenidToken(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + {/* } */} + {/*isCloud ? null : */} + + SAML SSO (v1.1) + + + + SSO Entrypoint (IdP) + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The entrypoint URL from your provider" + value={ssoEntrypoint} + onChange={(e) => { + setSsoEntrypoint(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + SSO Certificate (X509) + { + setSsoCertificate(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + {isCloud ? + + IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso + + : null} + + {isCloud ? null : ( + + + App Download URL + { + setAppDownloadUrl(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + App Download Branch + { + setAppDownloadBranch(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + Workflow Download URL + { + setWorkflowDownloadUrl(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + Workflow Download Branch + { + setWorkflowDownloadBranch(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + +
    + {orgSaveButton} +
    + {/* + + {expanded ? + + : + + } + + */} +
    +
    + ) +} + export default OrgHeaderexpanded; diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index e3cf400c..09635e31 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -307,7 +307,6 @@ const ParsedAction = (props) => { (action) => action.name.toLowerCase() === selectedAction.name.toLowerCase() ); - console.log("FOUNDACTION: ", foundAction); if (foundAction !== null && foundAction !== undefined) { var foundparams = []; for (let [paramkey,paramkeyval] in Object.entries(foundAction.parameters)) { @@ -549,6 +548,11 @@ const ParsedAction = (props) => { } } } + + if (parentNode.label === undefined) { + parentNode.label = "" + } + newActionList.push({ type: "action", id: parentNode.id, diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 15382695..d7ee12b6 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -5385,13 +5385,17 @@ If you're interested, please let me know a time that works for you, or set up a + {validIcon} style={{ minWidth: 65, maxWidth: 65, }} onClick={() => { + if (data.validation === null || data.validation === undefined) { + return + } + if (data.validation.workflow_id === undefined || data.validation.workflow_id === null || data.validation.workflow_id.length === 0) { toast.warn("No workflow runs found for this auth yet. Check back later.") return diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 7bfd4d0f..d0ef9308 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -139,6 +139,7 @@ import Draggable from "react-draggable"; import cytoscapestyle from "../defaultCytoscapeStyle.jsx"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; +import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx" import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; import { GetParsedPaths, internalIds, } from "../views/Apps.jsx"; import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; @@ -319,6 +320,7 @@ export function SetJsonDotnotation(jsonInput, inputKey) { export const green = "#86c142"; export const yellow = "#FECC00"; +export const red = "red"; export function removeParam(key, sourceURL) { if (sourceURL === undefined) { @@ -1641,7 +1643,6 @@ const releaseToConnectLabel = "Release to Connect" setExecutionData(responseJson) } else { if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING" || responseJson.status === "FINISHED") { - console.log("DONE!") stop() } @@ -6732,7 +6733,6 @@ const releaseToConnectLabel = "Release to Connect" const parentlabel = parentNode.data("label").toLowerCase().replace(" ", "_") const parentname = parentNode.data("app_name").toLowerCase().replace(" ", "_") if (!parentlabel.startsWith(parentname)+"_") { - console.log("Return 1") return } @@ -7135,7 +7135,6 @@ const releaseToConnectLabel = "Release to Connect" // Find how many executions it has var executions = 0 const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase())) - console.log("Matches: ", matchingExecutions.length) const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436" const decoratorNode = { position: { @@ -10204,6 +10203,7 @@ const releaseToConnectLabel = "Release to Connect" // Starts on current node and climbs UP the tree to the root object. // Sends back everything in it's path + // FIXME: Use the GetParentNodes in WorkflowValidationTimeline.jsx instead const getParents = (action) => { if (action === undefined || action === null) { return [] @@ -18717,6 +18717,7 @@ const releaseToConnectLabel = "Release to Connect" : null}
    + {executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
    @@ -18828,12 +18829,31 @@ const releaseToConnectLabel = "Release to Connect" {new Date(executionData.completed_at * 1000).toLocaleString("en-GB")}
    + ) : null} + + {executionData.workflow !== undefined && executionData.workflow !== null && executionData.status !== "EXECUTING" ? +
    + +
    + : null} +
    - {executionData.execution_argument !== undefined && - executionData.execution_argument.length > 0 + + {executionData.execution_argument !== undefined && executionData.execution_argument !== null && + executionData.execution_argument.length > 1 ? parsedExecutionArgument() - : null} + : + null} + + {executionData.results !== undefined && executionData.results !== null && executionData.results.length > 1 && @@ -19534,7 +19555,7 @@ const releaseToConnectLabel = "Release to Connect" if (stringjson.includes("\n") && !stringjson.includes("\n")) { return "Looks like you have a newline problem. Consider using the | replace: '\n', '\\n' }} filter in Liquid." } else { - return "The result looks like it should be JSON, but is invalid. Look for potential" + return "The result looks like it should be JSON, but is invalid. Look for potential single quotes instead of double quotes, missing commas or newlines" } } } From 98224e424615e1fcbcadcaf5d0bab84135902837 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 4 Jul 2024 00:12:14 +0200 Subject: [PATCH 064/336] Added first timeline tracker --- .../components/WorkflowValidationTimeline.jsx | 443 ++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 frontend/src/components/WorkflowValidationTimeline.jsx diff --git a/frontend/src/components/WorkflowValidationTimeline.jsx b/frontend/src/components/WorkflowValidationTimeline.jsx new file mode 100644 index 00000000..cbbb7bd6 --- /dev/null +++ b/frontend/src/components/WorkflowValidationTimeline.jsx @@ -0,0 +1,443 @@ +import React, { useState, } from "react"; +import { makeStyles, createStyles } from "@mui/styles"; +import { toast } from "react-toastify" + +import { + Tooltip, + Typography, + + Avatar, + AvatarGroup, +} from "@mui/material" + +import { + green, + yellow, + red, +} from "../views/AngularWorkflow.jsx" + +import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; +import theme from "../theme.jsx"; +const itemHeight = 40 + +export const getParentNodes = (workflow, action) => { + if (action === undefined || action === null) { + return [] + } + + if (workflow.actions === undefined || workflow.actions === null) { + workflow.actions = [] + } + + if (workflow.triggers === undefined || workflow.triggers === null) { + workflow.triggers = [] + } + + if (workflow.branches === undefined || workflow.branches === null) { + workflow.branches = [] + } + + var allkeys = [action.id]; + var handled = []; + var results = []; + + // maxiter = max amount of parent nodes to loop + // also handles breaks if there are issues + var iterations = 0; + var maxiter = 10; + while (true) { + for (let parentkey in allkeys) { + if (allkeys[parentkey] === undefined) { + continue + } + + var currentnode = workflow.actions.find((element) => element.id === allkeys[parentkey]) + if (currentnode === undefined) { + currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey]) + + if (currentnode === undefined) { + console.log("Could not find parent node for: ", allkeys[parentkey]) + continue + } + } + + if (handled.includes(currentnode.id)) { + continue + } else { + handled.push(currentnode.id); + results.push(currentnode); + } + + // Get the name / label here too? + if (currentnode.length === 0) { + continue; + } + + // FIXME: This part is only handling first level, + // but needs to recurse + var incomingEdges = [] + for (var branchkey in workflow.branches) { + const branch = workflow.branches[branchkey] + if (branch.destination_id !== currentnode.id) { + continue + } + + // Go up in the levels + const parents = getParentNodes(workflow, { + id: branch.source_id, + }) + if (parents.length > 0) { + incomingEdges = incomingEdges.concat(parents) + } + + incomingEdges.push(branch) + } + + for (let i = 0; i < incomingEdges.length; i++) { + var tmp = incomingEdges[i]; + if (tmp.decorator === true) { + continue + } + + if (!allkeys.includes(tmp.source_id)) { + allkeys.push(tmp.source_id) + } + } + } + + if (results.length === allkeys.length || iterations === maxiter) { + break + } + + iterations += 1 + } + + // Remove on the end as we don't want to remove everything + results = results.filter((data) => data.id !== action.id) + results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input" || data.app_name === "shuffle-subflow") + results.push({ label: "Execution Argument", type: "INTERNAL" }) + + return results +} + +const WorkflowValidationTimeline = (props) => { + const { workflow, originalWorkflow, apps, getParents, execution} = props + + + if (workflow === undefined || workflow === null) { + return null + } + + if (workflow.validation === undefined || workflow.validation === null) { + return null + } + + if (workflow.actions === undefined || workflow.actions === null) { + workflow.actions = [] + } + + if (workflow.triggers === undefined || workflow.triggers === null) { + workflow.triggers = [] + + } + + if (workflow.branches === undefined || workflow.branches === null) { + workflow.branches = [] + + } + + var results = [] + if (execution !== undefined) { + results = execution.results + } + + // 1. Find startnode + // 2. Map childnodes from it + const startnodeId = workflow.start + + // Find parent of startnodeId and if it's a webhook + var relevantactions = [] + for (var key in workflow.branches) { + const branch = workflow.branches[key] + if (branch.destination_id !== startnodeId) { + continue + } + + for (var triggerkey in workflow.triggers) { + const trigger = workflow.triggers[triggerkey] + if (trigger.trigger_type !== "WEBHOOK") { + continue + } + + if (trigger.id === branch.source_id) { + trigger.order = -1 + relevantactions.push(trigger) + break + } + } + } + + + if (getParents !== undefined) { + for (var key in workflow.actions) { + const action = workflow.actions[key] + if (action.id === startnodeId) { + action.order = 0 + relevantactions.push(action) + continue + } + + const parents = getParents(action) + //const parents = getParentNodes(workflow, action) + //console.log("PARENTS", key, parents) + if (parents !== undefined && parents !== null) { + const parentfound = parents.find((element) => element.id === startnodeId) + if (parentfound !== undefined) { + + // FIXME: add order here based on how many steps away from the startnode + // This just has the parent count + action.order = parents.length + + relevantactions.push(action) + } + } + } + } else { + for (var key in workflow.triggers) { + const trigger = workflow.triggers[key] + if (trigger.trigger_type !== "SUBFLOW" && trigger.trigger_type !== "USERINPUT") { + continue + } + + if (workflow.actions.find((element) => element.id === trigger.id) === undefined) { + workflow.actions.push(trigger) + } + } + + relevantactions = workflow.actions + } + + // Sort according to how many parents a node has. MAY be wrong~ + relevantactions.sort((a, b) => { + if (a.order === undefined) { + return 1 + } + + if (b.order === undefined) { + return -1 + } + + return a.order - b.order + }) + + // FIXME: Add other relevant items as well from subflows (?) + var nodecolor = "grey" + var branchcolor = "grey" + var skipped = false + + var previousTools = false + + return ( +
    +
    + {relevantactions.map((action, index) => { + action.result = {} + if (results !== undefined) { + const foundResult = results.find((element) => element.action.id === action.id) + if (foundResult !== undefined) { + action.result = foundResult + + action.status = foundResult.status + } + } + + const lastitem = index === relevantactions.length - 1 + if (!lastitem) { + if (action.app_name === "Shuffle Tools") { + if (action.status === "SUCCESS") { + branchcolor = red + + // Check action.result for the actual status + const validate = validateJson(action.result.result) + if (validate.valid) { + if (validate.result.success === true) { + branchcolor = green + } else { + branchcolor = "grey" + } + } + + + } else if (action.status === "SKIPPED") { + branchcolor = "grey" + } else { + if (action.status === undefined) { + branchcolor = green + } else { + branchcolor = red + } + } + + previousTools = true + return null + } else { + if (action.status === "SUCCESS") { + nodecolor = green + } else if (action.status === "SKIPPED") { + nodecolor = "grey" + } else { + if (action.status === undefined) { + nodecolor = green + } else { + nodecolor = red + } + } + } + } else { + nodecolor = "grey" + } + + if (action.status === "SKIPPED") { + skipped = true + } + + var image = "" + if (action.large_image !== undefined && action.large_image !== null && action.large_image !== "") { + image = action.large_image + } else { + if (originalWorkflow !== undefined) { + for (var key in originalWorkflow.actions) { + if (originalWorkflow.actions[key].id === action.id) { + image = originalWorkflow.actions[key].large_image + break + } + } + + if (image === "") { + for (var key in originalWorkflow.triggers) { + if (originalWorkflow.triggers[key].id === action.id) { + image = originalWorkflow.triggers[key].large_image + break + } + } + + } + } + } + + var founderror = "" + if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null) { + const foundError = workflow.validation.errors.find((element) => element.action_id === action.id) + if (foundError !== undefined) { + founderror = foundError.error + nodecolor = yellow + branchcolor = yellow + } + } + + if (skipped && !lastitem) { + nodecolor = "grey" + branchcolor = "grey" + } + + if (previousTools) { + previousTools = false + } else { + branchcolor = nodecolor + } + + var appgroup = [] + if (action.trigger_type === "WEBHOOK") { + nodecolor = green + branchcolor = green + } else if (action.app_name === "shuffle-subflow") { + if (action.status === "SUCCESS") { + nodecolor = green + branchcolor = green + } + + for (var subflowkey in workflow.validation.subflow_apps) { + const subflowApp = workflow.validation.subflow_apps[subflowkey] + if (subflowApp.error === action.id) { + appgroup.push(subflowApp) + } + } + + } + + var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3 + const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : "" + + return ( +
    + {lastitem ? + +
    + + : null} + + {appgroup.length > 0 ? + + {appgroup.map((subflowApp, subflowIndex) => { + var appimage = "" + if (apps !== undefined && apps !== null && apps.length > 0) { + for (var key in apps) { + const app = apps[key] + if (app.name === subflowApp.app_name) { + appimage = apps[key].large_image + break + } + } + } + + return ( + + + + ) + })} + + : + + {founderror.length > 0 ? founderror : ``} + + } placement="top"> + + + {image !== "" ? + + : null} + + + } + + {lastitem ? null : + +
    + + } +
    + ) + })} +
    + +
    + ) +} + +export default WorkflowValidationTimeline From a60fad69d3a77e73b314bc203d9af7c4ed574876 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:22:15 +0530 Subject: [PATCH 065/336] feat[k8s-cleanup]: adding a quick cleanup mechanism --- functions/onprem/orborus/orborus.go | 52 +++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index bb89a83f..6881c4a0 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -177,6 +177,51 @@ func getThisContainerId() { } func cleanupExistingNodes(ctx context.Context) error { + + if isKubernetes == "true" { + if kubernetesNamespace == "" { + kubernetesNamespace = "default" + } + + clientset, _, err := shuffle.GetKubernetesClient() + if err != nil { + log.Printf("[ERROR] Error getting kubernetes client:", err) + return err + } + + // Delete all pods + pods, err := clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Printf("[ERROR] Failed listing pods: %s", err) + return err + } + + for _, pod := range pods.Items { + err := clientset.CoreV1().Pods(kubernetesNamespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{}) + if err != nil { + log.Printf("[ERROR] Failed deleting pod %s: %s", pod.Name, err) + } + } + + // Delete all services + services, err := clientset.CoreV1().Services(kubernetesNamespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Printf("[ERROR] Failed listing services: %s", err) + return err + } + + for _, service := range services.Items { + err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{}) + if err != nil { + log.Printf("[ERROR] Failed deleting service %s: %s", service.Name, err) + } + } + + log.Printf("[INFO] Cleaned up all pods and services in namespace %s", kubernetesNamespace) + return nil + } + + serviceListOptions := types.ServiceListOptions{} services, err := dockercli.ServiceList( context.Background(), @@ -1661,11 +1706,12 @@ func main() { if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" { if isKubernetes != "true" { checkSwarmService(ctx) - log.Printf("[DEBUG] Cleaning up containers from previous run") - cleanupExistingNodes(ctx) - time.Sleep(time.Duration(5) * time.Second) } + log.Printf("[DEBUG] Cleaning up containers from previous run") + cleanupExistingNodes(ctx) + time.Sleep(time.Duration(5) * time.Second) + log.Printf("[DEBUG] Deploying worker image %s to swarm", workerImage) runString := "Run: \"docker service ls\" for more info" From 1f9a132b757af3f48aa021c1a7ff7c21635cd5bc Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 4 Jul 2024 17:28:07 +0530 Subject: [PATCH 066/336] Added branch flip feature --- frontend/src/components/ParsedAction.jsx | 95 +++++++++++++++++++++--- frontend/src/views/AngularWorkflow.jsx | 59 ++++++++------- 2 files changed, 115 insertions(+), 39 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 0d5ef733..9249361f 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useLayoutEffect } from "react"; +import React, { useState, useEffect, useLayoutEffect, useMemo } from "react"; import { toast } from 'react-toastify'; import { makeStyles, createStyles } from "@mui/styles"; import theme from '../theme.jsx'; @@ -207,16 +207,16 @@ const ParsedAction = (props) => { } }, [expansionModalOpen]) - useEffect(() => { - setParamValues(selectedAction.parameters.map((param) => { - return { - name: param.name, - value: param.value, - } - })) - },[ - selectedAction, selectedApp,setNewSelectedAction, workflow, - ]) +// useEffect(() => { +// setParamValues(selectedAction.parameters?.map((param) => { +// return { +// name: param.name, +// value: param.value, +// } +// })) +// },[ +// selectedAction, selectedApp,setNewSelectedAction, workflow, +// ]) useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { @@ -565,6 +565,79 @@ const ParsedAction = (props) => { setActionlist(newActionList); }, [workflow.execution_variables, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents]); + + const memoizedParam = useMemo(() => { + let appActions = []; + if (getParents) { + const parents = getParents(selectedAction); + if (parents.length > 1) { + const labels = []; + for (let parentNode of parents) { + if (parentNode.label !== "Execution Argument" && !labels.includes(parentNode.label)) { + labels.push(parentNode.label); + let exampleData = parentNode.example ?? ""; + if (!exampleData && workflowExecutions.length > 0) { + for (let exec of workflowExecutions) { + const foundResult = exec.results?.find(result => result.action.id === parentNode.id); + if (foundResult) { + const valid = validateJson(foundResult.result); + if (valid.valid && valid.result.success !== false) { + exampleData = valid.result; + break; + } + } + } + } + appActions.push({ + type: "action", + id: parentNode.id, + name: parentNode.label, + autocomplete: parentNode.label.split(" ").join("_"), + example: exampleData, + }); + } + } + } + } + + let newParameters = selectedAction.parameters?.map((param) => { + let paramvalue = param.value; + if(paramvalue.includes("$")){ + let actions = workflow.actions?.map((action) => { + return "$"+action.label.toLowerCase(); + }) + if(actionlist.length > 0){ + let appParentActions = appActions?.map(action => "$" + action.name.toLowerCase()); + let notPresentAction = actions?.filter((action) => !appParentActions?.includes(action)) + console.log("ACTIONS: ", actions) + console.log("APP ACTIONS: ", appParentActions) + console.log("NOT PRESENT: ", notPresentAction) + notPresentAction?.forEach((action) => { + console.log("Not included Action: ", action) + if(paramvalue.includes(action)){ + paramvalue = paramvalue.replace(action, "") + paramvalue = paramvalue.replace(/^\s*[\r\n]/gm, ""); + } + }) + } + } + console.log("After removing param value: ", paramvalue) + return {...param, value: paramvalue} + }); + selectedAction.parameters = newParameters; + setSelectedAction(selectedAction); + return newParameters; + },[actionlist,selectedAction,workflow.actions,workflow,selectedApp,setNewSelectedAction]) + + useEffect(() => { + setParamValues(memoizedParam.map((param) => { + return { + name: param.name, + value: param.value, + } + })) + },[memoizedParam]) + useEffect(() => { selectedNameChange(appActionName) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index e1bb6737..48fd2ff2 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -126,7 +126,7 @@ import { ArrowForward as ArrowForwardIcon, } from "@mui/icons-material"; - +import SwapHorizIcon from '@mui/icons-material/SwapHoriz'; //import * as cytoscape from "cytoscape"; import cytoscape from "cytoscape"; @@ -11672,10 +11672,10 @@ const releaseToConnectLabel = "Release to Connect" : null} -
    - {/* +
    + -
    - - { - setMenuPosition({ - top: event.pageY + 10, - left: event.pageX + 10, - }); - //setShowDropdownNumber(3) - setShowDropdown(true); - }} - /> - + + + { + event.preventDefault() + // setFieldCount(count) + setCodeEditorModalOpen(true) + setActiveDialog("codeeditor") + //setcodedata(data.value) + var parsedvalue = workflow.triggers[selectedTriggerIndex].parameters[1].value + // if (parsedvalue === undefined || parsedvalue === null) { + // parsedvalue = "" + // } + console.log("Data sending to codeeditor: ",{ + "name": workflow.triggers[selectedTriggerIndex].parameters[1].name, + "value": parsedvalue, + "field_number": 1, + "actionlist": actionlist, + "field_id": "subflow_field", + }) + setEditorData({ + "name": workflow.triggers[selectedTriggerIndex].parameters[1].name, + "value": parsedvalue, + "field_number": 1, + "actionlist": actionlist, + "field_id": "subflow_field", + }) + }} + /> + + + { + setMenuPosition({ + top: event.pageY + 10, + left: event.pageX + 10, + }); + //setShowDropdownNumber(3) + setShowDropdown(true); + }} + /> + + ), }} @@ -21767,108 +21803,154 @@ const releaseToConnectLabel = "Release to Connect"
    const changeActionParameterCodeMirror = (event, count, data, actionlist) => { - // Check if event.target.value is an array. If it is, split with comma - console.log("1 - SELECTED ACTION: ", selectedAction) - console.log("1 - DATA: ", data) + + if(selectedAction && selectedAction.parameters && selectedAction.parameters.length > 0){ - if (data.startsWith("${") && data.endsWith("}")) { - // PARAM FIX - Gonna use the ID field, even though it's a hack - const paramcheck = selectedAction.parameters.find(param => param.name === "body") - if (paramcheck !== undefined) { - // Escapes all double quotes - const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\""); - if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { - paramcheck["value_replace"] = [{ - "key": data.name, - "value": toReplace, - }] + // Check if event.target.value is an array. If it is, split with comma + console.log("1 - SELECTED ACTION: ", selectedAction) + console.log("1 - DATA: ", data) - } else { - const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name) - if (subparamindex === -1) { - paramcheck["value_replace"].push({ - "key": data.name, - "value": toReplace, - }) - } else { - paramcheck["value_replace"][subparamindex]["value"] = toReplace - } - } + if (data.startsWith("${") && data.endsWith("}")) { + // PARAM FIX - Gonna use the ID field, even though it's a hack + const paramcheck = selectedAction.parameters.find(param => param.name === "body") + if (paramcheck !== undefined) { + // Escapes all double quotes + const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\""); + if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { + paramcheck["value_replace"] = [{ + "key": data.name, + "value": toReplace, + }] - if (paramcheck["value_replace"] === undefined) { - selectedAction.parameters[count]["value_replace"] = paramcheck - } else { - //selectedActionParameters[count]["value_replace"] = paramcheck["value_replace"] - selectedAction.parameters[count]["value_replace"] = paramcheck["value_replace"] - } - setSelectedAction(selectedAction) - //setUpdate(Math.random()) - return - } - } + } else { + const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name) + if (subparamindex === -1) { + paramcheck["value_replace"].push({ + "key": data.name, + "value": toReplace, + }) + } else { + paramcheck["value_replace"][subparamindex]["value"] = toReplace + } + } - if (event.target.value[event.target.value.length-1] === "." && actionlist.length > 0) { - var curstring = "" - var record = false - for (let [key,keyval] in Object.entries(selectedAction.parameters[count].value)) { - const item = selectedAction.parameters[count].value[key] - if (record) { - curstring += item - } + if (paramcheck["value_replace"] === undefined) { + selectedAction.parameters[count]["value_replace"] = paramcheck + } else { + //selectedActionParameters[count]["value_replace"] = paramcheck["value_replace"] + selectedAction.parameters[count]["value_replace"] = paramcheck["value_replace"] + } + setSelectedAction(selectedAction) + //setUpdate(Math.random()) + return + } + } - if (item === "$") { - record = true - curstring = "" - } - } + if (event.target.value[event.target.value.length-1] === "." && actionlist.length > 0) { + var curstring = "" + var record = false + for (let [key,keyval] in Object.entries(selectedAction.parameters[count].value)) { + const item = selectedAction.parameters[count].value[key] + if (record) { + curstring += item + } - if (curstring.length > 0 && actionlist !== null) { - // Search back in the action list - curstring = curstring.split(" ").join("_").toLowerCase() - var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring) - if (actionItem !== undefined) { - console.log("Found item: ", actionItem) + if (item === "$") { + record = true + curstring = "" + } + } - var jsonvalid = true - try { - const tmp = String(JSON.parse(actionItem.example)) - if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) { - jsonvalid = false - } - } catch (e) { - jsonvalid = false - } - } - } - } + if (curstring.length > 0 && actionlist !== null) { + // Search back in the action list + curstring = curstring.split(" ").join("_").toLowerCase() + var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring) + if (actionItem !== undefined) { + console.log("Found item: ", actionItem) - console.log("2 - SELECTED ACTION: ", selectedAction) - console.log("2 - DATA: ", data) + var jsonvalid = true + try { + const tmp = String(JSON.parse(actionItem.example)) + if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) { + jsonvalid = false + } + } catch (e) { + jsonvalid = false + } + } + } + } - if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && count === 0) { - const parsedvalue = data - console.log("Parsed value: ", parsedvalue) - if (parsedvalue.includes("#")) { - const splitparsed = parsedvalue.split(".#.") - //console.log("Cant contain #: ", splitparsed) - if (splitparsed.length > 1) { - console.log("IN HERE AY") - //data.value = splitparsed[0] + console.log("2 - SELECTED ACTION: ", selectedAction) + console.log("2 - DATA: ", data) - selectedAction.parameters[0].value = splitparsed[0] - selectedAction.parameters[1].value = splitparsed[1] + if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && count === 0) { + const parsedvalue = data + console.log("Parsed value: ", parsedvalue) + if (parsedvalue.includes("#")) { + const splitparsed = parsedvalue.split(".#.") + //console.log("Cant contain #: ", splitparsed) + if (splitparsed.length > 1) { + console.log("IN HERE AY") + //data.value = splitparsed[0] - selectedAction.parameters[0].autocompleted = true - selectedAction.parameters[1].autocompleted = true - setUpdate(Math.random()) - } - } - } else { - selectedAction.parameters[count].autocompleted = false - selectedAction.parameters[count].value = data - } + selectedAction.parameters[0].value = splitparsed[0] + selectedAction.parameters[1].value = splitparsed[1] - setSelectedAction(selectedAction) + selectedAction.parameters[0].autocompleted = true + selectedAction.parameters[1].autocompleted = true + setUpdate(Math.random()) + } + } + } else { + selectedAction.parameters[count].autocompleted = false + selectedAction.parameters[count].value = data + } + + setSelectedAction(selectedAction) + } + + if(selectedTrigger && selectedTrigger.parameters && selectedTrigger.parameters.length > 0){ + + if (event.target.value[event.target.value.length-1] === "." && actionlist.length > 0) { + var curstring = "" + var record = false + for (let [key,keyval] in Object.entries(selectedTrigger.parameters[count].value)) { + const item = selectedTrigger.parameters[count].value[key] + if (record) { + curstring += item + } + + if (item === "$") { + record = true + curstring = "" + } + } + + if (curstring.length > 0 && actionlist !== null) { + // Search back in the action list + curstring = curstring.split(" ").join("_").toLowerCase() + var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring) + if (actionItem !== undefined) { + console.log("Found item: ", actionItem) + + var jsonvalid = true + try { + const tmp = String(JSON.parse(actionItem.example)) + if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) { + jsonvalid = false + } + } catch (e) { + jsonvalid = false + } + } + } + } + + selectedTrigger.parameters[count].value = data + setSelectedTrigger(selectedTrigger); + console.log("get into trigger controller", selectedTrigger,data) + } //setUpdate(Math.random()) } From 025489a5c7afd5ad06152fd271ebfce8fd8fb1fe Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:40:31 +0530 Subject: [PATCH 075/336] fix[k8s]: keeping old core pods (orborus, frontend, backend etc) --- functions/onprem/orborus/orborus.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index b1688487..3fbf16b7 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -179,6 +179,9 @@ func getThisContainerId() { func cleanupExistingNodes(ctx context.Context) error { if isKubernetes == "true" { + // of course, this doesn't clean up "nodes" but + // rather pods, services, roles etc. + if kubernetesNamespace == "" { kubernetesNamespace = "default" } @@ -197,6 +200,16 @@ func cleanupExistingNodes(ctx context.Context) error { } for _, pod := range pods.Items { + // check if pod.Name starts with: + // "backend-", "frontend-", "orborus-", "opensearch-" or "memcached-" + if strings.HasPrefix(pod.Name, "backend-") || + strings.HasPrefix(pod.Name, "frontend-") || + strings.HasPrefix(pod.Name, "orborus-") || + strings.HasPrefix(pod.Name, "opensearch-") || + strings.HasPrefix(pod.Name, "memcached-") { + continue + } + err := clientset.CoreV1().Pods(kubernetesNamespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{}) if err != nil { log.Printf("[ERROR] Failed deleting pod %s: %s", pod.Name, err) @@ -211,6 +224,13 @@ func cleanupExistingNodes(ctx context.Context) error { } for _, service := range services.Items { + if strings.Contains(service.Name, "opensearch") || + strings.Contains(service.Name, "memcached") || + strings.Contains(service.Name, "shuffle-backend") || strings.Contains(service.Name, "backend") || + strings.Contains(service.Name, "frontend") { + continue + } + err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{}) if err != nil { log.Printf("[ERROR] Failed deleting service %s: %s", service.Name, err) From 22f9987f4111a0f6e603464a63ba5d3291e02fc9 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:48:43 +0530 Subject: [PATCH 076/336] fix[k8s]: keeping old core pods (orborus, frontend, backend etc) [with better code] --- functions/onprem/orborus/orborus.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 3fbf16b7..4cfc603e 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -176,6 +176,17 @@ func getThisContainerId() { log.Printf(`[INFO] Started with containerId "%s"`, containerId) } + +func skipCheckInCleanup(name string) bool { + + return strings.HasPrefix(name, "backend-") || + strings.HasPrefix(name, "shuffle-backend") || + strings.HasPrefix(name, "frontend-") || + strings.HasPrefix(name, "orborus-") || + strings.HasPrefix(name, "opensearch-") || + strings.HasPrefix(name, "memcached-") +} + func cleanupExistingNodes(ctx context.Context) error { if isKubernetes == "true" { @@ -202,11 +213,7 @@ func cleanupExistingNodes(ctx context.Context) error { for _, pod := range pods.Items { // check if pod.Name starts with: // "backend-", "frontend-", "orborus-", "opensearch-" or "memcached-" - if strings.HasPrefix(pod.Name, "backend-") || - strings.HasPrefix(pod.Name, "frontend-") || - strings.HasPrefix(pod.Name, "orborus-") || - strings.HasPrefix(pod.Name, "opensearch-") || - strings.HasPrefix(pod.Name, "memcached-") { + if skipCheckInCleanup(pod.Name) { continue } @@ -224,10 +231,7 @@ func cleanupExistingNodes(ctx context.Context) error { } for _, service := range services.Items { - if strings.Contains(service.Name, "opensearch") || - strings.Contains(service.Name, "memcached") || - strings.Contains(service.Name, "shuffle-backend") || strings.Contains(service.Name, "backend") || - strings.Contains(service.Name, "frontend") { + if skipCheckInCleanup(service.Name) { continue } @@ -244,6 +248,10 @@ func cleanupExistingNodes(ctx context.Context) error { } for _, deployment := range deployments.Items { + if skipCheckInCleanup(deployment.Name) { + continue + } + err := clientset.AppsV1().Deployments(kubernetesNamespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{}) if err != nil { log.Printf("[ERROR] Failed deleting deployment %s: %s", deployment.Name, err) From 9653de2a6a142dc9e33a43bb7e0584ca59d61387 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:55:01 +0530 Subject: [PATCH 077/336] fix[k8s]: more cleanup fixes --- functions/onprem/orborus/orborus.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 4cfc603e..836db0ce 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -178,13 +178,12 @@ func getThisContainerId() { func skipCheckInCleanup(name string) bool { - - return strings.HasPrefix(name, "backend-") || - strings.HasPrefix(name, "shuffle-backend") || - strings.HasPrefix(name, "frontend-") || - strings.HasPrefix(name, "orborus-") || - strings.HasPrefix(name, "opensearch-") || - strings.HasPrefix(name, "memcached-") + return strings.HasPrefix(name, "backend") || + strings.HasPrefix(name, "shuffle-backend") || + strings.HasPrefix(name, "frontend") || + strings.HasPrefix(name, "orborus") || + strings.HasPrefix(name, "opensearch") || + strings.HasPrefix(name, "memcached") } func cleanupExistingNodes(ctx context.Context) error { From da76c43356fb026a50da8809b57191fb2ad6fa9c Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:55:45 +0530 Subject: [PATCH 078/336] fix[k8s]: more cleanup fixes (1) --- functions/onprem/orborus/orborus.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 836db0ce..9ce03cf5 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -181,9 +181,13 @@ func skipCheckInCleanup(name string) bool { return strings.HasPrefix(name, "backend") || strings.HasPrefix(name, "shuffle-backend") || strings.HasPrefix(name, "frontend") || + strings.HasPrefix(name, "shuffle-frontend") || strings.HasPrefix(name, "orborus") || + strings.HasPrefix(name, "shuffle-orborus") || strings.HasPrefix(name, "opensearch") || + strings.HasPrefix(name, "shuffle-opensearch") || strings.HasPrefix(name, "memcached") + strings.HasPrefix(name, "shuffle-memcached") } func cleanupExistingNodes(ctx context.Context) error { From 26733bc3cb1dff71c6d35ccf67258208d3491356 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Tue, 9 Jul 2024 18:28:33 +0530 Subject: [PATCH 079/336] fix[k8s]: more cleanup fixes (2) --- functions/onprem/orborus/orborus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 9ce03cf5..847cd29e 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -186,7 +186,7 @@ func skipCheckInCleanup(name string) bool { strings.HasPrefix(name, "shuffle-orborus") || strings.HasPrefix(name, "opensearch") || strings.HasPrefix(name, "shuffle-opensearch") || - strings.HasPrefix(name, "memcached") + strings.HasPrefix(name, "memcached") || strings.HasPrefix(name, "shuffle-memcached") } From fc086dd565f65362e3370b4ab275e4a940f5d6df Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 5 Jul 2024 13:55:09 +0530 Subject: [PATCH 080/336] Minor fix --- frontend/src/components/ParsedAction.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 937b3111..ee9b3163 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -629,6 +629,7 @@ const ParsedAction = (props) => { return {...param, value: paramvalue} }); selectedAction.parameters = newParameters; + setSelectedActionParameters(newParameters); setSelectedAction(selectedAction); return newParameters; },[actionlist,selectedAction,workflow.actions,workflow,selectedApp,setNewSelectedAction]) From 4f802557f0b282e59a33251f6d344fe10a26f9e3 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 5 Jul 2024 16:59:23 +0530 Subject: [PATCH 081/336] ParsedAction Fixes --- frontend/src/components/ParsedAction.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index ee9b3163..2866b840 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -177,16 +177,16 @@ const ParsedAction = (props) => { const classes = useStyles(); const [hideBody, setHideBody] = React.useState(true) const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false) - const [appActionName, setAppActionName] = React.useState(selectedAction.label); + const [appActionName, setAppActionName] = React.useState(selectedAction?.label); const [delay, setDelay] = React.useState(selectedAction?.execution_delay || 0); - const [prevActionName, setPrevActionName] = React.useState(selectedAction.label); + const [prevActionName, setPrevActionName] = React.useState(selectedAction?.label); const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []); const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); const [paramValues, setParamValues] = React.useState( - selectedAction?.parameters.map((param) => { + selectedAction?.parameters?.map((param) => { return { name: param.name, value: param.value, @@ -635,7 +635,7 @@ const ParsedAction = (props) => { },[actionlist,selectedAction,workflow.actions,workflow,selectedApp,setNewSelectedAction]) useEffect(() => { - setParamValues(memoizedParam.map((param) => { + setParamValues(memoizedParam?.map((param) => { return { name: param.name, value: param.value, From 3aa156de78080611892ed79ca677bd759fa2167e Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 11 Jul 2024 12:29:57 +0530 Subject: [PATCH 082/336] Added dynamic parent node name change feature in subflow arg. param --- frontend/src/components/ParsedAction.jsx | 73 ++++++++++++++++++++++++ frontend/src/views/AngularWorkflow.jsx | 1 + 2 files changed, 74 insertions(+) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 2866b840..4fec9a67 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -130,6 +130,7 @@ const ParsedAction = (props) => { setSelectedResult, selectedAction, setSelectedApp, + selectedTrigger, setSelectedTrigger, setSelectedEdge, setCurrentView, @@ -1885,6 +1886,78 @@ const ParsedAction = (props) => { } } + if(workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0) { + for(let [key,keyval] in Object.entries(workflow.triggers)) { + if(workflow.triggers[key].id === selectedTrigger.id) { + continue + } + + const params = workflow.triggers[key].parameters + if (params === null || params === undefined) { + continue + } + + for (let [subkey, subkeyval] in Object.entries(params)) { + const param = workflow.triggers[key].parameters[subkey]; + if(param.name === "argument"){ + if (!param.value.includes("$")) { + continue + } + + // Should have a smarter way of discovering node names + // Do regex? + // Finding index(es) and replacing at the location + // + + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (param.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = param.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.triggers[key].parameters[subkey].value) + const extralength = newname.length-parsedBaseLabel.length + param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) + + console.log("New: ", workflow.triggers[key].parameters[subkey].value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + } + } setWorkflow(workflow); setUpdate(Math.random()); setPrevActionName(name) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 1fe000b1..948d64fc 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -20103,6 +20103,7 @@ const releaseToConnectLabel = "Release to Connect" workflowExecutions={workflowExecutions} setSelectedResult={setSelectedResult} setSelectedApp={setSelectedApp} + selectedTrigger={selectedTrigger} setSelectedTrigger={setSelectedTrigger} setSelectedEdge={setSelectedEdge} setCurrentView={setCurrentView} From 6c593f2ed4624c43be65ba0ae9641cb4f752e466 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 12 Jul 2024 15:46:50 +0530 Subject: [PATCH 083/336] Fixed the subflow Jumpy rendering issue but the value updation is remaining --- frontend/src/views/AngularWorkflow.jsx | 596 +++++++++++++------------ 1 file changed, 321 insertions(+), 275 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 948d64fc..61d6fbfe 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -522,7 +522,9 @@ const AngularWorkflow = (defaultprops) => { const [lastSaved, setLastSaved] = React.useState(true); const [selectionOpen, setSelectionOpen] = React.useState(false); - + const [menuPosition, setMenuPosition] = useState(null); + const [showDropdown, setShowDropdown] = React.useState(false); + const [subflowActionList, setSubflowActionlist] = React.useState([]); // eslint-disable-next-line no-unused-vars const [_, setUpdate] = useState(""); // Used to force rendring, don't remove @@ -538,7 +540,7 @@ const AngularWorkflow = (defaultprops) => { const [distributedFromParent, setDistributedFromParent] = React.useState("") const [suborgWorkflows, setSuborgWorkflows] = React.useState([]) - + const [subflowExec, setSubflowExec] = React.useState("") const [suggestionBox, setSuggestionBox] = React.useState({ "position": { "top": 500, @@ -547,7 +549,6 @@ const AngularWorkflow = (defaultprops) => { "open": false, "attachedTo": "", }) - useEffect(() => { if (!firstrequest && isLoaded && isLoggedIn && editWorkflowModalOpen === false) { saveWorkflow(workflow) @@ -788,6 +789,148 @@ const releaseToConnectLabel = "Release to Connect" }, [selectedApp]) + useEffect(() => { + + const newActionList = []; + + // Process workflowExecutions + if (workflowExecutions.length > 0) { + for (let execution of workflowExecutions) { + const execArg = execution.execution_argument; + if (execArg && execArg.length > 0) { + const valid = validateJson(execArg); + if (valid.valid) { + newActionList.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: valid.result, + }); + break; + } + } + } + } + + if (newActionList.length === 0) { + // FIXME: Have previous execution values in here + newActionList.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: "hello", + }) + newActionList.push({ + type: "Shuffle Database", + name: "Shuffle Database", + value: "$shuffle_cache", + highlight: "shuffle_db", + autocomplete: "shuffle_cache", + example: "hello", + }) + } + + if ( + workflow.workflow_variables !== null && + workflow.workflow_variables !== undefined && + workflow.workflow_variables.length > 0 + ) { + for (let varkey in workflow.workflow_variables) { + const item = workflow.workflow_variables[varkey]; + newActionList.push({ + type: "workflow_variable", + name: item.name, + value: item.value, + id: item.id, + autocomplete: `${item.name.split(" ").join("_")}`, + example: item.value, + }); + } + } + + // FIXME: Add values from previous executions if they exist + if ( + workflow.execution_variables !== null && + workflow.execution_variables !== undefined && + workflow.execution_variables.length > 0 + ) { + for (let varkey in workflow.execution_variables) { + const item = workflow.execution_variables[varkey]; + newActionList.push({ + type: "execution_variable", + name: item.name, + value: item.value, + id: item.id, + autocomplete: `${item.name.split(" ").join("_")}`, + example: "", + }); + } + } + + if(getParents){ + var parents = getParents(selectedTrigger); + if (parents.length > 1) { + for (let parentkey in parents) { + const item = parents[parentkey]; + if (item.label === "Execution Argument") { + continue; + } + + var exampledata = item.example === undefined ? "" : item.example; + // Find previous execution and their variables + 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 = workflowExecutions[execkey].results.find( + (result) => result.action.id === item.id + ); + if (foundResult === undefined) { + continue; + } + + const validated = validateJson(foundResult.result) + if (validated.valid) { + exampledata = validateJson.result + break + } + } + } + + // 1. Take + const actionvalue = { + type: "action", + id: item.id, + name: item.label, + autocomplete: `${item.label.split(" ").join("_")}`, + example: exampledata, + } + newActionList.push(actionvalue); + } + } + } + + setSubflowActionlist(newActionList); + + },[selectedTrigger, workflowExecutions]); + + + useEffect(() => { + if(selectedTrigger.parameters !== undefined && selectedTrigger.parameters.length > 1){ + setSubflowExec(selectedTrigger?.parameters[1]?.value) + } + },[selectedTrigger,selectedTriggerIndex,subflowActionList]); + const [executionArgumentModalOpen, setExecutionArgumentModalOpen] = React.useState(false); // This should all be set once, not on every iteration @@ -4528,7 +4671,7 @@ const releaseToConnectLabel = "Release to Connect" } - + console.log("Selected Trigger: ", selectedTrigger) // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once // onNodeClick @@ -4540,9 +4683,11 @@ const releaseToConnectLabel = "Release to Connect" const data = event.target.data() if (data.app_name === "Shuffle Workflow") { - if ((data.parameters !== undefined) && (data.parameters.length > 0)) { + if ((data.parameters !== undefined) && (data?.parameters?.length > 0)) { getWorkflowApps(data.parameters[0].value) } + console.log("data", data) + // setSubflowExec(data?.parameters[1]?.value) } if (data.buttonType == "ACTIONSUGGESTION") { @@ -10220,7 +10365,7 @@ const releaseToConnectLabel = "Release to Connect" var maxiter = 10; while (true) { for (let parentkey in allkeys) { - var currentnode = cy.getElementById(allkeys[parentkey]); + var currentnode = cy?.getElementById(allkeys[parentkey]); if (currentnode === undefined || currentnode === null) { continue; } @@ -12660,124 +12805,154 @@ const releaseToConnectLabel = "Release to Connect" ); }; - const SubflowSidebar = () => { - const [menuPosition, setMenuPosition] = useState(null); - const [showDropdown, setShowDropdown] = React.useState(false); - const [actionlist, setActionlist] = React.useState([]); + if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { + if (workflow.triggers[selectedTriggerIndex] === undefined) { + return null; + } - if (actionlist.length === 0) { - // FIXME: Have previous execution values in here - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: "hello", - }) - actionlist.push({ - type: "Shuffle Database", - name: "Shuffle Database", - value: "$shuffle_cache", - highlight: "shuffle_db", - autocomplete: "shuffle_cache", - example: "hello", - }) + if ( + workflow.triggers[selectedTriggerIndex].parameters === undefined || + workflow.triggers[selectedTriggerIndex].parameters === null || + workflow.triggers[selectedTriggerIndex].parameters.length === 0 + ) { + workflow.triggers[selectedTriggerIndex].parameters = []; + workflow.triggers[selectedTriggerIndex].parameters[0] = { + name: "workflow", + value: "", + }; + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "argument", + value: "", + id:"subflow_field" + }; + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "user_apikey", + value: "", + }; + workflow.triggers[selectedTriggerIndex].parameters[3] = { + name: "startnode", + value: "", + }; + workflow.triggers[selectedTriggerIndex].parameters[4] = { + name: "check_result", + value: "false", + }; + workflow.triggers[selectedTriggerIndex].parameters[5] = { + name: "auth_override", + value: "", + }; + + /* + // API-key has been replaced by auth key for the execution. + // Parents can now automatically execute children without auth from a user, as long as the subflow in question is owned by the same org and the subflow is actually referencing it during checkin. + console.log("SETTINGS: ", userSettings); if ( - workflow.workflow_variables !== null && - workflow.workflow_variables !== undefined && - workflow.workflow_variables.length > 0 + userSettings !== undefined && + userSettings !== null && + userSettings.apikey !== null && + userSettings.apikey !== undefined && + userSettings.apikey.length > 0 ) { - for (let varkey in workflow.workflow_variables) { - const item = workflow.workflow_variables[varkey]; - actionlist.push({ - type: "workflow_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: item.value, - }); - } + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "user_apikey", + value: userSettings.apikey, + }; + } + */ + } + + var handleSubflowStartnodeSelection = (e) => { + setSubworkflowStartnode(e.target.value); + + if (e.target.value === null || e.target.value === undefined) { + return } - // FIXME: Add values from previous executions if they exist - if ( - workflow.execution_variables !== null && - workflow.execution_variables !== undefined && - workflow.execution_variables.length > 0 - ) { - for (let varkey in workflow.execution_variables) { - const item = workflow.execution_variables[varkey]; - actionlist.push({ - type: "execution_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: "", - }); - } - } + const branchId = uuidv4(); + const newbranch = { + source_id: workflow.triggers[selectedTriggerIndex].id, + destination_id: e.target.value.id, + source: workflow.triggers[selectedTriggerIndex].id, + target: e.target.value.id, + has_errors: false, + id: branchId, + _id: branchId, + label: "Subflow", + decorator: true, + }; - var parents = getParents(selectedTrigger); - if (parents.length > 1) { - for (let parentkey in parents) { - const item = parents[parentkey]; - if (item.label === "Execution Argument") { - continue; - } - - var exampledata = item.example === undefined ? "" : item.example; - // Find previous execution and their variables - 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 = workflowExecutions[execkey].results.find( - (result) => result.action.id === item.id - ); - if (foundResult === undefined) { - continue; - } - - const validated = validateJson(foundResult.result) - if (validated.valid) { - exampledata = validateJson.result - break - } + if (workflow.visual_branches !== undefined) { + if (workflow.visual_branches === null) { + workflow.visual_branches = [newbranch]; + } else if (workflow.visual_branches.length === 0) { + workflow.visual_branches.push(newbranch); + } else { + const foundIndex = workflow.visual_branches.findIndex( + (branch) => branch.source_id === newbranch.source_id + ); + if (foundIndex !== -1) { + const currentEdge = cy.getElementById( + workflow.visual_branches[foundIndex].id + ); + if ( + currentEdge !== undefined && + currentEdge !== null + ) { + currentEdge.remove(); } } - // 1. Take - const actionvalue = { - type: "action", - id: item.id, - name: item.label, - autocomplete: `${item.label.split(" ").join("_")}`, - example: exampledata, - } - actionlist.push(actionvalue); + workflow.visual_branches.splice(foundIndex, 1); + workflow.visual_branches.push(newbranch); } } - setActionlist(actionlist); - } + if (workflow.id === subworkflow.id) { + const cybranch = { + group: "edges", + source: newbranch.source_id, + target: newbranch.destination_id, + id: branchId, + data: newbranch, + }; + + cy.add(cybranch); + } + + console.log("Value to be set: ", e.target.value); + try { + workflow.triggers[ + selectedTriggerIndex + ].parameters[3].value = e.target.value.id; + } catch { + workflow.triggers[selectedTriggerIndex].parameters[3] = + { + name: "startnode", + value: e.target.value.id, + }; + } + + setWorkflow(workflow); + }; + } + + + var subflowtypes = [ + { + name: "Any", + }, + { + name: "Enrich", + } + ] - const handleMenuClose = () => { - setUpdate(Math.random()); - setMenuPosition(null); - }; + const handleMenuClose = () => { + setUpdate(Math.random()); + setMenuPosition(null); + }; - const handleItemClick = (values) => { - console.log("VALUES: ", values) + const handleItemClick = (values) => { if (values === undefined || values === null || values.length === 0) { return; } @@ -12799,164 +12974,30 @@ const releaseToConnectLabel = "Release to Connect" } */ - console.log("SELECTED TRIGGER: ", selectedTrigger) if (selectedTrigger.name === "Shuffle Workflow") { const toComplete = selectedTrigger.parameters[1].value + "$" + values[0].autocomplete selectedTrigger.parameters[1].value = toComplete + // setSubflowExec(selectedTrigger.parameters[1].value) setSelectedTrigger(selectedTrigger) + setWorkflow(workflow) } setUpdate(Math.random()); setShowDropdown(false); setMenuPosition(null); - }; + }; - const iconStyle = { - marginRight: 15, - }; + const handleSubflowExecChange = (e) => { + setSubflowExec(e.target.value) + // if(selectedTrigger.length > 0){ + // selectedTrigger.parameters[1].value = e.target.value + // setSelectedTrigger(selectedTrigger) + // setWorkflow(workflow) + // setLastSaved(false) + // } + } - - if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (workflow.triggers[selectedTriggerIndex] === undefined) { - return null; - } - - if ( - workflow.triggers[selectedTriggerIndex].parameters === undefined || - workflow.triggers[selectedTriggerIndex].parameters === null || - workflow.triggers[selectedTriggerIndex].parameters.length === 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = []; - workflow.triggers[selectedTriggerIndex].parameters[0] = { - name: "workflow", - value: "", - }; - workflow.triggers[selectedTriggerIndex].parameters[1] = { - name: "argument", - value: "", - id:"subflow_field" - }; - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "user_apikey", - value: "", - }; - workflow.triggers[selectedTriggerIndex].parameters[3] = { - name: "startnode", - value: "", - }; - workflow.triggers[selectedTriggerIndex].parameters[4] = { - name: "check_result", - value: "false", - }; - workflow.triggers[selectedTriggerIndex].parameters[5] = { - name: "auth_override", - value: "", - }; - - /* - // API-key has been replaced by auth key for the execution. - // Parents can now automatically execute children without auth from a user, as long as the subflow in question is owned by the same org and the subflow is actually referencing it during checkin. - console.log("SETTINGS: ", userSettings); - if ( - userSettings !== undefined && - userSettings !== null && - userSettings.apikey !== null && - userSettings.apikey !== undefined && - userSettings.apikey.length > 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "user_apikey", - value: userSettings.apikey, - }; - } - */ - } - - const handleSubflowStartnodeSelection = (e) => { - setSubworkflowStartnode(e.target.value); - - if (e.target.value === null || e.target.value === undefined) { - return - } - - const branchId = uuidv4(); - const newbranch = { - source_id: workflow.triggers[selectedTriggerIndex].id, - destination_id: e.target.value.id, - source: workflow.triggers[selectedTriggerIndex].id, - target: e.target.value.id, - has_errors: false, - id: branchId, - _id: branchId, - label: "Subflow", - decorator: true, - }; - - if (workflow.visual_branches !== undefined) { - if (workflow.visual_branches === null) { - workflow.visual_branches = [newbranch]; - } else if (workflow.visual_branches.length === 0) { - workflow.visual_branches.push(newbranch); - } else { - const foundIndex = workflow.visual_branches.findIndex( - (branch) => branch.source_id === newbranch.source_id - ); - if (foundIndex !== -1) { - const currentEdge = cy.getElementById( - workflow.visual_branches[foundIndex].id - ); - if ( - currentEdge !== undefined && - currentEdge !== null - ) { - currentEdge.remove(); - } - } - - workflow.visual_branches.splice(foundIndex, 1); - workflow.visual_branches.push(newbranch); - } - } - - if (workflow.id === subworkflow.id) { - const cybranch = { - group: "edges", - source: newbranch.source_id, - target: newbranch.destination_id, - id: branchId, - data: newbranch, - }; - - cy.add(cybranch); - } - - console.log("Value to be set: ", e.target.value); - try { - workflow.triggers[ - selectedTriggerIndex - ].parameters[3].value = e.target.value.id; - } catch { - workflow.triggers[selectedTriggerIndex].parameters[3] = - { - name: "startnode", - value: e.target.value.id, - }; - } - - setWorkflow(workflow); - } - - - const subflowtypes = [ - { - name: "Any", - }, - { - name: "Enrich", - } - ] - - return ( + const SubflowSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "SUBFLOW" ? null :

    @@ -13419,7 +13460,7 @@ const releaseToConnectLabel = "Release to Connect" setCodeEditorModalOpen(true) setActiveDialog("codeeditor") //setcodedata(data.value) - var parsedvalue = workflow.triggers[selectedTriggerIndex].parameters[1].value + var parsedvalue = workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value // if (parsedvalue === undefined || parsedvalue === null) { // parsedvalue = "" // } @@ -13427,14 +13468,14 @@ const releaseToConnectLabel = "Release to Connect" "name": workflow.triggers[selectedTriggerIndex].parameters[1].name, "value": parsedvalue, "field_number": 1, - "actionlist": actionlist, + "actionlist": subflowActionList, "field_id": "subflow_field", }) setEditorData({ "name": workflow.triggers[selectedTriggerIndex].parameters[1].name, "value": parsedvalue, "field_number": 1, - "actionlist": actionlist, + "actionlist": subflowActionList, "field_id": "subflow_field", }) }} @@ -13462,15 +13503,23 @@ const releaseToConnectLabel = "Release to Connect" fullWidth color="primary" placeholder="Some execution data" - defaultValue={ - workflow.triggers[selectedTriggerIndex].parameters[1].value - } - onBlur={(e) => { - setLastSaved(false) + // defaultValue={ + // workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value + // } + value={subflowExec} + onChange={(e) => { + // handleSubflowExecChange(e) + setSubflowExec(e.target.value) + // workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value + // setWorkflow(workflow) - workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value - setWorkflow(workflow) }} + // onBlur={(e) => { + // setLastSaved(false) + + // // workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value + // // setWorkflow(workflow) + // }} /> {!showDropdown ? null : - {actionlist.map((innerdata) => { + {subflowActionList.map((innerdata) => { const icon = innerdata.type === "action" ? ( @@ -13821,11 +13870,6 @@ const releaseToConnectLabel = "Release to Connect"

    - ); - } - - return null; - }; const CommentSidebar = () => { if (Object.getOwnPropertyNames(selectedComment).length > 0) { @@ -20139,7 +20183,7 @@ const releaseToConnectLabel = "Release to Connect" {/* Looks for triggers" */} {/* Only fixed the ones that require scrolling on a small screen */} {/* Most important: Actions. But these are a lot more complex */} - {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE" || selectedTrigger.trigger_type === "USERINPUT") ? + {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE" || selectedTrigger.trigger_type === "USERINPUT" || selectedTrigger.trigger_type === "SUBFLOW") ?
    {Object.getOwnPropertyNames(selectedTrigger).length > 0 ? selectedTrigger.trigger_type === "SCHEDULE" ? @@ -20150,17 +20194,19 @@ const releaseToConnectLabel = "Release to Connect" WebhookSidebar : selectedTrigger.trigger_type === "USERINPUT" ? UserinputSidebar + : selectedTrigger.trigger_type === "SUBFLOW" ? + SubflowSidebar : null : null}
    : null} - { + {/* { rightSideBarOpen && selectedTrigger.trigger_type === "SUBFLOW"&& Object.getOwnPropertyNames(selectedTrigger).length > 0 ?
    : null - } + } */} {/* Date: Tue, 16 Jul 2024 18:59:39 +0530 Subject: [PATCH 084/336] feat: adding all-in-one.yaml --- functions/kubernetes/all-in-one.yaml | 122 ++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 22 deletions(-) diff --git a/functions/kubernetes/all-in-one.yaml b/functions/kubernetes/all-in-one.yaml index 746215e9..9ac8dfaf 100644 --- a/functions/kubernetes/all-in-one.yaml +++ b/functions/kubernetes/all-in-one.yaml @@ -1,15 +1,27 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: shuffle + --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: shuffle-data + namespace: shuffle provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer --- apiVersion: v1 +metadata: + namespace: shuffle + creationTimestamp: null + labels: + io.kompose.service: backend-env + name: env data: BACKEND_HOSTNAME: shuffle-backend BACKEND_PORT: "5001" @@ -51,11 +63,13 @@ data: SHUFFLE_OPENSEARCH_APIKEY: "" SHUFFLE_OPENSEARCH_CERTIFICATE_FILE: "" SHUFFLE_OPENSEARCH_CLOUDID: "" + KUBERNETES_NAMESPACE: shuffle SHUFFLE_OPENSEARCH_INDEX_PREFIX: "" SHUFFLE_OPENSEARCH_PASSWORD: admin SHUFFLE_OPENSEARCH_PROXY: "" SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY: "true" SHUFFLE_OPENSEARCH_URL: https://opensearch:9200 + SHUFFLE_MEMCACHED: shuffle-memcached:11211 SHUFFLE_OPENSEARCH_USERNAME: admin SHUFFLE_ORBORUS_STARTUP_DELAY: "\t\t" SHUFFLE_PASS_APP_PROXY: "FALSE" @@ -68,11 +82,6 @@ data: REGISTRY_AUTH: "false" SHUFFLE_KUBERNETES_WORKER: "ghcr.io/shuffle/shuffle-worker:nightly" kind: ConfigMap -metadata: - creationTimestamp: null - labels: - io.kompose.service: backend-env - name: env --- @@ -110,7 +119,8 @@ roleRef: apiVersion: v1 kind: PersistentVolume metadata: - name: shuffle-os-pv + name: shuffle-os-pv + namespace: shuffle spec: capacity: storage: 10Gi # Adjust the storage size as per your requirements @@ -126,6 +136,7 @@ spec: apiVersion: v1 kind: PersistentVolumeClaim metadata: + namespace: shuffle creationTimestamp: null labels: io.kompose.service: opensearch-claim0 @@ -143,6 +154,7 @@ status: {} apiVersion: apps/v1 kind: Deployment metadata: + namespace: shuffle annotations: kompose.cmd: kompose convert -f docker-compose.yml kompose.version: 1.26.0 (40646f47) @@ -222,6 +234,7 @@ status: {} apiVersion: v1 kind: Service metadata: + namespace: shuffle annotations: kompose.cmd: kompose convert -f docker-compose.yml kompose.version: 1.26.0 (40646f47) @@ -244,7 +257,8 @@ status: apiVersion: v1 kind: PersistentVolume metadata: - name: shuffle-apps-pv + namespace: shuffle + name: shuffle-apps-pv spec: capacity: storage: 5Gi @@ -259,7 +273,8 @@ spec: apiVersion: v1 kind: PersistentVolume metadata: - name: shuffle-files-pv + namespace: shuffle + name: shuffle-files-pv spec: capacity: storage: 5Gi @@ -272,20 +287,21 @@ spec: --- - apiVersion: v1 - kind: PersistentVolumeClaim - metadata: - creationTimestamp: null - labels: - io.kompose.service: backend-files-claim - name: backend-files-claim - spec: - accessModes: - - ReadWriteOnce - storageClassName: shuffle-data - resources: - requests: - storage: 5Gi +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + namespace: shuffle + creationTimestamp: null + labels: + io.kompose.service: backend-files-claim + name: backend-files-claim +spec: + accessModes: + - ReadWriteOnce + storageClassName: shuffle-data + resources: + requests: + storage: 5Gi # status: {} --- @@ -293,6 +309,7 @@ spec: apiVersion: v1 kind: PersistentVolumeClaim metadata: + namespace: shuffle creationTimestamp: null labels: io.kompose.service: backend-apps-claim @@ -311,6 +328,48 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: + name: shuffle-memcached + namespace: shuffle +spec: + replicas: 1 + selector: + matchLabels: + app: shuffle-memcached + template: + metadata: + labels: + app: shuffle-memcached + spec: + containers: + - name: shuffle-memcached + image: memcached:latest + ports: + - containerPort: 11211 + resources: {} + restartPolicy: Always + + +--- + +apiVersion: v1 +kind: Service +metadata: + namespace: shuffle + name: shuffle-memcached +spec: + ports: + - port: 11211 + targetPort: 11211 + selector: + app: shuffle-memcached + type: ClusterIP + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: shuffle annotations: kompose.cmd: kompose convert -f docker-compose.yml kompose.version: 1.26.0 (40646f47) @@ -538,6 +597,11 @@ spec: configMapKeyRef: key: SHUFFLE_OPENSEARCH_APIKEY name: env + - name: SHUFFLE_MEMCACHED + valueFrom: + configMapKeyRef: + key: SHUFFLE_MEMCACHED + name: env - name: SHUFFLE_OPENSEARCH_CERTIFICATE_FILE valueFrom: configMapKeyRef: @@ -642,6 +706,7 @@ status: {} apiVersion: v1 kind: Service metadata: + namespace: shuffle annotations: kompose.cmd: kompose convert -f docker-compose.yml kompose.version: 1.26.0 (40646f47) @@ -664,6 +729,7 @@ status: apiVersion: apps/v1 kind: Deployment metadata: + namespace: shuffle annotations: kompose.cmd: kompose convert -f docker-compose.yml kompose.version: 1.26.0 (40646f47) @@ -705,6 +771,7 @@ status: {} apiVersion: v1 kind: Service metadata: + namespace: shuffle annotations: kompose.cmd: kompose convert -f docker-compose.yml kompose.version: 1.26.0 (40646f47) @@ -733,6 +800,7 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: + namespace: shuffle annotations: kompose.cmd: kompose convert -f docker-compose.yml kompose.version: 1.26.0 (40646f47) @@ -779,6 +847,11 @@ spec: configMapKeyRef: key: IS_KUBERNETES name: env + - name: KUBERNETES_NAMESPACE + valueFrom: + configMapKeyRef: + key: KUBERNETES_NAMESPACE + name: env - name: REGISTRY_URL valueFrom: configMapKeyRef: @@ -790,6 +863,11 @@ spec: key: SHUFFLE_KUBERNETES_WORKER name: env + - name: SHUFFLE_MEMCACHED + valueFrom: + configMapKeyRef: + key: SHUFFLE_MEMCACHED + name: env image: ghcr.io/shuffle/shuffle-orborus:nightly #imagePullPolicy: Never name: shuffle-orborus From 0dd5913072b9ed0c14ca3487d7545f31cffc3dfc Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Tue, 16 Jul 2024 20:07:32 +0530 Subject: [PATCH 085/336] feat: making all-in-one.yaml work on GKE --- functions/kubernetes/all-in-one.yaml | 96 ++++++++++++++-------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/functions/kubernetes/all-in-one.yaml b/functions/kubernetes/all-in-one.yaml index 9ac8dfaf..a428048f 100644 --- a/functions/kubernetes/all-in-one.yaml +++ b/functions/kubernetes/all-in-one.yaml @@ -116,22 +116,22 @@ roleRef: --- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: shuffle-os-pv - namespace: shuffle -spec: - capacity: - storage: 10Gi # Adjust the storage size as per your requirements - accessModes: - - ReadWriteOnce # This allows read-write access to a single node - persistentVolumeReclaimPolicy: Retain # Adjust the reclaim policy as per your needs - storageClassName: shuffle-data # Set the desired storage class - hostPath: - path: /mnt/shuffle-data/open-search +# apiVersion: v1 +# kind: PersistentVolume +# metadata: +# name: shuffle-os-pv +# namespace: shuffle +# spec: +# capacity: +# storage: 10Gi # Adjust the storage size as per your requirements +# accessModes: +# - ReadWriteOnce # This allows read-write access to a single node +# persistentVolumeReclaimPolicy: Retain # Adjust the reclaim policy as per your needs +# storageClassName: standard-rwo # Set the desired storage class +# hostPath: +# path: /mnt/shuffle-data/open-search ---- +# --- apiVersion: v1 kind: PersistentVolumeClaim @@ -144,7 +144,7 @@ metadata: spec: accessModes: - ReadWriteOnce - storageClassName: shuffle-data + storageClassName: standard-rwo resources: requests: storage: 500Mi @@ -254,38 +254,38 @@ status: --- -apiVersion: v1 -kind: PersistentVolume -metadata: - namespace: shuffle - name: shuffle-apps-pv -spec: - capacity: - storage: 5Gi - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: shuffle-data - hostPath: - path: /mnt/shuffle-data/backend +# apiVersion: v1 +# kind: PersistentVolume +# metadata: +# namespace: shuffle +# name: shuffle-apps-pv +# spec: +# capacity: +# storage: 5Gi +# accessModes: +# - ReadWriteOnce +# persistentVolumeReclaimPolicy: Retain +# storageClassName: shuffle-data +# hostPath: +# path: /mnt/shuffle-data/backend ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - namespace: shuffle - name: shuffle-files-pv -spec: - capacity: - storage: 5Gi - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: shuffle-data - hostPath: - path: /mnt/shuffle-data/backend +# --- +# apiVersion: v1 +# kind: PersistentVolume +# metadata: +# namespace: shuffle +# name: shuffle-files-pv +# spec: +# capacity: +# storage: 5Gi +# accessModes: +# - ReadWriteOnce +# persistentVolumeReclaimPolicy: Retain +# storageClassName: shuffle-data +# hostPath: +# path: /mnt/shuffle-data/backend ---- +# --- apiVersion: v1 kind: PersistentVolumeClaim @@ -298,7 +298,7 @@ metadata: spec: accessModes: - ReadWriteOnce - storageClassName: shuffle-data + storageClassName: standard-rwo resources: requests: storage: 5Gi @@ -317,7 +317,7 @@ metadata: spec: accessModes: - ReadWriteOnce - storageClassName: shuffle-data + storageClassName: standard-rwo resources: requests: storage: 5Gi From 0c035b6ac73a71712571584c651253582ac745a9 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 17 Jul 2024 15:14:03 +0530 Subject: [PATCH 086/336] Fixed the paramChange Issue and finally done with the rendering issue of Subflow --- .../src/components/ShuffleCodeEditor1.jsx | 54 +++++++++++-- frontend/src/views/AngularWorkflow.jsx | 75 ++++++------------- 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index ab4fee06..667fafe3 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -109,6 +109,7 @@ const CodeEditor = (props) => { setActiveDialog, fieldname, contentLoading, + selectedTrigger, } = props const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); @@ -436,7 +437,8 @@ const CodeEditor = (props) => { } const autoFormat = (input) => { - // Check if it's default too + if(selectedAction && selectedAction.parameters && selectedAction.parameters.length > 0){ + // Check if it's default too if (validation !== true) { // Should try to automatically fix this input @@ -475,6 +477,48 @@ const CodeEditor = (props) => { if (input !== localcodedata) { setlocalcodedata(input) } + } + + if(selectedTrigger && selectedTrigger.parameters && selectedTrigger.parameters.length > 0){ + if (validation !== true) { + + // Should try to automatically fix this input + console.log("Running AI input fixer") + if (aiSubmit !== undefined && parameterName !== undefined && selectedTrigger !== undefined) { + + // Should remove params from selectedAction that aren't parameterName + var tmpAction = JSON.parse(JSON.stringify(selectedTrigger)) + var tmpParams = selectedTrigger.parameters.filter((param) => param.name === parameterName) + + var aiMsg = `Make it valid for trigger ${tmpAction.label} with parameter ${parameterName}: ` + if (tmpParams.length > 0) { + aiMsg += tmpParams[0].value + } + + + if (localcodedata.startsWith("//")) { + aiMsg = localcodedata + } + + tmpAction.parameters = tmpParams + console.log("Parameters: ", tmpParams.length) + + aiSubmit(aiMsg, tmpAction) + } + + return + } + + try { + input = JSON.stringify(JSON.parse(input), null, 4) + } catch (e) { + console.log("Failed magic JSON stringification: ", e) + } + + if (input !== localcodedata) { + setlocalcodedata(input) + } + } } const findIndex = (line, loc) => { @@ -1767,7 +1811,6 @@ const CodeEditor = (props) => { // This is to make it so we don't need to handle these fixes on the // backend by itself var fixedcodedata = localcodedata - console.log("Fixedcodedata: ", fixedcodedata) const valid = validateJson(localcodedata, true) if (valid.valid) { fixedcodedata = JSON.stringify(valid.result, null, 2) @@ -1780,11 +1823,6 @@ const CodeEditor = (props) => { setcodedata(fixedcodedata); setExpansionModalOpen(false) } else if (changeActionParameterCodeMirror !== undefined) { - console.log("Entering in Submit onCLick") - console.log("Data passing to chnageActionParameterCodeMirror: ", fixedcodedata) - console.log("Fieldcount: ", fieldCount) - console.log("Actionlist: ", actionlist) - console.log("Event: ", event) //changeActionParameterCodeMirror(event, fieldCount, fixedcodedata) changeActionParameterCodeMirror(event, fieldCount, fixedcodedata, actionlist) setExpansionModalOpen(false) @@ -1808,4 +1846,4 @@ const CodeEditor = (props) => { ) } -export default CodeEditor; +export default CodeEditor; \ No newline at end of file diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 61d6fbfe..e9bb7b4c 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-multi-comp */ -import React, { useState, useEffect, useLayoutEffect, memo, useMemo, useRef } from "react"; +import React, { useState, useEffect, useLayoutEffect } from "react"; import ReactDOM from "react-dom" import theme from "../theme.jsx"; @@ -922,14 +922,8 @@ const releaseToConnectLabel = "Release to Connect" setSubflowActionlist(newActionList); - },[selectedTrigger, workflowExecutions]); - - - useEffect(() => { - if(selectedTrigger.parameters !== undefined && selectedTrigger.parameters.length > 1){ - setSubflowExec(selectedTrigger?.parameters[1]?.value) - } - },[selectedTrigger,selectedTriggerIndex,subflowActionList]); + },[selectedTrigger, workflowExecutions, workflow.workflow_variables, workflow.execution_variables, workflow.branches,workflow]); + const [executionArgumentModalOpen, setExecutionArgumentModalOpen] = React.useState(false); @@ -4671,7 +4665,6 @@ const releaseToConnectLabel = "Release to Connect" } - console.log("Selected Trigger: ", selectedTrigger) // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once // onNodeClick @@ -4686,8 +4679,6 @@ const releaseToConnectLabel = "Release to Connect" if ((data.parameters !== undefined) && (data?.parameters?.length > 0)) { getWorkflowApps(data.parameters[0].value) } - console.log("data", data) - // setSubflowExec(data?.parameters[1]?.value) } if (data.buttonType == "ACTIONSUGGESTION") { @@ -12976,9 +12967,14 @@ const releaseToConnectLabel = "Release to Connect" if (selectedTrigger.name === "Shuffle Workflow") { const toComplete = selectedTrigger.parameters[1].value + "$" + values[0].autocomplete - selectedTrigger.parameters[1].value = toComplete - // setSubflowExec(selectedTrigger.parameters[1].value) - setSelectedTrigger(selectedTrigger) + // selectedTrigger.parameters[1].value = toComplete + workflow.triggers[selectedTriggerIndex].parameters[1].value = toComplete + const foundfield = document.getElementById("subflow_field") + if (foundfield !== undefined && foundfield !== null) { + foundfield.value = toComplete + } + // setSelectedTrigger(selectedTrigger) + // setSubflowExec(toComplete) setWorkflow(workflow) } @@ -12987,16 +12983,6 @@ const releaseToConnectLabel = "Release to Connect" setMenuPosition(null); }; - const handleSubflowExecChange = (e) => { - setSubflowExec(e.target.value) - // if(selectedTrigger.length > 0){ - // selectedTrigger.parameters[1].value = e.target.value - // setSelectedTrigger(selectedTrigger) - // setWorkflow(workflow) - // setLastSaved(false) - // } - } - const SubflowSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "SUBFLOW" ? null :
    @@ -13287,6 +13273,7 @@ const releaseToConnectLabel = "Release to Connect" value: data } }) + document.activeElement.blur(); }} > @@ -13302,11 +13289,6 @@ const releaseToConnectLabel = "Release to Connect" backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius, }} - sx={{ - '& .MuiInputLabel-root': { - transition: 'none', // Disable the animation for the label - }, - }} {...params} label="Find your workflow" variant="outlined" @@ -13414,11 +13396,6 @@ const releaseToConnectLabel = "Release to Connect" backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius, }} - sx={{ - '& .MuiInputLabel-root': { - transition: 'none', // Disable the animation for the label - }, - }} {...params} label="Select a start-node (optional)" variant="outlined" @@ -13481,7 +13458,7 @@ const releaseToConnectLabel = "Release to Connect" }} /> - + { @@ -13503,23 +13480,15 @@ const releaseToConnectLabel = "Release to Connect" fullWidth color="primary" placeholder="Some execution data" - // defaultValue={ - // workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value - // } - value={subflowExec} - onChange={(e) => { - // handleSubflowExecChange(e) - setSubflowExec(e.target.value) - // workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value - // setWorkflow(workflow) + defaultValue={ + workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value + } + onBlur={(e) => { + workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value + setWorkflow(workflow) + setLastSaved(false) }} - // onBlur={(e) => { - // setLastSaved(false) - - // // workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value - // // setWorkflow(workflow) - // }} /> {!showDropdown ? null : Date: Wed, 17 Jul 2024 15:46:46 +0530 Subject: [PATCH 087/336] Added expandWindow button and actionList in UserInput and added parent node name change feature in that --- frontend/src/components/ParsedAction.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 360 ++++++++++++++++++++++- 2 files changed, 360 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 4fec9a67..9d583b31 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1899,7 +1899,7 @@ const ParsedAction = (props) => { for (let [subkey, subkeyval] in Object.entries(params)) { const param = workflow.triggers[key].parameters[subkey]; - if(param.name === "argument"){ + if(param.name === "argument" || param.name === "alertinfo"){ if (!param.value.includes("$")) { continue } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index e9bb7b4c..631d9c0c 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -12978,6 +12978,19 @@ const releaseToConnectLabel = "Release to Connect" setWorkflow(workflow) } + if(selectedTrigger.name === "User Input"){ + const toComplete = selectedTrigger.parameters[0].value + "$" + values[0].autocomplete + // selectedTrigger.parameters[1].value = toComplete + workflow.triggers[selectedTriggerIndex].parameters[0].value = toComplete + const foundfield = document.getElementById("userinput_info") + if (foundfield !== undefined && foundfield !== null) { + foundfield.value = toComplete + } + // setSelectedTrigger(selectedTrigger) + // setSubflowExec(toComplete) + setWorkflow(workflow) + } + setUpdate(Math.random()); setShowDropdown(false); setMenuPosition(null); @@ -14098,6 +14111,7 @@ const releaseToConnectLabel = "Release to Connect" ) { workflow.triggers[selectedTriggerIndex].parameters = []; workflow.triggers[selectedTriggerIndex].parameters[0] = { + id:"userinput_info", name: "alertinfo", value: "Do you want to continue the workflow? Start parameters: $exec", }; @@ -15003,6 +15017,7 @@ const releaseToConnectLabel = "Release to Connect"
    + + + { + event.preventDefault() + // setFieldCount(count) + setCodeEditorModalOpen(true) + setActiveDialog("codeeditor") + //setcodedata(data.value) + var parsedvalue = workflow?.triggers[selectedTriggerIndex]?.parameters[0]?.value + // if (parsedvalue === undefined || parsedvalue === null) { + // parsedvalue = "" + // } + console.log("Data sending to codeeditor: ",{ + "name": workflow.triggers[selectedTriggerIndex].parameters[0].name, + "value": parsedvalue, + "field_number": 0, + "actionlist": subflowActionList, + "field_id": "userinput_info", + }) + setEditorData({ + "name": workflow.triggers[selectedTriggerIndex].parameters[0].name, + "value": parsedvalue, + "field_number": 0, + "actionlist": subflowActionList, + "field_id": "userinput_info", + }) + }} + /> + + + { + setMenuPosition({ + top: event.pageY + 10, + left: event.pageX + 10, + }); + //setShowDropdownNumber(3) + setShowDropdown(true); + }} + /> + + + + ), }} fullWidth - rows="4" + rows="6" multiline defaultValue={ workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers[selectedTriggerIndex].parameters !== undefined && workflow.triggers[selectedTriggerIndex].parameters.length > 0 && workflow.triggers[selectedTriggerIndex].parameters[0] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined ? workflow.triggers[selectedTriggerIndex].parameters[0].value : "" @@ -15023,6 +15087,300 @@ const releaseToConnectLabel = "Release to Connect" setTriggerTextInformationWrapper(e.target.value); }} /> + {!showDropdown ? null : + { + handleMenuClose(); + }} + open={!!menuPosition} + style={{ + border: `2px solid #f85a3e`, + color: "white", + marginTop: 2, + }} + > + {subflowActionList.map((innerdata) => { + const icon = + innerdata.type === "action" ? ( + + ) : innerdata.type === "workflow_variable" || + innerdata.type === "execution_variable" ? ( + + ) : ( + + ); + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById( + "execution_argument_input_field" + ); + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e"; + } else { + exec_text_field.style.border = ""; + } + } + + // Also doing arguments + if ( + workflow.triggers !== undefined && + workflow.triggers !== null && + workflow.triggers.length > 0 + ) { + for (let triggerkey in workflow.triggers) { + const item = workflow.triggers[triggerkey]; + + if (cy !== undefined) { + var node = cy.getElementById(item.id); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + } + } + } + + const handleActionHover = (inside, actionId) => { + if (cy !== undefined) { + var node = cy.getElementById(actionId); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + }; + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true); + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id); + } + }; + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false); + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id); + } + }; + + var parsedPaths = []; + console.log("Found example data: ", innerdata.example) + if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } + + const coverColor = "#82ccc3" + + return parsedPaths.length > 0 ? ( + + {/* + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + backgroundColor: theme.palette.inputColor, + color: "white", + minWidth: 250, + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ) + + return ( + { }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {icon} {pathdata.name} +
    +
    +
    + ); + })} + + */} + + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + color: "white", + minWidth: 250, + maxWidth: 250, + maxHeight: 50, + overflow: "hidden", + }} + onClick={() => { + console.log("CLICKED: ", innerdata); + console.log(innerdata.example) + handleItemClick([innerdata]); + }} + > + + + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + + {innerdata.name} + + + + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + // + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ); + // + + const indentation_count = (pathdata.name.match(/\./g) || []).length+1 + const baseIndent =
    + //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 + const boxPadding = 0 + const namesplit = pathdata.name.split(".") + const newname = namesplit[namesplit.length-1] + return ( + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {Array(indentation_count).fill().map((subdata, subindex) => { + return ( + baseIndent + ) + })} + {icon} {newname} + {pathdata.type === "list" ? { + + }} /> : null} +
    +
    +
    + ); + })} + + + + ) : ( + handleMouseover()} + onMouseOut={() => { + handleMouseOut(); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + +
    + {icon} {innerdata.name} +
    +
    +
    + ); + })} + + }
    Date: Wed, 17 Jul 2024 22:14:01 +0530 Subject: [PATCH 088/336] Removed the auto variable remover logic from param while flipping the branch --- frontend/src/components/ParsedAction.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 2bdc7daf..c4cff82f 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -619,8 +619,9 @@ const ParsedAction = (props) => { notPresentAction?.forEach((action) => { console.log("Not included Action: ", action) if(paramvalue.includes(action)){ - paramvalue = paramvalue.replace(action, "") - paramvalue = paramvalue.replace(/^\s*[\r\n]/gm, ""); + + // paramvalue = paramvalue.replace(action, "") + // paramvalue = paramvalue.replace(/^\s*[\r\n]/gm, ""); } }) } From f09dbac2872d71d5802cb16aeac69bcf0ad326df Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 18 Jul 2024 14:56:24 +0530 Subject: [PATCH 089/336] Fixed that blank screen issue --- frontend/src/views/AngularWorkflow.jsx | 1865 ++++++++++++------------ 1 file changed, 939 insertions(+), 926 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 631d9c0c..3347d1cc 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -12796,61 +12796,6 @@ const releaseToConnectLabel = "Release to Connect" ); }; - if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (workflow.triggers[selectedTriggerIndex] === undefined) { - return null; - } - - if ( - workflow.triggers[selectedTriggerIndex].parameters === undefined || - workflow.triggers[selectedTriggerIndex].parameters === null || - workflow.triggers[selectedTriggerIndex].parameters.length === 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = []; - workflow.triggers[selectedTriggerIndex].parameters[0] = { - name: "workflow", - value: "", - }; - workflow.triggers[selectedTriggerIndex].parameters[1] = { - name: "argument", - value: "", - id:"subflow_field" - }; - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "user_apikey", - value: "", - }; - workflow.triggers[selectedTriggerIndex].parameters[3] = { - name: "startnode", - value: "", - }; - workflow.triggers[selectedTriggerIndex].parameters[4] = { - name: "check_result", - value: "false", - }; - workflow.triggers[selectedTriggerIndex].parameters[5] = { - name: "auth_override", - value: "", - }; - - /* - // API-key has been replaced by auth key for the execution. - // Parents can now automatically execute children without auth from a user, as long as the subflow in question is owned by the same org and the subflow is actually referencing it during checkin. - console.log("SETTINGS: ", userSettings); - if ( - userSettings !== undefined && - userSettings !== null && - userSettings.apikey !== null && - userSettings.apikey !== undefined && - userSettings.apikey.length > 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "user_apikey", - value: userSettings.apikey, - }; - } - */ - } var handleSubflowStartnodeSelection = (e) => { setSubworkflowStartnode(e.target.value); @@ -12925,7 +12870,6 @@ const releaseToConnectLabel = "Release to Connect" setWorkflow(workflow); }; - } var subflowtypes = [ @@ -12996,862 +12940,7 @@ const releaseToConnectLabel = "Release to Connect" setMenuPosition(null); }; - const SubflowSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "SUBFLOW" ? null : -
    - -

    - {selectedTrigger.app_name} -

    - - - -
    -
    - What are subflows? - - -
    -
    - Name - -
    -
    -
    - - - Delay - { - if (isNaN(event.target.value)) { - console.log("NAN: ", event.target.value) - return - } - - const parsedNumber = parseInt(event.target.value) - if (parsedNumber > 86400) { - console.log("Max number is 1 day (86400)") - return - } - - selectedTrigger.execution_delay = parseInt(event.target.value) - setSelectedTrigger(selectedTrigger) - }} - /> - - -
    -
    -
    - { - const newvalue = workflow.triggers[selectedTriggerIndex].parameters[4] === undefined || workflow.triggers[selectedTriggerIndex].parameters[4].value === "false"? "true" : "false"; - workflow.triggers[selectedTriggerIndex].parameters[4] = { - name: "check_result", - value: newvalue, - }; - - setWorkflow(workflow); - setUpdate(Math.random()); - }} - color="primary" - value="Wait for results" - /> - } - style={{ marginTop: 10 }} - label={
    Wait for results
    } - /> -
    -
    -
    -
    -
    - Select a workflow to execute -
    -
    - {workflow.triggers[selectedTriggerIndex].parameters[0].value - .length === 0 ? null : workflow.triggers[selectedTriggerIndex] - .parameters[0].value === props.match.params.key ? null : ( -
    - - - -
    - )} -
    - - {workflows === undefined || - workflows === null || - workflows.length === 0 ? null : ( - - option.id === value.id} - getOptionLabel={(option) => { - if ( - option === undefined || - option === null || - option.name === undefined || - option.name === null - ) { - return "No Workflow Selected"; - } - - const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - return newname; - }} - options={workflows} - fullWidth - style={{ - backgroundColor: theme.palette.inputColor, - height: 50, - borderRadius: theme.palette.borderRadius, - }} - onChange={(event, newValue) => { - setLastSaved(false) - console.log("Found value: ", newValue) - - var parsedinput = { target: { value: newValue } } - - // For variables - if (typeof newValue === 'string' && newValue.startsWith("$")) { - parsedinput = { - target: { - value: { - "name": newValue, - "id": newValue, - "actions": [], - "triggers": [], - } - } - } - } - - handleWorkflowSelectionUpdate(parsedinput) - }} - renderOption={(props, data, state) => { - if (data.id === workflow.id) { - data = workflow; - } - - //key={index} - return ( - - {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} - : null} - - Choose Subflow '{data.name}' - - - }> - { - getWorkflowApps(data.id); - handleWorkflowSelectionUpdate({ - target: { - value: data - } - }) - document.activeElement.blur(); - }} - > - - {data.name} - - - ) - }} - renderInput={(params) => { - return ( - - ); - }} - /> - )} - - {subworkflow === undefined || - subworkflow === null || - subworkflow.id === undefined || - subworkflow.actions === null || - subworkflow.actions === undefined || - subworkflow.actions.length === 0 ? null : ( - -
    -
    - Select the Startnode -
    -
    - option.id === value.id} - getOptionLabel={(option) => { - if (option === undefined || option === null || option.label === undefined || option.label === null) { - if (option.length === 36) { - - } - - return "TMP"; - } - - const newname = ( - option.label.charAt(0).toUpperCase() + option.label.substring(1) - ).replaceAll("_", " "); - return newname; - }} - options={subworkflow.actions} - fullWidth - style={{ - backgroundColor: theme.palette.inputColor, - height: 50, - borderRadius: theme.palette.borderRadius, - }} - onChange={(event, newValue) => { - setLastSaved(false) - handleSubflowStartnodeSelection({ target: { value: newValue } }) - }} - renderOption={(props, action, state) => { - const isParent = getParents(selectedTrigger).find( - (parent) => parent.id === action.id - ) - - return ( - { - if (subworkflow.id === workflow.id) { - handleActionHover(true, action.id) - } - }} - onMouseOut={() => { - if (subworkflow.id === workflow.id) { - handleActionHover(false, action.id) - } - }} - disabled={isCloud && isParent} - onClick={() => { - handleSubflowStartnodeSelection({ - target: { - value: action - } - }) - document.activeElement.blur(); - }} - style={{ - backgroundColor: theme.palette.inputColor, - color: isParent ? "red" : "white", - }} - value={action} - > - {action.label} - - ); - }} - renderInput={(params) => { - return ( - - ); - }} - /> -
    - )} -
    -
    - Execution Argument -
    -
    - - - - { - event.preventDefault() - // setFieldCount(count) - setCodeEditorModalOpen(true) - setActiveDialog("codeeditor") - //setcodedata(data.value) - var parsedvalue = workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value - // if (parsedvalue === undefined || parsedvalue === null) { - // parsedvalue = "" - // } - console.log("Data sending to codeeditor: ",{ - "name": workflow.triggers[selectedTriggerIndex].parameters[1].name, - "value": parsedvalue, - "field_number": 1, - "actionlist": subflowActionList, - "field_id": "subflow_field", - }) - setEditorData({ - "name": workflow.triggers[selectedTriggerIndex].parameters[1].name, - "value": parsedvalue, - "field_number": 1, - "actionlist": subflowActionList, - "field_id": "subflow_field", - }) - }} - /> - - - { - setMenuPosition({ - top: event.pageY + 10, - left: event.pageX + 10, - }); - //setShowDropdownNumber(3) - setShowDropdown(true); - }} - /> - - - - ), - }} - rows="6" - multiline - fullWidth - color="primary" - placeholder="Some execution data" - defaultValue={ - workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value - } - onBlur={(e) => { - workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value - setWorkflow(workflow) - setLastSaved(false) - - }} - /> - {!showDropdown ? null : - { - handleMenuClose(); - }} - open={!!menuPosition} - style={{ - border: `2px solid #f85a3e`, - color: "white", - marginTop: 2, - }} - > - {subflowActionList.map((innerdata) => { - const icon = - innerdata.type === "action" ? ( - - ) : innerdata.type === "workflow_variable" || - innerdata.type === "execution_variable" ? ( - - ) : ( - - ); - - const handleExecArgumentHover = (inside) => { - var exec_text_field = document.getElementById( - "execution_argument_input_field" - ); - if (exec_text_field !== null) { - if (inside) { - exec_text_field.style.border = "2px solid #f85a3e"; - } else { - exec_text_field.style.border = ""; - } - } - - // Also doing arguments - if ( - workflow.triggers !== undefined && - workflow.triggers !== null && - workflow.triggers.length > 0 - ) { - for (let triggerkey in workflow.triggers) { - const item = workflow.triggers[triggerkey]; - - if (cy !== undefined) { - var node = cy.getElementById(item.id); - if (node.length > 0) { - if (inside) { - node.addClass("shuffle-hover-highlight"); - } else { - node.removeClass("shuffle-hover-highlight"); - } - } - } - } - } - } - - const handleActionHover = (inside, actionId) => { - if (cy !== undefined) { - var node = cy.getElementById(actionId); - if (node.length > 0) { - if (inside) { - node.addClass("shuffle-hover-highlight"); - } else { - node.removeClass("shuffle-hover-highlight"); - } - } - } - }; - - const handleMouseover = () => { - if (innerdata.type === "Execution Argument") { - handleExecArgumentHover(true); - } else if (innerdata.type === "action") { - handleActionHover(true, innerdata.id); - } - }; - - const handleMouseOut = () => { - if (innerdata.type === "Execution Argument") { - handleExecArgumentHover(false); - } else if (innerdata.type === "action") { - handleActionHover(false, innerdata.id); - } - }; - - var parsedPaths = []; - console.log("Found example data: ", innerdata.example) - if (typeof innerdata.example === "object") { - parsedPaths = GetParsedPaths(innerdata.example, ""); - } - - const coverColor = "#82ccc3" - - return parsedPaths.length > 0 ? ( - - {/* - - {icon} {innerdata.name} -
    - } - parentMenuOpen={!!menuPosition} - style={{ - backgroundColor: theme.palette.inputColor, - color: "white", - minWidth: 250, - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - {parsedPaths.map((pathdata, index) => { - // FIXME: Should be recursive in here - const icon = - pathdata.type === "value" ? ( - - ) : pathdata.type === "list" ? ( - - ) : ( - - ) - - return ( - { }} - onClick={() => { - handleItemClick([innerdata, pathdata]); - }} - > - -
    - {icon} {pathdata.name} -
    -
    -
    - ); - })} - - */} - - - {icon} {innerdata.name} -
    - } - parentMenuOpen={!!menuPosition} - style={{ - color: "white", - minWidth: 250, - maxWidth: 250, - maxHeight: 50, - overflow: "hidden", - }} - onClick={() => { - console.log("CLICKED: ", innerdata); - console.log(innerdata.example) - handleItemClick([innerdata]); - }} - > - - - { - //console.log("HOVER: ", pathdata); - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - - {innerdata.name} - - - - {parsedPaths.map((pathdata, index) => { - // FIXME: Should be recursive in here - // - const icon = - pathdata.type === "value" ? ( - - ) : pathdata.type === "list" ? ( - - ) : ( - - ); - // - - const indentation_count = (pathdata.name.match(/\./g) || []).length+1 - const baseIndent =
    - //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 - const boxPadding = 0 - const namesplit = pathdata.name.split(".") - const newname = namesplit[namesplit.length-1] - return ( - { - //console.log("HOVER: ", pathdata); - }} - onClick={() => { - handleItemClick([innerdata, pathdata]); - }} - > - -
    - {Array(indentation_count).fill().map((subdata, subindex) => { - return ( - baseIndent - ) - })} - {icon} {newname} - {pathdata.type === "list" ? { - - }} /> : null} -
    -
    -
    - ); - })} - - - - ) : ( - handleMouseover()} - onMouseOut={() => { - handleMouseOut(); - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - -
    - {icon} {innerdata.name} -
    -
    -
    - ); - })} - - } - {/* -
    -
    - API-key -
    -
    - { - workflow.triggers[selectedTriggerIndex].parameters[2].value = - e.target.value; - setWorkflow(workflow); - }} - /> - */} -
    -
    - -
    -
    -
    -
    -
    - Authentication Override -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    + const CommentSidebar = () => { if (Object.getOwnPropertyNames(selectedComment).length > 0) { @@ -14038,20 +13127,32 @@ const releaseToConnectLabel = "Release to Connect" // Special SCHEDULE handler var trigger_header_auth = "" - if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers !== null && workflow.triggers !== undefined && workflow.triggers.length >= selectedTriggerIndex && workflow.triggers[selectedTriggerIndex] !== undefined ) { - if (selectedTrigger.trigger_type === "SCHEDULE" && workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null) { - console.log("Autofixing schedule") - workflow.triggers[selectedTriggerIndex].parameters = []; - workflow.triggers[selectedTriggerIndex].parameters[0] = { - name: "cron", - value: isCloud ? "*/25 * * * *" : "60", - }; - workflow.triggers[selectedTriggerIndex].parameters[1] = { - name: "execution_argument", - value: '{"example": {"json": "is cool"}}', - }; - setWorkflow(workflow); + if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers !== null && workflow.triggers !== undefined && workflow.triggers.length >= selectedTriggerIndex && workflow.triggers[selectedTriggerIndex] !== undefined ) { + + if (selectedTrigger.trigger_type === "SCHEDULE") { + if (workflow.triggers[selectedTriggerIndex] === undefined) { + return null; + } + + if ( + workflow.triggers[selectedTriggerIndex].parameters === undefined || + workflow.triggers[selectedTriggerIndex].parameters === null || + workflow.triggers[selectedTriggerIndex].parameters.length === 0 + ) { + console.log("Autofixing schedule") + + workflow.triggers[selectedTriggerIndex].parameters = []; + workflow.triggers[selectedTriggerIndex].parameters[0] = { + name: "cron", + value: isCloud ? "*/25 * * * *" : "60", + }; + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "execution_argument", + value: '{"example": {"json": "is cool"}}', + }; + setWorkflow(workflow); + } } else if (selectedTrigger.trigger_type === "WEBHOOK") { if (workflow.triggers[selectedTriggerIndex] === undefined) { return null; @@ -14143,9 +13244,63 @@ const releaseToConnectLabel = "Release to Connect" setWorkflow(workflow); } + }else if(selectedTrigger.trigger_type === "SUBFLOW"){ + + if ( + workflow.triggers[selectedTriggerIndex].parameters === undefined || + workflow.triggers[selectedTriggerIndex].parameters === null || + workflow.triggers[selectedTriggerIndex].parameters.length === 0 + ) { + workflow.triggers[selectedTriggerIndex].parameters = []; + workflow.triggers[selectedTriggerIndex].parameters[0] = { + name: "workflow", + value: "", + }; + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "argument", + value: "", + id:"subflow_field" + }; + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "user_apikey", + value: "", + }; + workflow.triggers[selectedTriggerIndex].parameters[3] = { + name: "startnode", + value: "", + }; + workflow.triggers[selectedTriggerIndex].parameters[4] = { + name: "check_result", + value: "false", + }; + workflow.triggers[selectedTriggerIndex].parameters[5] = { + name: "auth_override", + value: "", + }; + setWorkflow(workflow) + /* + // API-key has been replaced by auth key for the execution. + // Parents can now automatically execute children without auth from a user, as long as the subflow in question is owned by the same org and the subflow is actually referencing it during checkin. + console.log("SETTINGS: ", userSettings); + if ( + userSettings !== undefined && + userSettings !== null && + userSettings.apikey !== null && + userSettings.apikey !== undefined && + userSettings.apikey.length > 0 + ) { + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "user_apikey", + value: userSettings.apikey, + }; + } + */ + } } } + console.log(selectedTrigger) + const WebhookSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "WEBHOOK" ? null :

    @@ -15689,7 +14844,8 @@ const releaseToConnectLabel = "Release to Connect" }

    - const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null : + + const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "PIPELINE" ? null :

    {selectedTrigger.app_name}: {selectedTrigger.status} @@ -16194,6 +15350,863 @@ const releaseToConnectLabel = "Release to Connect"

    + const SubflowSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "SUBFLOW" ? null : +
    + +

    + {selectedTrigger.app_name} +

    + + + +
    + + What are subflows? + + +
    +
    + Name + +
    +
    +
    + + + Delay + { + if (isNaN(event.target.value)) { + console.log("NAN: ", event.target.value) + return + } + + const parsedNumber = parseInt(event.target.value) + if (parsedNumber > 86400) { + console.log("Max number is 1 day (86400)") + return + } + + selectedTrigger.execution_delay = parseInt(event.target.value) + setSelectedTrigger(selectedTrigger) + }} + /> + + +
    +
    +
    + { + const newvalue = workflow.triggers[selectedTriggerIndex].parameters[4] === undefined || workflow.triggers[selectedTriggerIndex].parameters[4].value === "false"? "true" : "false"; + workflow.triggers[selectedTriggerIndex].parameters[4] = { + name: "check_result", + value: newvalue, + }; + + setWorkflow(workflow); + setUpdate(Math.random()); + }} + color="primary" + value="Wait for results" + /> + } + style={{ marginTop: 10 }} + label={
    Wait for results
    } + /> +
    +
    +
    +
    +
    + Select a workflow to execute +
    +
    + {workflow.triggers[selectedTriggerIndex].parameters[0].value + .length === 0 ? null : workflow.triggers[selectedTriggerIndex] + .parameters[0].value === props.match.params.key ? null : ( +
    + + + +
    + )} +
    + + {workflows === undefined || + workflows === null || + workflows.length === 0 ? null : ( + + option.id === value.id} + getOptionLabel={(option) => { + if ( + option === undefined || + option === null || + option.name === undefined || + option.name === null + ) { + return "No Workflow Selected"; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + return newname; + }} + options={workflows} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + setLastSaved(false) + console.log("Found value: ", newValue) + + var parsedinput = { target: { value: newValue } } + + // For variables + if (typeof newValue === 'string' && newValue.startsWith("$")) { + parsedinput = { + target: { + value: { + "name": newValue, + "id": newValue, + "actions": [], + "triggers": [], + } + } + } + } + + handleWorkflowSelectionUpdate(parsedinput) + }} + renderOption={(props, data, state) => { + if (data.id === workflow.id) { + data = workflow; + } + + //key={index} + return ( + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.name} + : null} + + Choose Subflow '{data.name}' + + + }> + { + getWorkflowApps(data.id); + handleWorkflowSelectionUpdate({ + target: { + value: data + } + }) + document.activeElement.blur(); + }} + > + + {data.name} + + + ) + }} + renderInput={(params) => { + return ( + + ); + }} + /> + )} + + {subworkflow === undefined || + subworkflow === null || + subworkflow.id === undefined || + subworkflow.actions === null || + subworkflow.actions === undefined || + subworkflow.actions.length === 0 ? null : ( + +
    +
    + Select the Startnode +
    +
    + option.id === value.id} + getOptionLabel={(option) => { + if (option === undefined || option === null || option.label === undefined || option.label === null) { + if (option.length === 36) { + + } + + return "TMP"; + } + + const newname = ( + option.label.charAt(0).toUpperCase() + option.label.substring(1) + ).replaceAll("_", " "); + return newname; + }} + options={subworkflow.actions} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + setLastSaved(false) + handleSubflowStartnodeSelection({ target: { value: newValue } }) + }} + renderOption={(props, action, state) => { + const isParent = getParents(selectedTrigger).find( + (parent) => parent.id === action.id + ) + + return ( + { + if (subworkflow.id === workflow.id) { + handleActionHover(true, action.id) + } + }} + onMouseOut={() => { + if (subworkflow.id === workflow.id) { + handleActionHover(false, action.id) + } + }} + disabled={isCloud && isParent} + onClick={() => { + handleSubflowStartnodeSelection({ + target: { + value: action + } + }) + document.activeElement.blur(); + }} + style={{ + backgroundColor: theme.palette.inputColor, + color: isParent ? "red" : "white", + }} + value={action} + > + {action.label} + + ); + }} + renderInput={(params) => { + return ( + + ); + }} + /> +
    + )} +
    +
    + Execution Argument +
    +
    + + + + { + event.preventDefault() + // setFieldCount(count) + setCodeEditorModalOpen(true) + setActiveDialog("codeeditor") + //setcodedata(data.value) + var parsedvalue = workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value + // if (parsedvalue === undefined || parsedvalue === null) { + // parsedvalue = "" + // } + console.log("Data sending to codeeditor: ",{ + "name": workflow.triggers[selectedTriggerIndex].parameters[1].name, + "value": parsedvalue, + "field_number": 1, + "actionlist": subflowActionList, + "field_id": "subflow_field", + }) + setEditorData({ + "name": workflow.triggers[selectedTriggerIndex].parameters[1].name, + "value": parsedvalue, + "field_number": 1, + "actionlist": subflowActionList, + "field_id": "subflow_field", + }) + }} + /> + + + { + setMenuPosition({ + top: event.pageY + 10, + left: event.pageX + 10, + }); + //setShowDropdownNumber(3) + setShowDropdown(true); + }} + /> + + + + ), + }} + rows="6" + multiline + fullWidth + color="primary" + placeholder="Some execution data" + defaultValue={ + workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value + } + onBlur={(e) => { + workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value + setWorkflow(workflow) + setLastSaved(false) + + }} + /> + {!showDropdown ? null : + { + handleMenuClose(); + }} + open={!!menuPosition} + style={{ + border: `2px solid #f85a3e`, + color: "white", + marginTop: 2, + }} + > + {subflowActionList.map((innerdata) => { + const icon = + innerdata.type === "action" ? ( + + ) : innerdata.type === "workflow_variable" || + innerdata.type === "execution_variable" ? ( + + ) : ( + + ); + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById( + "execution_argument_input_field" + ); + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e"; + } else { + exec_text_field.style.border = ""; + } + } + + // Also doing arguments + if ( + workflow.triggers !== undefined && + workflow.triggers !== null && + workflow.triggers.length > 0 + ) { + for (let triggerkey in workflow.triggers) { + const item = workflow.triggers[triggerkey]; + + if (cy !== undefined) { + var node = cy.getElementById(item.id); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + } + } + } + + const handleActionHover = (inside, actionId) => { + if (cy !== undefined) { + var node = cy.getElementById(actionId); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + }; + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true); + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id); + } + }; + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false); + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id); + } + }; + + var parsedPaths = []; + console.log("Found example data: ", innerdata.example) + if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } + + const coverColor = "#82ccc3" + + return parsedPaths.length > 0 ? ( + + {/* + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + backgroundColor: theme.palette.inputColor, + color: "white", + minWidth: 250, + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ) + + return ( + { }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {icon} {pathdata.name} +
    +
    +
    + ); + })} + + */} + + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + color: "white", + minWidth: 250, + maxWidth: 250, + maxHeight: 50, + overflow: "hidden", + }} + onClick={() => { + console.log("CLICKED: ", innerdata); + console.log(innerdata.example) + handleItemClick([innerdata]); + }} + > + + + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + + {innerdata.name} + + + + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + // + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ); + // + + const indentation_count = (pathdata.name.match(/\./g) || []).length+1 + const baseIndent =
    + //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 + const boxPadding = 0 + const namesplit = pathdata.name.split(".") + const newname = namesplit[namesplit.length-1] + return ( + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {Array(indentation_count).fill().map((subdata, subindex) => { + return ( + baseIndent + ) + })} + {icon} {newname} + {pathdata.type === "list" ? { + + }} /> : null} +
    +
    +
    + ); + })} + + + + ) : ( + handleMouseover()} + onMouseOut={() => { + handleMouseOut(); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + +
    + {icon} {innerdata.name} +
    +
    +
    + ); + })} + + } + {/* +
    +
    + API-key +
    +
    + { + workflow.triggers[selectedTriggerIndex].parameters[2].value = + e.target.value; + setWorkflow(workflow); + }} + /> + */} +
    +
    + +
    +
    +
    +
    +
    + Authentication Override +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + const cytoscapeViewWidths = isMobile ? 50 : 950; const bottomBarStyle = { position: "fixed", From 6b04a9f6c08809d0efdeef744f34e46e21e41f94 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 18 Jul 2024 15:49:53 +0530 Subject: [PATCH 090/336] Fixed the param value issue in Trigger: Webhook & Scheduler --- frontend/src/views/AngularWorkflow.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index e562221f..d1a21599 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -14823,6 +14823,7 @@ const releaseToConnectLabel = "Release to Connect" setSelectedTrigger(trigger); setWorkflow(workflow); saveWorkflow(workflow); + setSelectedTrigger({}) }) .catch((error) => { From 5875745056cc5a44cfa13fb967f25a522bf9f8cd Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 18 Jul 2024 14:41:05 +0200 Subject: [PATCH 091/336] Merged in all UI fixes --- frontend/src/components/AppFramework.jsx | 5 +- frontend/src/components/AppSearchButtons.jsx | 4 +- frontend/src/components/Billing.jsx | 420 +++++++++++--- frontend/src/components/LicencePopup.jsx | 2 +- frontend/src/components/NewHeader.jsx | 518 ++++++++++-------- frontend/src/components/Priorities.jsx | 2 +- frontend/src/components/Priority.jsx | 21 + frontend/src/components/Searchfield.jsx | 2 +- .../components/WorkflowValidationTimeline.jsx | 68 ++- frontend/src/views/AngularWorkflow.jsx | 9 +- frontend/src/views/Usecases.jsx | 27 +- frontend/src/views/Workflows.jsx | 13 +- 12 files changed, 737 insertions(+), 354 deletions(-) diff --git a/frontend/src/components/AppFramework.jsx b/frontend/src/components/AppFramework.jsx index 00ef41c0..3d786443 100644 --- a/frontend/src/components/AppFramework.jsx +++ b/frontend/src/components/AppFramework.jsx @@ -36,6 +36,7 @@ import edgehandles from "cytoscape-edgehandles"; import cytoscape from "cytoscape"; import { toast } from 'react-toastify'; +import { isMobile } from 'react-device-detect'; cytoscape.use(edgehandles) @@ -2152,7 +2153,7 @@ const AppFramework = (props) => { { Object.getOwnPropertyNames(discoveryData).length > 0 ? - + {paperTitle.length > 0 ? @@ -2321,7 +2322,7 @@ const AppFramework = (props) => { elements={elements} minZoom={0.35} maxZoom={2.00} - style={{width: 560*scale, height: 560*scale, backgroundColor: theme.palette.backgroundColor, margin: "auto",}} + style={{width: isMobile?null:560*scale, height: 560*scale, backgroundColor: theme.palette.backgroundColor, margin: isMobile?null:"auto",}} stylesheet={frameworkStyle} boxSelectionEnabled={false} panningEnabled={false} diff --git a/frontend/src/components/AppSearchButtons.jsx b/frontend/src/components/AppSearchButtons.jsx index 262843ad..6bc3179a 100644 --- a/frontend/src/components/AppSearchButtons.jsx +++ b/frontend/src/components/AppSearchButtons.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from "react"; import theme from '../theme.jsx'; import ReactGA from 'react-ga4'; import { useNavigate, Link } from 'react-router-dom'; - +import { isMobile } from 'react-device-detect'; import { Search as Searchicon, CloudQueue as CloudQueueicon, Code as Codeicon, Close as Closeicon, Folder as Foldericon, LibraryBooks as LibraryBooksicon, Delete as DeleteIcon, Close as CloseIcon, } from '@mui/icons-material'; import aa from 'search-insights' import Deleteicon from '@mui/icons-material/Delete'; @@ -181,7 +181,7 @@ const AppSearchButtons = (props) => { width: 319, height: 395, flexShrink: 0, - marginLeft: 70, + marginLeft: isMobile? null:70, marginTop: 68, position: "absolute", zIndex: 100, diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index f7598852..97b6cd16 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -41,11 +41,12 @@ import { Close as CloseIcon, Delete, RestaurantRounded, + Cloud, } from "@mui/icons-material"; //import { useAlert import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; -import BillingStats from "../components/BillingStats.jsx"; +import BillingStats from "./BillingStats.jsx"; import { handlePayasyougo } from "../views/HandlePaymentNew.jsx" import DeleteIcon from '@mui/icons-material/Delete'; @@ -173,10 +174,10 @@ const Billing = (props) => { const paperStyle = { padding: 20, - height: "100%", - minHeight: 280, - maxWidth: 400, - width: "100%", + // maxWidth: 400, + width: 340, + height: 480, + // width: "100%", backgroundColor: theme.palette.platformColor, borderRadius: theme.palette.borderRadius * 2, border: "1px solid rgba(255,255,255,0.3)", @@ -470,9 +471,11 @@ const Billing = (props) => { style: { pointerEvents: "auto", color: "white", - minWidth: 750, + // minWidth: 750, + width: 340, padding: 30, - maxHeight: 700, + // maxHeight: 700, + height: 480, overflowY: "auto", overflowX: "hidden", zIndex: 10012, @@ -546,7 +549,7 @@ const Billing = (props) => {
    -
    +
    {top_text === "Base Cloud Access" && userdata.has_card_available === true ? { userdata.has_card_available === true ? "While you have a card attached to your account, Shuffle will no longer prevent workflows from running. Billing will occur at the start of each month." : - isCloud ? + isCloud ? `You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.` : - `You are not subscribed to any plan and are using the free, open source plan. This plan has no enforced limits, but scale issues may occur due to CPU congestion.` + `You are not subscribed to any plan and are using the free, open source plan. This plan has no enforced limits, but scale issues may occur due to CPU congestion.` }
    - + Billing email: {BillingEmail} {userdata.has_card_available === true && ( @@ -741,7 +744,7 @@ const Billing = (props) => { boxShadow: 'none', border: 'none', cursor: 'pointer', - transition: 'background-color 0.3s, color 0.3s' + transition: 'background-color 0.3s, color 0.3s', }} > Change @@ -813,12 +816,13 @@ const Billing = (props) => { variant="outlined" color="primary" style={{ - marginTop: 10, + marginTop: !userdata.has_card_available ? 20 : 10, borderRadius: 25, height: 40, - fontSize: 14, + fontSize: 16, color: "white", backgroundImage: userdata.has_card_available ? null : "linear-gradient(to right, #f86a3e, #f34079)", + textTransform: "none", }} onClick={() => { @@ -847,9 +851,10 @@ const Billing = (props) => { marginTop: 10, borderRadius: 25, height: 40, - fontSize: 14, + fontSize: 16, color: "white", backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", + textTransform: 'none' }} onClick={() => { if (isCloud) { @@ -904,9 +909,11 @@ const Billing = (props) => { const [editConsultation, setEditConsultation] = React.useState(false); const [openUpgradePlan, setOpenUpgradePlan] = React.useState(false); - const [consultationHours, setConsultationHours] = React.useState(5); + const [consultationHours, setConsultationHours] = React.useState(1); const [message, setMessage] = React.useState(""); const [hovered, setHovered] = React.useState(false) + const [getProfessionalServices, setGetProfessionalServices] = React.useState(false) + const [clickOnBuy, setClickOnBuy] = React.useState(false) const formatedHours = String(inputHour).padStart(2, "0") const formatedMinutes = String(inputMinutes).padStart(2, "0") @@ -959,13 +966,17 @@ const Billing = (props) => { return response.json(); }) .then((responseJson) => { - console.log("Response from consultation save: ", responseJson); if (responseJson.success === true) { toast.success("Consultation hours saved successfully"); setEditConsultation(false); } else { toast.error("Failed saving consultation hours."); } + if (inputHour > 0 || inputMinutes > 0) { + setGetProfessionalServices(true) + } else { + setGetProfessionalServices(false) + } }) .catch((error) => { console.log("Error: ", error); @@ -1001,7 +1012,6 @@ const Billing = (props) => { return response.json(); }) .then((responseJson) => { - console.log("Response from consultation save: ", responseJson); if (responseJson.success === true) { toast.success("Thank you for your request. We will get back to you soon."); setOpenUpgradePlan(false); @@ -1015,18 +1025,23 @@ const Billing = (props) => { }); } - var newPaperstyle = JSON.parse(JSON.stringify(paperStyle)) + useEffect(() => { + if (inputHour !== undefined && inputMinutes !== undefined && inputHour > 0 || inputMinutes > 0) { + setGetProfessionalServices(true) + } else { + setGetProfessionalServices(false) + } + }) return ( {
    - Current Plan includes total {inputHour} hours and {inputMinutes} minutes of consultation and management by our experts. + You currently have a total of {inputHour} hours and {inputMinutes} minutes of professional services available by our experts. -
    +
    {editConsultation ? <> { variant="contained" color="primary" onClick={handleCancel} + style={{ textTransform: 'none' }} > Cancel @@ -1082,48 +1098,113 @@ const Billing = (props) => { variant="contained" color="primary" onClick={toggleEditMode} + style={{ textTransform: 'none' }} > Edit )} - {editConsultation && } + {editConsultation && }
    : null} - + Features
    • - Debug/Create workflows with our experts + Build custom apps, integrations, and worklows for your specific use cases or applications
    • - Ask questions and get help with your workflows and integrations by our experts + Help solve / debug / update / add features and capabilities of the platform
    - +
    + + { setClickOnBuy(false) }}> + + You will be taken to Stripe to book professional service hours. You can adjust the number of hours on the left side of the Stripe page. + + + + + + + + + + + +
    setOpenUpgradePlan(false)} fullWidth @@ -1140,20 +1221,21 @@ const Billing = (props) => { - Enter the total hours of consultation you want to include in your plan. + Enter the total hours of consultation you want.
    setConsultationHours(val)} aria-labelledby="continuous-slider" - step={5} - min={5} - max={50} - style={{ width: '80%', color: theme.palette.primary.main }} // Adding primary color for better visibility + step={1} + min={1} + max={(inputHour === "0" && inputMinutes > 0) ? 1 : inputHour} + style={{ width: '80%', color: theme.palette.primary.main }} marks valueLabelDisplay="auto" /> +
    If you have any additional requirements or questions, please leave a message below. @@ -1163,17 +1245,17 @@ const Billing = (props) => { fullWidth multiline rows={4} - placeholder="Your message" + placeholder="What kind of services are you looking for?" style={{ marginTop: '16px', borderRadius: '8px' }} onChange={(e) => setMessage(e.target.value)} />
    @@ -1181,6 +1263,217 @@ const Billing = (props) => { ) } + const TrainingService = () => { + + const [hovered, setHovered] = React.useState(false) + const [openPrivateTraining, setOpenPrivateTraining] = React.useState(false) + const [PrivateTrainingMember, setPrivateTrainingMember] = React.useState(5) + const [message, setMessage] = React.useState(""); + + const handlePrivateTraining = () => { + + toast("Submitting your request for private training. Please wait...") + + const data = { + org_id: selectedOrganization.id, + trainingMembers: String(PrivateTrainingMember), + message: message + } + + const url = `${globalUrl}/api/v1/orgs/${selectedOrganization.id}/privateTraining` + + fetch(url, { + body: JSON.stringify(data), + mode: "cors", + method: "POST", + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }).then((response) => { + if (response.status !== 200) { + console.log("Error in response"); + } + return response.json(); + }).then((responseJson) => { + if (responseJson.success === true) { + toast.success("Your request for private training has been submitted successfully. We will get back to you soon.") + setOpenPrivateTraining(false) + } else { + toast.error("Failed sending request for private training. Please try again later or contact support@shuffler.io for help.") + } + }) + } + + return ( + setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + + Training + + + + Become a Shuffle Expert + +
    + + Public Training + +
      +
    • + + Public course on Automation for Security Professionals + +
    • +
    • + + Covers Shuffle Platform, Apps, Workflows, Usecases, JSON, Liquid Formatting, and more. + +
    • +
    + + Private Training + +
      +
    • + + Everything from Public Training + +
    • +
    • + + Customized for your team’s usecases, date and time, location, and more. + +
    • +
    +
    +
    + + + setOpenPrivateTraining(false)} + fullWidth + style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }} + PaperProps={{ + style: { + width: 500, + margin: 0, + } + }} + > + + Private Training + + + + Enter the total members for private training. Minimum 5 members required. + +
    + setPrivateTrainingMember(val)} + aria-labelledby="continuous-slider" + step={1} + min={5} + max={50} + style={{ width: '80%', color: theme.palette.primary.main }} + marks + valueLabelDisplay="auto" + /> + +
    + + If you have any additional requirements or questions, please leave a message below. + + setMessage(e.target.value)} + /> + +
    +
    +
    +
    + ) + } + const addDealModal = ( { {userdata.support === true ?
    For sales: Create  - + EU contract  or  - + NOT EU contract   -   @@ -1650,7 +1943,7 @@ const Billing = (props) => { : null} -
    +
    {isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null : { selectedOrganization={selectedOrganization} /> : null} + + + {isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && @@ -1966,9 +2262,9 @@ const Billing = (props) => {
    ) : null*/} -
    +
    Manage Billing diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx index 014fa46f..7fa355e0 100644 --- a/frontend/src/components/LicencePopup.jsx +++ b/frontend/src/components/LicencePopup.jsx @@ -690,7 +690,7 @@ const LicencePopup = (props) => { const priceItem = window.location.origin === "https://shuffler.io" ? shuffleVariant === 0 ? "app_executions" : "cores" : - shuffleVariant === 0 ? "price_1PWI5zDzMUgUjxHSKkz0fGdN" : "price_1NXjQqDzMUgUjxHSg690R4FP" + shuffleVariant === 0 ? "price_1PbO0cEJjT17t98NsfEMUlMn" : "price_1PbNnaEJjT17t98NLadq6Lhq" const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success` const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure` diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx index fa626601..ee486357 100644 --- a/frontend/src/components/NewHeader.jsx +++ b/frontend/src/components/NewHeader.jsx @@ -25,9 +25,10 @@ import { Divider, LinearProgress, AppBar, - Dialog, - DialogTitle, + Dialog, + DialogTitle, } from "@mui/material"; +import { makeStyles } from '@mui/styles'; import { Close as CloseIcon, @@ -45,10 +46,80 @@ import { Analytics as AnalyticsIcon, Lightbulb as LightbulbIcon, ExpandMore as ExpandMoreIcon, + KeyboardArrowDown as KeyboardArrowDownIcon } from "@mui/icons-material"; +import zIndex from "@mui/material/styles/zIndex.js"; -const hoverColor = "#f85a3e"; -const hoverOutColor = "#e8eaf6"; +const useStyles = makeStyles((theme) => ({ + menuButton: { + textTransform: "none !important", + fontStyle: "normal", + color: "#333", + textAlign: "center", + fontSize: "16px !important", + fontWeight: "500 !important", + display: "flex", + alignItems: "center", + '&:hover': { + backgroundColor: "transparent", + }, + }, + dropdownMenu: { + marginTop: theme.spacing(1), + borderRadius: "12px !important", + zIndex: 10, + "& .MuiPaper-root": { + border: "1px solid #f85a3e", + boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", + borderRadius: "12px !important", + top: -10, + overflow: "visible", + background: "#1A1A1A", + "&::before": { + content: '""', + display: "block", + position: "absolute", + top: -10, + left: "82%", + borderLeft: "10px solid transparent", + borderRight: "10px solid transparent", + borderBottom: "10px solid #f85a3e", + }, + }, + }, + dropdownMenuItem: { + padding: theme.spacing(2, 3), + fontSize: "16px", + fontWeight: 400, + color: "#fff", + background: "#1A1A1A", + borderRadius: 16, // Ensure the border radius matches the container + transition: "background-color 0.3s, color 0.3s", + '&:hover': { + color: "#1A73E8", + background: "#3c3c3c", + }, + }, + menuList: { + display: "flex", + flexDirection: "row", + alignItems: "center", + padding: 0, + margin: 0, + listStyle: "none", + textTransform: "none", + }, + cssStcg3yMenuList: { + borderRadius: "12px !important", + }, + divider: { + width: "80%", + border: "0.5px solid #494949", + backgroundColor: "#fff", + alignItems: "center", + marginLeft: 17 + }, +})); const Header = (props) => { const { @@ -64,35 +135,62 @@ const Header = (props) => { serverside, billingInfo, } = props; - - const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); - const [SoarHoverColor, setSoarHoverColor] = useState(hoverOutColor); - const [LoginHoverColor, setLoginHoverColor] = useState(hoverOutColor); - const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor); - const [HelpHoverColor, setHelpHoverColor] = useState(hoverOutColor); const [isHeader, setIsHeader] = React.useState(false); const [modalOpen, setModalOpen] = useState(false); + const [tooltipOpen, setTooltipOpen] = useState(false); const [anchorEl, setAnchorEl] = React.useState(null); const [anchorElAvatar, setAnchorElAvatar] = React.useState(null); const [subAnchorEl, setSubAnchorEl] = React.useState(null); const [upgradeHovered, setUpgradeHovered] = React.useState(false); const [showTopbar, setShowTopbar] = useState(false) - const stripeKey = typeof window === 'undefined' || window.location === undefined ? "" : window.location.origin === "https://shuffler.io" ? "pk_live_XAxwE2Fp9DEbEcNYw4UKmyby00vIlIPPRp" : "pk_test_EdxgKfqmQGXY5JLjdBqtuhCw00BHbiKJDB" + const stripeKey = typeof window === 'undefined' || window.location === undefined ? "" : window.location.origin === "https://shuffler.io" ? "pk_live_XAxwE2Fp9DEbEcNYw4UKmyby00vIlIPPRp" : "pk_test_51PXYYMEJjT17t98NbDkojZ3DRvsFUQBs35LGMx3i436BXwEBVFKB9nCvHt0Q3M4MG3dz4mHheuWvfoYvpaL3GmsG00k1Rb2ksO" let navigate = useNavigate(); + const classes = useStyles(); const handleClick = (event) => { setAnchorEl(event.currentTarget); }; + const handleMenuOpen = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleMenuClose = () => { + setAnchorEl(null); + }; + + const handleMenuItemClick = (path) => { + navigate(path); + handleMenuClose(); + }; + + const handleTooltipClose = () => { + setTooltipOpen(false); + }; + + const handleTooltipOpen = () => { + setTooltipOpen(true); + }; useEffect(() => { - const topbar = localStorage.getItem("topbar_closed") - if (topbar === "true") { - setShowTopbar(false) - } else { - setShowTopbar(true) - } + const topbar = localStorage.getItem("topbar_closed") + if (topbar === "true") { + setShowTopbar(false) + } else { + setShowTopbar(true) + } }, []) + const hoverColor = "#f85a3e"; + const hoverOutColor = "#e8eaf6"; + + const handleHover = (event) => { + event.target.style.color = hoverColor; + }; + + const handleHoverOut = (event) => { + event.target.style.color = hoverOutColor; + }; + const handleClose = () => { setAnchorEl(null); setAnchorElAvatar(null); @@ -103,6 +201,10 @@ const Header = (props) => { const hrefStyle = { color: hoverOutColor, textDecoration: "none", + textTransform: "none", + fontStyle: "normal", + width: "100%", + fontSize: "16px", }; const menuText = { @@ -229,47 +331,6 @@ const Header = (props) => { }); }; - // Rofl this is weird - const handleDocsHover = () => { - setDocsHoverColor(hoverColor); - }; - - const handleDocsHoverOut = () => { - setDocsHoverColor(hoverOutColor); - }; - - const handleHomeHover = () => { - setHomeHoverColor(hoverColor); - }; - - const handleHelpHover = () => { - setHelpHoverColor(hoverColor); - }; - - const handleHelpHoverOut = () => { - setHelpHoverColor(hoverOutColor); - }; - - const handleSoarHover = () => { - setSoarHoverColor(hoverColor); - }; - - const handleSoarHoverOut = () => { - setSoarHoverColor(hoverOutColor); - }; - - const handleHomeHoverOut = () => { - setHomeHoverColor(hoverOutColor); - }; - - const handleLoginHover = () => { - setLoginHoverColor(hoverColor); - }; - - const handleLoginHoverOut = () => { - setLoginHoverColor(hoverOutColor); - }; - const notificationWidth = 335 const imagesize = 22; const boxColor = "#86c142"; @@ -553,8 +614,8 @@ const Header = (props) => { aria-haspopup="true" onClick={(event) => { }} > - - {/*#f865f2*/} + + {/*#f865f2*/} @@ -690,67 +751,68 @@ const Header = (props) => { textAlign: "center", marginTop: "auto", marginBottom: "auto", - marginRight: 10, + // marginRight: 10, }; - const modalView = - { - setModalOpen(false); - }} - PaperProps={{ - style: { - color: "white", - minWidth: 850, - minHeight: 370, - padding: 20, - backgroundColor: "rgba(0, 0, 0, 1)", - borderRadius: theme.palette.borderRadius, - }, - }} - > - - - Upgrade your plan - - { - if (isCloud) { + const modalView = + { + setModalOpen(false); + }} + PaperProps={{ + style: { + color: "white", + minWidth: 850, + minHeight: 370, + padding: 20, + backgroundColor: "rgba(0, 0, 0, 1)", + borderRadius: theme.palette.borderRadius, + }, + }} + > + + + Upgrade your plan + + { + if (isCloud) { ReactGA.event({ category: "header", action: "close_Upgread_popup", label: "", - })}; - setModalOpen(false); - }} - style={{ - marginLeft: "auto", - position: "absolute", - top: 20, - right: 20, - }} - > - - - -
    - + + + +
    + -
    -
    + userdata={userdata} + stripeKey={stripeKey} + setModalOpen={setModalOpen} + {...props} + /> +
    +
    // Handle top bar or something const defaultTop = -2 @@ -792,13 +854,13 @@ const Header = (props) => { - + - {isCloud ? ( - - - - - - ) : null} - + + + {isCloud && ( + handleMenuItemClick('/pricing')}> + + Pricing + + + )} +
    + handleMenuItemClick('/professional-support')}> + + Professional Services + + +
    + handleMenuItemClick('/training')}> + + Training Courses + + +
    +
    + {/* - +
    */}
    { margin: "auto", }} > -
    +
    {
    @@ -984,10 +1053,9 @@ const Header = (props) => {
    {
    {
    {
    + {/* + + +
    + + Pricing & Services + +
    + +
    + */}
    @@ -1260,7 +1345,7 @@ const Header = (props) => { title={""} placement="left" > -
    +
    Add suborgs @@ -1285,14 +1370,17 @@ const Header = (props) => { marginRight: 7, marginTop: 0, }} + title={upgradeHovered ? "Upgrade License" : ""} + open={tooltipOpen} + onClose={handleTooltipClose} > @@ -1397,9 +1485,9 @@ const Header = (props) => {
    @@ -1421,9 +1509,9 @@ const Header = (props) => {
    About
    @@ -1465,9 +1553,7 @@ const Header = (props) => {
    @@ -1512,10 +1598,10 @@ const Header = (props) => { >
    Logout
    @@ -1541,50 +1627,12 @@ const Header = (props) => { const topbarHeight = showTopbar ? 40 : 0 const topbar = !isCloud || !showTopbar ? null : - curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/training" ? + curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/training" || curpath === "/professional-support" ?
    {/* Shuffle 1.4.0 is out! Read more about  */} - Early Success! More  - {/* - { - ReactGA.event({ - category: "landingpage", - action: "click_header_features", - label: "", - }) - - //if (window.drift !== undefined) { - // window.drift.api.startInteraction({ interactionId: 341911 }) - //} else { - // console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) - //} - }} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}> - Features - - - ,  - - { - ReactGA.event({ - category: "landingpage", - action: "click_header_pricing", - label: "", - }) - - navigate("/pricing") - - //if (window.drift !== undefined) { - // window.drift.api.startInteraction({ interactionId: 341911 }) - //} else { - // console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) - //} - }} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}> - Pricing - - -  and  */} + New Public  { ReactGA.event({ @@ -1595,18 +1643,18 @@ const Header = (props) => { navigate("/training") - }} style={{ cursor: "pointer", textDecoration: "none", fontWeight:600, color: "rgba(255,255,255,0.8)" }}> - Public Trainings + }} style={{ cursor: "pointer", textDecoration: "none", fontWeight: 600, color: "rgba(255,255,255,0.8)" }}> + Training Dates Released  Ahead! - { - setShowTopbar(false) + { + setShowTopbar(false) - // Set storage that it's clicked - localStorage.setItem("topbar_closed", "true") - }}> + // Set storage that it's clicked + localStorage.setItem("topbar_closed", "true") + }}>
    diff --git a/frontend/src/components/Priorities.jsx b/frontend/src/components/Priorities.jsx index 289d2b7c..0663ee93 100644 --- a/frontend/src/components/Priorities.jsx +++ b/frontend/src/components/Priorities.jsx @@ -356,7 +356,7 @@ const Priorities = (props) => { dismissNotification(data.id); }} > - Dismiss + Dismiss ) : null} diff --git a/frontend/src/components/Priority.jsx b/frontend/src/components/Priority.jsx index 67d56af7..29e1b0c6 100644 --- a/frontend/src/components/Priority.jsx +++ b/frontend/src/components/Priority.jsx @@ -27,6 +27,13 @@ const Priority = (props) => { const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; let navigate = useNavigate(); + if (window.location.pathname === "/workflows") { + const hidePriorities = localStorage.getItem("hidePriorities", "true") + if (hidePriorities === "true") { + return null + } + } + var realignedSrc = false var realignedDst = false let newdescription = priority.description @@ -176,6 +183,20 @@ const Priority = (props) => { diff --git a/frontend/src/components/Searchfield.jsx b/frontend/src/components/Searchfield.jsx index dc5f9179..c389f88d 100644 --- a/frontend/src/components/Searchfield.jsx +++ b/frontend/src/components/Searchfield.jsx @@ -124,7 +124,7 @@ const SearchField = props => { ); return ( -
    +
    {modalView} { // 1. Find startnode // 2. Map childnodes from it - const startnodeId = workflow.start + var startnodeId = workflow.start + + if (execution !== undefined && execution !== null) { + startnodeId = execution.start + } // Find parent of startnodeId and if it's a webhook var relevantactions = [] @@ -177,32 +181,38 @@ const WorkflowValidationTimeline = (props) => { } } + for (var key in workflow.actions) { + const action = workflow.actions[key] + if (action.id === startnodeId) { + action.order = 0 + relevantactions.push(action) + continue + } + + var parents = [] + if (getParents !== undefined) { + parents = getParents(action) + } else { + parents = getParentNodes(workflow, action) + } + + //const parents = getParentNodes(workflow, action) + //console.log("PARENTS", key, parents) + if (parents !== undefined && parents !== null && parents.length > 0) { + const parentfound = parents.find((element) => element.id === startnodeId) + if (parentfound !== undefined) { + + // FIXME: add order here based on how many steps away from the startnode + // This just has the parent count + action.order = parents.length - if (getParents !== undefined) { - for (var key in workflow.actions) { - const action = workflow.actions[key] - if (action.id === startnodeId) { - action.order = 0 relevantactions.push(action) - continue - } - - const parents = getParents(action) - //const parents = getParentNodes(workflow, action) - //console.log("PARENTS", key, parents) - if (parents !== undefined && parents !== null) { - const parentfound = parents.find((element) => element.id === startnodeId) - if (parentfound !== undefined) { - - // FIXME: add order here based on how many steps away from the startnode - // This just has the parent count - action.order = parents.length - - relevantactions.push(action) - } } } - } else { + } + + if (getParents === undefined) { + var newactions = [] for (var key in workflow.triggers) { const trigger = workflow.triggers[key] if (trigger.trigger_type !== "SUBFLOW" && trigger.trigger_type !== "USERINPUT") { @@ -210,11 +220,12 @@ const WorkflowValidationTimeline = (props) => { } if (workflow.actions.find((element) => element.id === trigger.id) === undefined) { - workflow.actions.push(trigger) + newactions.push(trigger) + //workflow.actions.push(trigger) } } - relevantactions = workflow.actions + relevantactions.push(...newactions) } // Sort according to how many parents a node has. MAY be wrong~ @@ -279,7 +290,10 @@ const WorkflowValidationTimeline = (props) => { } previousTools = true - return null + + if (startnodeId !== action.id) { + return null + } } else { if (action.status === "SUCCESS") { nodecolor = green @@ -405,7 +419,7 @@ const WorkflowValidationTimeline = (props) => { : - {founderror.length > 0 ? founderror : ``} + {founderror.length > 0 ? founderror : `${action.app_name.replaceAll('_', ' ')}`} } placement="top"> diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index e562221f..3dfae44a 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -7514,7 +7514,6 @@ const releaseToConnectLabel = "Release to Connect" } } else if (action.app_name === "Integration Framework") { const iconInfo = GetIconInfo(action) - console.log("FOUND INTEGRATION: iconInfo: ", iconInfo) if (iconInfo !== undefined && iconInfo !== null) { action.fillGradient = iconInfo.fillGradient action.iconBackground = iconInfo.iconBackgroundColor @@ -12462,7 +12461,6 @@ const releaseToConnectLabel = "Release to Connect" //setWorkflow(workflow); } - console.log("STARTNODE: ", startnode); } else { console.log("WORKFLOW: ", workflow); } @@ -16581,6 +16579,7 @@ const releaseToConnectLabel = "Release to Connect" } } + /* if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { console.log("Shift key pressed") if (!workflow.public && executionModalOpen) { @@ -16592,6 +16591,7 @@ const releaseToConnectLabel = "Release to Connect" setExecutionModalView(0); } } + */ }; document.addEventListener('keydown', handleKeyDown); @@ -21829,17 +21829,12 @@ const releaseToConnectLabel = "Release to Connect" } } - console.log("2 - SELECTED ACTION: ", selectedAction) - console.log("2 - DATA: ", data) - if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && count === 0) { const parsedvalue = data - console.log("Parsed value: ", parsedvalue) if (parsedvalue.includes("#")) { const splitparsed = parsedvalue.split(".#.") //console.log("Cant contain #: ", splitparsed) if (splitparsed.length > 1) { - console.log("IN HERE AY") //data.value = splitparsed[0] selectedAction.parameters[0].value = splitparsed[0] diff --git a/frontend/src/views/Usecases.jsx b/frontend/src/views/Usecases.jsx index f131455d..16c20375 100644 --- a/frontend/src/views/Usecases.jsx +++ b/frontend/src/views/Usecases.jsx @@ -62,6 +62,7 @@ import { TreeMapLabel, TreeMapRect, } from 'reaviz'; +import { isMobile } from "react-device-detect" const useStyles = makeStyles({ notchedOutline: { @@ -381,7 +382,7 @@ const UsecaseListComponent = (props) => { {usecase.name} - + {usecase.list.map((subcase, subindex) => { const selectedItem = subindex === expandedItem && index === expandedIndex @@ -426,7 +427,7 @@ const UsecaseListComponent = (props) => { const fixedName = subcase.name.toLowerCase().replace("_", " ") return ( - { + { if (fixedName === "reporting") { getUsecase(subcase, index, subindex) return @@ -443,7 +444,7 @@ const UsecaseListComponent = (props) => { }}> {!selectedItem ?
    - + {subcase.name} {finished ? @@ -478,7 +479,7 @@ const UsecaseListComponent = (props) => { { @@ -503,7 +504,7 @@ const UsecaseListComponent = (props) => { { @@ -618,7 +619,7 @@ const UsecaseListComponent = (props) => {
    -
    +
    {editing ?
    { }
    { }}> } series={ @@ -1675,15 +1677,15 @@ const Dashboard = (props) => { console.log("USECASES: ", usecases) const data = -
    -
    +
    +
    {keys.length > 0 ? : null}
    {usecases !== null && usecases !== undefined && usecases.length > 0 ? -
    +
    {usecases.map((usecase, index) => { return ( { marginRight: 10, paddingLeft: 5, paddingRight: 5, + marginTop: isMobile? 10:null, height: 28, cursor: "pointer", border: `1px solid ${usecase.color}`, diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 816f17c5..75d3b799 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -513,10 +513,15 @@ export const validateJson = (showResult) => { try { for (const [key, value] of Object.entries(result)) { if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) { + //console.log("CHECKING STRING: ", value) + const inside_result = validateJson(value) if (inside_result.valid) { + //console.log("INSIDE RESULT: ", inside_result.result) + if (typeof inside_result.result === "string") { - const newres = JSON.parse(inside_result.result) + const newres = JSON.parse(inside_result.result) + result[key] = newres } else { result[key] = inside_result.result @@ -1037,9 +1042,9 @@ const Workflows = (props) => { data.default_return_value, {}, false, - [], - "", - data.status, + [], + "", + data.status, ) .then((response) => { if (response !== undefined) { From ea6f6bcf7c174c47a272cb35138e57b4aff0016d Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Thu, 18 Jul 2024 14:31:35 +0000 Subject: [PATCH 092/336] [feature]: tip section support for docs --- frontend/src/views/Docs.jsx | 55 +++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 565f051f..9536dad7 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -1,15 +1,13 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react" - import { toast } from 'react-toastify'; import Markdown from 'react-markdown' - import theme from '../theme.jsx'; import ReactJson from "react-json-view"; import { isMobile } from "react-device-detect"; import { BrowserView, MobileView } from "react-device-detect"; import { useParams, useNavigate, Link } from "react-router-dom"; import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; - +import remarkGfm from 'remark-gfm' import { Grid, TextField, @@ -26,7 +24,6 @@ import { ListItemButton, ListItemText } from "@mui/material"; - import { Link as LinkIcon, Edit as EditIcon, @@ -35,7 +32,6 @@ import { FileCopy as FileCopyIcon } from "@mui/icons-material"; import { fontGrid } from "@mui/material/styles/cssUtils.js"; -import { active } from "d3"; const Body = { //maxWidth: 1000, @@ -46,6 +42,7 @@ const Body = { height: "100%", color: "white", position: "relative", + paddingTop: 40, //textAlign: "center", }; @@ -125,7 +122,7 @@ export const Paragrah = (props) => { return ( -
    +
    {element}
    ) @@ -155,9 +152,16 @@ export const OuterLink = (props) => { export const Img = (props) => { - return {props.alt}; + return( + {props.alt} + ) } + export const CodeHandler = (props) => { const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : "" @@ -369,8 +373,7 @@ const Docs = (defaultprops) => { hash = hash.split('?')[0] } if (hash) { - console.log("HASH: ", hash) - const element = document.getElementById(hash) + const element = document.getElementById(hash.toLowerCase()) if (element) { element.scrollIntoView({ behavior: "instant", @@ -454,6 +457,30 @@ const Docs = (defaultprops) => { minHeight: "80vh", }; + const noteLabelStyle = { + fontWeight: "bold", + color: "#f86a3e", + display: "block", + marginBottom: "5px", + }; + + const Blockquote = ({ children }) => { + + const textContent = children.map(child => + child.props && child.props.children ? child.props.children.join('') : child + ).join('').trim(); + + // Maybe some more contents.... + const isNote = textContent.startsWith("[!TIP]"); + return ( +
    + {isNote && Tips:} + {isNote ? textContent.replace("[!TIP]", "").trim() : children} +
    + ); + }; + + const Heading = (props) => { const [hover, setHover] = useState(false); var id = props.children[0].toLowerCase().toString() @@ -837,6 +864,12 @@ const Docs = (defaultprops) => { fontSize: isMobile ? "1.3rem" : "1.1rem", }; + const alertNote = { + padding: "10px", + borderLeft: "5px solid #f86a3e", + backgroundColor: "rgb(26,26,26)", + }; + const CustomButton = (props) => { const { title, icon, link } = props @@ -971,9 +1004,11 @@ const Docs = (defaultprops) => { h6: Heading, a: OuterLink, p: Paragrah, + blockquote: Blockquote, } + // PostDataBrowser Section const postDataBrowser = list === undefined || list === null ? null : ( @@ -1022,8 +1057,10 @@ const Docs = (defaultprops) => { Date: Fri, 19 Jul 2024 17:53:19 +0530 Subject: [PATCH 093/336] fix: temp fixing of all role issues --- functions/kubernetes/all-in-one.yaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/functions/kubernetes/all-in-one.yaml b/functions/kubernetes/all-in-one.yaml index a428048f..44189aca 100644 --- a/functions/kubernetes/all-in-one.yaml +++ b/functions/kubernetes/all-in-one.yaml @@ -80,11 +80,11 @@ data: IS_KUBERNETES: "true" REGISTRY_URL: "192.168.29.16:5000" REGISTRY_AUTH: "false" - SHUFFLE_KUBERNETES_WORKER: "ghcr.io/shuffle/shuffle-worker:nightly" + # SHUFFLE_KUBERNETES_WORKER: "ghcr.io/shuffle/shuffle-worker:nightly" + SHUFFLE_KUBERNETES_WORKER: gcr.io/shuffler/shuffle-worker-scale:latest kind: ConfigMap --- - apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: @@ -92,11 +92,17 @@ metadata: name: pod-manager rules: - apiGroups: [""] - resources: ["pods"] + resources: ["pods", "services", "deployments"] verbs: ["get", "list", "create", "update", "delete"] - apiGroups: ["batch"] resources: ["jobs"] verbs: ["create", "get", "list", "watch", "delete"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["rolebindings", "roles"] + verbs: ["get", "list", "create"] +- apiGroups: ["apps"] + resources: ["deployments", "pods", "services"] + verbs: ["create", "get", "list", "update", "delete"] --- @@ -838,8 +844,8 @@ spec: value: nightly - name: SHUFFLE_SCALE_REPLICAS value: "5" - #- name: SHUFFLE_SWARM_CONFIG - #value: run + - name: SHUFFLE_SWARM_CONFIG + value: run - name: SHUFFLE_WORKER_VERSION value: nightly - name: IS_KUBERNETES @@ -862,7 +868,6 @@ spec: configMapKeyRef: key: SHUFFLE_KUBERNETES_WORKER name: env - - name: SHUFFLE_MEMCACHED valueFrom: configMapKeyRef: From 28c516217aa5c9e1c18c2d19b93d74c2e60cf5bd Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:22:17 +0530 Subject: [PATCH 094/336] fix: temp fixing of all role issues --- functions/kubernetes/all-in-one.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/functions/kubernetes/all-in-one.yaml b/functions/kubernetes/all-in-one.yaml index 44189aca..9b54244f 100644 --- a/functions/kubernetes/all-in-one.yaml +++ b/functions/kubernetes/all-in-one.yaml @@ -80,8 +80,7 @@ data: IS_KUBERNETES: "true" REGISTRY_URL: "192.168.29.16:5000" REGISTRY_AUTH: "false" - # SHUFFLE_KUBERNETES_WORKER: "ghcr.io/shuffle/shuffle-worker:nightly" - SHUFFLE_KUBERNETES_WORKER: gcr.io/shuffler/shuffle-worker-scale:latest + SHUFFLE_KUBERNETES_WORKER: "ghcr.io/shuffle/shuffle-worker:nightly" kind: ConfigMap --- From 212d1a879aa1367dbad823063cf0ad19a711ee62 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Mon, 10 Jun 2024 10:49:38 +0000 Subject: [PATCH 095/336] added a function to handle file category change --- functions/onprem/orborus/orborus.go | 167 +++++++++++++++++++++++++--- 1 file changed, 149 insertions(+), 18 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 847cd29e..faa435bf 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -10,7 +10,7 @@ package main import ( "github.com/shuffle/shuffle-shared" - + "archive/zip" "bytes" "context" "encoding/json" @@ -29,6 +29,7 @@ import ( "strings" "sync" "time" + "path/filepath" //"os/signal" //"syscall" @@ -2010,6 +2011,13 @@ func main() { } toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + } else if incRequest.Type == "CATEGORY_CHANGE" { + err := handleFileCategoryChange() + if err != nil { + log.Printf("[ERROR] Failed to download the file category: %s", err) + } + + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) } else if incRequest.Type == "DOCKER_IMAGE_DOWNLOAD" { log.Printf("[INFO] Should delete -> download new image %#v", incRequest.ExecutionArgument) @@ -2021,7 +2029,11 @@ func main() { } toBeRemoved.Data = append(toBeRemoved.Data, incRequest) - } else { + } else if incRequest.Type == "CATEGORY_UPDATE" { + handleFileCategoryChange() + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + + }else { newrequests = append(newrequests, incRequest) } } @@ -2627,24 +2639,24 @@ func createPipeline(command, identifier string) (string, error) { log.Printf("[INFO] an existing pipeline found with ID: %s. it will be deleted", pipelineId) toBeDeleted = true } - if strings.Contains(command, "shuffler.io") { + // if strings.Contains(command, "shuffler.io") { - } else { - var scheme string - if strings.Contains(command, "http://") { - scheme = "http://" - } else if strings.Contains(command, "https://") { - scheme = "https://" - } + // } else { + // var scheme string + // if strings.Contains(command, "http://") { + // scheme = "http://" + // } else if strings.Contains(command, "https://") { + // scheme = "https://" + // } - startIndex := strings.Index(command, scheme) - if startIndex != -1 { - endIndex := startIndex + len(scheme) - endIndex += strings.Index(command[endIndex:], "/") - - command = command[:startIndex] + baseUrl + command[endIndex:] - } - } + // startIndex := strings.Index(command, scheme) + // if startIndex != -1 { + // endIndex := startIndex + len(scheme) + // endIndex += strings.Index(command[endIndex:], "/") + + // command = command[:startIndex] + baseUrl + command[endIndex:] + // } + // } requestBody := map[string]interface{}{ "definition": command, "name": identifier, @@ -2870,6 +2882,125 @@ func searchPipeline(identifier string) (string, error) { return "", errors.New("no existing pipeline found with name") } +func handleFileCategoryChange() error{ + apiEndpoint := "https://expert-acorn-v6vg4j4j5w7q2wg6g-5001.app.github.dev/api/v1/files/namespaces/hari" + apiKey := "23e57313-5f0f-4a20-bddd-a9059c980adf" + + req, err := http.NewRequest("GET", apiEndpoint, nil) + if err != nil { + return err + } + + req.Header.Add("Authorization", "Bearer "+apiKey) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return err + } + + out, err := os.Create("files.zip") + if err != nil { + return err + } + + defer out.Close() + defer os.Remove("files.zip") + + _, err = io.Copy(out, resp.Body) + if err != nil { + return err + } + + fmt.Println("ZIP file downloaded successfully.") + + err = extractZIP("files.zip", "unzipped_files") + if err != nil { + return err + } + + destPath := "/var/lib/tenzir/unzipped_files" + + err = copyToTenzir("unzipped_files", destPath) + if err != nil { + return err + } + + fmt.Println("Files copied to container successfully.") + return nil +} + +func extractZIP(zipFile, destDir string) error { + r, err := zip.OpenReader(zipFile) + if err != nil { + return err + } + defer r.Close() + + if err := os.MkdirAll(destDir, 0755); err != nil { + return err + } + + for _, f := range r.File { + err := extractFile(f, destDir) + if err != nil { + return err + } + } + + return nil +} + +func extractFile(f *zip.File, destDir string) error { + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + path := filepath.Join(destDir, f.Name) + + out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, rc) + return err +} + +func copyToTenzir(srcPath, destPath string) error { + containerName := "tenzir-node" + + // Check if the extracted_files directory exists in the container + checkCmd := exec.Command("docker", "exec", containerName, "test", "-d", destPath) + if err := checkCmd.Run(); err == nil { + rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-rf", destPath) + if err := rmCmd.Run(); err != nil { + return fmt.Errorf("error removing existing directory in container: %v", err) + } + } + + // Copy the new directory to the container + cpCmd := exec.Command("docker", "cp", srcPath, fmt.Sprintf("%s:%s", containerName, destPath)) + var out bytes.Buffer + cpCmd.Stdout = &out + cpCmd.Stderr = &out + + err := cpCmd.Run() + if err != nil { + return fmt.Errorf("error copying files: %v, output: %s", err, out.String()) + } + + return nil +} + // func savePipelineData(pipelineId, identifier, status string) error { // url := fmt.Sprintf("%s/api/v1/triggers/pipeline/save", baseUrl) From 94779e66136f2408678aa59ff0c62d857bc3df0e Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 11 Jun 2024 05:59:11 +0000 Subject: [PATCH 096/336] initial UI implementation for managing detection rules --- frontend/src/views/Detection.jsx | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 frontend/src/views/Detection.jsx diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx new file mode 100644 index 00000000..c2711b45 --- /dev/null +++ b/frontend/src/views/Detection.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Container, Box, TextField, Switch, Card, CardContent, IconButton, Typography, Button } from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import { styled } from '@mui/system'; + +const ConnectedButton = styled(Button)({ + backgroundColor: 'red', + color: 'white', +}); + +const RuleCard = ({ ruleName, description }) => ( + + +
    + {ruleName} +
    + + + + +
    +
    + {description} + + + {/* we need icons here ??? */} + + +
    +
    +); + +const Detection = () => { + return ( + + + + + Group 1 Title + + Not Connected to SIEM + + + + + Global disable/enable + + + + + + + + + ); +}; + +export default Detection; From e1d0857fd76aa693e14bffcf0ca2423518d892d1 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 11 Jun 2024 16:38:28 +0000 Subject: [PATCH 097/336] made the ui to look good --- frontend/src/App.jsx | 6 ++ frontend/src/views/Detection.jsx | 148 +++++++++++++++++++++++++------ 2 files changed, 126 insertions(+), 28 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 639b80bd..27517625 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -15,6 +15,7 @@ import HealthPage from "./components/HealthPage.jsx"; import theme from "./theme"; import Apps from "./views/Apps"; import AppCreator from "./views/AppCreator"; +import Dectection from "./views/Detection.jsx"; import Welcome from "./views/Welcome.jsx"; import Dashboard from "./views/Dashboard.jsx"; @@ -414,6 +415,11 @@ const App = (message, props) => { /> } /> + } + /> ( +const disableRule = (fileId) => { + +} + + +const RuleCard = ({ ruleName, description, ...otherProps }) => { + const [additionalProps, setAdditionalProps] = React.useState(otherProps); + return ( -
    - {ruleName} -
    - +
    + {ruleName} +
    + - +
    -
    - {description} - - +
    + + {description} + + + {/* we need icons here ??? */} - -); + ) +} -const Detection = () => { +const Detection = (props) => { + const {globalUrl} = props; + const [ruleInfo, setRuleInfo] = React.useState([]); + + const getSigmaInfo = () => { + const url = globalUrl + "/api/v1/files/detection/sigma_rules" + + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed to get sigma rules"); + } else { + setRuleInfo(responseJson); + } + + }), + ) + .catch((error) => { + console.log("Error in geting sigma files: ", error); + }); + } + + React.useEffect(() => { + getSigmaInfo() +}, []); + return ( - - + + Group 1 Title - Not Connected to SIEM + + Not Connected to SIEM + - + - - Global disable/enable + + + Global disable/enable + - - - + {ruleInfo.length > 0 && + ruleInfo.map((card) => ( + + ))} ); From c9da78c7812afb30773fcc65a184cb4cbf293fcc Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Wed, 12 Jun 2024 04:48:34 +0000 Subject: [PATCH 098/336] added toggle rule function to enable or disable the rule --- frontend/src/views/Detection.jsx | 149 ++++++++++++++++++------------- 1 file changed, 87 insertions(+), 62 deletions(-) diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx index e542c5f9..79cb71b7 100644 --- a/frontend/src/views/Detection.jsx +++ b/frontend/src/views/Detection.jsx @@ -19,86 +19,109 @@ const ConnectedButton = styled(Button)({ color: "white", }); -const disableRule = (fileId) => { - -} - - -const RuleCard = ({ ruleName, description, ...otherProps }) => { +const RuleCard = ({ ruleName, description, file_id, globalUrl, ...otherProps }) => { const [additionalProps, setAdditionalProps] = React.useState(otherProps); + + const handleSwitchChange = (event) => { + const isEnabled = event.target.checked; + toggleRule(file_id, !isEnabled, globalUrl, () => { + setAdditionalProps((prevProps) => ({ + ...prevProps, + is_enabled: isEnabled, + })); + }); + }; + return ( - - -
    - {ruleName} -
    - - - - + +
    + {ruleName} +
    + + + + +
    -
    - - {description} - - - - {/* we need icons here ??? */} - - - - ) -} + + {description} + + + + ); +}; + +const toggleRule = (fileId, isCurrentlyEnabled, globalUrl, callback) => { + const action = isCurrentlyEnabled ? "disable" : "enable"; + const url = `${globalUrl}/api/v1/files/${fileId}/${action}_rule`; + + fetch(url, { + method: "PUT", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast(`Failed to ${action} the rule`); + } else { + toast(`Rule ${action}d successfully`); + callback(); + } + }) + ) + .catch((error) => { + console.log(`Error in ${action}ing the rule: `, error); + toast(`An error occurred while ${action}ing the rule`); + }); +}; const Detection = (props) => { - const {globalUrl} = props; + const { globalUrl } = props; const [ruleInfo, setRuleInfo] = React.useState([]); const getSigmaInfo = () => { - const url = globalUrl + "/api/v1/files/detection/sigma_rules" - + const url = globalUrl + "/api/v1/files/detection/sigma_rules"; + fetch(url, { method: "GET", credentials: "include", headers: { "Content-Type": "application/json", }, - }) .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - toast("Failed to get sigma rules"); - } else { + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed to get sigma rules"); + } else { setRuleInfo(responseJson); - } - - }), - ) - .catch((error) => { - console.log("Error in geting sigma files: ", error); - }); - } + } + }) + ) + .catch((error) => { + console.log("Error in getting sigma files: ", error); + toast("An error occurred while fetching sigma rules"); + }); + }; React.useEffect(() => { - getSigmaInfo() -}, []); - + getSigmaInfo(); + }, []); + return ( @@ -136,9 +159,11 @@ const Detection = (props) => { {ruleInfo.length > 0 && ruleInfo.map((card) => ( ))} From a7d2a5d676ae161f52c454ac4c2f454f0ab476e7 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Wed, 12 Jun 2024 04:50:59 +0000 Subject: [PATCH 099/336] endpoints for getting sigma rule info and to disable and enable the rules --- backend/go-app/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index d24cbc64..1cc3c771 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5122,6 +5122,9 @@ func initHandlers() { r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleGetFileMeta).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/files/detection/sigma_rules", shuffle.HandleGetSigmaRules).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/files/{fileId}/disable_rule", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS") + r.HandleFunc("/api/v1/files/{fileId}/enable_rule", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS") // Introduced in 0.9.21 to handle notifications for e.g. failed Workflow r.HandleFunc("/api/v1/notifications", shuffle.HandleCreateNotification).Methods("POST", "OPTIONS") From 366b8438a1257dca5605e616d4b917833670cac4 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Wed, 12 Jun 2024 07:21:37 +0000 Subject: [PATCH 100/336] feat : support enabling and disabling sigma rules --- functions/onprem/orborus/orborus.go | 86 +++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index faa435bf..0654169f 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -3001,6 +3001,92 @@ func copyToTenzir(srcPath, destPath string) error { return nil } +func manageSigmaRule(fileName, action string) error { + containerName := "tenzir-node" + srcPath := "" + destPath := "" + + switch action { + case "disable": + srcPath = fmt.Sprintf("/var/lib/tenzir/sigma_files/%s", fileName) + destPath = "/var/lib/tenzir/disabled_rules" + case "enable": + srcPath = fmt.Sprintf("/var/lib/tenzir/disabled_rules/%s", fileName) + destPath = "/var/lib/tenzir/sigma_files" + default: + return fmt.Errorf("invalid action: %s", action) + } + + checkSrcCmd := exec.Command("docker", "exec", containerName, "test", "-f", srcPath) + if err := checkSrcCmd.Run(); err != nil { + return fmt.Errorf("source file does not exist: %v", err) + } + + checkDestCmd := exec.Command("docker", "exec", containerName, "test", "-d", destPath) + if err := checkDestCmd.Run(); err != nil { + mkdirCmd := exec.Command("docker", "exec", "-u", "root", containerName, "mkdir", "-p", destPath) + if err := mkdirCmd.Run(); err != nil { + return fmt.Errorf("error creating destination directory in container: %v", err) + } + } + + // Move the file to the destination directory or shall we copy it and then remove the file from source dir + mvCmd := exec.Command("docker", "exec", "-u", "root", containerName, "mv", srcPath, destPath) + if err := mvCmd.Run(); err != nil { + return fmt.Errorf("error moving file: %v", err) + } + + return nil +} + +func manageSigmaFolder(action string) error { + containerName := "tenzir-node" + sigmaPath := "/var/lib/tenzir/sigma_files" + disabledPath := "/var/lib/tenzir/disabled_sigma" + + if action == "disable" { + + checkSigmaCmd := exec.Command("docker", "exec", containerName, "test", "-d", sigmaPath) + if err := checkSigmaCmd.Run(); err != nil { + return fmt.Errorf("sigma_files directory does not exist: %v", err) + } + + // Rename sigma_files to disabled_sigma + renameCmd := exec.Command("docker", "exec", "-u", "root", containerName, "mv", sigmaPath, disabledPath) + if err := renameCmd.Run(); err != nil { + return fmt.Errorf("error renaming sigma_files to disabled_sigma: %v", err) + } + + // Create a new sigma_files directory + createCmd := exec.Command("docker", "exec", "-u", "root", containerName, "mkdir", sigmaPath) + if err := createCmd.Run(); err != nil { + return fmt.Errorf("error creating new sigma_files directory: %v", err) + } + } else if action == "enable" { + + checkDisabledCmd := exec.Command("docker", "exec", containerName, "test", "-d", disabledPath) + if err := checkDisabledCmd.Run(); err != nil { + return fmt.Errorf("disabled_sigma directory does not exist: %v", err) + } + + removeCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-rf", sigmaPath) + if err := removeCmd.Run(); err != nil { + return fmt.Errorf("error removing existing sigma_files directory: %v", err) + } + + // Rename disabled_sigma back to sigma_files + renameBackCmd := exec.Command("docker", "exec", "-u", "root", containerName, "mv", disabledPath, sigmaPath) + if err := renameBackCmd.Run(); err != nil { + return fmt.Errorf("error renaming disabled_sigma back to sigma_files: %v", err) + } + } else { + return fmt.Errorf("invalid action: %s", action) + } + + return nil +} + + // func savePipelineData(pipelineId, identifier, status string) error { // url := fmt.Sprintf("%s/api/v1/triggers/pipeline/save", baseUrl) From 813b641486611cde53679fdc7d732bcc910c5c37 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Wed, 12 Jun 2024 11:01:51 +0000 Subject: [PATCH 101/336] support for enabling and disabling sigma rules --- functions/onprem/orborus/orborus.go | 57 ++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 0654169f..979a6dc6 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -2011,13 +2011,6 @@ func main() { } toBeRemoved.Data = append(toBeRemoved.Data, incRequest) - } else if incRequest.Type == "CATEGORY_CHANGE" { - err := handleFileCategoryChange() - if err != nil { - log.Printf("[ERROR] Failed to download the file category: %s", err) - } - - toBeRemoved.Data = append(toBeRemoved.Data, incRequest) } else if incRequest.Type == "DOCKER_IMAGE_DOWNLOAD" { log.Printf("[INFO] Should delete -> download new image %#v", incRequest.ExecutionArgument) @@ -2029,11 +2022,47 @@ func main() { } toBeRemoved.Data = append(toBeRemoved.Data, incRequest) - } else if incRequest.Type == "CATEGORY_UPDATE" { - handleFileCategoryChange() + + } else if incRequest.Type == "CATEGORY_UPDATE" { + err := handleFileCategoryChange() + if err != nil { + log.Printf("[ERROR] Failed to download the file category: %s", err) + } + + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + + } else if incRequest.Type == "DISABLE_SIGMA_FILE" { + fileName := incRequest.ExecutionArgument + err = manageSigmaRule(fileName, "disable") + if err != nil { + log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err) + } + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) - }else { + } else if incRequest.Type == "ENABLE_SIGMA_FILE" { + fileName := incRequest.ExecutionArgument + err = manageSigmaRule(fileName, "enable") + if err != nil { + log.Printf("[ERROR] Failed to enable the sigma file %s, reason: %s",fileName, err) + } + + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + } else if incRequest.Type == "DISABLE_SIGMA_RULES" { + err := manageSigmaFolder("disable") + if err != nil { + log.Printf("[ERROR] Failed to disable the sigma rules: %s", err) + } + + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + } else if incRequest.Type == "ENABLE_SIGMA_RULES" { + err := manageSigmaFolder("enable") + if err != nil { + log.Printf("[ERROR] Failed to enable the sigma rules: %s", err) + } + + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + } else { newrequests = append(newrequests, incRequest) } } @@ -2883,7 +2912,7 @@ func searchPipeline(identifier string) (string, error) { } func handleFileCategoryChange() error{ - apiEndpoint := "https://expert-acorn-v6vg4j4j5w7q2wg6g-5001.app.github.dev/api/v1/files/namespaces/hari" + apiEndpoint := baseUrl+"/api/v1/files/namespaces/sigma" apiKey := "23e57313-5f0f-4a20-bddd-a9059c980adf" req, err := http.NewRequest("GET", apiEndpoint, nil) @@ -2919,14 +2948,14 @@ func handleFileCategoryChange() error{ fmt.Println("ZIP file downloaded successfully.") - err = extractZIP("files.zip", "unzipped_files") + err = extractZIP("files.zip", "sigma_rules") if err != nil { return err } - destPath := "/var/lib/tenzir/unzipped_files" + destPath := "/var/lib/tenzir/sigma_rules" - err = copyToTenzir("unzipped_files", destPath) + err = copyToTenzir("sigma_rules", destPath) if err != nil { return err } From 2318d92c3a87460fdc0f0b7ecbddeb0877120582 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Wed, 12 Jun 2024 16:32:28 +0000 Subject: [PATCH 102/336] renamed the group the name --- frontend/src/views/Detection.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx index 79cb71b7..c28ffc05 100644 --- a/frontend/src/views/Detection.jsx +++ b/frontend/src/views/Detection.jsx @@ -134,7 +134,7 @@ const Detection = (props) => { }} > - Group 1 Title + Sigma Detection Rules Not Connected to SIEM From 809fc2c202c53d445e4d28a6098cf56dc2836491 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Thu, 13 Jun 2024 07:00:22 +0000 Subject: [PATCH 103/336] rewriting the component structure --- frontend/src/App.jsx | 4 +- frontend/src/views/Detection.jsx | 111 +--------------------- frontend/src/views/DetectionDashboard.jsx | 102 ++++++++++++++++++++ frontend/src/views/EditRules.jsx | 46 +++++++++ frontend/src/views/RuleCard.jsx | 82 ++++++++++++++++ 5 files changed, 235 insertions(+), 110 deletions(-) create mode 100644 frontend/src/views/DetectionDashboard.jsx create mode 100644 frontend/src/views/EditRules.jsx create mode 100644 frontend/src/views/RuleCard.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 27517625..668cb557 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -15,7 +15,7 @@ import HealthPage from "./components/HealthPage.jsx"; import theme from "./theme"; import Apps from "./views/Apps"; import AppCreator from "./views/AppCreator"; -import Dectection from "./views/Detection.jsx"; +import DetectionDashBoard from "./views/DetectionDashboard.jsx"; import Welcome from "./views/Welcome.jsx"; import Dashboard from "./views/Dashboard.jsx"; @@ -418,7 +418,7 @@ const App = (message, props) => { } + element={} /> { - const [additionalProps, setAdditionalProps] = React.useState(otherProps); - - const handleSwitchChange = (event) => { - const isEnabled = event.target.checked; - toggleRule(file_id, !isEnabled, globalUrl, () => { - setAdditionalProps((prevProps) => ({ - ...prevProps, - is_enabled: isEnabled, - })); - }); - }; - - return ( - - -
    - {ruleName} -
    - - - - -
    -
    - - {description} - -
    -
    - ); -}; - -const toggleRule = (fileId, isCurrentlyEnabled, globalUrl, callback) => { - const action = isCurrentlyEnabled ? "disable" : "enable"; - const url = `${globalUrl}/api/v1/files/${fileId}/${action}_rule`; - - fetch(url, { - method: "PUT", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - toast(`Failed to ${action} the rule`); - } else { - toast(`Rule ${action}d successfully`); - callback(); - } - }) - ) - .catch((error) => { - console.log(`Error in ${action}ing the rule: `, error); - toast(`An error occurred while ${action}ing the rule`); - }); -}; - -const Detection = (props) => { - const { globalUrl } = props; - const [ruleInfo, setRuleInfo] = React.useState([]); - - const getSigmaInfo = () => { - const url = globalUrl + "/api/v1/files/detection/sigma_rules"; - - fetch(url, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - toast("Failed to get sigma rules"); - } else { - setRuleInfo(responseJson); - } - }) - ) - .catch((error) => { - console.log("Error in getting sigma files: ", error); - toast("An error occurred while fetching sigma rules"); - }); - }; - - React.useEffect(() => { - getSigmaInfo(); - }, []); - +const Detection = ({ globalUrl, ruleInfo, openEditBar }) => { return ( @@ -164,6 +58,7 @@ const Detection = (props) => { description={card.description} file_id={card.file_id} globalUrl={globalUrl} + openEditBar={() => openEditBar(card)} {...card} /> ))} diff --git a/frontend/src/views/DetectionDashboard.jsx b/frontend/src/views/DetectionDashboard.jsx new file mode 100644 index 00000000..50771d63 --- /dev/null +++ b/frontend/src/views/DetectionDashboard.jsx @@ -0,0 +1,102 @@ +import React, { useState, useEffect } from "react"; +import { Container} from "@mui/material"; +import { toast } from "react-toastify"; +import Detection from "./Detection"; +import EditComponent from "./EditRules"; + +const getSigmaInfo = (globalUrl, setRuleInfo) => { + const url = globalUrl + "/api/v1/files/detection/sigma_rules"; + + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed to get sigma rules"); + } else { + setRuleInfo(responseJson); + } + }) + ) + .catch((error) => { + console.log("Error in getting sigma files: ", error); + toast("An error occurred while fetching sigma rules"); + }); +}; + +const DetectionDashBoard = (props) => { + const { globalUrl } = props; + const [ruleInfo, setRuleInfo] = useState([]); + const [selectedRule, setSelectedRule] = useState(null); + const [fileData, setFileData] = useState("") + + useEffect(() => { + getSigmaInfo(globalUrl, setRuleInfo); + }, [globalUrl]); + + const openEditBar = (rule) => { + setSelectedRule(rule); + getFileContent(rule.file_id) + }; + + const handleSave = (updatedContent) => { + toast("this will be saved"); + setSelectedRule(null); // Close the edit bar after saving + }; + + const getFileContent = (file_id) => { + fetch(globalUrl + "/api/v1/files/" + file_id + "/content", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for file :O!"); + return ""; + } + return response.text(); + }) + .then((respdata) => { + if (respdata.length === 0) { + toast("Failed getting file. Is it deleted?"); + return; + } + return respdata + }) + .then((responseData) => { + + setFileData(responseData); + }) + .catch((error) => { + toast(error.toString()); + }); + }; + + return ( + + {selectedRule ? ( + + ) : null} + + + ); +}; + +export default DetectionDashBoard; diff --git a/frontend/src/views/EditRules.jsx b/frontend/src/views/EditRules.jsx new file mode 100644 index 00000000..501bb6cc --- /dev/null +++ b/frontend/src/views/EditRules.jsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { Box, Typography, Button, Switch, TextField } from '@mui/material'; + +const EditComponent = ({ ruleName, description, content, setContent, lastEdited, editedBy, onSave }) => { + + const handleSave = () => { + onSave(content); + }; + + return ( + + + {ruleName} + + + + + + {description} + + + Last edited: {lastEdited} + + + Edited By: {editedBy} + + + setContent(e.target.value)} + variant="outlined" + fullWidth + /> + + + + + + ); +}; + +export default EditComponent; diff --git a/frontend/src/views/RuleCard.jsx b/frontend/src/views/RuleCard.jsx new file mode 100644 index 00000000..e166ce6b --- /dev/null +++ b/frontend/src/views/RuleCard.jsx @@ -0,0 +1,82 @@ +import React from "react"; +import { + Card, + CardContent, + IconButton, + Typography, + Switch, +} from "@mui/material"; +import EditIcon from "@mui/icons-material/Edit"; +import { toast } from "react-toastify"; + +const RuleCard = ({ ruleName, description, file_id, globalUrl, openEditBar, ...otherProps }) => { + const [additionalProps, setAdditionalProps] = React.useState(otherProps); + + const handleSwitchChange = (event) => { + const isEnabled = event.target.checked; + toggleRule(file_id, !isEnabled, globalUrl, () => { + setAdditionalProps((prevProps) => ({ + ...prevProps, + is_enabled: isEnabled, + })); + }); + }; + + return ( + + +
    + {ruleName} +
    + openEditBar({ ruleName, description, file_id, ...additionalProps })}> + + + +
    +
    + + {description} + +
    +
    + ); +}; + +const toggleRule = (fileId, isCurrentlyEnabled, globalUrl, callback) => { + const action = isCurrentlyEnabled ? "disable" : "enable"; + const url = `${globalUrl}/api/v1/files/${fileId}/${action}_rule`; + + fetch(url, { + method: "PUT", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast(`Failed to ${action} the rule`); + } else { + toast(`Rule ${action}d successfully`); + callback(); + } + }) + ) + .catch((error) => { + console.log(`Error in ${action}ing the rule: `, error); + toast(`An error occurred while ${action}ing the rule`); + }); +}; + +export default RuleCard; From e90b698f84405219d7e8efd0544c197f49f08c58 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Thu, 13 Jun 2024 09:54:50 +0000 Subject: [PATCH 104/336] fixing bugs in rule editing --- frontend/src/views/Detection.jsx | 38 ++++++++++++++--------- frontend/src/views/DetectionDashboard.jsx | 11 +++++-- frontend/src/views/EditRules.jsx | 7 ++--- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx index 67b6a2ed..63567584 100644 --- a/frontend/src/views/Detection.jsx +++ b/frontend/src/views/Detection.jsx @@ -7,7 +7,7 @@ import { Typography, Button, } from "@mui/material"; -import RuleCard from "./RuleCard"; +import RuleCard from "./RuleCard"; import { styled } from "@mui/system"; const ConnectedButton = styled(Button)({ @@ -18,7 +18,7 @@ const ConnectedButton = styled(Button)({ const Detection = ({ globalUrl, ruleInfo, openEditBar }) => { return ( - + { - {ruleInfo.length > 0 && - ruleInfo.map((card) => ( - openEditBar(card)} - {...card} - /> - ))} + + {ruleInfo.length > 0 && + ruleInfo.map((card) => ( + openEditBar(card)} + {...card} + /> + ))} + ); diff --git a/frontend/src/views/DetectionDashboard.jsx b/frontend/src/views/DetectionDashboard.jsx index 50771d63..bd8a2813 100644 --- a/frontend/src/views/DetectionDashboard.jsx +++ b/frontend/src/views/DetectionDashboard.jsx @@ -39,6 +39,12 @@ const DetectionDashBoard = (props) => { getSigmaInfo(globalUrl, setRuleInfo); }, [globalUrl]); + useEffect(() => { + if (ruleInfo.length > 0) { + openEditBar(ruleInfo[0]); + } + }, [ruleInfo]); + const openEditBar = (rule) => { setSelectedRule(rule); getFileContent(rule.file_id) @@ -50,6 +56,7 @@ const DetectionDashBoard = (props) => { }; const getFileContent = (file_id) => { + setFileData(""); fetch(globalUrl + "/api/v1/files/" + file_id + "/content", { method: "GET", headers: { @@ -82,10 +89,10 @@ const DetectionDashBoard = (props) => { }; return ( - + {selectedRule ? ( + - {ruleName} - - - + {ruleName} {description} From f8c786b9ecea2577d8d6f4591462a76882557efc Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Thu, 13 Jun 2024 09:56:31 +0000 Subject: [PATCH 105/336] removing unnessary dependencies --- frontend/src/views/DetectionDashboard.jsx | 1 - frontend/src/views/EditRules.jsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/DetectionDashboard.jsx b/frontend/src/views/DetectionDashboard.jsx index bd8a2813..382d0dc8 100644 --- a/frontend/src/views/DetectionDashboard.jsx +++ b/frontend/src/views/DetectionDashboard.jsx @@ -52,7 +52,6 @@ const DetectionDashBoard = (props) => { const handleSave = (updatedContent) => { toast("this will be saved"); - setSelectedRule(null); // Close the edit bar after saving }; const getFileContent = (file_id) => { diff --git a/frontend/src/views/EditRules.jsx b/frontend/src/views/EditRules.jsx index 27788135..a433ebc3 100644 --- a/frontend/src/views/EditRules.jsx +++ b/frontend/src/views/EditRules.jsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { Box, Typography, Button, Switch, TextField } from '@mui/material'; +import React from 'react'; +import { Box, Typography, Button, TextField } from '@mui/material'; const EditComponent = ({ ruleName, description, content, setContent, lastEdited, editedBy, onSave }) => { From f081e907b3efc6d56971ea67f7587479ac2f2608 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Fri, 14 Jun 2024 04:45:20 +0000 Subject: [PATCH 106/336] made the file disabling logic in orborus easy --- frontend/src/App.jsx | 6 +-- functions/onprem/orborus/orborus.go | 57 +++++++++-------------------- 2 files changed, 20 insertions(+), 43 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 668cb557..ef77004a 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -416,9 +416,9 @@ const App = (message, props) => { } /> } + exact + path="/detections/sigma" + element={} /> Date: Fri, 14 Jun 2024 05:04:24 +0000 Subject: [PATCH 107/336] made the sigma rule disable logic better and simple --- functions/onprem/orborus/orborus.go | 67 ++++++----------------------- 1 file changed, 12 insertions(+), 55 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 2eef9435..71c9955d 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -2033,7 +2033,7 @@ func main() { } else if incRequest.Type == "DISABLE_SIGMA_FILE" { fileName := incRequest.ExecutionArgument - err = disableSigmaRule(fileName) + err = removeFile(fileName) if err != nil { log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err) } @@ -2041,18 +2041,11 @@ func main() { toBeRemoved.Data = append(toBeRemoved.Data, incRequest) } else if incRequest.Type == "DISABLE_SIGMA_RULES" { - err := manageSigmaFolder("disable") + err := removeAllFiles() if err != nil { log.Printf("[ERROR] Failed to disable the sigma rules: %s", err) } - toBeRemoved.Data = append(toBeRemoved.Data, incRequest) - } else if incRequest.Type == "ENABLE_SIGMA_RULES" { - err := manageSigmaFolder("enable") - if err != nil { - log.Printf("[ERROR] Failed to enable the sigma rules: %s", err) - } - toBeRemoved.Data = append(toBeRemoved.Data, incRequest) } else { newrequests = append(newrequests, incRequest) @@ -3029,7 +3022,7 @@ func copyToTenzir(srcPath, destPath string) error { return nil } -func disableSigmaRule(fileName string) error { +func removeFile(fileName string) error { containerName := "tenzir-node" srcPath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", fileName) @@ -3038,57 +3031,21 @@ func disableSigmaRule(fileName string) error { return fmt.Errorf("source file does not exist: %v", err) } - rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", srcPath) - if err := rmCmd.Run(); err != nil { - return fmt.Errorf("error removing file: %v", err) - } - - return nil + return removePath(containerName, srcPath) } -func manageSigmaFolder(action string) error { +func removeAllFiles() error { containerName := "tenzir-node" - sigmaPath := "/var/lib/tenzir/sigma_rules" + sigmaPath := "/var/lib/tenzir/sigma_rules/*" - if action == "disable" { + return removePath(containerName, sigmaPath) +} - checkSigmaCmd := exec.Command("docker", "exec", containerName, "test", "-d", sigmaPath) - if err := checkSigmaCmd.Run(); err != nil { - return fmt.Errorf("sigma_files directory does not exist: %v", err) - } - - // Rename sigma_files to disabled_sigma - renameCmd := exec.Command("docker", "exec", "-u", "root", containerName, "mv", sigmaPath, disabledPath) - if err := renameCmd.Run(); err != nil { - return fmt.Errorf("error renaming sigma_files to disabled_sigma: %v", err) - } - - // Create a new sigma_files directory - createCmd := exec.Command("docker", "exec", "-u", "root", containerName, "mkdir", sigmaPath) - if err := createCmd.Run(); err != nil { - return fmt.Errorf("error creating new sigma_files directory: %v", err) - } - } else if action == "enable" { - - checkDisabledCmd := exec.Command("docker", "exec", containerName, "test", "-d", disabledPath) - if err := checkDisabledCmd.Run(); err != nil { - return fmt.Errorf("disabled_sigma directory does not exist: %v", err) - } - - removeCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-rf", sigmaPath) - if err := removeCmd.Run(); err != nil { - return fmt.Errorf("error removing existing sigma_files directory: %v", err) - } - - // Rename disabled_sigma back to sigma_files - renameBackCmd := exec.Command("docker", "exec", "-u", "root", containerName, "mv", disabledPath, sigmaPath) - if err := renameBackCmd.Run(); err != nil { - return fmt.Errorf("error renaming disabled_sigma back to sigma_files: %v", err) - } - } else { - return fmt.Errorf("invalid action: %s", action) +func removePath(containerName, path string) error { + rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-r", path) + if err := rmCmd.Run(); err != nil { + return fmt.Errorf("error removing path: %v", err) } - return nil } From 29f294f2ef8e0dbc7ea8c21fdaa6b02f0b71a9cc Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 18 Jun 2024 12:43:19 +0000 Subject: [PATCH 108/336] removing the hard coded ui --- frontend/src/views/AngularWorkflow.jsx | 186 ++++++------------------- 1 file changed, 45 insertions(+), 141 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 70d9b761..00bc68de 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1437,7 +1437,7 @@ const releaseToConnectLabel = "Release to Connect" }); }; - const handleKafkaSubmit = (trigger) => { + const handleCommandSubmit = (trigger) => { if (trigger.trigger_type !== "PIPELINE") { toast("Unable to save the configuration"); return; @@ -1445,38 +1445,18 @@ const releaseToConnectLabel = "Release to Connect" trigger.parameters = [] - const topic = document.getElementById('topic')?.value; - const bootstrapServers = document.getElementById('bootstrap_servers')?.value; - const groupId = document.getElementById('group_id')?.value; - //const autoOffsetReset = document.getElementById('auto_offset_reset')?.value; + const command = document.getElementById('sigma')?.value - if(topic) { + if(command) { trigger.parameters.push({ - name: "topic", - value: topic - }); + name: "command", + value: command + }) } else { - toast("please enter the topic name"); + toast("Please enter the comamnd"); return; } - if (bootstrapServers) { - trigger.parameters.push({ - name: "bootstrap_servers", - value: bootstrapServers - }); - } else { - toast("please enter bootstrap server details"); - return; - } - - if (groupId) { - trigger.parameters.push({ - name: "group_id", - value: groupId - }); - } - // if (autoOffsetReset) { // trigger.parameters.push({ // name: "auto_offset_reset", @@ -15366,14 +15346,18 @@ const releaseToConnectLabel = "Release to Connect"
    { - // setSelectedOption("Syslog listener") - // setTenzirConfigModalOpen(true); - }} + if(selectedTrigger.status === "running"){ + toast("please stop the trigger to edit the configuration"); + return; + } else { + setSelectedOption("Syslog listener"); + setTenzirConfigModalOpen(true); + }}} style={{ border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: 10, - cursor: "not-allowed", + cursor: "pointer", marginTop: 5, display: "flex", alignItems: "center", @@ -15386,7 +15370,6 @@ const releaseToConnectLabel = "Release to Connect" onChange={() => setSelectedOption("Syslog listener")} value={"Syslog listener"} name="option" - disabled={true} /> } label="Start Syslog listener" @@ -15396,14 +15379,18 @@ const releaseToConnectLabel = "Release to Connect"
    { - // setSelectedOption("Sigma Rulesearch") - // setTenzirConfigModalOpen(true); - }} + if(selectedTrigger.status === "running"){ + toast("please stop the trigger to edit the configuration"); + return; + } else { + setSelectedOption("Sigma Rulesearch"); + setTenzirConfigModalOpen(true); + }}} style={{ border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: 10, - cursor: "not-allowed", + cursor: "pointer", marginTop: 5, display: "flex", alignItems: "center", @@ -15416,7 +15403,6 @@ const releaseToConnectLabel = "Release to Connect" onChange={() => setSelectedOption("Sigma Rulesearch")} value={"Sigma Rulesearch"} name="option" - disabled={true} /> } label="Run Sigma Rulesearch" @@ -15463,39 +15449,7 @@ const releaseToConnectLabel = "Release to Connect" disabled={selectedTrigger.status === "running"} onClick={() => { - const topic = (selectedTrigger?.parameters?.find(param => param.name === "topic")?.value) || '' - const bootstrapServers = (selectedTrigger?.parameters?.find(param => param.name === "bootstrap_servers")?.value) || '' - const groupId = (selectedTrigger?.parameters?.find(param => param.name === "group_id")?.value) || '' - // const autoOffsetReset = (selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || '' - let command = "from kafka" - - if(topic) { - command = `${command} -t ${topic}` - } else { - toast("please enter the topic name") - return; - } - if(bootstrapServers) { - command = `${command} -e -o stored -X bootstrap.servers=${bootstrapServers}` - } else { - toast("please enter the bootstrap servers details") - return; - } - - if(groupId) { - command = `${command},group.id=${groupId}` - } else { - command = `${command},group.id=${selectedTrigger.id}` - } - // if(autoOffsetReset) { - // command = `${command},auto.offset.reset=${autoOffsetReset}` - // } else { - // command = `${command},auto.offset.reset=earliest` - - // } - command = `${command},auto.offset.reset=earliest` - command = `${command},client.id=${selectedTrigger.id},enable.auto.commit=true,auto.commit.interval.ms=1` - command = `${command} read json | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` + const command = (selectedTrigger?.parameters?.find(param => param.name === "command")?.value) || '' const pipelineConfig = { command: command, @@ -21218,8 +21172,8 @@ const releaseToConnectLabel = "Release to Connect" pointerEvents: "auto", color: "white", minWidth: 600, - minHeight: 450, - maxHeight: 450, + minHeight: 200, + maxHeight: 200, padding: 15, overflow: "hidden", zIndex: 10012, @@ -21236,75 +21190,25 @@ const releaseToConnectLabel = "Release to Connect" overflowY: "auto", overflowX: isMobile ? "auto" : "hidden", }} - > - -
    Configuration options for {selectedOption}
    -
    + > - {selectedOption === "Kafka Queue" && ( - <> - Topic - param.name === "topic")?.value) || ''} - /> - bootstrap.servers - param.name === "bootstrap_servers")?.value) || ''} - /> - group.id - param.name === "group_id")?.value) || ''} - /> - {/* auto.offest.reset - param.name === "auto_offset_reset")?.value) || ''} - /> */} - - )} + + command + param.name === "command")?.value) || ''} + /> +
    @@ -84,19 +81,19 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, . + isCloud={isCloud} + expansionModalOpen={openCodeEditor} + setExpansionModalOpen={setOpenCodeEditor} + setcodedata={setFileData} + codedata={fileData} + isFileEditor={true} + key={fileData} // https://reactjs.org/docs/reconciliation.html#recursing-on-children + runUpdateText={UpdateText} + /> ); -}; +} const toggleRule = (fileId, isCurrentlyEnabled, globalUrl, callback) => { const action = isCurrentlyEnabled ? "disable" : "enable"; From 2cc0d3d57639a9b6677870e00b49d50ce4fe5078 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 18 Jun 2024 16:11:29 +0000 Subject: [PATCH 114/336] fixing the entire directory getting deleted instead of contents inside it --- functions/onprem/orborus/orborus.go | 30 +++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index f1459c00..9c84c03d 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -2024,7 +2024,13 @@ func main() { toBeRemoved.Data = append(toBeRemoved.Data, incRequest) } else if incRequest.Type == "CATEGORY_UPDATE" { - err := handleFileCategoryChange() + + err := deployTenzirNode() + if err != nil{ + log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err) + } + + err = handleFileCategoryChange() if err != nil { log.Printf("[ERROR] Failed to download the file category: %s", err) } @@ -2033,6 +2039,11 @@ func main() { } else if incRequest.Type == "DISABLE_SIGMA_FILE" { fileName := incRequest.ExecutionArgument + err := deployTenzirNode() + if err != nil{ + log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err) + } + err = removeFile(fileName) if err != nil { log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err) @@ -2041,7 +2052,13 @@ func main() { toBeRemoved.Data = append(toBeRemoved.Data, incRequest) } else if incRequest.Type == "DISABLE_SIGMA_FOLDER" { - err := removeAllFiles() + + err := deployTenzirNode() + if err != nil{ + log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err) + } + + err = removeAllFiles() if err != nil { log.Printf("[ERROR] Failed to disable the sigma rules: %s", err) } @@ -3021,7 +3038,12 @@ func removeAllFiles() error { containerName := "tenzir-node" sigmaPath := "/var/lib/tenzir/sigma_rules/*" - return removePath(containerName, sigmaPath) + cmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("rm -rf %s", sigmaPath)) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error removing files: %v, output: %s", err, output) + } + return nil } func removeFile(fileName string) error { @@ -3038,7 +3060,7 @@ func removeFile(fileName string) error { func removePath(containerName, path string) error { rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-rf", path) - output, err := rmCmd.CombinedOutput() + output, err := rmCmd.CombinedOutput() if err != nil { return fmt.Errorf("error removing path: %v, output: %s", err, output) } From 5620d8a76b97a2fad1a9ceb092a4e6b81011f569 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 18 Jun 2024 16:11:52 +0000 Subject: [PATCH 115/336] adding the trigger url to the command --- frontend/src/views/AngularWorkflow.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 00bc68de..aca59723 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -15449,7 +15449,8 @@ const releaseToConnectLabel = "Release to Connect" disabled={selectedTrigger.status === "running"} onClick={() => { - const command = (selectedTrigger?.parameters?.find(param => param.name === "command")?.value) || '' + let command = (selectedTrigger?.parameters?.find(param => param.name === "command")?.value) || '' + command = `${command} | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` const pipelineConfig = { command: command, From 3595881a349daab527cfbb5971d7954f81203426 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 18 Jun 2024 17:11:16 +0000 Subject: [PATCH 116/336] trying to parse the json logs properly --- backend/go-app/main.go | 63 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 8bb75ae9..713abda8 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1978,7 +1978,6 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { } func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { - if request.Method != "POST" { request.Method = "POST" } @@ -1999,7 +1998,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { location := strings.Split(request.URL.String(), "/") var pipelineId string - + if location[1] == "api" { if len(location) <= 4 { log.Printf("[INFO] Couldn't handle location. Too short in pipeline: %d", len(location)) @@ -2013,7 +2012,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { userAgent := request.Header.Get("User-Agent") if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") { - log.Printf("[AUDIT] Blocking googlebot and microsoftbot for pielines. UA: '%s'", userAgent) + log.Printf("[AUDIT] Blocking googlebot and microsoftbot for pipelines. UA: '%s'", userAgent) resp.WriteHeader(400) resp.Write([]byte(`{"success": false, "reason": "Google/Microsoft preview bots not allowed. Please change the useragent."}`)) return @@ -2058,7 +2057,23 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { return } - parsedBody := shuffle.GetExecutionbody(body) + // Parse concatenated JSON logs + jsonList, err := parseConcatenatedJSONLogs(string(body)) + if err != nil { + log.Printf("[DEBUG] JSON parsing error: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } +// fix this + parsedBody, err := string(jsonList) + if err != nil { + log.Printf("[ERROR] Failed to marshal jsonList: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false}`)) + return + } + newBody := shuffle.ExecutionStruct{ Start: pipeline.StartNode, ExecutionSource: "pipeline", @@ -2093,8 +2108,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { } if len(pipeline.StartNode) == 0 { - log.Printf("[WARNING] No start node for pipeline %s - running with workflow default.", pipeline.TriggerId) - + log.Printf("[WARNING] No start node for pipeline %s - running with workflow default.") } newRequest := &http.Request{ @@ -2115,6 +2129,43 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp))) } +func parseConcatenatedJSONLogs(logs string) ([]map[string]interface{}, error) { + var jsonList []map[string]interface{} + var currentObject []rune + var depth int + + for _, char := range logs { + if char == '{' { + depth++ + } + if char == '}' { + depth-- + } + + currentObject = append(currentObject, char) + + // When depth is 0, it means we have a complete JSON object but will this work ?? + if depth == 0 && len(currentObject) > 0 { + var jsonObject map[string]interface{} + err := json.Unmarshal([]byte(string(currentObject)), &jsonObject) + if err != nil { + log.Printf("[WARNING] JSON unmarshal error: %s. Skipping this object.", err) + } else { + jsonList = append(jsonList, jsonObject) + } + currentObject = nil + } + } + + currentObject = []rune(strings.TrimSpace(string(currentObject))) + if len(currentObject) > 0 { + log.Printf("[WARNING] Incomplete JSON object found: %s. Skipping this object.", string(currentObject)) + } + + return jsonList, nil +} + + func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error { data, err := json.Marshal(action) if err != nil { From ad92e5e72272e15363001cd1e5d5fb0c3c854111 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Fri, 21 Jun 2024 10:28:49 +0000 Subject: [PATCH 117/336] made the pipeline to parse json logs --- backend/go-app/main.go | 51 ++++++++++------------------- functions/onprem/orborus/orborus.go | 15 ++++++--- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 713abda8..d5fd7c6b 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -2065,8 +2065,8 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(`{"success": false}`)) return } -// fix this - parsedBody, err := string(jsonList) + + parsedBody, err := json.Marshal(jsonList) if err != nil { log.Printf("[ERROR] Failed to marshal jsonList: %s", err) resp.WriteHeader(500) @@ -2077,7 +2077,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { newBody := shuffle.ExecutionStruct{ Start: pipeline.StartNode, ExecutionSource: "pipeline", - ExecutionArgument: parsedBody, + ExecutionArgument: string(parsedBody), } workflow, err := shuffle.GetWorkflow(ctx, pipeline.WorkflowId) @@ -2130,42 +2130,25 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { } func parseConcatenatedJSONLogs(logs string) ([]map[string]interface{}, error) { - var jsonList []map[string]interface{} - var currentObject []rune - var depth int + var jsonList []map[string]interface{} + decoder := json.NewDecoder(strings.NewReader(logs)) - for _, char := range logs { - if char == '{' { - depth++ - } - if char == '}' { - depth-- - } + for decoder.More() { + var jsonObject map[string]interface{} + if err := decoder.Decode(&jsonObject); err != nil { + log.Printf("[WARNING] JSON decoding error: %s. Skipping this object.", err) + continue + } + jsonList = append(jsonList, jsonObject) + } - currentObject = append(currentObject, char) + if err := decoder.Decode(&struct{}{}); err != io.EOF { + return nil, fmt.Errorf("error after decoding all JSON objects: %v", err) + } - // When depth is 0, it means we have a complete JSON object but will this work ?? - if depth == 0 && len(currentObject) > 0 { - var jsonObject map[string]interface{} - err := json.Unmarshal([]byte(string(currentObject)), &jsonObject) - if err != nil { - log.Printf("[WARNING] JSON unmarshal error: %s. Skipping this object.", err) - } else { - jsonList = append(jsonList, jsonObject) - } - currentObject = nil - } - } - - currentObject = []rune(strings.TrimSpace(string(currentObject))) - if len(currentObject) > 0 { - log.Printf("[WARNING] Incomplete JSON object found: %s. Skipping this object.", string(currentObject)) - } - - return jsonList, nil + return jsonList, nil } - func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error { data, err := json.Marshal(action) if err != nil { diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 9c84c03d..b2ec0e80 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -2462,7 +2462,7 @@ func handlePipeline(incRequest shuffle.ExecutionRequest) error { log.Printf("[ERROR] Failed searching for Pipeline with name %s reason:%s ", identifier, err) return err } - _, err = updatePipelineState(pipelineId, "stop") + _, err = updatePipelineState(command, pipelineId, "stop") if err != nil { log.Printf("[ERROR] Failed to stop Pipeline: %s reason:%s ", pipelineId, err) return err @@ -2482,7 +2482,7 @@ func handlePipeline(incRequest shuffle.ExecutionRequest) error { log.Printf("[ERROR] Failed searching for Pipeline with name %s reason:%s ", identifier, err) return err } - _, err = updatePipelineState(pipelineId, "start") + _, err = updatePipelineState(command, pipelineId, "start") if err != nil { log.Printf("[ERROR] Failed to start Pipeline: %s reason:%s ", pipelineId, err) return err @@ -2689,7 +2689,11 @@ func createPipeline(command, identifier string) (string, error) { // } // } - command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | sigma /var/lib/tenzir/rule.yaml | to https://shuffler.io/api/v1/hooks/webhook_d295c43a-e322-4afc-9a59-af167ae7c190" + //command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | sigma /var/lib/tenzir/rule.yaml" + //command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | import" + //command = "export | to https://expert-acorn-v6vg4j4j5w7q2wg6g-5001.app.github.dev/api/v1/hooks/webhook_623eab3f-0af4-4d40-abb9-699d9a493411" + log.Printf("[HARI] this is the command %s", command) + requestBody := map[string]interface{}{ "definition": command, "name": identifier, @@ -2697,7 +2701,7 @@ func createPipeline(command, identifier string) (string, error) { "autostart": map[string]bool{ "created": true, "completed": false, - "failed": true, + "failed": false, }, "autodelete": map[string]bool{ "completed": false, @@ -2763,13 +2767,14 @@ func createPipeline(command, identifier string) (string, error) { return id, nil } -func updatePipelineState(pipelineId, action string) (string, error) { +func updatePipelineState(command, pipelineId, action string) (string, error) { url := fmt.Sprintf("%s/api/v0/pipeline/update", tenzirUrl) forwardMethod := "POST" requestBody := map[string]interface{}{ "id": pipelineId, + "definition": command, "action": action, "autostart": map[string]bool{ "created": true, From ab3c755ad3ca7ebf96060f66ce245ccf436d9f13 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Sun, 23 Jun 2024 09:13:08 +0000 Subject: [PATCH 118/336] sending tenzir health check status to backend --- backend/go-app/main.go | 41 ++++++++++++ functions/onprem/orborus/orborus.go | 98 ++++++++++++++--------------- 2 files changed, 90 insertions(+), 49 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index d5fd7c6b..6fd0ff22 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -2149,6 +2149,46 @@ func parseConcatenatedJSONLogs(logs string) ([]map[string]interface{}, error) { return jsonList, nil } +func handleTenzirHealthUpdate(resp http.ResponseWriter, request *http.Request) { + if request.Method != "POST" { + request.Method = "POST" + } + + type HealthUpdate struct { + Status string `json:"status"` + } + + var healthUpdate HealthUpdate + err := json.NewDecoder(request.Body).Decode(&healthUpdate) + if err != nil { + resp.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(resp, "Failed to decode JSON: %v", err) + return + } + ctx := context.Background() + status := healthUpdate.Status + + result, err := shuffle.GetDisabledRules(ctx) + if (err != nil && err.Error() != "rules doesn't exist") || err == nil { + result.IsTenzirActive = status + result.LastActive = time.Now().Unix() + + err = shuffle.StoreDisabledRules(ctx, *result) + if err != nil { + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) + return + } + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false}`)) + return +} + func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error { data, err := json.Marshal(action) if err != nil { @@ -5091,6 +5131,7 @@ func initHandlers() { r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS") //r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/pipelines/{key}", handlePipelineCallback).Methods("POST", "GET", "PATCH", "PUT", "DELETE", "OPTIONS") + r.HandleFunc("/api/v1/pipelines/tenzir_node_health", handleTenzirHealthUpdate).Methods("POST","OPTIONS") r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS") //r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS") diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index b2ec0e80..49bb2bca 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -1806,6 +1806,12 @@ func main() { log.Printf("[WARNING] Defaulting to environment name %s. Set environment variable ENVIRONMENT_NAME to change. This should be the same as in the frontend action.", environment) } + if tenzirUrl == "" { + tenzirUrl = "http://localhost:5160" + log.Printf("[WARNING] SHUFFLE_TENZIR_URL not set, falling back to default URL: %s",tenzirUrl) + } + + // FIXME - during init, BUILD and/or LOAD worker and app_sdk // Build/load app_sdk so it can be loaded as 127.0.0.1:5000/walkoff_app_sdk log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!") @@ -2254,7 +2260,7 @@ func main() { } } - + _ = sendTenzirHealthStatus() time.Sleep(time.Duration(sleepTime) * time.Second) } } @@ -2414,11 +2420,6 @@ func main() { // docker run tenzir/tenzir:latest 'from http://192.168.86.44:5002/api/v1/orgs/7e9b9007-5df2-4b47-bca5-c4d267ef2943/cache/CIDR%20ranges?type=text&authorization=cec9d01f-09b2-4419-8a0a-76c6046e3fef read lines | to http://192.168.86.44:5002/api/v1/hooks/webhook_665ace5f-f27b-496a-a365-6e07eb61078c write lines' func handlePipeline(incRequest shuffle.ExecutionRequest) error { - if tenzirUrl == "" { - tenzirUrl = "http://localhost:5160" - log.Printf("[WARNING] SHUFFLE_TENZIR_URL not set, falling back to default URL: %s", tenzirUrl) - } - err := deployTenzirNode() if err != nil { log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err) @@ -3072,53 +3073,52 @@ func removePath(containerName, path string) error { return nil } -// func savePipelineData(pipelineId, identifier, status string) error { +func sendTenzirHealthStatus() error { + var status string + url := fmt.Sprintf("%s/api/v1/triggers/pipeline/tenzir_node_health", baseUrl) + err := checkTenzirNode() + if err != nil { + return err + } else { + status = "active" + } -// url := fmt.Sprintf("%s/api/v1/triggers/pipeline/save", baseUrl) -// identifierWithoutPrefix := strings.TrimPrefix(identifier, "shuffle-") + forwardMethod := "POST" + payload := map[string]interface{}{ + "status": status, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + log.Printf("[ERROR] Failed to marshal payload: %s", err) + return err + } + forwardData := bytes.NewBuffer(payloadBytes) + req, err := http.NewRequest( + forwardMethod, + url, + forwardData, + ) + if err != nil { + log.Printf("[ERROR] Failed to create HTTP request: %s", err) + return err + } + req.Header.Set("Content-Type", "application/json") -// forwardMethod := "PUT" + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + log.Printf("[ERROR] Failed to send HTTP request: %s", err) + return err + } + defer resp.Body.Close() -// payload := map[string]interface{}{ -// "pipeline_id": pipelineId, -// "trigger_id": identifierWithoutPrefix, -// "status": status, -// } + if resp.StatusCode != 200 { + log.Printf("[ERROR] Received non-successful HTTP status code: %d", resp.StatusCode) + return fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode) + } -// payloadBytes, err := json.Marshal(payload) -// if err != nil { -// log.Printf("[ERROR] Failed to marshal payload: %s", err) -// return err -// } - -// forwardData := bytes.NewBuffer(payloadBytes) - -// req, err := http.NewRequest( -// forwardMethod, -// url, -// forwardData, -// ) -// if err != nil { -// log.Printf("[ERROR] Failed to create HTTP request: %s", err) -// return err -// } -// req.Header.Set("Content-Type", "application/json") - -// client := &http.Client{Timeout: 10 * time.Second} -// resp, err := client.Do(req) -// if err != nil { -// log.Printf("[ERROR] Failed to send HTTP request: %s", err) -// return err -// } -// defer resp.Body.Close() - -// if resp.StatusCode != 200 { -// log.Printf("[ERROR] Received non-successful HTTP status code: %d", resp.StatusCode) -// return fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode) -// } - -// return nil -// } + return nil +} // Is this ok to do with Docker? idk :) func getRunningWorkers(ctx context.Context, workerTimeout int) int { From ecfb78e82d1f7027265c47b20a00e3ec1e805857 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Sun, 23 Jun 2024 09:16:03 +0000 Subject: [PATCH 119/336] showing the tenzir active status in the UI --- frontend/src/views/Detection.jsx | 50 +++++++++++++++++++------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx index 80d2f821..63ca8ac4 100644 --- a/frontend/src/views/Detection.jsx +++ b/frontend/src/views/Detection.jsx @@ -11,14 +11,13 @@ import { toast } from "react-toastify"; import RuleCard from "./RuleCard"; import { styled } from "@mui/system"; -const ConnectedButton = styled(Button)({ - backgroundColor: "red", +const ConnectedButton = styled(Button)(({ theme, isConnected }) => ({ + backgroundColor: isConnected ? "green" : "red", color: "white", -}); +})); -const handleDirectoryChange = ( folderDisabled, setFolderDisabled, globalUrl) => { - - const action = folderDisabled ? "enable_folder" : "disable_folder" +const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl) => { + const action = folderDisabled ? "enable_folder" : "disable_folder"; const url = `${globalUrl}/api/v1/files/detection/${action}`; fetch(url, { @@ -31,8 +30,8 @@ const handleDirectoryChange = ( folderDisabled, setFolderDisabled, globalUrl) => .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === true) { - if (action === "enable_folder") setFolderDisabled(false); - else setFolderDisabled(true); + if (action === "enable_folder") setFolderDisabled(false); + else setFolderDisabled(true); } else { //toast(`failed to disable rule`); } @@ -42,13 +41,19 @@ const handleDirectoryChange = ( folderDisabled, setFolderDisabled, globalUrl) => console.log(`Error in ${action} the rule: `, error); toast(`An error occurred while ${action} the rule`); }); +}; -} - -const Detection = ({ globalUrl, ruleInfo, folderDisabled, setFolderDisabled, openEditBar }) => { +const Detection = ({ + globalUrl, + ruleInfo, + folderDisabled, + setFolderDisabled, + openEditBar, + isTenzirActive, +}) => { return ( - + Sigma Detection Rules - - Not Connected to SIEM + + {isTenzirActive ? "Connected to SIEM" : "Not Connected to SIEM"} Global disable/enable - handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl)} - /> + + handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl) + } + /> Date: Sun, 23 Jun 2024 16:49:17 +0000 Subject: [PATCH 120/336] adding health check for tenzir --- backend/go-app/main.go | 2 +- functions/onprem/orborus/orborus.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 6fd0ff22..202aa891 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5131,7 +5131,6 @@ func initHandlers() { r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS") //r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/pipelines/{key}", handlePipelineCallback).Methods("POST", "GET", "PATCH", "PUT", "DELETE", "OPTIONS") - r.HandleFunc("/api/v1/pipelines/tenzir_node_health", handleTenzirHealthUpdate).Methods("POST","OPTIONS") r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS") //r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS") @@ -5200,6 +5199,7 @@ func initHandlers() { r.HandleFunc("/api/v1/files/detection/sigma_rules", shuffle.HandleGetSigmaRules).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/detection/{fileId}/{action}", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/files/detection/{action}", shuffle.HandleFolderToggle).Methods("PUT", "OPTIONS") + r.HandleFunc("/api/v1/detection/siem/node_health", handleTenzirHealthUpdate).Methods("POST","OPTIONS") // Introduced in 0.9.21 to handle notifications for e.g. failed Workflow r.HandleFunc("/api/v1/notifications", shuffle.HandleCreateNotification).Methods("POST", "OPTIONS") diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 49bb2bca..17a76611 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -1913,6 +1913,7 @@ func main() { log.Printf("[INFO] Waiting for executions at %s with Environment %#v", fullUrl, environment) hasStarted := false for { + _ = sendTenzirHealthStatus() if req.Method == "POST" { // Should find data to send (memory etc.) @@ -2260,7 +2261,6 @@ func main() { } } - _ = sendTenzirHealthStatus() time.Sleep(time.Duration(sleepTime) * time.Second) } } @@ -2580,9 +2580,9 @@ func deployTenzirNode() error { } func checkTenzirNode() error { - retries := 20 - retryInterval := 3 * time.Second - url := fmt.Sprintf("%s/api/v0/ping", tenzirUrl) + retries := 5 + retryInterval := 3 * time.Second + url := fmt.Sprintf("%s/api/v0/ping",tenzirUrl) forwardMethod := "POST" client := http.Client{} @@ -3075,7 +3075,7 @@ func removePath(containerName, path string) error { func sendTenzirHealthStatus() error { var status string - url := fmt.Sprintf("%s/api/v1/triggers/pipeline/tenzir_node_health", baseUrl) + url := fmt.Sprintf("%s/api/v1/detection/siem/node_health", baseUrl) err := checkTenzirNode() if err != nil { return err @@ -3116,7 +3116,7 @@ func sendTenzirHealthStatus() error { log.Printf("[ERROR] Received non-successful HTTP status code: %d", resp.StatusCode) return fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode) } - + log.Printf("this is send successfully") return nil } From e24550a64106775a7d7a5ae7f6069e9cab838bbe Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Sun, 23 Jun 2024 16:49:53 +0000 Subject: [PATCH 121/336] refactored and adding support for searching --- frontend/src/views/Detection.jsx | 132 ++++++++++++++++++++-- frontend/src/views/DetectionDashboard.jsx | 9 +- 2 files changed, 130 insertions(+), 11 deletions(-) diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx index 63ca8ac4..8b6214a1 100644 --- a/frontend/src/views/Detection.jsx +++ b/frontend/src/views/Detection.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState, useRef } from "react"; import { Container, Box, @@ -7,6 +7,7 @@ import { Typography, Button, } from "@mui/material"; +import { Publish as PublishIcon } from "@mui/icons-material"; import { toast } from "react-toastify"; import RuleCard from "./RuleCard"; import { styled } from "@mui/system"; @@ -51,6 +52,95 @@ const Detection = ({ openEditBar, isTenzirActive, }) => { + const [searchQuery, setSearchQuery] = useState(""); + const uploadRef = useRef(null); + + const uploadFiles = (files) => { + for (const key in files) { + try { + const filename = files[key].name; + const filedata = new FormData(); + filedata.append("shuffle_file", files[key]); + + if (typeof files[key] === "object") { + handleCreateFile(filename, filedata); + } + } catch (e) { + console.log("Error in dropzone: ", e); + } + } + + setTimeout(() => { + // Additional logic if needed + }, 2500); + }; + + const handleCreateFile = (filename, file) => { + const data = { + filename: filename, + org_id: "default", + workflow_id: "global", + namespace: "sigma", + }; + + fetch(globalUrl + "/api/v1/files/create", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(data), + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return; + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + handleFileUpload(responseJson.id, file); + } else { + toast("Failed to upload file ", filename); + } + }) + .catch((error) => { + toast("Failed to upload file ", filename); + console.log(error.toString()); + }); + }; + + const handleFileUpload = (file_id, file) => { + fetch(`${globalUrl}/api/v1/files/${file_id}/upload`, { + method: "POST", + credentials: "include", + body: file, + }) + .then((response) => { + if (response.status !== 200 && response.status !== 201) { + console.log("Status not 200 for apps :O!"); + toast("File was created, but failed to upload."); + return; + } + + return response.json(); + }) + .then((responseJson) => { + // Handle the response as needed + }) + .catch((error) => { + toast(error.toString()); + }); + }; + + const filteredRules = ruleInfo.filter((rule) => + rule.title.toLowerCase().includes(searchQuery.toLowerCase()) || + rule.description.toLowerCase().includes(searchQuery.toLowerCase()) + ); + return ( @@ -65,10 +155,7 @@ const Detection = ({ Sigma Detection Rules - + {isTenzirActive ? "Connected to SIEM" : "Not Connected to SIEM"} @@ -80,7 +167,36 @@ const Detection = ({ mb: 2, }} > - + + setSearchQuery(e.target.value)} + /> + + { + uploadFiles(event.target.files); + }} + /> + Global disable/enable @@ -102,8 +218,8 @@ const Detection = ({ p: 1, }} > - {ruleInfo.length > 0 && - ruleInfo.map((card) => ( + {filteredRules.length > 0 && + filteredRules.map((card) => ( { +const getSigmaInfo = (globalUrl, setRuleInfo, setFolderDisabled, setIsTenzirActive) => { const url = globalUrl + "/api/v1/files/detection/sigma_rules"; fetch(url, { @@ -21,6 +21,8 @@ const getSigmaInfo = (globalUrl, setRuleInfo, setFolderDisabled) => { } else { setRuleInfo(responseJson.sigma_info); setFolderDisabled(responseJson.folder_disabled); + setIsTenzirActive(responseJson.is_tenzir_active); + } }) ) @@ -35,11 +37,12 @@ const DetectionDashBoard = (props) => { const [ruleInfo, setRuleInfo] = useState([]); const [selectedRule, setSelectedRule] = useState(null); const [fileData, setFileData] = React.useState(""); + const [isTenzirActive, setIsTenzirActive] = React.useState(false); const [folderDisabled, setFolderDisabled] = useState(false); useEffect(() => { - getSigmaInfo(globalUrl, setRuleInfo, setFolderDisabled); + getSigmaInfo(globalUrl, setRuleInfo, setFolderDisabled, setIsTenzirActive); }, [folderDisabled]); useEffect(() => { @@ -103,7 +106,7 @@ const DetectionDashBoard = (props) => { onSave={handleSave} /> ) : null} */} - + ); }; From 073b9f3f790355f6c7d87028efdb0edcbc4b81bd Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Mon, 24 Jun 2024 20:28:32 +0530 Subject: [PATCH 122/336] adding a select option for the sigma rules --- frontend/src/views/AngularWorkflow.jsx | 226 +++++++++++++++---------- 1 file changed, 140 insertions(+), 86 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index aca59723..9b7755ac 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -534,6 +534,7 @@ const AngularWorkflow = (defaultprops) => { const [listCache, setListCache] = React.useState([]); const [selectedOption, setSelectedOption] = React.useState(""); const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false); + const [rules, setRules] = React.useState([]); const [distributedFromParent, setDistributedFromParent] = React.useState("") const [suborgWorkflows, setSuborgWorkflows] = React.useState([]) @@ -992,6 +993,12 @@ const releaseToConnectLabel = "Release to Connect" } }, [authenticationModalOpen]) + useEffect(() =>{ + if (tenzirConfigModalOpen === false) return; + + getSigmaInfo(); + },[tenzirConfigModalOpen]) + const listOrgCache = (orgId) => { fetch(`${globalUrl}/api/v1/orgs/${orgId}/list_cache`, { method: "GET", @@ -8308,6 +8315,32 @@ const releaseToConnectLabel = "Release to Connect" }); }; + const getSigmaInfo = () => { + const url = globalUrl + "/api/v1/files/detection/sigma_rules"; + + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed to get sigma rules"); + } else { + setRules(responseJson.sigma_info); + + } + }) + ) + .catch((error) => { + console.log("Error in getting sigma files: ", error); + toast("An error occurred while fetching sigma rules"); + }); + }; + const parsedHeight = isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 50 const appViewStyle = { marginLeft: 5, @@ -21160,95 +21193,116 @@ const releaseToConnectLabel = "Release to Connect" ) : null; - const tenzirConfigModal = tenzirConfigModalOpen ? ( - { + if (!tenzirConfigModalOpen) return null; + + const [loading, setLoading] = useState(true); + const [selectedRules, setSelectedRules] = useState([]); + + const handleRuleChange = (event) => { + setSelectedRules(event.target.value); + }; + + const handleSelectAll = () => { + const allEnabledRules = rules.filter(rule => rule.is_enabled).map(rule => rule.file_id); + setSelectedRules(allEnabledRules); + }; + + const handleClose = () => { + setTenzirConfigModalOpen(false); + }; + + const handleSubmit = () => { + const selectedRuleFiles = rules + .filter(rule => selectedRules.includes(rule.file_id)); + + console.log('Selected Rule Files:', selectedRuleFiles); + console.log('Selected Rule File Names:', selectedRuleFiles.map(rule => rule.file_id)); + + setTenzirConfigModalOpen(false); + }; + + + const enabledSigmaInfo = rules.filter(rule => rule.is_enabled); + + + {loading ? ( + + ) : ( +
    -
    - - - command - param.name === "command")?.value) || ''} - /> + + {selectedOption === 'sigmaRule' && ( + <> + + Select Sigma Rules + + + + + )} + + + + + +
    + )} - - - - - -
    - - { - setTenzirConfigModalOpen(false); - }} - > - - -
    - ) : null; + + + +
    +} From 6873a2202498eaa82c330c57819344afd9bd5e6c Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Tue, 25 Jun 2024 12:00:03 +0530 Subject: [PATCH 123/336] saving the selected rules to the trigger --- frontend/src/views/AngularWorkflow.jsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 9b7755ac..2e3bb393 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -21215,14 +21215,29 @@ const releaseToConnectLabel = "Release to Connect" const handleSubmit = () => { const selectedRuleFiles = rules .filter(rule => selectedRules.includes(rule.file_id)); + + if (selectedTrigger.trigger_type !== "PIPELINE") { + toast("Unable to save the configuration"); + return; + } + + selectedTrigger.parameters = selectedRuleFiles; + console.log('Selected Rule Files:', selectedRuleFiles); console.log('Selected Rule File Names:', selectedRuleFiles.map(rule => rule.file_id)); setTenzirConfigModalOpen(false); }; - + useEffect(()=>{ + if (selectedTrigger.trigger_type !== "PIPELINE") { + //toast("Unable to save the configuration"); + return; + } + setSelectedRules(selectedTrigger.parameters); + },[]) + const enabledSigmaInfo = rules.filter(rule => rule.is_enabled); Date: Tue, 25 Jun 2024 12:01:37 +0530 Subject: [PATCH 124/336] Merge branch '2.0.0' of github.com:satti-hari-krishna-reddy/Shuffle into 2.0.0 --- frontend/src/views/AngularWorkflow.jsx | 181 +++++++++++++------------ 1 file changed, 92 insertions(+), 89 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 2e3bb393..cc4d0b40 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -15416,7 +15416,7 @@ const releaseToConnectLabel = "Release to Connect" toast("please stop the trigger to edit the configuration"); return; } else { - setSelectedOption("Sigma Rulesearch"); + setSelectedOption("SigmaRule"); setTenzirConfigModalOpen(true); }}} style={{ @@ -15432,8 +15432,8 @@ const releaseToConnectLabel = "Release to Connect" setSelectedOption("Sigma Rulesearch")} + checked={selectedOption === "SigmaRule"} + onChange={() => setSelectedOption("SigmaRule")} value={"Sigma Rulesearch"} name="option" /> @@ -21193,25 +21193,23 @@ const releaseToConnectLabel = "Release to Connect" ) : null; - const tenzirConfigModal = () => { - if (!tenzirConfigModalOpen) return null; - - const [loading, setLoading] = useState(true); + const TenzirConfigModal = () => { + const [loading, setLoading] = useState(false); const [selectedRules, setSelectedRules] = useState([]); - + const handleRuleChange = (event) => { setSelectedRules(event.target.value); }; - + const handleSelectAll = () => { const allEnabledRules = rules.filter(rule => rule.is_enabled).map(rule => rule.file_id); setSelectedRules(allEnabledRules); }; - + const handleClose = () => { setTenzirConfigModalOpen(false); }; - + const handleSubmit = () => { const selectedRuleFiles = rules .filter(rule => selectedRules.includes(rule.file_id)); @@ -21239,85 +21237,90 @@ const releaseToConnectLabel = "Release to Connect" },[]) const enabledSigmaInfo = rules.filter(rule => rule.is_enabled); - - - {loading ? ( - - ) : ( -
    - - {selectedOption === 'sigmaRule' && ( - <> - - Select Sigma Rules - - - - - )} - - - - - -
    - )} - - - - -
    -} + {loading ? ( + + ) : ( +
    + + {selectedOption === 'SigmaRule' && ( + <> + + Select Sigma Rules + + + + + )} + + + + + +
    + )} + + + + + + ); + } + @@ -21897,7 +21900,7 @@ const releaseToConnectLabel = "Release to Connect" {codePopoutModal} {workflowRevisions} {authenticationModal} - {tenzirConfigModal} + {} {/*editWorkflowModal*/} {authgroupModal} {executionArgumentModal} From 6ec94335649e951c6c9cd84c00d3db9d775344cb Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 25 Jun 2024 12:35:49 +0000 Subject: [PATCH 125/336] making the select rules option work for pipelines --- backend/go-app/main.go | 4 ++ frontend/src/views/AngularWorkflow.jsx | 93 ++++++++++++++++++-------- 2 files changed, 70 insertions(+), 27 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 202aa891..274f1145 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5199,7 +5199,11 @@ func initHandlers() { r.HandleFunc("/api/v1/files/detection/sigma_rules", shuffle.HandleGetSigmaRules).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/detection/{fileId}/{action}", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/files/detection/{action}", shuffle.HandleFolderToggle).Methods("PUT", "OPTIONS") + r.HandleFunc("/api/v1/detection/siem/node_health", handleTenzirHealthUpdate).Methods("POST","OPTIONS") + r.HandleFunc("/api/v1/detection/{triggerId}/selected_rules", shuffle.HandleGetSelectedRules).Methods("GET","OPTIONS") + r.HandleFunc("/api/v1/detection/{triggerId}/selected_rules/save", shuffle.HandleSaveSelectedRules).Methods("POST","OPTIONS") + // Introduced in 0.9.21 to handle notifications for e.g. failed Workflow r.HandleFunc("/api/v1/notifications", shuffle.HandleCreateNotification).Methods("POST", "OPTIONS") diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index cc4d0b40..f3acbf27 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -21194,9 +21194,37 @@ const releaseToConnectLabel = "Release to Connect" ) : null; const TenzirConfigModal = () => { - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [selectedRules, setSelectedRules] = useState([]); + useEffect(() => { + if (tenzirConfigModalOpen) { + try { + const url = globalUrl + "/api/v1/detection/" + selectedTrigger.id + "/selected_rules" + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then(response => response.json()) + .then(data => { + const savedRules = data.selected_rules.map(rule => rule.file_id); + setSelectedRules(savedRules); + setLoading(false); + }) + .catch(error => { + console.error('Error fetching selected rules:', error); + setLoading(false); + }); + } catch (error) { + console.error('Error:', error); + setLoading(false); + } + } + }, [tenzirConfigModalOpen]); + const handleRuleChange = (event) => { setSelectedRules(event.target.value); }; @@ -21211,31 +21239,43 @@ const releaseToConnectLabel = "Release to Connect" }; const handleSubmit = () => { - const selectedRuleFiles = rules - .filter(rule => selectedRules.includes(rule.file_id)); - - if (selectedTrigger.trigger_type !== "PIPELINE") { - toast("Unable to save the configuration"); - return; - } - - selectedTrigger.parameters = selectedRuleFiles; - - - console.log('Selected Rule Files:', selectedRuleFiles); - console.log('Selected Rule File Names:', selectedRuleFiles.map(rule => rule.file_id)); - - setTenzirConfigModalOpen(false); - }; - - useEffect(()=>{ - if (selectedTrigger.trigger_type !== "PIPELINE") { - //toast("Unable to save the configuration"); - return; + const selectedRuleFiles = rules.filter(rule => selectedRules.includes(rule.file_id)); + + const payload = { + selected_rules: selectedRuleFiles.map(rule => ({ + file_name: rule.file_name, + title: rule.title, + description: rule.description, + file_id: rule.file_id, + is_enabled: rule.is_enabled, + })) + }; + + try { + const url = globalUrl + "/api/v1/detection/" + selectedTrigger.id + "/selected_rules/save" + fetch(url, { + method: 'POST', + credentials: "include", + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }) + .then(response => response.json()) + .then(data => { + console.log('Rules saved successfully:', data); + setTenzirConfigModalOpen(false); + }) + .catch(error => { + console.error('Error saving selected rules:', error); + toast("Unable to save the configuration"); + }); + } catch (error) { + console.error('Error:', error); + toast("Unable to save the configuration"); } - setSelectedRules(selectedTrigger.parameters); - },[]) - + }; + const enabledSigmaInfo = rules.filter(rule => rule.is_enabled); if (!tenzirConfigModalOpen) return null; @@ -21319,8 +21359,7 @@ const releaseToConnectLabel = "Release to Connect" ); - } - + }; From c6b124da3adf5bd40342e7ad3acfbe3480439c80 Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Tue, 25 Jun 2024 21:30:43 +0530 Subject: [PATCH 126/336] reverting select rules and adding the kafka ui back --- frontend/src/views/AngularWorkflow.jsx | 318 +++++++++++++------------ 1 file changed, 172 insertions(+), 146 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index f3acbf27..f4604e15 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -535,6 +535,7 @@ const AngularWorkflow = (defaultprops) => { const [selectedOption, setSelectedOption] = React.useState(""); const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false); const [rules, setRules] = React.useState([]); + const [sigmaFilesNames, setSigmaFileNames] = React.useState("") const [distributedFromParent, setDistributedFromParent] = React.useState("") const [suborgWorkflows, setSuborgWorkflows] = React.useState([]) @@ -993,12 +994,6 @@ const releaseToConnectLabel = "Release to Connect" } }, [authenticationModalOpen]) - useEffect(() =>{ - if (tenzirConfigModalOpen === false) return; - - getSigmaInfo(); - },[tenzirConfigModalOpen]) - const listOrgCache = (orgId) => { fetch(`${globalUrl}/api/v1/orgs/${orgId}/list_cache`, { method: "GET", @@ -1473,6 +1468,59 @@ const releaseToConnectLabel = "Release to Connect" setTenzirConfigModalOpen(false); }; + + const handleSubmit = (trigger) => { + if (trigger.trigger_type !== "PIPELINE") { + toast("Unable to save the configuration"); + return; + } + if (selectedOption == "kafka Queue") { + trigger.parameters = [] + + const topic = document.getElementById('topic')?.value + const bootstrapServers = document.getElementById('bootstrap_servers')?.value + const groupId = document.getElementById('group_id')?.value + const autoOffsetReset = document.getElementById('auto_offset_reset')?.value; + + if(topic) { + trigger.parameters.push({ + name: "topic", + value: topic + }) + } else { + toast("Please enter the topic name"); + return; + } + + if (bootstrapServers) { + trigger.parameters.push({ + name: "bootstrap_servers", + value: bootstrapServers + }); + } else { + toast("please enter bootstrap server details"); + return; + } + + if (groupId) { + trigger.parameters.push({ + name: "group_id", + value: groupId + }); + } + + if (autoOffsetReset) { + trigger.parameters.push({ + name: "auto_offset_reset", + value: autoOffsetReset + }); + } + + setTenzirConfigModalOpen(false); + } + + }; + const handleColoring = (actionId, status, label) => { if (cy === undefined) { @@ -15417,7 +15465,18 @@ const releaseToConnectLabel = "Release to Connect" return; } else { setSelectedOption("SigmaRule"); - setTenzirConfigModalOpen(true); + const command = `export | sigma /var/lib/tenzir/sigma_rules | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` + const pipelineConfig = { + command: command, + name: selectedTrigger.label, + type: "create", + environment: selectedTrigger.environment, + workflow_id: workflow.id, + trigger_id: selectedTrigger.id, + start_node: "", + }; + submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig); + }}} style={{ border: "1px solid rgba(255,255,255,0.3)", @@ -21194,90 +21253,6 @@ const releaseToConnectLabel = "Release to Connect" ) : null; const TenzirConfigModal = () => { - const [loading, setLoading] = useState(true); - const [selectedRules, setSelectedRules] = useState([]); - - useEffect(() => { - if (tenzirConfigModalOpen) { - try { - const url = globalUrl + "/api/v1/detection/" + selectedTrigger.id + "/selected_rules" - fetch(url, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }) - .then(response => response.json()) - .then(data => { - const savedRules = data.selected_rules.map(rule => rule.file_id); - setSelectedRules(savedRules); - setLoading(false); - }) - .catch(error => { - console.error('Error fetching selected rules:', error); - setLoading(false); - }); - } catch (error) { - console.error('Error:', error); - setLoading(false); - } - } - }, [tenzirConfigModalOpen]); - - const handleRuleChange = (event) => { - setSelectedRules(event.target.value); - }; - - const handleSelectAll = () => { - const allEnabledRules = rules.filter(rule => rule.is_enabled).map(rule => rule.file_id); - setSelectedRules(allEnabledRules); - }; - - const handleClose = () => { - setTenzirConfigModalOpen(false); - }; - - const handleSubmit = () => { - const selectedRuleFiles = rules.filter(rule => selectedRules.includes(rule.file_id)); - - const payload = { - selected_rules: selectedRuleFiles.map(rule => ({ - file_name: rule.file_name, - title: rule.title, - description: rule.description, - file_id: rule.file_id, - is_enabled: rule.is_enabled, - })) - }; - - try { - const url = globalUrl + "/api/v1/detection/" + selectedTrigger.id + "/selected_rules/save" - fetch(url, { - method: 'POST', - credentials: "include", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }) - .then(response => response.json()) - .then(data => { - console.log('Rules saved successfully:', data); - setTenzirConfigModalOpen(false); - }) - .catch(error => { - console.error('Error saving selected rules:', error); - toast("Unable to save the configuration"); - }); - } catch (error) { - console.error('Error:', error); - toast("Unable to save the configuration"); - } - }; - - const enabledSigmaInfo = rules.filter(rule => rule.is_enabled); - if (!tenzirConfigModalOpen) return null; return ( @@ -21302,65 +21277,116 @@ const releaseToConnectLabel = "Release to Connect" }, }} > - {loading ? ( - - ) : ( -
    - - {selectedOption === 'SigmaRule' && ( - <> - - Select Sigma Rules - - - - - )} - - - - - -
    - )} + +
    Configuration options for Kafka
    +
    + + {selectedOption === "Kafka Queue" ? ( +
    + Topic + param.name === "topic", + )?.value || "" + } + /> + bootstrap.servers + param.name === "bootstrap_servers", + )?.value || "" + } + /> + group.id + param.name === "group_id", + )?.value || "" + } + /> + auto.offest.reset + param.name === "auto_offset_reset", + )?.value || "" + } + /> +
    + ) : null}{" "} +
    - - - + + + + ); }; - + const SuggestionBoxUi = () => { From a738be78bbed9afe11141f1156723d6ec8e47e03 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 25 Jun 2024 16:36:59 +0000 Subject: [PATCH 127/336] fixing few typos --- frontend/src/views/AngularWorkflow.jsx | 45 ++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index f4604e15..7a16c6e9 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1474,7 +1474,7 @@ const releaseToConnectLabel = "Release to Connect" toast("Unable to save the configuration"); return; } - if (selectedOption == "kafka Queue") { + if (selectedOption === "Kafka Queue") { trigger.parameters = [] const topic = document.getElementById('topic')?.value @@ -15541,8 +15541,41 @@ const releaseToConnectLabel = "Release to Connect" disabled={selectedTrigger.status === "running"} onClick={() => { - let command = (selectedTrigger?.parameters?.find(param => param.name === "command")?.value) || '' - command = `${command} | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` + if (selectedOption === "Kafka Queue"){ + + const topic = (selectedTrigger?.parameters?.find(param => param.name === "topic")?.value) || '' + const bootstrapServers = (selectedTrigger?.parameters?.find(param => param.name === "bootstrap_servers")?.value) || '' + const groupId = (selectedTrigger?.parameters?.find(param => param.name === "group_id")?.value) || '' + const autoOffsetReset = (selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || '' + let command = "from kafka" + + if(topic) { + command = `${command} -t ${topic}` + } else { + toast("please enter the topic name") + return; + } + if(bootstrapServers) { + command = `${command} -e -o stored -X bootstrap.servers=${bootstrapServers}` + } else { + toast("please enter the bootstrap servers details") + return; + } + + if(groupId) { + command = `${command},group.id=${groupId}` + } else { + command = `${command},group.id=${selectedTrigger.id}` + } + if(autoOffsetReset) { + command = `${command},auto.offset.reset=${autoOffsetReset}` + } else { + command = `${command},auto.offset.reset=earliest` + + } + command = `${command},auto.offset.reset=earliest` + command = `${command},client.id=${selectedTrigger.id},enable.auto.commit=true,auto.commit.interval.ms=1` + command = `${command} read json | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` const pipelineConfig = { command: command, @@ -15554,7 +15587,7 @@ const releaseToConnectLabel = "Release to Connect" start_node: "", }; submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig); - }} + }}} color="primary" > Start @@ -21268,8 +21301,8 @@ const releaseToConnectLabel = "Release to Connect" pointerEvents: "auto", color: "white", minWidth: 600, - minHeight: 200, - maxHeight: 200, + minHeight: 550, + maxHeight: 550, padding: 15, overflow: "hidden", zIndex: 10012, From d4ce234fa9c0d3dd59e7596ea9cd1e30dbefab47 Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Wed, 26 Jun 2024 16:34:19 +0530 Subject: [PATCH 128/336] adding endpoint option for syslog --- frontend/src/views/AngularWorkflow.jsx | 61 +++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 7a16c6e9..2ed1f493 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1517,6 +1517,20 @@ const releaseToConnectLabel = "Release to Connect" } setTenzirConfigModalOpen(false); + } else if (selectedOption === "Syslog listener") { + trigger.parameters = [] + + const endpoint = document.getElementById('endpoint')?.value + + if(endpoint) { + trigger.parameters.push({ + name: "endpoint", + value: endpoint + }) + } else { + toast("Please enter your endpoint"); + return; + } } }; @@ -15587,7 +15601,28 @@ const releaseToConnectLabel = "Release to Connect" start_node: "", }; submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig); - }}} + } else if (selectedOption === "Syslog listener"){ + let command = "" + const endpoint = (selectedTrigger?.parameters?.find(param => param.name === "endpoint")?.value) || '' + if(endpoint) { + command = `from tcp://${endpoint} | read syslog | import` + } else { + toast("please enter the topic name") + return; + } + + const pipelineConfig = { + command: command, + name: selectedTrigger.label, + type: "create", + environment: selectedTrigger.environment, + workflow_id: workflow.id, + trigger_id: selectedTrigger.id, + start_node: "", + }; + submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig); + } + }} color="primary" > Start @@ -21394,6 +21429,30 @@ const releaseToConnectLabel = "Release to Connect" />
    ) : null}{" "} + +{selectedOption === "Syslog listener" ? ( +
    + End Point + param.name === "endpoint", + )?.value || "" + } + /> +
    + ) : null}{" "} From 78b6d9e0673c2f86c62f73e99b13311fd87239e2 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Thu, 27 Jun 2024 05:22:49 +0000 Subject: [PATCH 129/336] bug fixes --- backend/go-app/main.go | 2 +- frontend/src/views/Detection.jsx | 4 ++-- frontend/src/views/DetectionDashboard.jsx | 2 +- functions/onprem/orborus/orborus.go | 4 +--- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 274f1145..7864a427 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -2169,7 +2169,7 @@ func handleTenzirHealthUpdate(resp http.ResponseWriter, request *http.Request) { status := healthUpdate.Status result, err := shuffle.GetDisabledRules(ctx) - if (err != nil && err.Error() != "rules doesn't exist") || err == nil { + if (err != nil && err.Error() == "rules doesn't exist") || err == nil { result.IsTenzirActive = status result.LastActive = time.Now().Unix() diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx index 8b6214a1..f7e2d135 100644 --- a/frontend/src/views/Detection.jsx +++ b/frontend/src/views/Detection.jsx @@ -136,7 +136,7 @@ const Detection = ({ }); }; - const filteredRules = ruleInfo.filter((rule) => + const filteredRules = ruleInfo?.filter((rule) => rule.title.toLowerCase().includes(searchQuery.toLowerCase()) || rule.description.toLowerCase().includes(searchQuery.toLowerCase()) ); @@ -218,7 +218,7 @@ const Detection = ({ p: 1, }} > - {filteredRules.length > 0 && + {filteredRules?.length > 0 && filteredRules.map((card) => ( { }, [folderDisabled]); useEffect(() => { - if (ruleInfo.length > 0) { + if (ruleInfo?.length > 0) { openEditBar(ruleInfo[0]); } }, [ruleInfo]); diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 17a76611..d08f2bed 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -102,6 +102,7 @@ var swarmNetworkName = os.Getenv("SHUFFLE_SWARM_NETWORK_NAME") var orborusLabel = os.Getenv("SHUFFLE_ORBORUS_LABEL") var memcached = os.Getenv("SHUFFLE_MEMCACHED") var tenzirUrl = os.Getenv("SHUFFLE_TENZIR_URL") +var apiKey = os.Getenv("AUTH_FOR_ORBORUS") var executionIds = []string{} var namespacemade = false // For K8s @@ -2923,8 +2924,6 @@ func searchPipeline(identifier string) (string, error) { func handleFileCategoryChange() error{ apiEndpoint := baseUrl+"/api/v1/files/namespaces/sigma" - apiKey := "12e7150e-1e03-4834-a839-de4688f50ad0" - req, err := http.NewRequest("GET", apiEndpoint, nil) if err != nil { return err @@ -3116,7 +3115,6 @@ func sendTenzirHealthStatus() error { log.Printf("[ERROR] Received non-successful HTTP status code: %d", resp.StatusCode) return fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode) } - log.Printf("this is send successfully") return nil } From 4708d7d9078924a97813ebb9f92dac0572b09e61 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Thu, 27 Jun 2024 11:59:31 +0000 Subject: [PATCH 130/336] adding api key to use for orborus to download files --- .env | 1 + 1 file changed, 1 insertion(+) diff --git a/.env b/.env index 298128cc..5d6b0288 100755 --- a/.env +++ b/.env @@ -40,6 +40,7 @@ BACKEND_HOSTNAME=shuffle-backend BACKEND_PORT=5001 FRONTEND_PORT=3001 FRONTEND_PORT_HTTPS=3443 +AUTH_FOR_ORBORUS = # CHANGE THIS IF YOU WANT GOOD LOCAL EXECUTIONS: OUTER_HOSTNAME=shuffle-backend From dbbe54a8c6362c7f8b82dd48af3913e52f8fde19 Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Wed, 3 Jul 2024 11:09:05 +0530 Subject: [PATCH 131/336] adding an endpoint to spin up tenzir node --- backend/go-app/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 7864a427..4ac495de 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5200,6 +5200,7 @@ func initHandlers() { r.HandleFunc("/api/v1/files/detection/{fileId}/{action}", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/files/detection/{action}", shuffle.HandleFolderToggle).Methods("PUT", "OPTIONS") + r.HandleFunc("/api/v1/detection/siem/connect", shuffle.HandleConnectSiem).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/detection/siem/node_health", handleTenzirHealthUpdate).Methods("POST","OPTIONS") r.HandleFunc("/api/v1/detection/{triggerId}/selected_rules", shuffle.HandleGetSelectedRules).Methods("GET","OPTIONS") r.HandleFunc("/api/v1/detection/{triggerId}/selected_rules/save", shuffle.HandleSaveSelectedRules).Methods("POST","OPTIONS") From ccad70a2ee4cca6f1d862b7e9309b07ff85f86c3 Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Wed, 3 Jul 2024 11:09:52 +0530 Subject: [PATCH 132/336] orborus can now start the tenzir node based on the request --- functions/onprem/orborus/orborus.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index d08f2bed..391a1620 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -2072,7 +2072,16 @@ func main() { } toBeRemoved.Data = append(toBeRemoved.Data, incRequest) - } else { + } else if incRequest.Type == "START_TENZIR" { + + err := deployTenzirNode() + if err != nil{ + log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err) + } + + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + + } else { newrequests = append(newrequests, incRequest) } } From d7741db32f8e19d21dbca7a25b5cf2ab6701d413 Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Wed, 3 Jul 2024 11:10:13 +0530 Subject: [PATCH 133/336] adding connect to siem button --- frontend/src/views/Detection.jsx | 58 ++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx index f7e2d135..3f909758 100644 --- a/frontend/src/views/Detection.jsx +++ b/frontend/src/views/Detection.jsx @@ -10,12 +10,7 @@ import { import { Publish as PublishIcon } from "@mui/icons-material"; import { toast } from "react-toastify"; import RuleCard from "./RuleCard"; -import { styled } from "@mui/system"; - -const ConnectedButton = styled(Button)(({ theme, isConnected }) => ({ - backgroundColor: isConnected ? "green" : "red", - color: "white", -})); +import CircularProgress from "@material-ui/core/CircularProgress"; const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl) => { const action = folderDisabled ? "enable_folder" : "disable_folder"; @@ -54,6 +49,7 @@ const Detection = ({ }) => { const [searchQuery, setSearchQuery] = useState(""); const uploadRef = useRef(null); + const [loading, setLoading] = useState(false); const uploadFiles = (files) => { for (const key in files) { @@ -136,6 +132,41 @@ const Detection = ({ }); }; + const handleConnectClick = () => { + if (!isTenzirActive) { + setLoading(true); + const url = `${globalUrl}/api/v1/detection/siem/connect`; + + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === true) { + setTimeout(() => { + setLoading(false); + window.location.reload(); + }, 5000); + } else { + setLoading(false); + toast("Failed to connect to SIEM"); + } + }) + ) + .catch((error) => { + setLoading(false); + console.log(`Error in connecting to SIEM: `, error); + toast("An error occurred while connecting to SIEM"); + }); + } else { + console.log("Already connected to SIEM"); + } + }; + const filteredRules = ruleInfo?.filter((rule) => rule.title.toLowerCase().includes(searchQuery.toLowerCase()) || rule.description.toLowerCase().includes(searchQuery.toLowerCase()) @@ -155,9 +186,14 @@ const Detection = ({ Sigma Detection Rules - - {isTenzirActive ? "Connected to SIEM" : "Not Connected to SIEM"} - +
    setSearchQuery(e.target.value)} /> -
    + const defaultEnvironment = environments.find( + (env) => env.default && env.Name.toLowerCase() !== "cloud" + ); + + if (selectedTrigger.environment === "onprem" && defaultEnvironment !== undefined) { + selectedTrigger.environment = defaultEnvironment.Name + setSelectedTrigger(selectedTrigger) } + const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null :

    From 295bcf07a450078c71f0fd72469a63659da4034e Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Tue, 16 Jul 2024 20:25:13 +0530 Subject: [PATCH 135/336] adding a new endpoint for downloading files from a repo --- backend/go-app/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 4ac495de..8f247fc8 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5188,6 +5188,7 @@ func initHandlers() { // PS: For cloud, this has to use cloud storage. // https://developer.box.com/reference/get-files-id-content/ r.HandleFunc("/api/v1/files/download_remote", shuffle.HandleDownloadRemoteFiles).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v2/files/download_remote", shuffle.HandleDownloadRemoteFiles2).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/files/namespaces/{namespace}", shuffle.HandleGetFileNamespace).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}/content", shuffle.HandleGetFileContent).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/create", shuffle.HandleCreateFile).Methods("POST", "OPTIONS") From 94749e679e66dca9421d20071068456d13094d93 Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Tue, 16 Jul 2024 20:26:11 +0530 Subject: [PATCH 136/336] making the pipeline side bar to auto select the defualt env initially --- frontend/src/views/AngularWorkflow.jsx | 96 ++++++++++---------------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index b74a7afd..c3ce2581 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -8290,11 +8290,11 @@ const releaseToConnectLabel = "Release to Connect" toast("Pipeline deleted!") return } - + if (trigger.parameters){ trigger.parameters.push({ name: data.name, value: data.command, - }); + });} if (data.type === "stop") trigger.status = "stopped"; else trigger.status = "running"; @@ -8302,7 +8302,6 @@ const releaseToConnectLabel = "Release to Connect" setSelectedTrigger(trigger); setWorkflow(workflow); - console.log("Should set the status to running and save"); saveWorkflow(workflow); } }) @@ -15347,7 +15346,7 @@ const releaseToConnectLabel = "Release to Connect" (env) => env.default && env.Name.toLowerCase() !== "cloud" ); - if (selectedTrigger.environment === "onprem" && defaultEnvironment !== undefined) { + if (selectedTrigger.trigger_type === "PIPELINE" && selectedTrigger.environment === "onprem" && defaultEnvironment !== undefined) { selectedTrigger.environment = defaultEnvironment.Name setSelectedTrigger(selectedTrigger) } @@ -15450,11 +15449,25 @@ const releaseToConnectLabel = "Release to Connect" key="syslogListener" onClick={() => { if(selectedTrigger.status === "running"){ - toast("please stop the trigger to edit the configuration"); + //toast("please stop the trigger to edit the configuration"); return; } else { setSelectedOption("Syslog listener"); - setTenzirConfigModalOpen(true); + const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` + const command = `from tcp://192.168.1.100:5162 read syslog | import` + const pipelineConfig = { + command: command, + name: selectedTrigger.label, + type: "create", + environment: selectedTrigger.environment, + workflow_id: workflow.id, + trigger_id: selectedTrigger.id, + start_node: "", + url:url, + }; + submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig); + + }}} style={{ border: "1px solid rgba(255,255,255,0.3)", @@ -15470,12 +15483,15 @@ const releaseToConnectLabel = "Release to Connect" control={ setSelectedOption("Syslog listener")} + onChange={() => { + if (selectedTrigger.status !== "running"){ + setSelectedOption("Syslog listener")}} + } value={"Syslog listener"} name="option" /> } - label="Start Syslog listener" + label= {selectedOption === "Syslog listener" && selectedTrigger.status === "running" ? "listening at 192.168.1.100:5162" : "Start Syslog listener"} />

    @@ -15483,11 +15499,12 @@ const releaseToConnectLabel = "Release to Connect" key="sigmaRulesearch" onClick={() => { if(selectedTrigger.status === "running"){ - toast("please stop the trigger to edit the configuration"); + // toast("please stop the trigger to edit the configuration"); return; } else { setSelectedOption("SigmaRule"); - const command = `export | sigma /var/lib/tenzir/sigma_rules | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` + const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` + const command = `export | sigma /var/lib/tenzir/sigma_rules | to ${url}` const pipelineConfig = { command: command, name: selectedTrigger.label, @@ -15496,6 +15513,7 @@ const releaseToConnectLabel = "Release to Connect" workflow_id: workflow.id, trigger_id: selectedTrigger.id, start_node: "", + url:url, }; submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig); @@ -15514,7 +15532,9 @@ const releaseToConnectLabel = "Release to Connect" control={ setSelectedOption("SigmaRule")} + onChange={() => { + if (selectedTrigger.status !== "running"){ + setSelectedOption("SigmaRule")}}} value={"Sigma Rulesearch"} name="option" /> @@ -15547,7 +15567,9 @@ const releaseToConnectLabel = "Release to Connect" control={ setSelectedOption("Kafka Queue")} + onChange={() => { + if (selectedTrigger.status !== "running"){ + setSelectedOption("Kafka Queue")}}} value={"Kafka Queue"} name="option" /> @@ -15564,7 +15586,7 @@ const releaseToConnectLabel = "Release to Connect" onClick={() => { if (selectedOption === "Kafka Queue"){ - + const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` const topic = (selectedTrigger?.parameters?.find(param => param.name === "topic")?.value) || '' const bootstrapServers = (selectedTrigger?.parameters?.find(param => param.name === "bootstrap_servers")?.value) || '' const groupId = (selectedTrigger?.parameters?.find(param => param.name === "group_id")?.value) || '' @@ -15594,10 +15616,10 @@ const releaseToConnectLabel = "Release to Connect" } else { command = `${command},auto.offset.reset=earliest` - } + } command = `${command},auto.offset.reset=earliest` command = `${command},client.id=${selectedTrigger.id},enable.auto.commit=true,auto.commit.interval.ms=1` - command = `${command} read json | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}` + command = `${command} read json | to ${url}` const pipelineConfig = { command: command, @@ -15607,28 +15629,9 @@ const releaseToConnectLabel = "Release to Connect" workflow_id: workflow.id, trigger_id: selectedTrigger.id, start_node: "", + url: url, }; submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig); - } else if (selectedOption === "Syslog listener"){ - let command = "" - const endpoint = (selectedTrigger?.parameters?.find(param => param.name === "endpoint")?.value) || '' - if(endpoint) { - command = `from tcp://${endpoint} | read syslog | import` - } else { - toast("please enter the topic name") - return; - } - - const pipelineConfig = { - command: command, - name: selectedTrigger.label, - type: "create", - environment: selectedTrigger.environment, - workflow_id: workflow.id, - trigger_id: selectedTrigger.id, - start_node: "", - }; - submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig); } }} color="primary" @@ -21438,29 +21441,6 @@ const releaseToConnectLabel = "Release to Connect"
    ) : null}{" "} -{selectedOption === "Syslog listener" ? ( -
    - End Point - param.name === "endpoint", - )?.value || "" - } - /> -
    - ) : null}{" "} From d6b78612df49b9a415c640d71c53ee13775bb3bf Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Tue, 16 Jul 2024 20:27:09 +0530 Subject: [PATCH 137/336] making enable / disable button to work only when connected to the siem --- frontend/src/views/Detection.jsx | 100 ++----------- frontend/src/views/DetectionDashboard.jsx | 174 ++++++++++++++-------- frontend/src/views/RuleCard.jsx | 78 +++++----- 3 files changed, 164 insertions(+), 188 deletions(-) diff --git a/frontend/src/views/Detection.jsx b/frontend/src/views/Detection.jsx index 3f909758..c86eeba2 100644 --- a/frontend/src/views/Detection.jsx +++ b/frontend/src/views/Detection.jsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from "react"; +import React, { useState } from "react"; import { Container, Box, @@ -7,12 +7,16 @@ import { Typography, Button, } from "@mui/material"; -import { Publish as PublishIcon } from "@mui/icons-material"; import { toast } from "react-toastify"; import RuleCard from "./RuleCard"; import CircularProgress from "@material-ui/core/CircularProgress"; -const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl) => { +const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isTenzirActive) => { + + if (!isTenzirActive) { + toast("connect to siem first for global enable/disable to work"); + return; + } const action = folderDisabled ? "enable_folder" : "disable_folder"; const url = `${globalUrl}/api/v1/files/detection/${action}`; @@ -44,94 +48,11 @@ const Detection = ({ ruleInfo, folderDisabled, setFolderDisabled, - openEditBar, isTenzirActive, }) => { const [searchQuery, setSearchQuery] = useState(""); - const uploadRef = useRef(null); const [loading, setLoading] = useState(false); - const uploadFiles = (files) => { - for (const key in files) { - try { - const filename = files[key].name; - const filedata = new FormData(); - filedata.append("shuffle_file", files[key]); - - if (typeof files[key] === "object") { - handleCreateFile(filename, filedata); - } - } catch (e) { - console.log("Error in dropzone: ", e); - } - } - - setTimeout(() => { - // Additional logic if needed - }, 2500); - }; - - const handleCreateFile = (filename, file) => { - const data = { - filename: filename, - org_id: "default", - workflow_id: "global", - namespace: "sigma", - }; - - fetch(globalUrl + "/api/v1/files/create", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - body: JSON.stringify(data), - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for apps :O!"); - return; - } - - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success === true) { - handleFileUpload(responseJson.id, file); - } else { - toast("Failed to upload file ", filename); - } - }) - .catch((error) => { - toast("Failed to upload file ", filename); - console.log(error.toString()); - }); - }; - - const handleFileUpload = (file_id, file) => { - fetch(`${globalUrl}/api/v1/files/${file_id}/upload`, { - method: "POST", - credentials: "include", - body: file, - }) - .then((response) => { - if (response.status !== 200 && response.status !== 201) { - console.log("Status not 200 for apps :O!"); - toast("File was created, but failed to upload."); - return; - } - - return response.json(); - }) - .then((responseJson) => { - // Handle the response as needed - }) - .catch((error) => { - toast(error.toString()); - }); - }; - const handleConnectClick = () => { if (!isTenzirActive) { setLoading(true); @@ -150,7 +71,7 @@ const Detection = ({ setTimeout(() => { setLoading(false); window.location.reload(); - }, 5000); + }, 15000); } else { setLoading(false); toast("Failed to connect to SIEM"); @@ -240,8 +161,9 @@ const Detection = ({ - handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl) + handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isTenzirActive) } + disabled={!isTenzirActive} /> @@ -263,7 +185,7 @@ const Detection = ({ file_id={card.file_id} globalUrl={globalUrl} folderDisabled={folderDisabled} - openEditBar={() => openEditBar(card)} + isTenzirActive={isTenzirActive} {...card} /> ))} diff --git a/frontend/src/views/DetectionDashboard.jsx b/frontend/src/views/DetectionDashboard.jsx index e09814e4..4b2b1b06 100644 --- a/frontend/src/views/DetectionDashboard.jsx +++ b/frontend/src/views/DetectionDashboard.jsx @@ -1,68 +1,45 @@ import React, { useState, useEffect } from "react"; -import { Container} from "@mui/material"; +import { Container, CircularProgress, Typography } from "@mui/material"; import { toast } from "react-toastify"; import Detection from "./Detection"; -import EditComponent from "./EditRules"; - -const getSigmaInfo = (globalUrl, setRuleInfo, setFolderDisabled, setIsTenzirActive) => { - const url = globalUrl + "/api/v1/files/detection/sigma_rules"; - - fetch(url, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - toast("Failed to get sigma rules"); - } else { - setRuleInfo(responseJson.sigma_info); - setFolderDisabled(responseJson.folder_disabled); - setIsTenzirActive(responseJson.is_tenzir_active); - - } - }) - ) - .catch((error) => { - console.log("Error in getting sigma files: ", error); - toast("An error occurred while fetching sigma rules"); - }); -}; const DetectionDashBoard = (props) => { const { globalUrl } = props; - const [ruleInfo, setRuleInfo] = useState([]); - const [selectedRule, setSelectedRule] = useState(null); - const [fileData, setFileData] = React.useState(""); - const [isTenzirActive, setIsTenzirActive] = React.useState(false); - + const [ruleInfo, setRuleInfo] = useState(null); + const [, setSelectedRule] = useState(null); + const [, setFileData] = useState(""); + const [isTenzirActive, setIsTenzirActive] = useState(false); const [folderDisabled, setFolderDisabled] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [importAttempts, setImportAttempts] = useState(0); + const maxImportAttempts = 2; useEffect(() => { - getSigmaInfo(globalUrl, setRuleInfo, setFolderDisabled, setIsTenzirActive); - }, [folderDisabled]); + const fetchTimeout = setTimeout(() => { + fetchSigmaInfo(); + }, 1000); // Delay by 1 second + + return () => clearTimeout(fetchTimeout); + }, [globalUrl]); useEffect(() => { - if (ruleInfo?.length > 0) { - openEditBar(ruleInfo[0]); + if (ruleInfo && ruleInfo.length === 0 && importAttempts < maxImportAttempts) { + importSigmaFromUrl(); } }, [ruleInfo]); const openEditBar = (rule) => { setSelectedRule(rule); - getFileContent(rule.file_id) + fetchFileContent(rule.file_id); }; const handleSave = (updatedContent) => { - toast("this will be saved"); + toast("This will be saved"); }; - const getFileContent = (file_id) => { + const fetchFileContent = (file_id) => { setFileData(""); - fetch(globalUrl + "/api/v1/files/" + file_id + "/content", { + fetch(`${globalUrl}/api/v1/files/${file_id}/content`, { method: "GET", headers: { "Content-Type": "application/json", @@ -77,38 +54,109 @@ const DetectionDashBoard = (props) => { } return response.text(); }) - .then((respdata) => { + .then((respdata) => { if (respdata.length === 0) { toast("Failed getting file. Is it deleted?"); return; } - return respdata - }) - .then((responseData) => { - - setFileData(responseData); + setFileData(respdata); }) .catch((error) => { toast(error.toString()); }); }; + const fetchSigmaInfo = () => { + const url = `${globalUrl}/api/v1/files/detection/sigma_rules`; + setIsLoading(true); + + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed to get sigma rules"); + } else { + setRuleInfo(responseJson.sigma_info || []); + setFolderDisabled(responseJson.folder_disabled); + setIsTenzirActive(responseJson.is_tenzir_active); + } + setIsLoading(false); + }) + .catch((error) => { + setIsLoading(false); + console.log("Error in getting sigma files: ", error); + toast("An error occurred while fetching sigma rules"); + setRuleInfo([]); + }); + }; + + const importSigmaFromUrl = () => { + setIsLoading(true); + setImportAttempts((prevAttempts) => prevAttempts + 1); + + const url = "https://github.com/satti-hari-krishna-reddy/shuffle_sigma"; + const folder = "sigma"; + + const parsedData = { + url: url, + path: folder, + field_3: "main", + }; + + toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`); + fetch(`${globalUrl}/api/v2/files/download_remote`, { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(parsedData), + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success) { + toast("Successfully loaded files from " + url); + fetchSigmaInfo(); // Fetch again after successful import + } else { + toast(responseJson.reason ? `Failed loading: ${responseJson.reason}` : "Failed loading"); + } + setIsLoading(false); + }) + .catch((error) => { + toast(error.toString()); + setIsLoading(false); + }); + }; + + if (isLoading && (!ruleInfo || ruleInfo.length === 0)) { + return ( + +
    + + Downloading rules, please wait... +
    +
    + ); + } + return ( - - {/* {selectedRule ? ( - - ) : null} */} - + + - ); + ); }; export default DetectionDashBoard; diff --git a/frontend/src/views/RuleCard.jsx b/frontend/src/views/RuleCard.jsx index fe334f85..b35d993a 100644 --- a/frontend/src/views/RuleCard.jsx +++ b/frontend/src/views/RuleCard.jsx @@ -10,7 +10,7 @@ import EditIcon from "@mui/icons-material/Edit"; import { toast } from "react-toastify"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; -const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, ...otherProps }) => { +const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, isTenzirActive, ...otherProps }) => { const [openCodeEditor, setOpenCodeEditor] = React.useState(false); const [fileData, setFileData] = React.useState(""); const [isEnabled, setIsEnabled] = React.useState(otherProps.is_enabled); @@ -22,6 +22,10 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, . toast("enable the directory to enable individual rules"); return; } + if (!isTenzirActive) { + toast("connect to the siem to enable/disable the rule"); + return; + } const newIsEnabled = event.target.checked; toggleRule(file_id, !newIsEnabled, globalUrl, () => { setIsEnabled(newIsEnabled); @@ -73,6 +77,7 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, .
    @@ -121,42 +126,43 @@ const toggleRule = (fileId, isCurrentlyEnabled, globalUrl, callback) => { toast(`An error occurred while ${action}ing the rule`); }); }; - const openEditBar = (file_id, setOpenCodeEditor, setFileData, globalUrl) => { - getFileContent(file_id, setFileData, globalUrl); + +const openEditBar = (file_id, setOpenCodeEditor, setFileData, globalUrl) => { + getFileContent(file_id, setFileData, globalUrl) + setOpenCodeEditor(true); - }; +}; - const getFileContent = (file_id, setFileData, globalUrl) => { - setFileData(""); - fetch(globalUrl + "/api/v1/files/" + file_id + "/content", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", +const getFileContent = (file_id, setFileData, globalUrl) => { + setFileData(""); + fetch(globalUrl + "/api/v1/files/" + file_id + "/content", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for file :O!"); + return ""; + } + return response.text(); }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for file :O!"); - return ""; - } - return response.text(); - }) - .then((respdata) => { - if (respdata.length === 0) { - toast("Failed getting file. Is it deleted?"); - return; - } - return respdata - }) - .then((responseData) => { - - setFileData(responseData); - }) - .catch((error) => { - toast(error.toString()); - }); - }; - + .then((respdata) => { + if (respdata.length === 0) { + toast("Failed getting file. Is it deleted?"); + return; + } + return respdata + }) + .then((responseData) => { + + setFileData(responseData); + }) + .catch((error) => { + toast(error.toString()); + }); +}; export default RuleCard; From 9cf3f2fd6333ea46ce6dcbd1a519fd8552cb4c91 Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Tue, 16 Jul 2024 20:28:00 +0530 Subject: [PATCH 138/336] a lot of things --- functions/onprem/orborus/orborus.go | 316 ++++++++++++++++++++++------ 1 file changed, 246 insertions(+), 70 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 391a1620..411571bb 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -2052,14 +2052,28 @@ func main() { log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err) } - err = removeFile(fileName) + err = disableRule(fileName) if err != nil { log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err) } - toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) - } else if incRequest.Type == "DISABLE_SIGMA_FOLDER" { + } else if incRequest.Type == "ENABLE_SIGMA_FILE" { + fileName := incRequest.ExecutionArgument + err := deployTenzirNode() + if err != nil{ + log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err) + } + + err = enableRule(fileName) + if err != nil { + log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err) + } + + toBeRemoved.Data = append(toBeRemoved.Data, incRequest) + + } else if incRequest.Type == "DISABLE_SIGMA_FOLDER" { err := deployTenzirNode() if err != nil{ @@ -2526,9 +2540,19 @@ func deployTenzirNode() error { return nil } - containerInfo, err := dockercli.ContainerInspect(ctx, containerName) - if err != nil { - if dockerclient.IsErrNotFound(err) { + containerInfo, err := dockercli.ContainerInspect(ctx, containerName) + if err != nil { + if dockerclient.IsErrNotFound(err) { + // Create network if it doesn't exist + networkName := "tenzir-network" + networkSubnet := "192.168.1.0/24" + networkGateway := "192.168.1.1" + + err = createNetworkIfNotExists(ctx, networkName, networkSubnet, networkGateway) + if err != nil { + log.Printf("[ERROR] Failed to create network: %s", err) + return err + } // Check if image exists _, _, err := dockercli.ImageInspectWithRaw(ctx, imageName) @@ -2589,30 +2613,6 @@ func deployTenzirNode() error { return nil } -func checkTenzirNode() error { - retries := 5 - retryInterval := 3 * time.Second - url := fmt.Sprintf("%s/api/v0/ping",tenzirUrl) - forwardMethod := "POST" - - client := http.Client{} - req, err := http.NewRequest(forwardMethod, url, nil) - if err != nil { - log.Printf("[ERROR] Failed to create HTTP request: %s", err) - return err - } - - for i := 0; i < retries; i++ { - resp, err := client.Do(req) - if err == nil && resp.StatusCode == http.StatusOK { - return nil - } - time.Sleep(retryInterval) - } - - return fmt.Errorf("tenzir node is not available") -} - func createAndStartTenzirNode(ctx context.Context, containerName, imageName string, containerStartOptions container.StartOptions) error { healthconfig := &container.HealthConfig{ Test: []string{"tenzir --connection-timeout=30s --connection-retry-delay=1s 'api /ping'"}, @@ -2628,23 +2628,34 @@ func createAndStartTenzirNode(ctx context.Context, containerName, imageName stri Entrypoint: []string{containerName}, } - hostConfig := &container.HostConfig{ - PortBindings: nat.PortMap{ - "5160/tcp": []nat.PortBinding{{HostPort: "5160"}}, - }, - Mounts: []mount.Mount{ - { - Type: mount.TypeVolume, - Source: containerName, - Target: "/var/lib/tenzir/", - }, - }, - VolumeDriver: "local", - } - _, err := dockercli.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName) - if err != nil { - return err - } + hostConfig := &container.HostConfig{ + PortBindings: nat.PortMap{ + "5160/tcp": []nat.PortBinding{{HostPort: "5160"}}, + }, + Mounts: []mount.Mount{ + { + Type: mount.TypeVolume, + Source: containerName, + Target: "/var/lib/tenzir/", + }, + }, + VolumeDriver: "local", + } + + networkingConfig := &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + "tenzir-network": { + IPAMConfig: &network.EndpointIPAMConfig{ + IPv4Address: "192.168.1.100", + }, + }, + }, + } + + _, err := dockercli.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName) + if err != nil { + return err + } err = dockercli.ContainerStart(ctx, containerName, containerStartOptions) if err != nil { @@ -2653,16 +2664,76 @@ func createAndStartTenzirNode(ctx context.Context, containerName, imageName stri } log.Printf("[INFO] Tenzir Node container started successfully") - log.Printf("[INFO] Waiting for Tenzir to become available ...") - err = checkTenzirNode() - if err != nil { - return err - } - log.Printf("[INFO] Successfully deployed Tenzir Node !") + log.Printf("[INFO] Waiting for Tenzir to become available ...") + err = checkTenzirNode() + if err != nil { + return err + } + log.Printf("[INFO] Successfully deployed Tenzir Node!") return nil } +func createNetworkIfNotExists(ctx context.Context, networkName, subnet, gateway string) error { + networks, err := dockercli.NetworkList(ctx, types.NetworkListOptions{}) + if err != nil { + return err + } + + for _, network := range networks { + if network.Name == networkName { + // Network exists + return nil + } + } + + ipamConfig := &network.IPAM{ + Config: []network.IPAMConfig{ + { + Subnet: subnet, + Gateway: gateway, + }, + }, + } + + networkCreate := types.NetworkCreate{ + CheckDuplicate: true, + Driver: "bridge", + IPAM: ipamConfig, + } + + _, err = dockercli.NetworkCreate(ctx, networkName, networkCreate) + if err != nil { + return err + } + + return nil +} + +func checkTenzirNode() error { + retries := 5 + retryInterval := 3 * time.Second + url := fmt.Sprintf("%s/api/v0/ping",tenzirUrl) + forwardMethod := "POST" + + client := http.Client{} + req, err := http.NewRequest(forwardMethod, url, nil) + if err != nil { + log.Printf("[ERROR] Failed to create HTTP request: %s", err) + return err + } + + for i := 0; i < retries; i++ { + resp, err := client.Do(req) + if err == nil && resp.StatusCode == http.StatusOK { + return nil + } + time.Sleep(retryInterval) + } + + return fmt.Errorf("tenzir node is not available") +} + func createPipeline(command, identifier string) (string, error) { toBeDeleted := false @@ -2702,8 +2773,6 @@ func createPipeline(command, identifier string) (string, error) { //command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | sigma /var/lib/tenzir/rule.yaml" //command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | import" - //command = "export | to https://expert-acorn-v6vg4j4j5w7q2wg6g-5001.app.github.dev/api/v1/hooks/webhook_623eab3f-0af4-4d40-abb9-699d9a493411" - log.Printf("[HARI] this is the command %s", command) requestBody := map[string]interface{}{ "definition": command, @@ -2931,8 +3000,8 @@ func searchPipeline(identifier string) (string, error) { return "", errors.New("no existing pipeline found with name") } -func handleFileCategoryChange() error{ - apiEndpoint := baseUrl+"/api/v1/files/namespaces/sigma" +func handleFileCategoryChange() error { + apiEndpoint := baseUrl + "/api/v1/files/namespaces/sigma" req, err := http.NewRequest("GET", apiEndpoint, nil) if err != nil { return err @@ -2943,12 +3012,12 @@ func handleFileCategoryChange() error{ client := &http.Client{} resp, err := client.Do(req) if err != nil { - return err + return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return err + return fmt.Errorf("received non-200 response: %s", resp.Status) } out, err := os.Create("files.zip") @@ -2961,10 +3030,10 @@ func handleFileCategoryChange() error{ _, err = io.Copy(out, resp.Body) if err != nil { - return err + return err } - fmt.Println("ZIP file downloaded successfully.") + log.Println("ZIP file downloaded successfully.") err = extractZIP("files.zip", "sigma_rules") if err != nil { @@ -2978,7 +3047,45 @@ func handleFileCategoryChange() error{ return err } - fmt.Println("Files copied to container successfully.") + log.Println("Files copied to container successfully.") + + checkDisabledDirCmd := exec.Command("docker", "exec", "tenzir-node", "sh", "-c", "test -d /var/lib/tenzir/disabled_rules") + if err := checkDisabledDirCmd.Run(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { + // Directory does not exist, nothing to do + log.Println("[DEBUG] /var/lib/tenzir/disabled_rules does not exist.") + return nil + } + + return fmt.Errorf("error checking disabled rules directory: %v", err) + } + + // List files in /var/lib/tenzir/disabled_rules + listFilesCmd := exec.Command("docker", "exec", "tenzir-node", "sh", "-c", "ls /var/lib/tenzir/disabled_rules") + output, err := listFilesCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error listing files in disabled rules directory: %v, output: %s", err, output) + } + + files := strings.Split(strings.TrimSpace(string(output)), "\n") + for _, file := range files { + disabledFilePath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", file) + checkFileCmd := exec.Command("docker", "exec", "tenzir-node", "sh", "-c", fmt.Sprintf("test -f %s", disabledFilePath)) + if err := checkFileCmd.Run(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { + log.Printf("[ERROR] File does not exist: %s, moving on.\n", disabledFilePath) + continue + } + return fmt.Errorf("error checking file: %v", err) + } + + deleteFileCmd := exec.Command("docker", "exec", "-u", "root", "tenzir-node", "sh", "-c", fmt.Sprintf("rm -f %s", disabledFilePath)) + if err := deleteFileCmd.Run(); err != nil { + return fmt.Errorf("error deleting file: %v", err) + } + log.Printf("[INFO] Deleted file: %s\n", disabledFilePath) + } + return nil } @@ -3025,7 +3132,6 @@ func extractFile(f *zip.File, destDir string) error { func copyToTenzir(srcPath, destPath string) error { containerName := "tenzir-node" - // Check if the sigma_rules directory exists in the container checkCmd := exec.Command("docker", "exec", containerName, "test", "-d", destPath) if err := checkCmd.Run(); err == nil { rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-rf", destPath) @@ -3034,7 +3140,6 @@ func copyToTenzir(srcPath, destPath string) error { } } - // Copy the new directory to the container cpCmd := exec.Command("docker", "cp", srcPath, fmt.Sprintf("%s:%s", containerName, destPath)) var out bytes.Buffer cpCmd.Stdout = &out @@ -3049,10 +3154,19 @@ func copyToTenzir(srcPath, destPath string) error { } func removeAllFiles() error { - containerName := "tenzir-node" - sigmaPath := "/var/lib/tenzir/sigma_rules/*" + containerName := "tenzir-node" + sigmaPath := "/var/lib/tenzir/sigma_rules/*" - cmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("rm -rf %s", sigmaPath)) + checkCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("ls %s", sigmaPath)) + checkOutput, checkErr := checkCmd.CombinedOutput() + if checkErr != nil { + if strings.Contains(string(checkOutput), "No such file or directory") { + return nil // nothing to delete + } + return fmt.Errorf("error checking files: %v, output: %s", checkErr, checkOutput) + } + + cmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("rm -rf %s", sigmaPath)) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error removing files: %v, output: %s", err, output) @@ -3064,16 +3178,21 @@ func removeFile(fileName string) error { containerName := "tenzir-node" srcPath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", fileName) - checkSrcCmd := exec.Command("docker", "exec", containerName, "test", "-f", srcPath) + checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath)) if err := checkSrcCmd.Run(); err != nil { - return fmt.Errorf("source file does not exist: %v", err) + // If the file does not exist, simply return nil + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { + log.Printf("[ERROR] No such file: %s, nothing to delete\n", srcPath) + return nil + } + return fmt.Errorf("error checking source file: %v", err) } return removePath(containerName, srcPath) } func removePath(containerName, path string) error { - rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-rf", path) + rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("rm -rf %s", path)) output, err := rmCmd.CombinedOutput() if err != nil { return fmt.Errorf("error removing path: %v, output: %s", err, output) @@ -3127,6 +3246,63 @@ func sendTenzirHealthStatus() error { return nil } +func disableRule(fileName string) error { + containerName := "tenzir-node" + srcPath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", fileName) + destDir := "/var/lib/tenzir/disabled_rules" + destPath := fmt.Sprintf("%s/%s", destDir, fileName) + + checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath)) + if err := checkSrcCmd.Run(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { + fmt.Printf("File does not exist: %s\n", srcPath) + return nil // Nothing to disable + } + return fmt.Errorf("error checking source file: %v", err) + } + + checkDestDirCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mkdir -p %s", destDir)) + if err := checkDestDirCmd.Run(); err != nil { + return fmt.Errorf("error ensuring destination directory exists: %v", err) + } + + moveCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mv %s %s", srcPath, destPath)) + if err := moveCmd.Run(); err != nil { + return fmt.Errorf("error moving file: %v", err) + } + + fmt.Printf("File %s moved to %s successfully.\n", fileName, destDir) + return nil +} + +func enableRule(fileName string) error { + containerName := "tenzir-node" + srcPath := fmt.Sprintf("/var/lib/tenzir/disabled_rules/%s", fileName) + destDir := "/var/lib/tenzir/sigma_rules" + destPath := fmt.Sprintf("%s/%s", destDir, fileName) + + checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath)) + if err := checkSrcCmd.Run(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { + fmt.Printf("File does not exist: %s\n", srcPath) + return nil // Nothing to enable + } + return fmt.Errorf("error checking source file: %v", err) + } + + checkDestDirCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mkdir -p %s", destDir)) + if err := checkDestDirCmd.Run(); err != nil { + return fmt.Errorf("error ensuring destination directory exists: %v", err) + } + moveCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mv %s %s", srcPath, destPath)) + if err := moveCmd.Run(); err != nil { + return fmt.Errorf("error moving file: %v", err) + } + + fmt.Printf("File %s moved to %s successfully.\n", fileName, destDir) + return nil +} + // Is this ok to do with Docker? idk :) func getRunningWorkers(ctx context.Context, workerTimeout int) int { //log.Printf("[DEBUG] Getting running workers with API version %s", dockerApiVersion) From b33c45543ac1fa4454514f45a5b014997a390319 Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Mon, 22 Jul 2024 11:35:55 +0530 Subject: [PATCH 139/336] pointing to the correct url --- backend/go-app/main.go | 2 +- frontend/src/views/DetectionDashboard.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 8f247fc8..860a16ef 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5188,7 +5188,7 @@ func initHandlers() { // PS: For cloud, this has to use cloud storage. // https://developer.box.com/reference/get-files-id-content/ r.HandleFunc("/api/v1/files/download_remote", shuffle.HandleDownloadRemoteFiles).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v2/files/download_remote", shuffle.HandleDownloadRemoteFiles2).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/files/download_remote_enhanced", shuffle.HandleEnhancedDownloadRemoteFiles).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/files/namespaces/{namespace}", shuffle.HandleGetFileNamespace).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}/content", shuffle.HandleGetFileContent).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/create", shuffle.HandleCreateFile).Methods("POST", "OPTIONS") diff --git a/frontend/src/views/DetectionDashboard.jsx b/frontend/src/views/DetectionDashboard.jsx index 4b2b1b06..3d4eb871 100644 --- a/frontend/src/views/DetectionDashboard.jsx +++ b/frontend/src/views/DetectionDashboard.jsx @@ -110,7 +110,7 @@ const DetectionDashBoard = (props) => { }; toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`); - fetch(`${globalUrl}/api/v2/files/download_remote`, { + fetch(`${globalUrl}/api/v1/files/download_remote_enhanced`, { method: "POST", mode: "cors", headers: { From 2b90a1b9afb55f5fff3b577c4fae604e7ea72b3c Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 22 Jul 2024 16:08:46 +0530 Subject: [PATCH 140/336] Added the error message on the field about the variable --- frontend/src/components/ParsedAction.jsx | 119 +++++++---------------- 1 file changed, 35 insertions(+), 84 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index c4cff82f..208b822e 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -184,15 +184,8 @@ const ParsedAction = (props) => { const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []); - const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); - const [paramValues, setParamValues] = React.useState( - selectedAction?.parameters?.map((param) => { - return { - name: param.name, - value: param.value, - } - }) - ); + const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); + const [paramUpdate, setParamUpdate] = React.useState(""); const [actionlist, setActionlist] = React.useState([]); const [jsonList, setJsonList] = React.useState([]); const [showDropdown, setShowDropdown] = React.useState(false); @@ -207,16 +200,6 @@ const ParsedAction = (props) => { } }, [expansionModalOpen]) -// useEffect(() => { -// setParamValues(selectedAction.parameters?.map((param) => { -// return { -// name: param.name, -// value: param.value, -// } -// })) -// },[ -// selectedAction, selectedApp,setNewSelectedAction, workflow, -// ]) useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { @@ -417,8 +400,8 @@ const ParsedAction = (props) => { } // Only set selected action parameters if they have changed - if (selectedAction.parameters && selectedAction.parameters.length > 0) { - setSelectedActionParameters(selectedAction.parameters); + if (selectedAction?.parameters && selectedAction?.parameters.length > 0) { + setSelectedActionParameters(selectedAction?.parameters); } // Only set selected variable parameter if it is null or undefined @@ -433,6 +416,7 @@ const ParsedAction = (props) => { useEffect(() => { const newActionList = []; + const parentActionList = []; // Process workflowExecutions if (workflowExecutions.length > 0) { @@ -560,89 +544,54 @@ const ParsedAction = (props) => { autocomplete: parentNode.label.split(" ").join("_"), example: exampleData, }); - } - } - } - } - // Update the actionlist state - setActionlist(newActionList); - }, [workflow.execution_variables, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents]); - - - const memoizedParam = useMemo(() => { - let appActions = []; - if (getParents) { - const parents = getParents(selectedAction); - if (parents.length > 1) { - const labels = []; - for (let parentNode of parents) { - if (parentNode.label !== "Execution Argument" && !labels.includes(parentNode.label)) { - labels.push(parentNode.label); - let exampleData = parentNode.example ?? ""; - if (!exampleData && workflowExecutions.length > 0) { - for (let exec of workflowExecutions) { - const foundResult = exec.results?.find(result => result.action.id === parentNode.id); - if (foundResult) { - const valid = validateJson(foundResult.result); - if (valid.valid && valid.result.success !== false) { - exampleData = valid.result; - break; - } - } - } - } - appActions.push({ + parentActionList.push({ type: "action", id: parentNode.id, name: parentNode.label, autocomplete: parentNode.label.split(" ").join("_"), example: exampleData, }); + + } } } } - let newParameters = selectedAction.parameters?.map((param) => { + let newParameters = selectedAction?.parameters?.map((param) => { let paramvalue = param.value; + let errorVars = []; if(paramvalue.includes("$")){ let actions = workflow.actions?.map((action) => { return "$"+action.label.toLowerCase(); }) - if(actionlist.length > 0){ - let appParentActions = appActions?.map(action => "$" + action.name.toLowerCase()); + if(newActionList.length > 0){ + let appParentActions = parentActionList?.map(action => "$" + action.name.toLowerCase()); let notPresentAction = actions?.filter((action) => !appParentActions?.includes(action)) - console.log("ACTIONS: ", actions) - console.log("APP ACTIONS: ", appParentActions) - console.log("NOT PRESENT: ", notPresentAction) notPresentAction?.forEach((action) => { - console.log("Not included Action: ", action) if(paramvalue.includes(action)){ - + errorVars.push(action); // paramvalue = paramvalue.replace(action, "") // paramvalue = paramvalue.replace(/^\s*[\r\n]/gm, ""); } }) } } - console.log("After removing param value: ", paramvalue) - return {...param, value: paramvalue} - }); - selectedAction.parameters = newParameters; - setSelectedActionParameters(newParameters); - setSelectedAction(selectedAction); - return newParameters; - },[actionlist,selectedAction,workflow.actions,workflow,selectedApp,setNewSelectedAction]) - useEffect(() => { - setParamValues(memoizedParam?.map((param) => { - return { - name: param.name, - value: param.value, + let message = ""; + if(errorVars.length > 0){ + if(errorVars.length === 1){ + message = errorVars[0] + " is not accessible in this action"; + }else{ + message = errorVars.join(", ") + " are not accessible in this action"; + } } - })) - },[memoizedParam]) + return {...param, value: paramvalue, error: message} + }); + setSelectedActionParameters(newParameters); + setActionlist(newActionList); + }, [workflow.execution_variables,paramUpdate, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents,setNewSelectedAction]); useEffect(() => { selectedNameChange(appActionName) @@ -653,13 +602,14 @@ const ParsedAction = (props) => { },[appActionName,delay]) const handleParamChange = (event, count,data) => { - const newParams = [...paramValues]; + const newParams = [...selectedActionParameters]; newParams.map((param) => { if (param.name === data.name) { param.value = event.target.value; } }) - setParamValues(newParams); + setSelectedActionParameters(newParams); + setParamUpdate(event.target.value); changeActionParameter(event, count, data) } const calculateHelpertext = (input_data) => { @@ -1248,7 +1198,7 @@ const ParsedAction = (props) => { } // FIXME: Issue #40 - selectedActionParameters not reset - if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { + if (Object.getOwnPropertyNames(selectedAction)?.length > 0 && selectedActionParameters?.length > 0) { var wrapperapp = { "id": "", "name": "noapp", @@ -2759,7 +2709,7 @@ const ParsedAction = (props) => { {suggestionInfo()} - {selectedActionParameters.map((data, count) => { + {selectedActionParameters?.map((data, count) => { if (data.variant === "") { data.variant = "STATIC_VALUE"; } @@ -3214,10 +3164,12 @@ const ParsedAction = (props) => { color="primary" // defaultValue={data.value} value={ - paramValues.find((param) => param.name === data.name) !== undefined - ? paramValues.find((param) => param.name === data.name).value - : "" + data?.value } + error={ + data?.error?.length > 0 ? true : false + } + helperText={data?.error?.length > 0 ? data.error : returnHelperText(data.name, data.value)} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', @@ -3240,7 +3192,6 @@ const ParsedAction = (props) => { // changeActionParameter(event, count, data); handleParamChange(event, count, data) }} - helperText={returnHelperText(data.name, data.value)} onBlur={(event) => { baseHelperText = calculateHelpertext(event.target.value) if (setLastSaved !== undefined) { From b26950546fe479d17d332ef0d2bd2b11c42f0a4f Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 22 Jul 2024 19:21:23 +0530 Subject: [PATCH 141/336] Fixed the app crash Issue --- frontend/src/components/ParsedAction.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 034859e0..0c0398dd 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -2696,7 +2696,7 @@ const ParsedAction = (props) => { // selectedAction.selectedAuthentication = e.target.value // selectedAction.authentication_id = e.target.value.id if ( - !selectedAction.auth_not_required && + // !selectedAction.auth_not_required && selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== From e22f6e470908c0025efa30a7ba4ab9b194dea7c1 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 24 Jul 2024 13:17:42 +0530 Subject: [PATCH 142/336] Added error in Auth field for unescaped dollar --- frontend/src/components/ParsedAction.jsx | 37 +++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 208b822e..31378963 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -566,10 +566,11 @@ const ParsedAction = (props) => { let actions = workflow.actions?.map((action) => { return "$"+action.label.toLowerCase(); }) - if(newActionList.length > 0){ + if(newActionList?.length > 0){ let appParentActions = parentActionList?.map(action => "$" + action.name.toLowerCase()); let notPresentAction = actions?.filter((action) => !appParentActions?.includes(action)) notPresentAction?.forEach((action) => { + action = action.replace(" ", "_"); if(paramvalue.includes(action)){ errorVars.push(action); // paramvalue = paramvalue.replace(action, "") @@ -582,9 +583,20 @@ const ParsedAction = (props) => { let message = ""; if(errorVars.length > 0){ if(errorVars.length === 1){ - message = errorVars[0] + " is not accessible in this action"; + message = errorVars[0] + " is not accessible in this action."; }else{ - message = errorVars.join(", ") + " are not accessible in this action"; + message = errorVars.join(", ") + " are not accessible in this action."; + } + } + + if (param?.configuration) { + let regex = /(^|[^\\])\$/; + if (regex.test(paramvalue)) { + if(message.length > 0){ + message += "\nUse \"\\$\" instead of \"$\"."; + }else{ + message = "Use \"\\$\" instead of \"$\"."; + } } } return {...param, value: paramvalue, error: message} @@ -1137,6 +1149,15 @@ const ParsedAction = (props) => { return helperText } + const errorHelperText = (name, value, error) => { + return ( +
    + {error} +
    + ); + } + + const analyzeFields = () => { if (selectedAction === undefined || selectedAction === null) { @@ -3169,7 +3190,7 @@ const ParsedAction = (props) => { error={ data?.error?.length > 0 ? true : false } - helperText={data?.error?.length > 0 ? data.error : returnHelperText(data.name, data.value)} + helperText={data?.error?.length > 0 ? errorHelperText(data?.name,data?.value,data?.error) : returnHelperText(data.name, data.value)} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', @@ -3946,6 +3967,14 @@ const ParsedAction = (props) => { - Description: {description} + { + data?.configuration ? + ( + + - Use "\$" instead of "$" + + ) : null + } ); From 56d96b331a69fac6890959086110542cbe5077c6 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Jul 2024 17:02:29 +0200 Subject: [PATCH 143/336] Force build new nginx without confd --- backend/app_sdk/app_base.py | 26 ++++++++++++++++++++++---- frontend/Dockerfile | 23 ++++++++++++++--------- frontend/entrypoint.sh | 5 +++-- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 415f1e94..a751319b 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -632,6 +632,7 @@ class AppBase: # I wonder if this actually works url = "%s%s" % (self.base_url, stream_path) + self.logger.info(f"[DEBUG][%s] Sending result to %s" % (self.current_execution_id, url)) try: log_contents = "disabled: add env SHUFFLE_LOGS_DISABLED=true to Orborus to re-enable logs for apps. Can not be enabled natively in Cloud except in Hybrid mode." @@ -656,6 +657,13 @@ class AppBase: except Exception as e: pass + # Check if type of headers is right + if not isinstance(headers, dict): + headers = {} + + if not "User-Agent" in headers: + headers["User-Agent"] = "Shuffle App" + try: finished = False ret = {} @@ -684,23 +692,23 @@ class AppBase: headerauth = headers["Authorization"] try: - self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}'. Execution ID: %d, Authorization: %d, Header Auth: %d" % (len(action_result["execution_id"]), len(action_result["authorization"]), len(headerauth))) except Exception as e: self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}' (no detail)") - pass time.sleep(sleeptime) # Proxyerrror except requests.exceptions.ProxyError as e: + self.logger.info(f"[ERROR][{self.current_execution_id}] Proxy error in send_result for url '{url}': {e}") + self.proxy_config = {} continue except requests.exceptions.RequestException as e: - time.sleep(sleeptime) + self.logger.info(f"[ERROR][{self.current_execution_id}] Request error in send_result for url '{url}': {e}") # Check if we have a read timeout. If we do, exit as we most likely sent the result without getting a good result if "Read timed out" in str(e): @@ -713,24 +721,34 @@ class AppBase: finished = True break + time.sleep(sleeptime) + #time.sleep(5) continue except TimeoutError as e: + self.logger.info(f"[ERROR][{self.current_execution_id}] Timeout error in send_result for url '{url}': {e}") + time.sleep(sleeptime) #time.sleep(5) continue except requests.exceptions.ConnectionError as e: + self.logger.info(f"[ERROR][{self.current_execution_id}] Connection error in send_result for url '{url}': {e}") + time.sleep(sleeptime) #time.sleep(5) continue except http.client.RemoteDisconnected as e: + self.logger.info(f"[ERROR][{self.current_execution_id}] RemoteDisconnected error in send_result for url '{url}': {e}") + time.sleep(sleeptime) #time.sleep(5) continue except urllib3.exceptions.ProtocolError as e: + self.logger.info(f"[ERROR][{self.current_execution_id}] ProtocolError error in send_result for url '{url}': {e}") + time.sleep(0.1) #time.sleep(5) @@ -3668,7 +3686,7 @@ class AppBase: #self.logger.info() if not multiexecution: - self.logger.info("NOT MULTI EXEC") + #self.logger.info("NOT MULTI EXEC") # Runs a single iteration here new_params = self.validate_unique_fields(params) if isinstance(new_params, list) and len(new_params) == 1: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index e9d6d683..32b494c6 100755 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -25,29 +25,34 @@ COPY ./*.json /usr/src/app/ RUN npm run build --loglevel verbose 2>&1 # Production environment -FROM nginx:1.21.5 +FROM nginx:1.26.0 RUN mkdir -p /usr/share/nginx/html/build RUN mkdir -p /usr/share/nginx/html/css RUN mkdir -p /usr/share/nginx/html/js RUN mkdir -p /usr/share/nginx/html/img -COPY --from=builder /usr/src/app/build /usr/share/nginx/html -#Localhost certificate challenge: Y#XwrJ#DoZGz2w6x +# Localhost certificate challenge: Y#XwrJ#DoZGz2w6x +# Cert challenge doesn't matter to be here or not, as ALL production setups should be using their own certificates + reverse proxy: https://shuffler.io/docs/configuration#using-the-nginx-reverse-proxy-for-tls/ssl +COPY --from=builder /usr/src/app/build /usr/share/nginx/html COPY --from=builder /usr/src/app/certs/fullchain.pem /etc/nginx/fullchain.cert.pem COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem # install CONFD -ENV CONFD_VERSION 0.16.0 RUN apt-get update && apt-get install -y curl && apt-get clean -RUN curl -sSL https://github.com/kelseyhightower/confd/releases/download/v${CONFD_VERSION}/confd-${CONFD_VERSION}-linux-amd64 -o /usr/local/bin/confd && \ - chmod +x /usr/local/bin/confd -COPY ./confd /etc/confd +COPY ./confd/templates/nginx.conf /etc/nginx/nginx.conf +## OLD CONFD THINGS (not compatible with arm) +#ENV CONFD_VERSION 0.16.0 +#RUN curl -sSL https://github.com/kelseyhightower/confd/releases/download/v${CONFD_VERSION}/confd-${CONFD_VERSION}-linux-amd64 -o /usr/local/bin/confd && \ +# chmod +x /usr/local/bin/confd +#COPY ./confd /etc/confd # rewrite command & entrypoint with ours -COPY ./entrypoint.sh / -ENTRYPOINT [ "/entrypoint.sh" ] +#COPY ./entrypoint.sh / +#ENTRYPOINT [ "/entrypoint.sh" ] + + CMD ["nginx", "-g", "daemon off;"] EXPOSE 80 diff --git a/frontend/entrypoint.sh b/frontend/entrypoint.sh index 09be2558..f5542b05 100755 --- a/frontend/entrypoint.sh +++ b/frontend/entrypoint.sh @@ -1,7 +1,8 @@ #!/bin/bash -# generate configs -/usr/local/bin/confd -backend="env" -confdir="/etc/confd" -onetime +# generate configs - is this necessary? +# Removing confd if possible +#/usr/local/bin/confd -backend="env" -confdir="/etc/confd" -onetime # run main command exec "$@" From 94a2f4ce876345f6ae8185d8eb5dbd56bca9d119 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Jul 2024 17:35:33 +0200 Subject: [PATCH 144/336] Started using pure nginx conf syntax --- frontend/Dockerfile | 4 +++- frontend/confd/templates/nginx.conf | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 32b494c6..6602208d 100755 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,6 +1,8 @@ # Build environment FROM node:21 as builder +ENV NODE_OPTIONS="--max-old-space-size=4096" + RUN mkdir /usr/src/app WORKDIR /usr/src/app ENV PATH /usr/src/app/node_modules/.bin:$PATH @@ -52,7 +54,7 @@ COPY ./confd/templates/nginx.conf /etc/nginx/nginx.conf #COPY ./entrypoint.sh / #ENTRYPOINT [ "/entrypoint.sh" ] - +RUN export BACKEND_HOSTNAME=shuffle-backend CMD ["nginx", "-g", "daemon off;"] EXPOSE 80 diff --git a/frontend/confd/templates/nginx.conf b/frontend/confd/templates/nginx.conf index 3bb02c27..a119d69d 100755 --- a/frontend/confd/templates/nginx.conf +++ b/frontend/confd/templates/nginx.conf @@ -71,7 +71,8 @@ http { } location ~ /api/v(1|2) { - proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001; + #proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001; + proxy_pass http://$BACKEND_HOSTNAME:5001; proxy_buffering off; proxy_http_version 1.1; @@ -113,7 +114,7 @@ http { # Get the hostname from environment here? location ~ /api/v(1|2) { - proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001; + proxy_pass http://$BACKEND_HOSTNAME:5001; proxy_buffering off; proxy_http_version 1.1; From 4e7d03debbf83fa828ee1cfa919a848ef700eafd Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Jul 2024 18:25:43 +0200 Subject: [PATCH 145/336] Another try with entrypoint rewrites --- frontend/Dockerfile | 8 ++++---- frontend/confd/templates/nginx.conf | 6 +++--- frontend/entrypoint.sh | 8 +++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 6602208d..fad87105 100755 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -43,7 +43,7 @@ COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem # install CONFD RUN apt-get update && apt-get install -y curl && apt-get clean -COPY ./confd/templates/nginx.conf /etc/nginx/nginx.conf +COPY ./confd/templates/nginx.conf /etc/nginx/nginx.conf.tmpl ## OLD CONFD THINGS (not compatible with arm) #ENV CONFD_VERSION 0.16.0 @@ -51,10 +51,10 @@ COPY ./confd/templates/nginx.conf /etc/nginx/nginx.conf # chmod +x /usr/local/bin/confd #COPY ./confd /etc/confd # rewrite command & entrypoint with ours -#COPY ./entrypoint.sh / -#ENTRYPOINT [ "/entrypoint.sh" ] -RUN export BACKEND_HOSTNAME=shuffle-backend +COPY ./entrypoint.sh / +ENV BACKEND_HOSTNAME="shuffle-backend" +ENTRYPOINT [ "/entrypoint.sh" ] CMD ["nginx", "-g", "daemon off;"] EXPOSE 80 diff --git a/frontend/confd/templates/nginx.conf b/frontend/confd/templates/nginx.conf index a119d69d..2c9df91e 100755 --- a/frontend/confd/templates/nginx.conf +++ b/frontend/confd/templates/nginx.conf @@ -71,8 +71,7 @@ http { } location ~ /api/v(1|2) { - #proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001; - proxy_pass http://$BACKEND_HOSTNAME:5001; + proxy_pass http://${BACKEND_HOSTNAME}:5001; proxy_buffering off; proxy_http_version 1.1; @@ -114,7 +113,8 @@ http { # Get the hostname from environment here? location ~ /api/v(1|2) { - proxy_pass http://$BACKEND_HOSTNAME:5001; + proxy_pass http://${BACKEND_HOSTNAME}:5001; + proxy_buffering off; proxy_http_version 1.1; diff --git a/frontend/entrypoint.sh b/frontend/entrypoint.sh index f5542b05..af1d0a43 100755 --- a/frontend/entrypoint.sh +++ b/frontend/entrypoint.sh @@ -1,8 +1,6 @@ -#!/bin/bash +#!/usr/bin/env sh +set -eu -# generate configs - is this necessary? -# Removing confd if possible -#/usr/local/bin/confd -backend="env" -confdir="/etc/confd" -onetime +envsubst '${BACKEND_HOSTNAME}' < /etc/nginx/nginx.conf.tmpl > /etc/nginx/nginx.conf -# run main command exec "$@" From d5274e9da1d69f350ce4ef63655adb69daff5b45 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Jul 2024 19:46:53 +0200 Subject: [PATCH 146/336] Optimized .env and docker-compose to work better with opensearch setups --- .env | 5 +++-- docker-compose.yml | 27 ++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.env b/.env index 298128cc..417e860f 100755 --- a/.env +++ b/.env @@ -97,14 +97,15 @@ SHUFFLE_MAX_EXECUTION_DEPTH= DATASTORE_EMULATOR_HOST=shuffle-database:8000 #SHUFFLE_OPENSEARCH_URL=http://shuffle-opensearch:9200 SHUFFLE_OPENSEARCH_URL=https://shuffle-opensearch:9200 -SHUFFLE_OPENSEARCH_USERNAME="admin" -SHUFFLE_OPENSEARCH_PASSWORD="StrongShufflePassword321!" SHUFFLE_OPENSEARCH_CERTIFICATE_FILE= SHUFFLE_OPENSEARCH_APIKEY= SHUFFLE_OPENSEARCH_CLOUDID= SHUFFLE_OPENSEARCH_PROXY= SHUFFLE_OPENSEARCH_INDEX_PREFIX= SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true +SHUFFLE_OPENSEARCH_USERNAME="admin" +SHUFFLE_OPENSEARCH_PASSWORD="StrongShufflePassword321!" # In use for the first time setup of OpenSearch + backend of Shuffle +OPENSEARCH_INITIAL_ADMIN_PASSWORD="StrongShufflePassword321!" # In use for the first time setup of OpenSearch #Tenzir related SHUFFLE_TENZIR_URL= diff --git a/docker-compose.yml b/docker-compose.yml index 2f096df2..fcc7c0cb 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: frontend: - image: ghcr.io/shuffle/shuffle-frontend:latest + image: ghcr.io/shuffle/shuffle-frontend:nightly container_name: shuffle-frontend hostname: shuffle-frontend ports: @@ -15,7 +15,7 @@ services: depends_on: - backend backend: - image: ghcr.io/shuffle/shuffle-backend:latest + image: ghcr.io/shuffle/shuffle-backend:nightly container_name: shuffle-backend hostname: ${BACKEND_HOSTNAME} # Here for debugging: @@ -34,7 +34,7 @@ services: - SHUFFLE_FILE_LOCATION=/shuffle-files restart: unless-stopped orborus: - image: ghcr.io/shuffle/shuffle-orborus:latest + image: ghcr.io/shuffle/shuffle-orborus:nightly container_name: shuffle-orborus hostname: shuffle-orborus networks: @@ -48,9 +48,6 @@ services: - ENVIRONMENT_NAME=${ENVIRONMENT_NAME} - BASE_URL=http://${OUTER_HOSTNAME}:5001 - DOCKER_API_VERSION=1.40 - - SHUFFLE_BASE_IMAGE_NAME=${SHUFFLE_BASE_IMAGE_NAME} - - SHUFFLE_BASE_IMAGE_REGISTRY=${SHUFFLE_BASE_IMAGE_REGISTRY} - - SHUFFLE_BASE_IMAGE_TAG_SUFFIX=${SHUFFLE_BASE_IMAGE_TAG_SUFFIX} - HTTP_PROXY=${HTTP_PROXY} - HTTPS_PROXY=${HTTPS_PROXY} - SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY} @@ -74,7 +71,6 @@ services: - node.name=shuffle-opensearch - node.store.allow_mmap=false - discovery.seed_hosts=shuffle-opensearch - - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${SHUFFLE_OPENSEARCH_PASSWORD} ulimits: memlock: soft: -1 @@ -83,7 +79,7 @@ services: soft: 65536 hard: 65536 volumes: - - ${DB_LOCATION}:/usr/share/opensearch/data:z + - shuffle-database:/usr/share/opensearch/data:z ports: - 9200:9200 networks: @@ -129,13 +125,18 @@ services: # networks: # - shuffle # + +volumes: + shuffle-database: + driver: local + driver_opts: + type: none + device: ${DB_LOCATION} + o: bind + networks: shuffle: driver: bridge - - # uncomment to set MTU for swarm mode. - # MTU should be whatever is your host's preferred MTU is. - # Refer to this doc to figure out what your host's MTU is: - # https://shuffler.io/docs/troubleshooting#TLS_timeout_error/Timeout_Errors/EOF_Errors # driver_opts: # com.docker.network.driver.mtu: 1460 + # uncomment to set MTU for swarm mode. MTU should be whatever is your host's preferred MTU is: https://shuffler.io/docs/troubleshooting#TLS_timeout_error/Timeout_Errors/EOF_Errors From 2fe2772e3bd2f1906e16446c970a04ad9d7fbe9f Mon Sep 17 00:00:00 2001 From: satti-hari-krishna-reddy Date: Mon, 29 Jul 2024 14:13:24 +0530 Subject: [PATCH 147/336] track the sigma rules --- backend/go-app/main.go | 47 +++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 860a16ef..23486930 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -2122,6 +2122,9 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { if err == nil { resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId))) + + // Track Sigma rules + trackSigmaRules(ctx, pipeline.OrgId, jsonList) return } @@ -2130,23 +2133,39 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { } func parseConcatenatedJSONLogs(logs string) ([]map[string]interface{}, error) { - var jsonList []map[string]interface{} - decoder := json.NewDecoder(strings.NewReader(logs)) + var jsonList []map[string]interface{} + decoder := json.NewDecoder(strings.NewReader(logs)) - for decoder.More() { - var jsonObject map[string]interface{} - if err := decoder.Decode(&jsonObject); err != nil { - log.Printf("[WARNING] JSON decoding error: %s. Skipping this object.", err) - continue - } - jsonList = append(jsonList, jsonObject) - } + for decoder.More() { + var jsonObject map[string]interface{} + if err := decoder.Decode(&jsonObject); err != nil { + log.Printf("[WARNING] JSON decoding error: %s. Skipping this object.", err) + continue + } + jsonList = append(jsonList, jsonObject) + } - if err := decoder.Decode(&struct{}{}); err != io.EOF { - return nil, fmt.Errorf("error after decoding all JSON objects: %v", err) - } + if err := decoder.Decode(&struct{}{}); err != io.EOF { + return nil, fmt.Errorf("error after decoding all JSON objects: %v", err) + } - return jsonList, nil + return jsonList, nil +} + +func trackSigmaRules(ctx context.Context, orgId string, jsonList []map[string]interface{}) { + ruleCount := make(map[string]int) + for _, logEntry := range jsonList { + if rule, ok := logEntry["rule"].(map[string]interface{}); ok { + if ruleName, ok := rule["title"].(string); ok { + ruleCount[ruleName]++ + } + } + } + + for ruleName, count := range ruleCount { + shuffle.IncrementCache(ctx, orgId, ruleName, count) + log.Printf("[INFO] Rule %s incremented by %d", ruleName, count) + } } func handleTenzirHealthUpdate(resp http.ResponseWriter, request *http.Request) { From b719d4d7f08e65b5c7611f1de1819702163e4f14 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 30 Jul 2024 16:15:29 +0530 Subject: [PATCH 148/336] Added UIbox tooltip --- frontend/src/components/ParsedAction.jsx | 163 +++++++++++++++++++---- 1 file changed, 136 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 034859e0..acc5d517 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -46,7 +46,8 @@ import { CircularProgress, Switch, Collapse, - Autocomplete + Autocomplete, + Box } from "@mui/material"; import { @@ -182,6 +183,7 @@ const ParsedAction = (props) => { const [prevActionName, setPrevActionName] = React.useState(selectedAction.label); const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); + const [hiddenParameters, setHiddenParameters] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []); const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); @@ -199,6 +201,7 @@ const ParsedAction = (props) => { const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); const [showAutocomplete, setShowAutocomplete] = React.useState(false); const [menuPosition, setMenuPosition] = useState(null); + const [uiBox, setUiBox] = useState(null); const isIntegration = selectedAction.app_id === "integration" useEffect(() => { @@ -1237,6 +1240,7 @@ const ParsedAction = (props) => { } }); } + setHiddenDescription(false) document.activeElement.blur(); }} > @@ -2326,8 +2330,63 @@ const ParsedAction = (props) => { } } + + const actionDescription = ( + + + + {params.inputProps.value} + + { + event.preventDefault(); + event.stopPropagation(); + }} + + onClick={() => { + setHiddenDescription(true) + const inputElement = document.getElementById(uiBox); + if (inputElement) { + inputElement.focus(); + } + }}> + + + + + + + Description: {selectedAction?.description} + + + + ); + return ( - + { label={isIntegration ? "Choose a category" : "Find Actions"} variant="outlined" name={`disable_autocomplete_${Math.random()}`} - - /> + /> + ); }} /> @@ -2543,7 +2602,7 @@ const ParsedAction = (props) => { fullWidth disabled={selectedAction.description === undefined || selectedAction.description === null || selectedAction.description.length === 0} onClick={() => { - setHiddenDescription(!hiddenDescription) + setHiddenParameters(!hiddenParameters) }} > Parameters @@ -2661,7 +2720,7 @@ const ParsedAction = (props) => { />
    : null} - {selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenDescription === false ? ( + {selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenParameters === false ? (
    { } multiline = data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline - + + const description = data.description === undefined ? "" : data?.description; + + const tooltipDescription = ( + + + + {tmpitem.charAt(0).toUpperCase() + tmpitem.slice(1)} + + { + event.preventDefault(); + event.stopPropagation(); + }} + + onClick={() => { + setUiBox("closed") + const inputElement = document.getElementById(uiBox); + if (inputElement) { + inputElement.focus(); + } + }}> + + + + + + + Required: {data.required === true || data.configuration === true ? "True" : "False"} + + + Description: {description} + + + Ex. : {data?.example.length > 0 ? data.example : "No example available"} + + + + ); + var datafield = ( + { handleParamChange(event, count, data) }} helperText={returnHelperText(data.name, data.value)} + onFocus={(event) => { + setUiBox(event.target.id) + console.log(event.target.id) + + }} onBlur={(event) => { baseHelperText = calculateHelpertext(event.target.value) if (setLastSaved !== undefined) { setLastSaved(false) } + setUiBox("closed") }} /> + ); // Finds headers from a string to be used for autocompletion @@ -3904,24 +4033,6 @@ const ParsedAction = (props) => { ); }; - const description = - data.description === undefined ? "" : data.description; - const tooltipDescription = ( - - - - Required:{" "} - {data.required === true || data.configuration === true - ? "True" - : "False"} - - - - Example: {data.example} - - - - Description: {description} - - - ); //var itemColor = "#f85a3e" //if (!data.required) { @@ -4001,9 +4112,7 @@ const ParsedAction = (props) => { marginBottom: "auto", }} > - {tmpitem} -
    {/*selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null : From 9734a014b943ede4521f286350b582c626f1a913 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 31 Jul 2024 19:30:47 +0200 Subject: [PATCH 149/336] Updates from cloud pre merge --- frontend/src/components/Billing.jsx | 11 +- frontend/src/components/Branding.jsx | 72 +++++++++---- frontend/src/components/LicencePopup.jsx | 2 +- frontend/src/components/NewHeader.jsx | 24 +++-- frontend/src/components/ParsedAction.jsx | 129 ++++++++++++++++++++--- frontend/src/components/Searchfield.jsx | 2 +- frontend/src/views/Admin.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 88 ++++++++++------ frontend/src/views/Apps.jsx | 7 +- 9 files changed, 253 insertions(+), 84 deletions(-) diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index 97b6cd16..d5b39411 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -54,7 +54,6 @@ const Billing = (props) => { const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props; //const alert = useAlert(); let navigate = useNavigate(); - const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false); const [dealList, setDealList] = React.useState([]); const [dealName, setDealName] = React.useState(""); @@ -1055,10 +1054,10 @@ const Billing = (props) => { Consultation & Management
    - + You currently have a total of {inputHour} hours and {inputMinutes} minutes of professional services available by our experts. -
    +
    {editConsultation ? <> { {editConsultation && }
    : null} - + Features

    + {workflowAsCode && ( + + + + )} {!distributedFromParent ? isCorrectOrg ? null : @@ -16706,11 +16763,12 @@ const releaseToConnectLabel = "Release to Connect" style={{ border: "1px solid rgba(255,255,255,0.1)", position: "absolute", - bottom: 130, - left: leftBarSize+20, + bottom: 140, + left: userdata?.support ? leftSideBarOpenByClick ? 620 : 435 : leftBarSize + 20, color: "white", padding: 10, borderRadius: theme.palette?.borderRadius, + transition: "left 0.3s ease, top 0.3s ease", }} > @@ -17488,7 +17546,7 @@ const releaseToConnectLabel = "Release to Connect" // console.log(allowList, userdata.public_username) const leftView = workflow.public === true ? -
    +
    +
    @@ -20330,7 +20388,7 @@ const releaseToConnectLabel = "Release to Connect" wheelSensitivity={0.25} style={{ width: cytoscapeWidth, - height: bodyHeight - appBarSize - 5, + height: userdata?.support ? bodyHeight - 20 : bodyHeight - appBarSize - 5, backgroundColor: theme.palette.surfaceColor, }} stylesheet={cystyle} @@ -22225,7 +22283,7 @@ const releaseToConnectLabel = "Release to Connect" const loadedCheck = isLoaded && workflowDone ? ( -
    +
    {newView} @@ -22359,7 +22417,7 @@ const releaseToConnectLabel = "Release to Connect" />
    ) : ( -
    +
    Loading Workflow & Apps... diff --git a/frontend/src/views/SetAuthentication.jsx b/frontend/src/views/SetAuthentication.jsx index bb99e037..63aec23c 100755 --- a/frontend/src/views/SetAuthentication.jsx +++ b/frontend/src/views/SetAuthentication.jsx @@ -302,7 +302,7 @@ const SetAuthentication = (props) => { } return ( -
    +
    { return { - datagrid: { - border: 0, - "& .MuiDataGrid-columnsContainer": { - backgroundColor: - theme?.palette?.type === "light" ? "#fafafa" : theme?.palette?.inputColor, - }, - "& .MuiDataGrid-iconSeparator": { - display: "none", - }, - "& .MuiDataGrid-colCell, .MuiDataGrid-cell": { - borderRight: `1px solid ${ - theme?.palette?.type === "light" ? "white" : "#303030" - }`, - }, - "& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": { - borderBottom: `1px solid ${ - theme?.palette?.type === "light" ? "#f0f0f0" : "#303030" - }`, - }, - "& .MuiDataGrid-cell": { - color: - theme?.palette?.type === "light" ? "white" : "rgba(255,255,255,0.65)", - }, - "& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption": - { - borderRadius: 0, - color: "white", - }, - }, - } + datagrid: { + border: 0, + "& .MuiDataGrid-columnsContainer": { + backgroundColor: + theme?.palette?.type === "light" ? "#fafafa" : theme?.palette?.inputColor, + }, + "& .MuiDataGrid-iconSeparator": { + display: "none", + }, + "& .MuiDataGrid-colCell, .MuiDataGrid-cell": { + borderRight: `1px solid ${theme?.palette?.type === "light" ? "white" : "#303030" + }`, + }, + "& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": { + borderBottom: `1px solid ${theme?.palette?.type === "light" ? "#f0f0f0" : "#303030" + }`, + }, + "& .MuiDataGrid-cell": { + color: + theme?.palette?.type === "light" ? "white" : "rgba(255,255,255,0.65)", + }, + "& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption": + { + borderRadius: 0, + color: "white", + }, + }, + } }) // Takes an action in Shuffle and @@ -157,7 +155,7 @@ export const GetIconInfo = (action) => { "preview", ], }, - { key: "add", values: ["add", "accept", ] }, + { key: "add", values: ["add", "accept",] }, { key: "delete", values: ["delete", "remove", "clear", "clean", "dismiss",] }, { key: "send", @@ -175,7 +173,7 @@ export const GetIconInfo = (action) => { }, { key: "repeat", - values: ["repeat", "retry", "pause", "skip", "copy", "replicat", "demo", ], + values: ["repeat", "retry", "pause", "skip", "copy", "replicat", "demo",], }, { key: "execute", values: ["execute", "run", "play", "raise"] }, { key: "extract", values: ["extract", "unpack", "decompress", "open"] }, @@ -208,8 +206,8 @@ export const GetIconInfo = (action) => { var selectedKey = "" if (action.app_name == "Integration Framework") { - selectedKey = "magic" - }else if (action.name === undefined || action.name === null) { + selectedKey = "magic" + } else if (action.name === undefined || action.name === null) { } else { const actionname = action.name.toLowerCase() for (var key in iconList) { @@ -231,27 +229,27 @@ export const GetIconInfo = (action) => { const defaultColor = "#f76b1c"; const defaultGradient = ["#fad961", "#f76b1c"]; const parsedIcons = { - magic: { + magic: { icon: "M7.5 5.6 10 7 8.6 4.5 10 2 7.5 3.4 5 2l1.4 2.5L5 7zm12 9.8L17 14l1.4 2.5L17 19l2.5-1.4L22 19l-1.4-2.5L22 14zM22 2l-2.5 1.4L17 2l1.4 2.5L17 7l2.5-1.4L22 7l-1.4-2.5zm-7.63 5.29a.9959.9959 0 0 0-1.41 0L1.29 18.96c-.39.39-.39 1.02 0 1.41l2.34 2.34c.39.39 1.02.39 1.41 0L16.7 11.05c.39-.39.39-1.02 0-1.41zm-1.03 5.49-2.12-2.12 2.44-2.44 2.12 2.12z", iconColor: "white", iconBackgroundColor: "red", originalIcon: "", - fillGradient: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#8A2BE2"], - }, - communication: { + fillGradient: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#8A2BE2"], + }, + communication: { icon: "M9.89516 7.71433H8.60945V5.1429H9.89516V7.71433ZM9.89516 10.2858H8.60945V9.00004H9.89516V10.2858ZM14.3952 2.57147H4.10944C3.76845 2.57147 3.44143 2.70693 3.20031 2.94805C2.95919 3.18917 2.82373 3.51619 2.82373 3.85719V15.4286L5.39516 12.8572H14.3952C14.7362 12.8572 15.0632 12.7217 15.3043 12.4806C15.5454 12.2395 15.6809 11.9125 15.6809 11.5715V3.85719C15.6809 3.14361 15.1023 2.57147 14.3952 2.57147Z", iconColor: "white", iconBackgroundColor: "#8acc3f", originalIcon: "", fillGradient: ["#8acc3f", "#459622"], - }, - cases: { + }, + cases: { icon: "M15.6408 8.39233H18.0922V10.0287H15.6408V8.39233ZM0.115234 8.39233H2.56663V10.0287H0.115234V8.39233ZM9.92083 0.21051V2.66506H8.28656V0.21051H9.92083ZM3.31839 2.25596L5.05889 4.00687L3.89856 5.16051L2.15807 3.42596L3.31839 2.25596ZM13.1485 3.99869L14.8808 2.25596L16.0493 3.42596L14.3088 5.16051L13.1485 3.99869ZM9.10369 4.30142C10.404 4.30142 11.651 4.81863 12.5705 5.73926C13.4899 6.65989 14.0065 7.90854 14.0065 9.21051C14.0065 11.0269 13.0178 12.6141 11.5551 13.4651V14.9378C11.5551 15.1548 11.469 15.3629 11.3158 15.5163C11.1625 15.6698 10.9547 15.756 10.738 15.756H7.46943C7.25271 15.756 7.04487 15.6698 6.89163 15.5163C6.73839 15.3629 6.6523 15.1548 6.6523 14.9378V13.4651C5.18963 12.6141 4.2009 11.0269 4.2009 9.21051C4.2009 7.90854 4.71744 6.65989 5.63689 5.73926C6.55635 4.81863 7.80339 4.30142 9.10369 4.30142ZM10.738 16.5741V17.3923C10.738 17.6093 10.6519 17.8174 10.4986 17.9709C10.3454 18.1243 10.1375 18.2105 9.92083 18.2105H8.28656C8.06984 18.2105 7.862 18.1243 7.70876 17.9709C7.55552 17.8174 7.46943 17.6093 7.46943 17.3923V16.5741H10.738ZM8.28656 14.1196H9.92083V12.3769C11.3345 12.0169 12.3722 10.7323 12.3722 9.21051C12.3722 8.34253 12.0279 7.5101 11.4149 6.89634C10.8019 6.28259 9.97056 5.93778 9.10369 5.93778C8.23683 5.93778 7.40546 6.28259 6.79249 6.89634C6.17953 7.5101 5.83516 8.34253 5.83516 9.21051C5.83516 10.7323 6.87292 12.0169 8.28656 12.3769V14.1196Z", iconColor: "white", iconBackgroundColor: "#8acc3f", originalIcon: "", fillGradient: ["#8acc3f", "#459622"], - }, + }, cache_add: { icon: "M11 3C6.58 3 3 4.79 3 7C3 9.21 6.58 11 11 11C15.42 11 19 9.21 19 7C19 4.79 15.42 3 11 3ZM3 9V12C3 14.21 6.58 16 11 16C15.42 16 19 14.21 19 12V9C19 11.21 15.42 13 11 13C6.58 13 3 11.21 3 9ZM3 14V17C3 19.21 6.58 21 11 21C12.41 21 13.79 20.81 15 20.46V17.46C13.79 17.81 12.41 18 11 18C6.58 18 3 16.21 3 14ZM20 14V17H17V19H20V22H22V19H25V17H22V14", iconColor: "white", @@ -421,67 +419,67 @@ const chipStyle = { }; export const collapseField = (field) => { - if (field === undefined || field === null) { - return true - } + if (field === undefined || field === null) { + return true + } - if (field.name === "headers" || field.name === "cookies") { - return true - } + if (field.name === "headers" || field.name === "cookies") { + return true + } - if (field.type === "array") { - return true - } + if (field.type === "array") { + return true + } - // If more than 10 keys in object, collapse - if (field.type === "object") { - if (Object.keys(field.src).length > 7) { - return true - } - } + // If more than 10 keys in object, collapse + if (field.type === "object") { + if (Object.keys(field.src).length > 7) { + return true + } + } - return false + return false } export const validateJson = (showResult) => { - if (showResult === undefined || showResult === null) { - return { - valid: false, - result: "", - } - } + if (showResult === undefined || showResult === null) { + return { + valid: false, + result: "", + } + } - if (typeof showResult === 'string') { - showResult = showResult.split(" False").join(" false") - showResult = showResult.split(" True").join(" true") + if (typeof showResult === 'string') { + showResult = showResult.split(" False").join(" false") + showResult = showResult.split(" True").join(" true") - showResult.replaceAll("False,", "false,") - showResult.replaceAll("True,", "true,") - } + showResult.replaceAll("False,", "false,") + showResult.replaceAll("True,", "true,") + } - if (typeof showResult === "object" || typeof showResult === "array") { - return { - valid: true, - result: showResult, - } - } + if (typeof showResult === "object" || typeof showResult === "array") { + return { + valid: true, + result: showResult, + } + } - if (showResult[0] === "\"") { - return { - valid: false, - result: showResult, - } - } + if (showResult[0] === "\"") { + return { + valid: false, + result: showResult, + } + } var jsonvalid = true try { if (!showResult.includes("{") && !showResult.includes("[")) { jsonvalid = false - return { - valid: jsonvalid, - result: showResult, - }; + return { + valid: jsonvalid, + result: showResult, + }; } } catch (e) { @@ -498,94 +496,94 @@ export const validateJson = (showResult) => { var result = showResult; try { - result = jsonvalid ? JSON.parse(showResult, {"storeAsString": true}) : showResult; + result = jsonvalid ? JSON.parse(showResult, { "storeAsString": true }) : showResult; } catch (e) { ////console.log("Failed parsing JSON even though its valid: ", e) jsonvalid = false; } - if (jsonvalid === false) { + if (jsonvalid === false) { - if (typeof showResult === 'string') { - showResult = showResult.trim() - } + if (typeof showResult === 'string') { + showResult = showResult.trim() + } - try { - var newstr = showResult.replaceAll("'", '"') + try { + var newstr = showResult.replaceAll("'", '"') - // Basic workarounds for issues with Python Dicts -> JSON - if (newstr.includes(": None")) { - newstr = newstr.replaceAll(": None", ': null') - } + // Basic workarounds for issues with Python Dicts -> JSON + if (newstr.includes(": None")) { + newstr = newstr.replaceAll(": None", ': null') + } - if (newstr.includes("[\"{") && newstr.includes("}\"]")) { - newstr = newstr.replaceAll("[\"{", '[{') - newstr = newstr.replaceAll("}\"]", '}]') - } + if (newstr.includes("[\"{") && newstr.includes("}\"]")) { + newstr = newstr.replaceAll("[\"{", '[{') + newstr = newstr.replaceAll("}\"]", '}]') + } - if (newstr.includes("{\"[") && newstr.includes("]\"}")) { - newstr = newstr.replaceAll("{\"[", '[{') - newstr = newstr.replaceAll("]\"}", '}]') - } + if (newstr.includes("{\"[") && newstr.includes("]\"}")) { + newstr = newstr.replaceAll("{\"[", '[{') + newstr = newstr.replaceAll("]\"}", '}]') + } - result = JSON.parse(newstr) - jsonvalid = true - } catch (e) { + result = JSON.parse(newstr) + jsonvalid = true + } catch (e) { - //console.log("Failed parsing JSON even though its valid (2): ", e) - jsonvalid = false - } - } + //console.log("Failed parsing JSON even though its valid (2): ", e) + jsonvalid = false + } + } - if (jsonvalid && typeof result === "number") { - jsonvalid = false - } + if (jsonvalid && typeof result === "number") { + jsonvalid = false + } - // This is where we start recursing - if (jsonvalid) { - // Check fields if they can be parsed too - try { - for (const [key, value] of Object.entries(result)) { - if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) { - //console.log("CHECKING STRING: ", value) + // This is where we start recursing + if (jsonvalid) { + // Check fields if they can be parsed too + try { + for (const [key, value] of Object.entries(result)) { + if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) { + //console.log("CHECKING STRING: ", value) - const inside_result = validateJson(value) - if (inside_result.valid) { - //console.log("INSIDE RESULT: ", inside_result.result) + const inside_result = validateJson(value) + if (inside_result.valid) { + //console.log("INSIDE RESULT: ", inside_result.result) - if (typeof inside_result.result === "string") { - const newres = JSON.parse(inside_result.result) + if (typeof inside_result.result === "string") { + const newres = JSON.parse(inside_result.result) - result[key] = newres - } else { - result[key] = inside_result.result - } - } - } else { + result[key] = newres + } else { + result[key] = inside_result.result + } + } + } else { - // Usually only reaches here if raw array > dict > value - if (typeof showResult !== "array") { - for (const [subkey, subvalue] of Object.entries(value)) { - if (typeof subvalue === "string" && (subvalue.startsWith("{") || subvalue.startsWith("["))) { - const inside_result = validateJson(subvalue) - if (inside_result.valid) { - if (typeof inside_result.result === "string") { - const newres = JSON.parse(inside_result.result) - result[key][subkey] = newres - } else { - result[key][subkey] = inside_result.result - } - } - } + // Usually only reaches here if raw array > dict > value + if (typeof showResult !== "array") { + for (const [subkey, subvalue] of Object.entries(value)) { + if (typeof subvalue === "string" && (subvalue.startsWith("{") || subvalue.startsWith("["))) { + const inside_result = validateJson(subvalue) + if (inside_result.valid) { + if (typeof inside_result.result === "string") { + const newres = JSON.parse(inside_result.result) + result[key][subkey] = newres + } else { + result[key][subkey] = inside_result.result + } + } + } - } - } - } - } - } catch (e) { - //console.log("Failed parsing inside json subvalues: ", e) - } - } + } + } + } + } + } catch (e) { + //console.log("Failed parsing inside json subvalues: ", e) + } + } return { valid: jsonvalid, @@ -608,7 +606,7 @@ const useDropzoneStyles = () => { //Wrapper for the dropzone component const DropzoneWrapper = memo(({ onDrop, WorkflowView }) => { - const dropzoneStyles = useDropzoneStyles(); + const dropzoneStyles = useDropzoneStyles(); return ( @@ -621,7 +619,7 @@ const Workflows = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata, checkLogin } = props; document.title = "Shuffle - Workflows"; - let navigate = useNavigate(); + let navigate = useNavigate(); const classes = useStyles(theme) const imgSize = 60; @@ -665,14 +663,14 @@ const Workflows = (props) => { const [view, setView] = React.useState("grid"); const [filters, setFilters] = React.useState([]); const [submitLoading, setSubmitLoading] = React.useState(false); - const [actionImageList, setActionImageList] = React.useState([{"large_image": ""}]) + const [actionImageList, setActionImageList] = React.useState([{ "large_image": "" }]) const [firstLoad, setFirstLoad] = React.useState(true); const [showMoreClicked, setShowMoreClicked] = React.useState(false); const [usecases, setUsecases] = React.useState([]); const [allUsecases, setAllUsecases] = React.useState({ - "success": false, - }); + "success": false, + }); const [appFramework, setAppFramework] = React.useState({}); const [drawerOpen, setDrawerOpen] = React.useState(false) const [videoViewOpen, setVideoViewOpen] = React.useState(false) @@ -682,54 +680,54 @@ const Workflows = (props) => { const [apps, setApps] = React.useState([]); - const drawerWidth = drawerOpen ? 325 : 0 + const drawerWidth = drawerOpen ? 325 : 0 - const sidebarKey = "getting_started_sidebar" - if (isLoggedIn === true && gettingStartedItems.length === 0 && (userdata.tutorials !== undefined && userdata.tutorials !== null && userdata.tutorials.length > 0) && workflowDone === true) { + const sidebarKey = "getting_started_sidebar" + if (isLoggedIn === true && gettingStartedItems.length === 0 && (userdata.tutorials !== undefined && userdata.tutorials !== null && userdata.tutorials.length > 0) && workflowDone === true) { - const activeFiltered = userdata.tutorials.filter((item) => item.active === true) - if (activeFiltered.length > 0) { - var newfiltered = [] - for (var key in activeFiltered) { - if (activeFiltered[key].name === "Discover Usecases") { - if (workflows.length > 1) { - activeFiltered[key].done = true - activeFiltered[key].description = `${workflows.length} workflows created` - } - } + const activeFiltered = userdata.tutorials.filter((item) => item.active === true) + if (activeFiltered.length > 0) { + var newfiltered = [] + for (var key in activeFiltered) { + if (activeFiltered[key].name === "Discover Usecases") { + if (workflows.length > 1) { + activeFiltered[key].done = true + activeFiltered[key].description = `${workflows.length} workflows created` + } + } - newfiltered.push(activeFiltered[key]) - } - setGettingStartedItems(activeFiltered) + newfiltered.push(activeFiltered[key]) + } + setGettingStartedItems(activeFiltered) - /* - const sidebar = localStorage.getItem(sidebarKey) - if (sidebar === null || sidebar === undefined) { - console.log("No sidebar defined") - - localStorage.setItem(sidebarKey, "open"); - setDrawerOpen(true) - } else { - if (sidebar === "open") { - setDrawerOpen(true) - } else { - setDrawerOpen(false) - } - } - */ - } + /* + const sidebar = localStorage.getItem(sidebarKey) + if (sidebar === null || sidebar === undefined) { + console.log("No sidebar defined") + + localStorage.setItem(sidebarKey, "open"); + setDrawerOpen(true) + } else { + if (sidebar === "open") { + setDrawerOpen(true) + } else { + setDrawerOpen(false) + } + } + */ + } - } + } const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const findWorkflow = (filters) => { - console.log("Using filters: ", filters) + console.log("Using filters: ", filters) if (filters.length === 0) { setFilteredWorkflows(workflows); - handleKeysetting(allUsecases, workflows) + handleKeysetting(allUsecases, workflows) return; } @@ -744,10 +742,10 @@ const Workflows = (props) => { ); } - if (curWorkflow.tags !== undefined && curWorkflow.tags !== null && curWorkflow.tags.length > 0) { - // Make them all lowercase - curWorkflow.tags = curWorkflow.tags.map((tag) => tag.toLowerCase()) - } + if (curWorkflow.tags !== undefined && curWorkflow.tags !== null && curWorkflow.tags.length > 0) { + // Make them all lowercase + curWorkflow.tags = curWorkflow.tags.map((tag) => tag.toLowerCase()) + } if (found.every((v) => v !== true)) { @@ -767,18 +765,18 @@ const Workflows = (props) => { } else if (curWorkflow.org_id === filter) { return true; } else if (curWorkflow.usecase_ids !== undefined && curWorkflow.usecase_ids !== null && curWorkflow.usecase_ids.length > 0) { - // Check if the usecase is the right category - for (var key in usecases) { - if (usecases[key].name.toLowerCase() !== newfilter) { - continue - } + // Check if the usecase is the right category + for (var key in usecases) { + if (usecases[key].name.toLowerCase() !== newfilter) { + continue + } - for (var subkey in usecases[key].list) { - if (curWorkflow.usecase_ids.includes(usecases[key].list[subkey].name)) { - return true - } - } - } + for (var subkey in usecases[key].list) { + if (curWorkflow.usecase_ids.includes(usecases[key].list[subkey].name)) { + return true + } + } + } } else if ( curWorkflow.actions !== null && curWorkflow.actions !== undefined @@ -793,7 +791,7 @@ const Workflows = (props) => { return true; } } - } + } return false; }); @@ -805,59 +803,59 @@ const Workflows = (props) => { } } - console.log("Changing workflow filter, and finding new usecase mappings!") + console.log("Changing workflow filter, and finding new usecase mappings!") if (newWorkflows.length !== workflows.length) { - handleKeysetting(allUsecases, newWorkflows) + handleKeysetting(allUsecases, newWorkflows) setFilteredWorkflows(newWorkflows); } }; - const getApps = () => { - try { - const appstorage = localStorage.getItem("apps") - const privateapps = JSON.parse(appstorage) - setApps(privateapps) - } catch (e) { - //console.log("Failed to get apps from localstorage: ", e) - } + const getApps = () => { + try { + const appstorage = localStorage.getItem("apps") + const privateapps = JSON.parse(appstorage) + setApps(privateapps) + } catch (e) { + //console.log("Failed to get apps from localstorage: ", e) + } - fetch(`${globalUrl}/api/v1/apps`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for apps :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - setApps(responseJson); - }) - .catch((error) => { - console.log("App loading error: "+error.toString()); - }); - } + fetch(`${globalUrl}/api/v1/apps`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + setApps(responseJson); + }) + .catch((error) => { + console.log("App loading error: " + error.toString()); + }); + } const addFilter = (data) => { if (data === null || data === undefined) { - console.log("No filter data") + console.log("No filter data") return; } if (data.includes("<") && data.includes(">")) { - console.log("Filter includes < or >") + console.log("Filter includes < or >") return; } if (filters.includes(data) || filters.includes(data.toLowerCase())) { - console.log("Filter already has the data") + console.log("Filter already has the data") return; } @@ -972,7 +970,7 @@ const Workflows = (props) => { you, randomize ID's and remove your authentication. - The published workflow is yours, and you can always change your public workflows after they are released. + The published workflow is yours, and you can always change your public workflows after they are released.
    - + + + {view === "list" && ( + { - // ); - // } + // ] + + // function TourButton() { + // const tour = useContext(ShepherdTourContext); + + // return ( + // + // ); + // } const WorkflowView = memo(() => { if (workflows.length === 0) { } - var workflowDelay = -150 - var appDelay = -75 + var workflowDelay = -150 + var appDelay = -75 - const foundPriority = userdata === undefined || userdata === null || userdata.priorities === undefined || userdata.priorities === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) + const foundPriority = userdata === undefined || userdata === null || userdata.priorities === undefined || userdata.priorities === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) return (
    - -
    - {!isMobile && !hasWorkflows && usecases !== null && usecases !== undefined && usecases.length > 0 ? -
    - {usecases.map((usecase, index) => { - if (usecase.name === "5. Verify") { - return null - } - const percentDone = usecase.matches.length > 0 ? parseInt(usecase.matches.length/usecase.list.length*100) : 0 - if (percentDone === 0) { - usecase = findMatches(usecase, workflows) - } +
    + {!isMobile && !hasWorkflows && usecases !== null && usecases !== undefined && usecases.length > 0 ? +
    + {usecases.map((usecase, index) => { + if (usecase.name === "5. Verify") { + return null + } - return ( - { - console.log("Filters: ", filters, usecase.name.toLowerCase()) - if (!filters.includes(usecase.name.toLowerCase())) { - addFilter(usecase.name) - } else { - removeFilter(filters.indexOf(usecase.name.toLowerCase())) - } + const percentDone = usecase.matches.length > 0 ? parseInt(usecase.matches.length / usecase.list.length * 100) : 0 + if (percentDone === 0) { + usecase = findMatches(usecase, workflows) + } - }} - > - - - {usecase.name} - - - {usecase.matches.length}/{usecase.list.length} - - - - ) - })} -
    - : null} -
    + return ( + { + console.log("Filters: ", filters, usecase.name.toLowerCase()) + if (!filters.includes(usecase.name.toLowerCase())) { + addFilter(usecase.name) + } else { + removeFilter(filters.indexOf(usecase.name.toLowerCase())) + } + + }} + > + + + {usecase.name} + + + {usecase.matches.length}/{usecase.list.length} + + + + ) + })} +
    + : null} +
    {!isMobile && - actionImageList !== undefined && + actionImageList !== undefined && actionImageList !== null && actionImageList.length > 0 ? (
    { //data.large_image = theme.palette.defaultImage } - if (data.app_name.toLowerCase() === "integration framework") { - return null - } + if (data.app_name.toLowerCase() === "integration framework") { + return null + } - const returnData = - - { - console.log("FILTER: ", data); - addFilter(data.app_name); - }} - > - - -
    - {data.app_name} -
    -
    -
    -
    -
    + const returnData = + + { + console.log("FILTER: ", data); + addFilter(data.app_name); + }} + > + + +
    + {data.app_name} +
    +
    +
    +
    +
    - if (firstLoad) { - appDelay += 75 - } else { - //appDelay = 0 - return returnData - } + if (firstLoad) { + appDelay += 75 + } else { + //appDelay = 0 + return returnData + } return ( - - {/**/} - {returnData} - {/**/} - - ); - })} + + {/**/} + {returnData} + {/**/} + + ); + })}
    ) : null} - {userdata.priorities !== undefined && userdata.priorities !== null && userdata.priorities.length > 0 && userdata.priorities[0].name.includes("CPU") && userdata.priorities[0].active === true ? -
    0 && userdata.priorities[0].name.includes("CPU") && userdata.priorities[0].active === true ? +
    -
    - - {userdata.priorities[0].name} - -
    - - +
    + + {userdata.priorities[0].name} + +
    + + {userdata.priorities[0].description}
    -
    -
    - - {/* +
    +
    + + {/* */} -
    -
    - : null} +
    +
    + : null} - {foundPriority != null && workflows.length < 6 ? - - : null} + {foundPriority != null && workflows.length < 6 ? + + : null} -
    - {view === "grid" ? ( - - {/**/} - - {/**/} +
    + {view === "grid" ? ( + + {/**/} + + {/**/} - {filteredWorkflows.map((data, index) => { - // Shouldn't be a part of this list - if (data.public === true) { - return null - } + {filteredWorkflows.map((data, index) => { + // Shouldn't be a part of this list + if (data.public === true) { + return null + } - if (firstLoad) { - workflowDelay += 75 - } else { - return ( - - - - ) - } + if (firstLoad) { + workflowDelay += 75 + } else { + return ( + + + + ) + } - return ( - - {/**/} - - - - {/**/} - - ) - })} - - ) : ( - - )} -
    + return ( + + {/**/} + + + + {/**/} + + ) + })} +
    + ) : ( + + )} +
    - {foundPriority != null && filteredWorkflows.length > 6 ? - - : null} + {foundPriority != null && filteredWorkflows.length > 6 ? + + : null} -
    -
    -
    +
    +
    +
    ); }); @@ -3994,7 +4004,7 @@ const Workflows = (props) => { const workflowDownloadModalOpen = loadWorkflowsModalOpen ? ( {}} + onClose={() => { }} PaperProps={{ style: { backgroundColor: theme.palette.surfaceColor, @@ -4121,166 +4131,166 @@ const Workflows = (props) => { ) : null; - //const + //const //const [percentDone, setPercentDone] = React.useState(0) - const percentDone = gettingStartedItems.filter((item) => item.done).length / gettingStartedItems.length * 100 + const percentDone = gettingStartedItems.filter((item) => item.done).length / gettingStartedItems.length * 100 - const GettingStartedItem = ({item, index}) => { - const [clicked, setClicked] = React.useState(false) - const doneIcon = item.done ? : + const GettingStartedItem = ({ item, index }) => { + const [clicked, setClicked] = React.useState(false) + const doneIcon = item.done ? : - return ( -
    setClicked(true)} - > - - {doneIcon} {index + 1}. {item.name} - - {clicked ? - - - {item.description} - - - - - - : - null - } -
    - ) - } + return ( +
    setClicked(true)} + > + + {doneIcon} {index + 1}. {item.name} + + {clicked ? + + + {item.description} + + + + + + : + null + } +
    + ) + } - const gettingStartedDrawer = true == true ? null : - -
    - - Getting Started - - - { - e.preventDefault(); - setDrawerOpen(false) + const gettingStartedDrawer = true == true ? null : + +
    + + Getting Started + + + { + e.preventDefault(); + setDrawerOpen(false) - localStorage.setItem(sidebarKey, "closed"); - }} - > - - - -
    -
    - - Setup progress: {isNaN(percentDone) ? 0 : percentDone}% - + localStorage.setItem(sidebarKey, "closed"); + }} + > + + + +
    +
    + + Setup progress: {isNaN(percentDone) ? 0 : percentDone}% + - + - - Follow these steps to get you up and running! - - - { - setVideoViewOpen(true) - }}> - Watch 2-min introduction video - -
    -
    - {gettingStartedItems.map((item, index) => { - return ( - - ) - })} -
    -
    - - const videoView = - { - setVideoViewOpen(false) - }} - PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: 560, - minHeight: 415, - textAlign: "center", - }, - }} - > - - Welcome to Shuffle! - + + Follow these steps to get you up and running! + + + { + setVideoViewOpen(true) + }}> + Watch 2-min introduction video + +
    +
    + {gettingStartedItems.map((item, index) => { + return ( + + ) + })} +
    +
    - - { - e.preventDefault(); - setVideoViewOpen(false) - }} - > - - - + const videoView = + { + setVideoViewOpen(false) + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: 560, + minHeight: 415, + textAlign: "center", + }, + }} + > + + Welcome to Shuffle! + - - + + { + e.preventDefault(); + setVideoViewOpen(false) + }} + > + + + + + +
    const loadedCheck = isLoaded && isLoggedIn && workflowDone ? (
    - {/* + {/* */} - + {/*modalView*/} {deleteModal} {exportVerifyModal} @@ -4298,34 +4308,34 @@ const Workflows = (props) => {
    : null*/} - {isMobile ? null : gettingStartedDrawer} - {videoView} + {isMobile ? null : gettingStartedDrawer} + {videoView} - {modalOpen === true ? - - : null} - {/*
    + workflows={workflows} + apps={apps} + setWorkflows={setWorkflows} + /> + : null} + {/*
    Need assistance? Ask our support team (it's free!).
    */} -
    +
    ) : (
    Date: Sat, 30 Nov 2024 15:04:11 +0100 Subject: [PATCH 298/336] Worker rebuilds --- backend/go-app/go.mod | 4 ++-- functions/onprem/orborus/go.mod | 2 +- functions/onprem/worker/go.mod | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 6e864514..d944ad0d 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -2,7 +2,7 @@ module shuffle go 1.22.0 -replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared toolchain go1.22.2 @@ -20,7 +20,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.6.78 + github.com/shuffle/shuffle-shared v0.6.90 golang.org/x/crypto v0.22.0 google.golang.org/api v0.176.1 google.golang.org/grpc v1.63.2 diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod index 0c7b28df..33577275 100644 --- a/functions/onprem/orborus/go.mod +++ b/functions/onprem/orborus/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v27.0.2+incompatible github.com/docker/go-connections v0.5.0 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.6.83 + github.com/shuffle/shuffle-shared v0.6.90 k8s.io/api v0.30.2 k8s.io/apimachinery v0.30.2 ) diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod index b22a8427..57db7dde 100644 --- a/functions/onprem/worker/go.mod +++ b/functions/onprem/worker/go.mod @@ -6,7 +6,7 @@ require ( github.com/docker/docker v26.1.0+incompatible github.com/gorilla/mux v1.8.1 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.6.74 + github.com/shuffle/shuffle-shared v0.6.90 k8s.io/api v0.30.2 k8s.io/apimachinery v0.30.2 k8s.io/client-go v0.30.2 From ae3034730356388827c572ec5e7c695d28c23018 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 30 Nov 2024 15:23:13 +0100 Subject: [PATCH 299/336] Gomod fixes. Now frontend pls --- .github/workflows/dockerbuild.yaml | 4 ---- backend/Dockerfile | 2 +- backend/go-app/go.sum | 2 ++ frontend/package.json | 3 +-- frontend/src/views/AngularWorkflow.jsx | 1 - functions/onprem/orborus/go.sum | 6 ++++-- functions/onprem/worker/go.sum | 2 ++ 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index 1ade4e48..ef4848e0 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -25,10 +25,6 @@ jobs: path: backend version: nightly experimental: true - - app: app_sdk - path: backend/app_sdk - version: nightly - experimental: true - app: orborus path: functions/onprem/orborus version: nightly diff --git a/backend/Dockerfile b/backend/Dockerfile index e34f9c11..c591da21 100755 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -11,7 +11,7 @@ ADD ./go-app/docker.go /app ADD ./go-app/go.mod /app # Required files for code generation -ADD ./app_sdk/app_base.py /app_sdk +RUN wget -O /app_sdk/app_base.py https://raw.githubusercontent.com/Shuffle/app_sdk/refs/heads/main/shuffle_sdk/shuffle_sdk.py ADD ./app_gen /app_gen RUN go get -v diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index 28b022f4..608eb833 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -336,6 +336,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shuffle/shuffle-shared v0.6.77 h1:KKtM50xW2DLuRHINxhp3uXrNH0AhiwkeiiU93a8fB3A= github.com/shuffle/shuffle-shared v0.6.77/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= +github.com/shuffle/shuffle-shared v0.6.90 h1:FzIYtEt44eWgEsW/9tj2ki7qq8FEm/HWXUok+THp72M= +github.com/shuffle/shuffle-shared v0.6.90/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/frontend/package.json b/frontend/package.json index f71c766d..7426ee23 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,6 @@ "@uiw/codemirror-themes": "^4.21.9", "@uiw/react-codemirror": "^4.21.21", "algoliasearch": "^4.8.3", - "chart.js": "^3.0.0", "class-transformer": "^0.2.0", "codemirror": "^6.0.1", "cpx": "^1.5.0", @@ -61,7 +60,7 @@ "react-alice-carousel": "^2.6.4", "react-avatar-editor": "^11.1.0", "react-beforeunload": "^2.2.1", - "react-chartjs-2": "^5.0.0", + "react-chartjs-2": "^2.11.2", "react-cookie": "^4.0.1", "react-cytoscapejs": "^2.0.0", "react-device-detect": "^2.2.3", diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index afd1fc7b..a5685474 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -154,7 +154,6 @@ import ExtraApps from "../components/ExtraApps.jsx" import EditWorkflow from "../components/EditWorkflow.jsx" import { act } from "react"; import { Context } from "../context/ContextApi.jsx"; -import transitions from "@material-ui/core/styles/transitions.js"; // import AppStats from "../components/AppStats.jsx"; const noImage = "/public/no_image.png"; diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum index b858d549..02b90f14 100644 --- a/functions/onprem/orborus/go.sum +++ b/functions/onprem/orborus/go.sum @@ -303,10 +303,12 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shuffle/shuffle-shared v0.6.74 h1:os3BDSFZnl4U8ZgsTAY8IsTDADcMXhbc1rS9UMa0BIY= github.com/shuffle/shuffle-shared v0.6.74/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= -github.com/shuffle/shuffle-shared v0.6.83 h1:gceT91WtFqh3h9juzTipDhWpxZLfrdtbcsnK+XNj57g= -github.com/shuffle/shuffle-shared v0.6.83/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= github.com/shuffle/shuffle-shared v0.6.79 h1:MIy5kcShHYN05ov/50YJ+la1C2v1rL8IENapOvX9I8U= github.com/shuffle/shuffle-shared v0.6.79/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= +github.com/shuffle/shuffle-shared v0.6.83 h1:gceT91WtFqh3h9juzTipDhWpxZLfrdtbcsnK+XNj57g= +github.com/shuffle/shuffle-shared v0.6.83/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= +github.com/shuffle/shuffle-shared v0.6.90 h1:FzIYtEt44eWgEsW/9tj2ki7qq8FEm/HWXUok+THp72M= +github.com/shuffle/shuffle-shared v0.6.90/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/functions/onprem/worker/go.sum b/functions/onprem/worker/go.sum index 65296ed9..bac5896e 100644 --- a/functions/onprem/worker/go.sum +++ b/functions/onprem/worker/go.sum @@ -298,6 +298,8 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shuffle/shuffle-shared v0.6.74 h1:os3BDSFZnl4U8ZgsTAY8IsTDADcMXhbc1rS9UMa0BIY= github.com/shuffle/shuffle-shared v0.6.74/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= +github.com/shuffle/shuffle-shared v0.6.90 h1:FzIYtEt44eWgEsW/9tj2ki7qq8FEm/HWXUok+THp72M= +github.com/shuffle/shuffle-shared v0.6.90/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= From d81f9d79b860620f9189e6273fc90761075ccdcc Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 30 Nov 2024 15:28:19 +0100 Subject: [PATCH 300/336] Fixed health bar chart build chartjs issue --- frontend/src/components/HealthBarChart.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/components/HealthBarChart.jsx b/frontend/src/components/HealthBarChart.jsx index 8f95c0c6..a4f05273 100644 --- a/frontend/src/components/HealthBarChart.jsx +++ b/frontend/src/components/HealthBarChart.jsx @@ -1,8 +1,5 @@ import React from 'react'; import { Bar } from 'react-chartjs-2'; -import { Chart, registerables } from 'chart.js'; - -Chart.register(...registerables); const HealthBarChart = (props) => { const { globalUrl, filteredData, options, onBarClick } = props; From a9b4f5bb6fc4224bb2276bba3bf2cbd81058508d Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 30 Nov 2024 15:44:19 +0100 Subject: [PATCH 301/336] Build with missing files --- frontend/src/App.jsx | 18 + frontend/src/components/ApiExplorer.jsx | 3051 ++++++++++++++++++++ frontend/src/components/ExecutionPanel.jsx | 546 ++++ frontend/src/components/MFASetUP.jsx | 197 ++ frontend/src/views/ApiExplorerWrapper.jsx | 1787 ++++++++++++ frontend/src/views/CodeWorkflow.jsx | 466 +++ 6 files changed, 6065 insertions(+) create mode 100644 frontend/src/components/ApiExplorer.jsx create mode 100644 frontend/src/components/ExecutionPanel.jsx create mode 100644 frontend/src/components/MFASetUP.jsx create mode 100644 frontend/src/views/ApiExplorerWrapper.jsx create mode 100644 frontend/src/views/CodeWorkflow.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 17bcab31..e2c7a41d 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -45,6 +45,11 @@ import AlertTemplate from "./components/AlertTemplate"; import { isMobile } from "react-device-detect"; import RuntimeDebugger from "./components/RuntimeDebugger.jsx" +import MFASetUp from './components/MFASetUP.jsx'; +import ApiExplorerWrapper from './views/ApiExplorerWrapper.jsx'; +import LeftSideBar from './components/LeftSideBar.jsx'; +import CodeWorkflow from './views/CodeWorkflow.jsx'; + import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; @@ -204,6 +209,11 @@ const App = (message, props) => { {curpath.includes("/workflows") && curpath.includes("/run") ?
    : + isLoggedIn ? +
    + +
    + :
    { /> } /> + } /> { /> } /> + } /> } /> } /> + + } /> + } /> + } /> + { /> } /> + } />
    + ); +} + +function a11yProps(index) { + return { + id: `simple-tab-${index}`, + "aria-controls": `simple-tabpanel-${index}`, + }; +} + +const RequestMethods = [ + { + value: "GET", + color: "#61afee", + }, + { + value: "POST", + color: "#49cc90", + }, + { + value: "DELETE", + color: "#f93e3e", + }, + { + value: "PUT", + color: "#fca130", + }, + { + value: "PATCH", + color: "#50e3c2", + }, + { + value: "CONNECT", + color: "#ff69b4", + }, + { + value: "HEAD", + color: "#9012fe", + }, +]; + +const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, selectedAppData, ConfigurationTab }) => { + const [actions, setActions] = useState([]); + const [info, setInfo] = useState({}); + const [serverurl, setServerUrl] = useState(""); + const [selectedActionIndex, setSelectedActionIndex] = useState(0); + const [ExampleBody, setExampleBody] = useState({}); + const [filteredActions, setFilteredActions] = useState([]); + + const getJsonObject = (properties) => { + + let jsonObject = {}; + for (let key in properties) { + const property = properties[key]; + + let subloop = false; + if (property.hasOwnProperty("type")) { + if (property.type === "object" || property.type === "array") { + subloop = true; + } + } + + if (subloop) { + if ( + property.hasOwnProperty("items") && + property.items.hasOwnProperty("properties") + ) { + const jsonret = getJsonObject(property.items.properties); + if (property.type === "array") { + jsonObject[key] = [jsonret]; + } else { + jsonObject[key] = jsonret; + } + } else { + if (property.hasOwnProperty("properties")) { + const jsonret = getJsonObject(property.properties); + if (property.type === "array") { + jsonObject[key] = [jsonret]; + } else { + jsonObject[key] = jsonret; + } + } else { + } + } + } else { + if (property.hasOwnProperty("example")) { + jsonObject[key] = property.example; + } else if ( + property.hasOwnProperty("enum") && + property.enum.length > 0 + ) { + jsonObject[key] = property.enum[0]; + } else if (property.hasOwnProperty("default")) { + jsonObject[key] = property.default; + } else if (property.hasOwnProperty("maximum")) { + jsonObject[key] = property.maximum; + } else if (property.hasOwnProperty("minimum")) { + jsonObject[key] = property.minimum; + } else if (property.hasOwnProperty("type")) { + if (property.type === "integer" || property.type === "number") { + jsonObject[key] = 0; + } else if (property.type === "boolean") { + jsonObject[key] = false; + } else if (property.type === "string") { + jsonObject[key] = ""; + } else { + } + } else { + } + } + } + + return jsonObject; + }; + + const handleGetRef = (parameter, data) => { + try { + if (parameter === null || parameter["$ref"] === undefined) { + return parameter; + } + } catch (e) { + return parameter; + } + + const paramsplit = parameter["$ref"].split("/"); + if (paramsplit[0] !== "#") { + return parameter; + } + + var newitem = data; + for (let paramkey in paramsplit) { + var tmpparam = paramsplit[paramkey]; + if (tmpparam === "#") { + continue; + } + + if (newitem[tmpparam] === undefined) { + return parameter; + } + + newitem = newitem[tmpparam]; + } + return newitem; + }; + + useEffect(() => { + if (openapi !== undefined && openapi !== null) { + parseIncomingOpenapiData(openapi); + } + }, [openapi]); + + const parseIncomingOpenapiData = useCallback((data) => { + if (data.info !== null && data.info !== undefined) { + setInfo(data.info); + } + + try { + if (data.info !== null && data.info !== undefined) { + if (data.info.title !== undefined && data.info.title !== null) { + if (data.info.title.endsWith(" API")) { + data.info.title = data.info.title.substring( + 0, + data.info.title.length - 4 + ); + } else if (data.info.title.endsWith("API")) { + data.info.title = data.info.title.substring( + 0, + data.info.title.length - 3 + ); + } + } + + document.title = data.info.title + " Rest API" + + if ( + data.info["x-catefies"] !== undefined && + data.info["x-categories"].length > 0 + ) { + if (Array.isArray(data.info["x-categories"])) { + } else { + } + } + } + } catch (e) {} + + try { + if (data.tags !== undefined && data.tags.length > 0) { + var newtags = []; + for (let tagkey in data.tags) { + if (data.tags[tagkey]?.name.length > 50) { + continue; + } + + newtags.push(data.tags[tagkey]?.name); + } + + if (newtags.length > 10) { + newtags = newtags.slice(0, 9); + } + } + } catch (e) {} + + // This is annoying (: + // Weird generator problems to be handle + var securitySchemes = undefined; + try { + if (data.securitySchemes !== undefined) { + securitySchemes = data.securitySchemes; + if (securitySchemes === undefined) { + securitySchemes = data.securityDefinitions; + } + } + + if (securitySchemes === undefined && data.components !== undefined) { + securitySchemes = data.components.securitySchemes; + if (securitySchemes === undefined) { + securitySchemes = data.components.securityDefinitions; + } + } + } catch (e) {} + + const allowedfunctions = [ + "GET", + "CONNECT", + "HEAD", + "DELETE", + "POST", + "PATCH", + "PUT", + ]; + + var newActions = []; + var wordlist = {}; + var all_categories = []; + var parentUrl = ""; + + if (data.paths !== null && data.paths !== undefined) { + for (let [path, pathvalue] of Object.entries(data.paths)) { + for (let [method, methodvalue] of Object.entries(pathvalue)) { + if (methodvalue === null) { + continue; + } + + if (!allowedfunctions.includes(method.toUpperCase())) { + // Typical YAML issue + if (method !== "parameters") { + //toast("Skipped method (not allowed): " + method); + } + continue; + } + + var tmpname = methodvalue.summary; + if ( + methodvalue.operationId !== undefined && + methodvalue.operationId !== null && + methodvalue.operationId.length > 0 && + (tmpname === undefined || tmpname.length === 0) + ) { + tmpname = methodvalue.operationId; + } + + if (tmpname !== undefined && tmpname !== null) { + tmpname = tmpname.replaceAll(".", " "); + } + + if ( + (tmpname === undefined || tmpname === null) && + methodvalue.description !== undefined && + methodvalue.description !== null && + methodvalue.description.length > 0 + ) { + tmpname = methodvalue.description + .replaceAll(".", " ") + .replaceAll("_", " "); + } + + var newaction = { + name: tmpname, + description: methodvalue.description, + url: path, + file_field: "", + method: method.toUpperCase(), + headers: "", + queries: [], + paths: [], + body: "", + errors: [], + example_response: "", + action_label: "No Label", + required_bodyfields: [], + }; + + if ( + methodvalue["x-label"] !== undefined && + methodvalue["x-label"] !== null + ) { + // FIX: Map labels only if they're actually in the category list + newaction.action_label = methodvalue["x-label"]; + } + + if ( + methodvalue["x-required-fields"] !== undefined && + methodvalue["x-required-fields"] !== null + ) { + newaction.required_bodyfields = methodvalue["x-required-fields"]; + } + + if ( + newaction.url !== undefined && + newaction.url !== null && + newaction.url.includes("_shuffle_replace_") + ) { + //const regex = /_shuffle_replace_\d/i; + const regex = /_shuffle_replace_\d+/i; + + newaction.url = newaction.url.replaceAll( + new RegExp(regex, "g"), + "" + ); + } + + // Finding category + if (path.includes("/")) { + const pathsplit = path.split("/"); + // Stupid way of finding a category/grouping + for (let splitkey in pathsplit) { + if (pathsplit[splitkey].includes("_shuffle_replace_")) { + //const regex = /_shuffle_replace_\d/i; + const regex = /_shuffle_replace_\d+/i; + pathsplit[splitkey] = pathsplit[splitkey].replaceAll( + new RegExp(regex, "g"), + "" + ); + } + + if ( + pathsplit[splitkey].length > 0 && + pathsplit[splitkey] !== "v1" && + pathsplit[splitkey] !== "v2" && + pathsplit[splitkey] !== "api" && + pathsplit[splitkey] !== "1.0" && + pathsplit[splitkey] !== "apis" + ) { + newaction["category"] = pathsplit[splitkey]; + if (!all_categories.includes(pathsplit[splitkey])) { + all_categories.push(pathsplit[splitkey]); + } + break; + } + } + } + + if (path === "/files/{file_id}/content") { + } + + // Typescript? I think not ;) + if (methodvalue["requestBody"] !== undefined) { + if ( + methodvalue["requestBody"]["$ref"] !== undefined && + methodvalue["requestBody"]["$ref"] !== null + ) { + // Handle ref + const parameter = handleGetRef( + { $ref: methodvalue["requestBody"]["$ref"] }, + data + ); + if ( + parameter.content !== undefined && + parameter.content !== null + ) { + methodvalue["requestBody"]["content"] = parameter.content; + } + } + + if (methodvalue["requestBody"]["content"] !== undefined) { + // Handle content - XML or JSON + // + if ( + methodvalue["requestBody"]["content"]["application/json"] !== + undefined + ) { + if ( + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ] !== undefined && + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ] !== null + ) { + try { + if ( + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ]["properties"] !== undefined + ) { + // Read out properties from a JSON object + const jsonObject = getJsonObject( + methodvalue["requestBody"]["content"][ + "application/json" + ]["schema"]["properties"] + ); + if (jsonObject !== undefined && jsonObject !== null) { + try { + newaction["body"] = JSON.stringify( + jsonObject, + null, + 2 + ); + } catch (e) {} + } + + //newaction["body"] = JSON.stringify(jsonObject, null, 2); + + var tmpobject = {}; + for (let prop of methodvalue["requestBody"]["content"][ + "application/json" + ]["schema"]["properties"]) { + tmpobject[prop] = `\$\{${prop}\}`; + } + for (let subkey in methodvalue["requestBody"]["content"][ + "application/json" + ]["schema"]["required"]) { + const tmpitem = + methodvalue["requestBody"]["content"][ + "application/json" + ]["schema"]["required"][subkey]; + tmpobject[tmpitem] = `\$\{${tmpitem}\}`; + } + + newaction["body"] = JSON.stringify(tmpobject, null, 2); + } else if ( + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ]["$ref"] !== undefined && + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ]["$ref"] !== null + ) { + const retRef = handleGetRef( + methodvalue["requestBody"]["content"][ + "application/json" + ]["schema"], + data + ); + var newbody = {}; + for (let propkey in retRef.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + newbody[parsedkey] = "${" + parsedkey + "}"; + } + + newaction["body"] = JSON.stringify(newbody, null, 2); + } + } catch (e) {} + } + } else if ( + methodvalue["requestBody"]["content"]["application/xml"] !== + undefined + ) { + //newaction["headers"] = "" + //"Content-Type=application/xml\nAccept=application/xml"; + if ( + methodvalue["requestBody"]["content"]["application/xml"][ + "schema" + ] !== undefined && + methodvalue["requestBody"]["content"]["application/xml"][ + "schema" + ] !== null + ) { + try { + if ( + methodvalue["requestBody"]["content"]["application/xml"][ + "schema" + ]["properties"] !== undefined + ) { + for (let [prop, propvalue] of Object.entries( + methodvalue["requestBody"]["content"][ + "application/xml" + ]["schema"]["properties"] + )) { + tmpobject[prop] = `\$\{${prop}\}`; + } + + for (let [subkey, subkeyval] in Object.entries( + methodvalue["requestBody"]["content"][ + "application/xml" + ]["schema"]["required"] + )) { + const tmpitem = + methodvalue["requestBody"]["content"][ + "application/xml" + ]["schema"]["required"][subkey]; + tmpobject[tmpitem] = `\$\{${tmpitem}\}`; + } + + //newaction["body"] = XML.stringify(tmpobject, null, 2) + } + } catch (e) {} + } + } else { + if ( + methodvalue["requestBody"]["content"]["example"] !== undefined + ) { + if ( + methodvalue["requestBody"]["content"]["example"][ + "example" + ] !== undefined + ) { + newaction["body"] = + methodvalue["requestBody"]["content"]["example"][ + "example" + ]; + } + } + + if ( + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ] !== undefined + ) { + if ( + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"] !== undefined && + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"] !== null + ) { + try { + if ( + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"]["type"] === "object" + ) { + const fieldname = + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"]["properties"]["fieldname"]; + + if (fieldname !== undefined) { + newaction.file_field = fieldname["value"]; + } else { + for (const [subkey, subvalue] of Object.entries( + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"]["properties"] + )) { + if (subkey.includes("file")) { + newaction.file_field = subkey; + break; + } + } + + if ( + newaction.file_field === undefined || + newaction.file_field === null || + newaction.file_field.length === 0 + ) { + } + } + } else { + } + } catch (e) {} + } + } else { + var schemas = []; + const content = methodvalue["requestBody"]["content"]; + if (content !== undefined && content !== null) { + for (const [subkey, subvalue] of Object.entries(content)) { + if ( + subvalue["schema"] !== undefined && + subvalue["schema"] !== null + ) { + if ( + subvalue["schema"]["$ref"] !== undefined && + subvalue["schema"]["$ref"] !== null + ) { + if (!schemas.includes(subvalue["schema"]["$ref"])) { + schemas.push(subvalue["schema"]["$ref"]); + } + } + } else { + if ( + subvalue["example"] !== undefined && + subvalue["example"] !== null + ) { + newaction["body"] = subvalue["example"]; + } else { + } + } + } + } + + try { + if (schemas.length === 1) { + const parameter = handleGetRef( + { $ref: schemas[0] }, + data + ); + if ( + parameter.properties !== undefined && + parameter["type"] === "object" + ) { + var newbody = {}; + for (let propkey in parameter.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + if ( + parameter.properties[propkey].type === undefined + ) { + continue; + } + + if (parameter.properties[propkey].type === "string") { + if ( + parameter.properties[propkey].description !== + undefined + ) { + newbody[parsedkey] = + parameter.properties[propkey].description; + } else { + newbody[parsedkey] = ""; + } + } else if ( + parameter.properties[propkey].type.includes( + "int" + ) || + parameter.properties[propkey].type.includes( + "uint64" + ) + ) { + newbody[parsedkey] = 0; + } else if ( + parameter.properties[propkey].type.includes( + "boolean" + ) + ) { + newbody[parsedkey] = false; + } else if ( + parameter.properties[propkey].type.includes("array") + ) { + newbody[parsedkey] = []; + } else { + newbody[parsedkey] = []; + } + } + + newaction["body"] = JSON.stringify(newbody, null, 2); + } else { + } + } + } catch (e) {} + } + } + } + } + + if ( + methodvalue.responses !== undefined && + methodvalue.responses !== null + ) { + if (methodvalue.responses.default !== undefined) { + if (methodvalue.responses.default.content !== undefined) { + if ( + methodvalue.responses.default.content["text/plain"] !== + undefined + ) { + if ( + methodvalue.responses.default.content["text/plain"][ + "schema" + ] !== undefined + ) { + if ( + methodvalue.responses.default.content["text/plain"][ + "schema" + ]["example"] !== undefined + ) { + newaction.example_response = + methodvalue.responses.default.content["text/plain"][ + "schema" + ]["example"]; + } + + if ( + methodvalue.responses.default.content["text/plain"][ + "schema" + ]["format"] === "binary" && + methodvalue.responses.default.content["text/plain"][ + "schema" + ]["type"] === "string" + ) { + newaction.example_response = "shuffle_file_download"; + } + } + } + } + } else { + var selectedReturn = ""; + if (methodvalue.responses["200"] !== undefined) { + selectedReturn = "200"; + } else if (methodvalue.responses["201"] !== undefined) { + selectedReturn = "201"; + } + + // Parsing examples. This should be standardized lol + if (methodvalue.responses[selectedReturn] !== undefined) { + const selectedExample = methodvalue.responses[selectedReturn]; + if (selectedExample["content"] !== undefined) { + if ( + selectedExample["content"]["application/json"] !== undefined + ) { + if ( + selectedExample["content"]["application/json"][ + "schema" + ] !== undefined && + selectedExample["content"]["application/json"][ + "schema" + ] !== null + ) { + if ( + selectedExample["content"]["application/json"][ + "schema" + ]["properties"] !== undefined && + selectedExample["content"]["application/json"][ + "schema" + ]["properties"] !== null + ) { + const jsonObject = getJsonObject( + selectedExample["content"]["application/json"][ + "schema" + ]["properties"] + ); + if (jsonObject !== undefined && jsonObject !== null) { + try { + newaction.example_response = JSON.stringify( + jsonObject, + null, + 2 + ); + } catch (e) {} + } + } + + if ( + selectedExample["content"]["application/json"][ + "schema" + ]["$ref"] !== undefined + ) { + const parameter = handleGetRef( + selectedExample["content"]["application/json"][ + "schema" + ], + data + ); + if ( + parameter.properties !== undefined && + parameter["type"] === "object" + ) { + var newbody = {}; + for (let propkey in parameter.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + if ( + parameter.properties[propkey].type === undefined + ) { + continue; + } + + if ( + parameter.properties[propkey].type === "string" + ) { + if ( + parameter.properties[propkey].description !== + undefined + ) { + newbody[parsedkey] = + parameter.properties[propkey].description; + } else { + newbody[parsedkey] = ""; + } + } else if ( + parameter.properties[propkey].type.includes("int") + ) { + newbody[parsedkey] = 0; + } else if ( + parameter.properties[propkey].type.includes( + "boolean" + ) + ) { + newbody[parsedkey] = false; + } else if ( + parameter.properties[propkey].type.includes( + "array" + ) + ) { + //const parameter = handleGetRef(selectedExample["content"]["application/json"]["schema"], data) + newbody[parsedkey] = []; + } else { + newbody[parsedkey] = []; + } + } + newaction.example_response = JSON.stringify( + newbody, + null, + 2 + ); + } else { + } + } else { + // Just selecting the first one. bleh. + if ( + selectedExample["content"]["application/json"][ + "schema" + ]["allOf"] !== undefined + ) { + var selectedComponent = + selectedExample["content"]["application/json"][ + "schema" + ]["allOf"]; + if (selectedComponent.length >= 1) { + selectedComponent = selectedComponent[0]; + + const parameter = handleGetRef( + selectedComponent, + data + ); + if ( + parameter.properties !== undefined && + parameter["type"] === "object" + ) { + var newbody = {}; + for (let propkey in parameter.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + if ( + parameter.properties[propkey].type === + undefined + ) { + continue; + } + + if ( + parameter.properties[propkey].type === + "string" + ) { + if ( + parameter.properties[propkey] + .description !== undefined + ) { + newbody[parsedkey] = + parameter.properties[propkey].description; + } else { + newbody[parsedkey] = ""; + } + } else if ( + parameter.properties[propkey].type.includes( + "int" + ) + ) { + newbody[parsedkey] = 0; + } else if ( + parameter.properties[propkey].type.includes( + "boolean" + ) + ) { + newbody[parsedkey] = false; + } else { + newbody[parsedkey] = []; + } + } + + newaction.example_response = JSON.stringify( + newbody, + null, + 2 + ); + //newaction.example_response = JSON.stringify(parameter.properties, null, 2) + } else { + //newaction.example_response = parameter.properties + } + } else { + } + } else if ( + selectedExample["content"]["application/json"][ + "schema" + ]["properties"] !== undefined + ) { + if ( + selectedExample["content"]["application/json"][ + "schema" + ]["properties"]["data"] !== undefined + ) { + const parameter = handleGetRef( + selectedExample["content"]["application/json"][ + "schema" + ]["properties"]["data"], + data + ); + if ( + parameter.properties !== undefined && + parameter["type"] === "object" + ) { + var newbody = {}; + for (let propkey in parameter.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + if ( + parameter.properties[propkey].type === + undefined + ) { + continue; + } + + if ( + parameter.properties[propkey].type === + "string" + ) { + if ( + parameter.properties[propkey] + .description !== undefined + ) { + newbody[parsedkey] = + parameter.properties[propkey].description; + } else { + newbody[parsedkey] = ""; + } + } else if ( + parameter.properties[propkey].type.includes( + "int" + ) + ) { + newbody[parsedkey] = 0; + } else { + newbody[parsedkey] = []; + } + } + + newaction.example_response = JSON.stringify( + newbody, + null, + 2 + ); + //newaction.example_response = JSON.stringify(parameter.properties, null, 2) + } else { + //newaction.example_response = parameter.properties + } + } + } + } + } + } + } + } + } + } + + for (let paramkey in methodvalue.parameters) { + const parameter = handleGetRef( + methodvalue.parameters[paramkey], + data + ); + + if (parameter.in === "query") { + var tmpaction = { + description: parameter.description, + name: parameter?.name, + required: parameter.required, + in: "query", + }; + + if ( + parameter.example !== undefined && + parameter.example !== null + ) { + tmpaction.example = parameter.example; + } + + if (parameter.required === undefined) { + tmpaction.required = false; + } + + newaction.queries.push(tmpaction); + } else if (parameter.in === "path") { + // FIXME - parse this to the URL too + newaction.paths.push(parameter?.name); + + // FIXME: This doesn't follow OpenAPI3 exactly. + // https://swagger.io/docs/specification/describing-request-body/ + // https://swagger.io/docs/specification/describing-parameters/ + // Need to split the data. + } else if (parameter.in === "body") { + // FIXME: Add tracking for components + // E.G: https://raw.githubusercontent.com/owentl/Shuffle/master/gosecure.yaml + if ( + parameter.example !== undefined && + parameter.example !== null + ) { + if ( + newaction.body === undefined || + newaction.body === null || + newaction.body.length < 5 + ) { + newaction.body = parameter.example; + } + } + } else if (parameter.in === "header") { + newaction.headers += `${parameter?.name}=${parameter.example}\n`; + } else { + } + } + + // Check if body is valid JSON. + if ( + newaction.body !== undefined && + newaction.body !== null && + newaction.body.length > 0 + ) { + // Trim starting / ending newlines, spaces and tabs + newaction.body = newaction.body.trim(); + } + + if (newaction?.name === "" || newaction?.name === undefined) { + // Find a unique part of the string + // FIXME: Looks for length between /, find the one where they differ + // Should find others with the same START to their path + // Make a list of reserved names? Aka things that show up only once + if (Object.getOwnPropertyNames(wordlist).length === 0) { + for (let [newpath, pathvalue] of Object.entries(data.paths)) { + const newpathsplit = newpath.split("/"); + + for (let splitkey in newpathsplit) { + const pathitem = newpathsplit[splitkey].toLowerCase(); + if (wordlist[pathitem] === undefined) { + wordlist[pathitem] = 1; + } else { + wordlist[pathitem] += 1; + } + } + } + } + + // Remove underscores and make it normal with upper case etc + const urlsplit = path.split("/"); + if (urlsplit.length > 0) { + var curname = ""; + for (let urlkey in urlsplit) { + var subpath = urlsplit[urlkey]; + if (wordlist[subpath] > 2 || subpath.length < 1) { + continue; + } + + curname = subpath; + break; + } + + // FIXME: If name exists, + // FIXME: Check if first part of parsedname is verb, otherwise use method + const parsedname = curname + .split("_") + .join(" ") + .split("-") + .join(" ") + .split("{") + .join(" ") + .split("}") + .join(" ") + .trim(); + if (parsedname.length === 0) { + newaction.errors.push("Missing name"); + } else { + const newname = + method.charAt(0).toUpperCase() + + method.slice(1) + + " " + + parsedname; + const searchactions = newActions.find( + (data) => data?.name === newname + ); + + if (searchactions !== undefined) { + newaction.errors.push("Missing name"); + } else { + newaction.name = newname; + } + } + } else { + newaction.errors.push("Missing name"); + } + } + + //newaction.action_label = "No Label" + newActions.push(newaction); + } + } + + if (data.servers !== undefined && data.servers.length > 0) { + var firstUrl = data.servers[0].url; + if ( + firstUrl.includes("{") && + firstUrl.includes("}") && + data.servers[0].variables !== undefined + ) { + const regex = /{\w+}/g; + const found = firstUrl.match(regex); + if (found !== null) { + for (let foundkey in found) { + const item = found[foundkey].slice(1, found[foundkey].length - 1); + const foundVar = data.servers[0].variables[item]; + if (foundVar["default"] !== undefined) { + firstUrl = firstUrl.replace( + found[foundkey], + foundVar["default"] + ); + } + } + } + } + + if (firstUrl.endsWith("/")) { + parentUrl = firstUrl.slice(0, firstUrl.length - 1); + } else { + parentUrl = firstUrl; + } + } + } + var prefixCheck = "/v1"; + if (parentUrl.includes("/")) { + const urlsplit = parentUrl.split("/"); + if (urlsplit.length > 2) { + // Skip if http:// in it too + prefixCheck = "/" + urlsplit.slice(3).join("/"); + } + + if ( + prefixCheck.length > 0 && + prefixCheck !== "/" && + prefixCheck.startsWith("/") + ) { + for (var actionKey in newActions) { + const action = newActions[actionKey]; + + if ( + action.url !== undefined && + action.url !== null && + action.url.startsWith(prefixCheck) + ) { + newActions[actionKey].url = action.url.slice( + prefixCheck.length, + action.url.length + ); + } + } + } + } + + setServerUrl(parentUrl); + var newActions2 = []; + // Remove with duplicate action URLs + for (var actionKey in newActions) { + const action = newActions[actionKey]; + if (action.url === undefined || action.url === null) { + continue; + } + + var found = false; + for (var actionKey2 in newActions2) { + const action2 = newActions2[actionKey2]; + if (action2.url === undefined || action2.url === null) { + continue; + } + + if (action.url === action2.url) { + found = true; + break; + } + } + + if (!found) { + newActions2.push(action); + } else { + newActions2.push(action); + } + } + + newActions = newActions2; + + // Rearrange them by which has action_label + const firstActions = newActions.filter( + (data) => + data.action_label !== undefined && + data.action_label !== null && + data.action_label !== "No Label" + ); + const secondActions = newActions.filter( + (data) => + data.action_label === undefined || + data.action_label === null || + data.action_label === "No Label" + ); + newActions = firstActions.concat(secondActions); + setActions(newActions); + setExampleBody(newActions[0]?.body); + }, [openapi]); + + return ( +
    + + + + +
    + ); +}); + +export default ApiExplorer; + + +const ActionResponseAndRequest = memo(({ConfigurationTab, selectedAppData,actions, info, HandleApiExecution, userdata, filteredActions, setFilteredActions, serverurl, globalUrl, setSelectedActionIndex, ExampleBody, setExampleBody, selectedActionIndex}) => { + const [apiResponse, setApiResponse] = useState({}); + const [isLoading, setIsLoading] = useState(false); + const loadAction = 10; + const loadedAction = useRef(null); + + const loadMoreActions = useCallback(() => { + if (isLoading || filteredActions.length >= actions.length) return; + + setIsLoading(true); + + setFilteredActions((prevActions) => { + const newActions = actions.slice(prevActions.length, prevActions.length + loadAction); + setIsLoading(false); + return [...prevActions, ...newActions]; + }); + }, [isLoading, actions.length, filteredActions.length, loadAction]); + + // Scroll position reference + const scrollPosition = useRef(0); + + // Handle scroll event with debounce + const handleScroll = useCallback(() => { + const actionContainer = loadedAction.current; + if ( + actionContainer && + actionContainer.scrollTop + actionContainer.clientHeight >= actionContainer.scrollHeight - 10 + ) { + loadMoreActions(); + } + scrollPosition.current = actionContainer?.scrollTop || 0; + }, [loadMoreActions]); + + // Add scroll event listener on mount and remove on unmount + useEffect(() => { + const actionContainer = loadedAction.current; + if (actionContainer) { + actionContainer.addEventListener("scroll", handleScroll); + } + return () => { + if (actionContainer) { + actionContainer.removeEventListener("scroll", handleScroll); + } + }; + }, [handleScroll]); + + + // Restore scroll position when the component rerenders or new items are added + useEffect(() => { + const actionContainer = loadedAction.current; + if (actionContainer) { + actionContainer.scrollTop = scrollPosition.current; + } + }, [actions, filteredActions]); + + useEffect(() => { + if (actions?.length > 0 && filteredActions?.length === 0) { + setFilteredActions(actions.slice(0, loadAction)); + } + }, [actions?.length]); + + return ( + +
    +
    + {filteredActions.map((action, index) => ( +
    + +
    + ))} +
    + + +
    + )}) + + +const ActionsList = memo(({ + actions, + selectedActionIndex, + setSelectedActionIndex, + setExampleBody, + setFilteredActions, + filteredActions, + userdata, + info, + openapi, +}) => { + + const [searchQuery, setSearchQuery] = useState(""); + const [visibleActions, setVisibleActions] = useState([]); + + + useEffect(() => { + if (visibleActions?.length === 0 && actions?.length > 0) { + setVisibleActions(actions) + } + }, [actions?.length]) + + const handleActionClick = (index, action) => { + const actionId = action.name.replace(/ /g, "-").replace(/_/g, "-"); + setSelectedActionIndex(index); + setExampleBody(action.example_response); + + const actionIndex = actions.findIndex((act) => { + const id = act.name.replace(/ /g, "-").replace(/_/g, "-"); + return id === actionId; + }); + + if (actionIndex !== -1 && !filteredActions.some((act) => { + const id = act.name.replace(/ /g, "-").replace(/_/g, "-"); + return id === actionId; + })) { + const newActionToLoad = [ + ...filteredActions, + ...actions.slice(filteredActions.length, actionIndex + 1) + ]; + setFilteredActions(newActionToLoad); + } + + // Update URL hash and scroll to action + window.history.pushState(null, "", `#${actionId}`); + const actionElement = document.getElementById(actionId); + if (actionElement) { + actionElement.scrollIntoView({ behavior: "smooth", block: "start" }); + } +}; + + const handleSearch = (e) => { + const query = e.target.value; + setSearchQuery(query); + if (query.length === 0) { + setVisibleActions(actions); + } else { + setVisibleActions( + actions.filter((action) => + action.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) + ); + } + }; + return ( +
    +
    +
    + {info?.title ? ( +
    + app logo + + {info.title} + +
    + ) : ( + + Api Explorer + + )} +
    +
    + + + + ), + style: { height: "100%", marginTop: 10, width: '90%', }, + }} + sx={{ + marginLeft: 2, + width:'100%', + "& .MuiOutlinedInput-root fieldset": { + border: "1px solid rgba(73, 73, 73, 1)", + }, + }} + /> +
    + {visibleActions.length > 0 ? ( + visibleActions.map((action, actionIndex) => ( + + )) + ) : ( +
    + No actions found +
    + )} +
    +
    + ); +}); + + + +const Action = memo(( + { + action, + index, + serverurl, + setApiResponse, + setExampleBody, + globalUrl, + info, + setSelectedActionIndex, + selectedActionIndex, + HandleApiExecution, + ConfigurationTab, + }, + ) => + { + const [RequestHeader, setRequestHeader] = useState([{ key: "Content-Type", value: "application/json" }]); + const [RequestBody, setRequestBody] = useState(action?.body); + const editorRef = useRef(null); + const [AceEditorHeight, setAceEditorHeight] = useState(275) + const [baseUrl, setBaseUrl] = useState(serverurl) + const [path, setPath] = useState(action?.url) + const inputRef = useRef(null); + const [shouldChageInputFocus, setShouldChangeInputFocus] = useState(true); + const [disableExecuteButton, setDisableExecuteButton] = useState(false); + const [showResponseLoader, setShowResponseLoader] = useState(false); + const [appAuthentication, setAppAuthentication] = useState([]) + const parseHeaders = (headersString) => { + if (headersString?.length > 0) { + const headersArray = headersString.split("\n"); + const parsedHeaders = headersArray + .map((header) => { + const [key, value] = header.split("="); // Split by '=' to get key-value pairs + + // Only proceed if both key and value exist, and neither is undefined + if (key && value) { + return { key: key.trim(), value: value.trim() }; + } + return null; // Return null if the header is invalid + }) + .filter(Boolean); // Filter out any null values + + setRequestHeader(parsedHeaders); + } + }; + + useEffect(() => { + if (action?.headers) { + parseHeaders(action.headers); + } + }, []); + + const [RequestParams, setRequestParams] = useState([ + { + key: "", + value: "", + }, + ]); + + const [curTab, setCurTab] = useState(0) + const [actionUrl, setActionUrl] = useState(action?.url) + + const [selectedMethod, setSelectedMethod] = useState(action?.method) + + const fix_url = (newUrl) => { + if (newUrl.includes("hhttp")) { + newUrl = newUrl.replace("hhttp", "http"); + } + + if (newUrl.includes("http:/") && !newUrl.includes("http://")) { + newUrl = newUrl.replace("http:/", "http://"); + } + if (newUrl.includes("https:/") && !newUrl.includes("https://")) { + newUrl = newUrl.replace("https:/", "https://"); + } + if (newUrl.includes("http:///")) { + newUrl = newUrl.replace("http:///", "http://"); + } + if (newUrl.includes("https:///")) { + newUrl = newUrl.replace("https:///", "https://"); + } + if (!newUrl.includes("http://") && !newUrl.includes("https://")) { + newUrl = `http://${newUrl}`; + } + return newUrl; + }; + + function isValidMethod(method) { + const validMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]; + method = method.toUpperCase(); + + if (validMethods.includes(method)) { + return method; + } else { + throw new Error(`Invalid HTTP method: ${method}`); + } + } + + function fixHeader(headers) { + if (Array.isArray(headers)) { + return headers.reduce((acc, header) => { + if (header.key.trim() !== "" || header.value.trim() !== "") { + acc[header.key.trim()] = header.value.trim(); + } + return acc; + }, {}); + } + + const parsedHeaders = {}; + + if (typeof headers === 'string' && headers) { + const splitHeaders = headers.split("\n"); + + splitHeaders.forEach(header => { + let splitItem; + if (header.includes(":")) { + splitItem = ":"; + } else if (header.includes("=")) { + splitItem = "="; + } else { + return; + } + + const splitHeader = header.split(splitItem); + if (splitHeader.length >= 2) { + const key = splitHeader[0].trim(); + const value = splitHeader.slice(1).join(splitItem).trim(); + parsedHeaders[key] = value; + } + }); + } + + return parsedHeaders; + } + + function fixParams(queries) { + if (Array.isArray(queries)) { + return queries + .filter(query => query.key.trim() !== "" || query.value.trim() !== "") + .map(query => ({ key: query.key.trim(), value: query.value.trim() })); + } + + const parsedQueries = []; + if (typeof queries === 'string') { + if (!queries.trim()) return parsedQueries; + const cleanedQueries = queries.trim().replace(/\s+/g, " "); + const splittedQueries = cleanedQueries.split("&"); + splittedQueries.forEach(query => { + if (!query.includes("=")) { + console.info("Skipping as there is no '=' in the query"); + return; + } + const [key, value] = query.split("="); + if (!key.trim() || !value.trim()) { + console.info("Skipping because either key or value is not present in query"); + return; + } + parsedQueries.push({ key: key.trim(), value: value.trim() }); + }); + } + + return parsedQueries; + } + + async function prepareResponse(response) { + try { + const parsedHeaders = {}; + response.headers.forEach((value, key) => { + parsedHeaders[key] = value; + }); + + const cookies = {}; + if (response.headers.has("set-cookie")) { + const cookieHeader = response.headers.get("set-cookie").split(";"); + cookieHeader.forEach(cookie => { + const [key, value] = cookie.split("="); + if (key && value) { + cookies[key.trim()] = value.trim(); + } + }); + } + + const textData = await response.text(); + + let parsedBody; + try { + parsedBody = JSON.parse(textData); + } catch (error) { + console.error("Error parsing JSON response:", error); + parsedBody = textData; + } + + return { + success: true, + status: response.status, + url: response.url, + body: parsedBody, + headers: parsedHeaders, + cookies: cookies, + }; + } catch (error) { + console.error("Error preparing response:", error); + return { + success: false, + status: response?.status, + error: error.message, + }; + } + } + + const handleRequestWithCustomAction = async (selectedMethod, baseUrl, path, RequestHeader, RequestBody, RequestParams, info, action) => { + + if((HandleApiExecution !== undefined || HandleApiExecution !== null) && typeof HandleApiExecution === 'function'){ + + try { + if (baseUrl.length === 0) { + setBaseUrl(serverurl) + } + if (path.length === 0) { + setPath(action.url) + } + + const apiResponse = await HandleApiExecution( + selectedMethod, baseUrl, path, RequestHeader, RequestBody, RequestParams, info, action, setCurTab, + ) + + const response = { + "action name" : action.name.replaceAll("_", " "), + ...apiResponse + }; + + if (typeof response.result === "string") { + try { + response.result = JSON.parse(response.result); + } catch (parseError) { + console.error("Error parsing result:", parseError); + toast.error("Error parsing response result."); + } + } + + setDisableExecuteButton(false); + setApiResponse(response); // Set the response with the action name added + setShowResponseLoader(false); + + return apiResponse; + + } catch (error) { + console.error("Error during HandleApiExecution:", error); + toast.error(`Error: ${error.message}`); + return { error: error.message }; + } + } else{ + + const newUrl = fix_url(baseUrl); + let validMethod; + try { + validMethod = isValidMethod(selectedMethod); + } catch (error) { + console.error(error); + toast.error(error.message); + return { error: error.message }; + } + try { + + if (path && !path.startsWith('/')) { + path = '/' + path; + } + + const finalUrl = newUrl + path; + const newHeader = fixHeader(RequestHeader); + const newParams = fixParams(RequestParams); + + if (typeof RequestBody === 'object') { + try { + RequestBody = JSON.stringify(RequestBody); + } catch (error) { + console.error(`Error: ${error}`); + toast.error("Invalid JSON format for request body: ", error); + return { error: "Invalid JSON format for request body" }; + } + } + const queryString = new URLSearchParams(newParams.map(param => [param.key, param.value])).toString(); + const fullUrl = queryString ? `${finalUrl}?${queryString}` : finalUrl; + const response = await fetch(fullUrl, { + method: validMethod, + headers: newHeader, + body: validMethod !== 'GET' ? RequestBody : undefined, + }); + + const preparedResponse = await prepareResponse(response); + + setApiResponse(preparedResponse); + + return preparedResponse; + + } catch (error) { + console.error("Error:", error); + toast.error(`${error.message} Please ensure all fields are filled out correctly and try again.`); + return { error: error.message }; + } + } + }; + + const addRequestParamsRow = () => { + setRequestParams((prevRows) => { + const updatedRows = [...RequestParams, { key: "", value: "" }]; + return updatedRows; + }); + }; + + const handleRequestParamsChange = (rowIndex, field, value) => { + setRequestParams( + RequestParams.map((row, index) => { + return { + ...row, + [field]: index === rowIndex ? value : row[field], + }; + }) + ); + }; + + const addRow = () => { + setRequestHeader((prevRows) => { + const updatedRows = [...RequestHeader, { key: "", value: "" }]; + return updatedRows; + }); + }; + + const handleInputChange = (rowIndex, field, value) => { + setRequestHeader( + RequestHeader.map((row, i) => { + return { + ...row, + [field]: i === rowIndex ? value : row[field], + }; + }) + ); + }; + const handleChangeTab = (actionIndex, newValue) => { + setCurTab(newValue); + }; + + const shouldShowBodyTab = ![ + "GET", + "CONNECT", + "OPTIONS", + "TRACE", + "HEAD", + ].includes(selectedMethod); + + const extractParamsFromText = (text) => { + const params = []; + const queryString = text.split("?")[1]; + + if (queryString) { + const pairs = queryString.split("&"); + pairs.forEach((pair) => { + const [key, value] = pair.split("="); + if (key && value) { + params.push({ key, value }); + } + }); + } + + return params.length > 0 ? params : [{ key: "", value: "" }]; + }; + + const actionRef = useRef(null); + const scrollTimeoutRef = useRef(null); + const [isUserInteracting, setIsUserInteracting] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + throttle((entries) => { + if (!isUserInteracting) return; + let nextSelectedActionIndex = null; + + entries.forEach((entry) => { + if (entry.isIntersecting && selectedActionIndex !== index) { + nextSelectedActionIndex = index; + } + }); + + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + + if (nextSelectedActionIndex !== null) { + scrollTimeoutRef.current = setTimeout(() => { + if (selectedActionIndex !== nextSelectedActionIndex) { + setSelectedActionIndex(nextSelectedActionIndex); + const actionId = action.name.replace(/ /g, "-").replace(/_/g, "-"); + window.history.pushState(null, "", `#${actionId}`); + setExampleBody(action?.example_response); + document.getElementById(`action-list-${nextSelectedActionIndex}`).scrollIntoView({ behavior: "smooth", block: "center" }); + } + }, 300); + } + }, 200), + { threshold: 0.5 } + ); + + if (actionRef.current) { + observer.observe(actionRef.current); + } + + return () => { + if (actionRef.current) { + observer.unobserve(actionRef.current); + } + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + }; + }, [isUserInteracting]); + + const handleAceEditorChange = (value) => { + setRequestBody(value); + if (editorRef.current) { + const editor = editorRef.current.editor; + const lineHeight = editor.renderer.lineHeight; + const minHeight = 100; + const maxHeight = 300; + const session = editor.getSession(); + const screenLength = session.getScreenLength(); + const contentHeight = screenLength * lineHeight; + const padding = 20; + + // Calculate new height + let newHeight = Math.min( + Math.max( + minHeight, + contentHeight + padding + ), + maxHeight + ); + + if (newHeight !== AceEditorHeight) { + if (value.length < (editorRef.current._lastValue || '').length) { + if (contentHeight + padding < AceEditorHeight) { + setAceEditorHeight(newHeight); + } + } else { + if (contentHeight + padding > AceEditorHeight) { + setAceEditorHeight(newHeight); + } + } + } + editorRef.current._lastValue = value; + } + }; + + useEffect(() => { + if (editorRef.current) { + const editor = editorRef.current.editor; + editor.commands.addCommand({ + name: "executeOnCtrlEnter", + bindKey: { win: "Ctrl-Enter", mac: "Command-Enter" }, + exec: () => { + setShowResponseLoader(true); + setDisableExecuteButton(true); + handleRequestWithCustomAction( + selectedMethod, + baseUrl, + path, + RequestHeader, + RequestBody, + RequestParams, + info, + action + ); + }, + }); + } + }, [editorRef.current]); + + + return ( +
    setIsUserInteracting(true)} + > +
    + + {action.name} + +
    + + { + if (e.key === "Enter") { + if (actionUrl.length === 0) { + toast.error("URL cannot be empty"); + return; + }else{ + setShowResponseLoader(true); + setDisableExecuteButton(true) + handleRequestWithCustomAction(selectedMethod, baseUrl, path, RequestHeader, RequestBody, RequestParams, info, action); + } + }} + } + onClick={(e) => { + const clickPosition = e.target.selectionStart; + const baseUrlLength = baseUrl.length; + if (shouldChageInputFocus) { + e.target.setSelectionRange(baseUrlLength + clickPosition, baseUrlLength + clickPosition); + setShouldChangeInputFocus(false); + } + }} + onFocus={(e) => { + const validParams = RequestParams.filter(param => param.key.trim().length > 0 && param.value.trim().length > 0); + const fullUrl = validParams.length > 0 ? `${baseUrl}${path}?${validParams.map(param => `${param.key}=${param.value}`).join("&")}` : `${baseUrl}${path}`; + if (fullUrl.length === 0) { + setActionUrl(serverurl + action?.url); + }else{ + setActionUrl(fullUrl); + } + if(!shouldChageInputFocus){ + setShouldChangeInputFocus(true); + } + }} + + onBlur={(e) => { + if(e.target.value.trim().length === 0) { + setActionUrl(path); + }else if(path.length === 0){ + setActionUrl(action?.url); + }else if(baseUrl.length === 0){ + setBaseUrl(serverurl) + setActionUrl(path) + }else{ + const validParams = RequestParams.filter(param => param.key.trim().length > 0 && param.value.trim().length > 0); + const validPath = validParams?.length > 0 ? `${path}?${validParams.map(param => `${param.key}=${param.value}`).join("&")}` : path; + setActionUrl(validPath) + } + setShouldChangeInputFocus(false); + }} + + onChange={(e) => { + const newUrl = e.target.value; + setActionUrl(newUrl); + const params = extractParamsFromText(newUrl); + setRequestParams(params); + + if (newUrl.startsWith("http://") || newUrl.startsWith("https://")) { + try { + const url = new URL(newUrl); + setBaseUrl(url.origin); + const newPath = decodeURIComponent(url.pathname);; + + setPath(newPath); + } catch (error) { + console.error("Invalid URL:", error); + } + } + }} + /> + + +
    + {showResponseLoader? ( + + ) : null} + +
    + + handleChangeTab(index, newValue) + } + aria-label="basic tabs example" + > + + Headers + + {...a11yProps(0)} + /> + {shouldShowBodyTab && ( + + Body + + {...a11yProps(1)} + /> + )} + + Params + + {...a11yProps(shouldShowBodyTab ? 2 : 1)} + /> + {ConfigurationTab ? ( + + Configuration + + {...a11yProps(shouldShowBodyTab ? 3 : 2)} + /> + ) : null} + +
    + + + + + + + Key + + + Value + + + + + {RequestHeader.map((row, rowIndex) => ( + + + + handleInputChange( + rowIndex, + "key", + e.target.value + ) + } + inputProps={{ + style: { + backgroundColor: "rgba(33, 33, 33, 1)", + padding: "4px 8px", + }, + }} + sx={{ + "& .MuiOutlinedInput-root": { + "& fieldset": { + border: "none", + }, + "&:hover fieldset": { + border: "none", + }, + "&.Mui-focused fieldset": { + border: "none", + }, + }, + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + document.getElementById( + `header-value-${index}-${rowIndex}` + ).focus(); + } + if (e.key === "Backspace" && row.key.length === 0 && rowIndex !== 0) { + setRequestHeader(RequestHeader.filter((header, i) => i !== rowIndex)); + document.getElementById( + `header-value-${index}-${rowIndex - 1}` + ).focus(); + e.preventDefault() + } + }} + /> + + + + handleInputChange( + rowIndex, + "value", + e.target.value + ) + } + inputProps={{ + endAdornment: ( + + + + ), + style: { + backgroundColor: "rgba(33, 33, 33, 1)", + padding: "4px 8px", + }, + }} + sx={{ + "& .MuiOutlinedInput-root": { + "& fieldset": { + border: "none", + }, + "&:hover fieldset": { + border: "none", + }, + "&.Mui-focused fieldset": { + border: "none", + }, + }, + }} + onKeyDown={(e) => { + if (e.key === "Enter" && !e.ctrlKey) { + addRow(); + setTimeout(() => { + document.getElementById( + `header-key-${index}-${rowIndex + 1}` + ).focus(); + }, 0); + } + if (e.ctrlKey && e.key === "Enter") { + setShowResponseLoader(true); + setDisableExecuteButton(true) + handleRequestWithCustomAction(selectedMethod, baseUrl, path, RequestHeader, RequestBody, RequestParams, info, action); + } + if (e.key === "Backspace" && row.value.length === 0 && rowIndex !== 0) { + setRequestHeader(RequestHeader.filter((header, i) => i !== rowIndex)); + document.getElementById( + `header-value-${index}-${rowIndex - 1}` + ).focus(); + e.preventDefault() + }} + } + /> + + + ))} + +
    +
    + +
    + {shouldShowBodyTab && ( + + + + )} + + + + + + + Key + + + Value + + + + + {RequestParams.map((row, rowIndex) => ( + + + + handleRequestParamsChange( + rowIndex, + "key", + e.target.value + ) + } + onKeyDown={(e) => { + if (e.key === "Enter") { + document.getElementById( + `param-value-${index}-${rowIndex}` + ).focus(); + } + if (e.key === "Backspace" && row.value.length === 0 && rowIndex !== 0) { + setRequestParams(RequestParams.filter((param, i) => i !== rowIndex)); + document.getElementById( + `param-value-${index}-${rowIndex - 1}` + ).focus(); + } + }} + /> + + + + handleRequestParamsChange( + rowIndex, + "value", + e.target.value + ) + } + onKeyDown={(e) => { + if (e.key === "Enter" && !e.ctrlKey) { + addRequestParamsRow(); + setTimeout(() => { + document.getElementById( + `param-key-${index}-${rowIndex + 1}` + ).focus(); + }, 0); + } + if (e.ctrlKey && e.key === "Enter") { + setShowResponseLoader(true); + setDisableExecuteButton(true) + handleRequestWithCustomAction(selectedMethod, baseUrl, path, RequestHeader, RequestBody, RequestParams, info, action); + } + + if (e.key === "Backspace" && row.value.length === 0 && rowIndex !== 0) { + setRequestParams(RequestParams.filter((param, i) => i !== rowIndex)); + document.getElementById( + `param-value-${index}-${rowIndex - 1}` + ).focus(); + e.preventDefault() + } + } + } + /> + + + ))} + +
    +
    + +
    + {(ConfigurationTab && ((shouldShowBodyTab && curTab === 3 ) || (!shouldShowBodyTab && curTab === 2)))? ( + + ) : null} +
    +
    +
    +
    + + {action.name.replaceAll("_", " ")} + +

    + {action.description + ? action.description + : ""} +

    +
    +
    +
    + ); +}) + +const ActionResponse = memo(({ apiResponse, ExampleBody, userdata }) => { + const [height, setHeight] = useState("14vh") + const [responseTabIndex, setResponseTabIndex] = useState(0) + const [oldResponse, setOldResponse] = useState(apiResponse) + const [highlight, setHighlight] = useState(false) + + const MIN_HEIGHT = 50 + + useEffect(() => { + var apiResp = apiResponse + var oldResp = oldResponse + try { + apiResp = JSON.stringify(apiResponse) + } catch (error) { + //console.error("Error parsing JSON response:", error); + } + + try { + oldResp = JSON.stringify(oldResponse) + } catch (error) { + //console.error("Error parsing JSON response:", error); + } + + if (apiResp === oldResp) { + return + } + + setOldResponse(apiResponse) + + //console.log("CHANGES MADE: ", apiResponse, oldResponse) + //toast("CHANGES!") + //console.log("HEIGHT: ", height) + + if (height === "14vh") { + setHeight("30vh") + } else { + // Check if height is less than 250px + var heightNum = 0 + try { + heightNum = parseInt(height.slice(0, -2)) + } catch (error) { + } + + if (heightNum < 350) { + setHeight("350px") + } + } + + setHighlight(true) + setTimeout(() => { + setHighlight(false) + }, 2000) + }, [apiResponse, ExampleBody]) + + const handleReactJsonClipboard = (copy) => { + const elementName = "copy_element_shuffle"; + let copyText = document.getElementById(elementName); + + if (copyText) { + if (copy.namespace && copy.name && copy.src) { + copy = copy.src; + } + + const clipboard = navigator.clipboard; + if (!clipboard) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + let stringified = JSON.stringify(copy); + if (stringified.startsWith('"') && stringified.endsWith('"')) { + stringified = stringified.slice(1, -1); + } + + navigator.clipboard.writeText(stringified); + toast("Copied value to clipboard, NOT json path."); + } else { + console.log("Failed to copy from " + elementName + ": ", copyText); + } + }; + + const stopResizing = () => { + window.removeEventListener("mousemove", startResizing); + window.removeEventListener("mouseup", stopResizing); + }; + + const initResize = (e) => { + e.preventDefault(); + window.addEventListener("mousemove", startResizing); + window.addEventListener("mouseup", stopResizing); + }; + + const startResizing = useCallback((e) => { + const newHeight = window.innerHeight - e.clientY; + if (newHeight >= MIN_HEIGHT) { + setHeight(`${newHeight}px`); + } + }, []); + + const formData = (exampleBody) => { + try { + return exampleBody ? JSON.parse(exampleBody) : {}; + } catch (error) { + console.error("Error parsing the example string:", error); + return {}; + } + }; + + useEffect(() => { + const handleResize = () => { + const newHeight = window.innerHeight * 0.1; + setHeight(`${newHeight}px`); + }; + + window.addEventListener('resize', handleResize); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return ( + +
    + {highlight === true ? + + : null + } +
    + setResponseTabIndex(newValue)} + > + Response} + {...a11yProps(0)} + /> + Example Response} + disabled={ExampleBody === undefined || ExampleBody === null || ExampleBody === ""} + {...a11yProps(1)} + /> + History} + disabled={true} + {...a11yProps(2)} + /> + +
    +
    + + + + + + + + +
    +
    +
    + ); +}); + +const ResponseTabWrapper = memo(({ apiResponse }) => { + const handleReactJsonClipboard = (copy) => { + const elementName = "copy_element_shuffle"; + let copyText = document.getElementById(elementName); + + if (copyText) { + if (copy.namespace && copy.name && copy.src) { + copy = copy.src; + } + + const clipboard = navigator.clipboard; + if (!clipboard) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + let stringified = JSON.stringify(copy); + if (stringified.startsWith('"') && stringified.endsWith('"')) { + stringified = stringified.slice(1, -1); + } + + navigator.clipboard.writeText(stringified); + toast("Copied value to clipboard, NOT json path."); + } else { + console.log("Failed to copy from " + elementName + ": ", copyText); + } + }; + + return( + { + return collapseField(jsonField) + }} + iconStyle={theme.palette.jsonIconStyle} + collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength} + enableClipboard={handleReactJsonClipboard} + displayDataTypes={false} + name={false} + /> + )}) + +const PaddingWrapper = memo(({ userdata, children }) => { + const { leftSideBarOpenByClick, windowWidth } = useContext(Context); + return ( +
    = 1920 ? "calc(100% - 630px)" : "calc(100% - 570px)" + : windowWidth >= 1920 ? "calc(100vw - 460px)": "calc(100% - 410px)" + : windowWidth >= 1920 ? "calc(100% - 370px)" : "calc(100% - 320px)", + backgroundColor: "#1a1a1a", + position: "fixed", + bottom: 0, + right: 0, + display: "flex", + flexDirection: "column", + borderTop: "1px solid #212121", + transition: "width 0.3s ease", + minHeight: "10%", + }} + > + {children} +
    + ); +}); + +const ApiResponseWrapper = memo(({ children, userdata }) => { + return ( + + {children} + + ); +}); diff --git a/frontend/src/components/ExecutionPanel.jsx b/frontend/src/components/ExecutionPanel.jsx new file mode 100644 index 00000000..e3712bdf --- /dev/null +++ b/frontend/src/components/ExecutionPanel.jsx @@ -0,0 +1,546 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { Box, Typography, IconButton, CircularProgress, Tooltip } from '@mui/material'; +import { CheckCircle, Error, ArrowBack, Close, Cached as CachedIcon, Pause as PauseIcon } from '@mui/icons-material'; +import theme from '../theme.jsx'; +import ReactJson from "react-json-view-ssr"; +import { toast } from 'react-toastify'; +import { validateJson } from "../views/Workflows.jsx"; +// import HandleJsonCopy from "./ShuffleCodeEditor1"; + +const STATUS_CONFIG = { + EXECUTING: { + color: '#64B5F6', + icon: () => , + label: 'Executing' + }, + SUCCESS: { + color: '#4CAF50', + icon: () => , + label: 'Success' + }, + FINISHED: { + color: '#4CAF50', + icon: () => , + label: 'Finished' + }, + ABORTED: { + color: '#F44336', + icon: () => , + label: 'Aborted' + } +}; + +let to_be_copied = "" + +const handleReactJsonClipboard = (copy) => { + toast("Copied JSON path to clipboard, NOT Path") +}; + + +const HandleJsonCopy = (base, copy, base_node_name) => { + if (typeof copy.name === "string") { + copy.name = copy.name.replaceAll(" ", "_"); + } + + //lol + if (typeof base === 'object' || typeof base === 'dict') { + base = JSON.stringify(base) + } + + if (base_node_name === "execution_argument" || base_node_name === "Execution Argument") { + base_node_name = "exec" + } + + console.log("COPY: ", base_node_name, copy); + + //var newitem = JSON.parse(base); + var newitem = validateJson(base).result + to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_"); + for (let copykey in copy.namespace) { + if (copy.namespace[copykey].includes("Results for")) { + continue; + } + + if (newitem !== undefined && newitem !== null) { + newitem = newitem[copy.namespace[copykey]]; + if (!isNaN(copy.namespace[copykey])) { + to_be_copied += ".#"; + } else { + to_be_copied += "." + copy.namespace[copykey]; + } + } + } + + if (newitem !== undefined && newitem !== null) { + newitem = newitem[copy.name]; + if (!isNaN(copy.name)) { + to_be_copied += ".#"; + } else { + to_be_copied += "." + copy.name; + } + } + + to_be_copied.replaceAll(" ", "_"); + const elementName = "copy_element_shuffle"; + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + console.log("NAVIGATOR: ", navigator); + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(to_be_copied); + copyText.select(); + copyText.setSelectionRange(0, 99999); /* For mobile devices * + + /* Copy the text inside the text field */ + document.execCommand("copy"); + toast("Copied JSON path to clipboard.") + console.log("COPYING!"); + } else { + console.log("Couldn't find element ", elementName); + } +} + +const ExecuteWorkflow = async (executionData, globalUrl) => { + try { + const workflowData = executionData.workflow; + + // Execute workflow with original parameters + await fetch(`${globalUrl}/api/v1/workflows/${workflowData.id}/execute`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + credentials: 'include', + body: workflowData + }).then(response => { + window.location.href = `/workflows/${workflowData.id}/code?execution_id=` + response.json().execution_id; + }); + + } catch (error) { + console.error('Error re-executing workflow:', error); + } +}; + +const ExecutionsList = ({ executions, onSelectExecution, activeExecutionId }) => { + return ( + + {executions.map((execution) => { + const status = STATUS_CONFIG[execution.status] || STATUS_CONFIG.ABORTED; + return ( + onSelectExecution(execution)} + sx={{ + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + py: 1, + px: 2, + borderBottom: '1px solid #2A2A2A', + backgroundColor: activeExecutionId === execution.execution_id ? + 'rgba(255,255,255,0.05)' : 'transparent', + '&:hover': { + backgroundColor: 'rgba(255,255,255,0.05)' + } + }} + > + {status.icon()} + + + + {new Date(execution.started_at * 1000).toLocaleString()} + + + {status.label} + + + + ); + })} + + ); +}; + +const ExecutionDetail = ({ execution: initialExecution, onBack, globalUrl, onExecutionUpdate, selectedAction, executeWorkflow }) => { + const [execution, setExecution] = useState(initialExecution); + const [status, setStatus] = useState(STATUS_CONFIG[execution.status] || STATUS_CONFIG.EXECUTING); + const [validResult, setValidResult] = useState("{}") + + const abortExecution = async () => { + try { + await fetch(`${globalUrl}/api/v1/workflows/${execution.workflow.id}/executions/${execution.execution_id}/abort`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }).then((response) => { + if (response.ok) { + const updatedExecution = { + ...execution, + status: "ABORTED", + }; + setExecution(updatedExecution); + onExecutionUpdate(updatedExecution); + } + }); + + } catch (error) { + console.log("Abort error:", error); + } + }; + + useEffect(() => { + setStatus(STATUS_CONFIG[execution.status] || STATUS_CONFIG.EXECUTING); + }, [execution]); + + const pollExecutionStatus = useCallback(async () => { + try { + const response = await fetch(`${globalUrl}/api/v1/streams/results`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + execution_id: execution.execution_id, + authorization: execution.authorization, + }), + }); + + if (response.ok) { + const data = await response.json(); + const currentStatus = data.results?.[0]?.status || 'EXECUTING'; + + const updatedExecution = { + ...execution, + ...data, + status: currentStatus + }; + + setExecution(updatedExecution); + onExecutionUpdate(updatedExecution); + + } + } catch (error) { + console.error('Polling error:', error); + } + }, [execution, globalUrl, onExecutionUpdate]); + + useEffect(() => { + let pollTimeout; + if (execution.status === 'EXECUTING') { + pollTimeout = setTimeout(() => pollExecutionStatus(), 3000); + } + + if (execution?.results?.length === 1) { + setValidResult(JSON.parse(execution?.results[0]?.result || "{}")) + } + + return () => clearTimeout(pollTimeout); + }, [execution.status, pollExecutionStatus]); + + return ( + + + + + + + Execution Details + + {status.icon()} + + {status.label} + + + + {execution.status === "EXECUTING" && ( + + + + + + )} + + + { + ExecuteWorkflow( + execution, + globalUrl + ); + }} + sx={{ color: theme.palette.primary.main }} + > + + + + + + + + + + Started at + + + {new Date(execution.started_at * 1000).toLocaleString()} + + + + + + Execution ID + + + {execution.execution_id} + + + + + + + Result + + +
    +                {execution?.status === 'EXECUTING' ? (
    +                  
    +                    
    +                    Executing...
    +                  
    +                ) : (
    +                  execution?.results?.length === 1 ?
    +                     {
    +                        handleReactJsonClipboard(copy);
    +                      }}
    +                      collapsed={false}
    +                      displayDataTypes={false}
    +                      onSelect={(select) => {
    +                        var basename = "exec"
    +                        if (selectedAction !== undefined && selectedAction !== null && Object.keys(selectedAction).length !== 0) {
    +                          basename = selectedAction.label.toLowerCase().replaceAll(" ", "_")
    +                        }
    +                        HandleJsonCopy(validResult, select, basename)
    +                      }}
    +                      name={"JSON autocompletion"}
    +                    /> :
    +                     { }}
    +                      displayDataTypes={false}
    +                      name={"JSON autocompletion"}
    +                    />
    +                )}
    +              
    +
    +
    +
    +
    +
    + ); +}; +const ExecutionPanel = ({ + workflow, + globalUrl, + onClose, + currentExecution, + mainAction +}) => { + const [executions, setExecutions] = useState([]); + const [selectedExecution, setSelectedExecution] = useState(null); + const [loading, setLoading] = useState(true); + + const handleExecutionUpdate = useCallback((updatedExecution) => { + setExecutions(prevExecutions => { + const updatedExecutions = [...prevExecutions]; + const index = updatedExecutions.findIndex( + e => e.execution_id === updatedExecution.execution_id + ); + if (index !== -1) { + updatedExecutions[index] = updatedExecution; + } + return updatedExecutions; + }); + }, []); + + const fetchExecutions = useCallback(async () => { + setLoading(true); + try { + const response = await fetch(`${globalUrl}/api/v2/workflows/${workflow.id}/executions`, { + credentials: 'include', + }); + if (response.ok) { + const data = await response.json(); + setExecutions(data.executions); + + const urlParams = new URLSearchParams(window.location.search); + const executionId = urlParams.get('execution_id'); + if (executionId) { + const execution = data.executions.find(e => e.execution_id === executionId); + if (execution) { + setSelectedExecution(execution); + } + } + } + } catch (error) { + console.error('Failed to fetch executions:', error); + } finally { + setLoading(false); + } + }, [workflow.id, globalUrl]); + + useEffect(() => { + fetchExecutions(); + }, [fetchExecutions]); + + useEffect(() => { + if (currentExecution?.execution_id) { + setExecutions(prev => { + const existingIndex = prev.findIndex(e => e.execution_id === currentExecution.execution_id); + if (existingIndex === -1) { + return [currentExecution, ...prev]; + } + const updated = [...prev]; + updated[existingIndex] = currentExecution; + return updated; + }); + setSelectedExecution(currentExecution); + } + }, [currentExecution]); + + return ( + + {loading && !executions.length ? ( + + + + ) : selectedExecution ? ( + { + setSelectedExecution(null); + const url = new URL(window.location); + url.searchParams.delete('execution_id'); + window.history.pushState({}, '', url); + fetchExecutions(); + }} + globalUrl={globalUrl} + selecteAction={mainAction} + onExecutionUpdate={handleExecutionUpdate} + /> + ) : ( + <> + + + Execution History + + + + + + + { + setSelectedExecution(execution); + const url = new URL(window.location); + url.searchParams.set('execution_id', execution.execution_id); + window.history.pushState({}, '', url); + }} + activeExecutionId={currentExecution?.execution_id} + /> + + + )} + + ); +}; + + +export default ExecutionPanel; diff --git a/frontend/src/components/MFASetUP.jsx b/frontend/src/components/MFASetUP.jsx new file mode 100644 index 00000000..270a81a9 --- /dev/null +++ b/frontend/src/components/MFASetUP.jsx @@ -0,0 +1,197 @@ +import React, { useEffect, useState } from "react"; +import { Paper, Typography, Box, CircularProgress, TextField, Button } from "@mui/material"; +import { toast } from "react-toastify"; + +const MFASetup = ({ isLoaded, globalUrl, setCookie }) => { + const [image2FA, setImage2FA] = useState(""); + const [secret2FA, setSecret2FA] = useState(""); + const [mfaCode, setMfaCode] = useState(""); + const [code, setCode] = useState(null); + + useEffect(() => { + handleGet2FACode(); + }, []); + + useEffect(() => { + if (isLoaded) { + const code = window.location.pathname.split("/")[2]; + setMfaCode(code); + } + }, [isLoaded]); + + const handleGet2FACode = () => { + if (mfaCode === "") { + return; + } + + fetch(`${globalUrl}/api/v1/users/${mfaCode}/get2fa`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status === 404) { + toast("User not found. Redirecting to login page in 3 seconds..."); + setTimeout(() => { + window.location.pathname = "/login"; + return; + }, 3000); + } + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + setImage2FA(responseJson.reason); + setSecret2FA(responseJson.extra); + } + }) + .catch((error) => { + toast(error.toString()); + }); + }; + + useEffect(() => { + if (mfaCode) { + handleGet2FACode(); + } + }, [mfaCode]); + + const handleVerify2FA = (mfaCode, code, changeMFAActive) => { + const data = { + code: code, + changeMFAActive: changeMFAActive, + }; + + toast("Verifying 2fa code. Please wait..."); + + fetch(`${globalUrl}/api/v1/users/${mfaCode}/set2fa`, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => { + if (response.status === 500) { + toast("Wrong code sent. Please try again."); + return; + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + toast.success("Successfully setup 2fa. Redirecting in 3 seconds..."); + for (var key in responseJson["cookies"]) { + setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }); + } + + const tmpView = new URLSearchParams(window.location.search).get("view"); + if (tmpView !== undefined && tmpView !== null) { + var newUrl = `/${tmpView}`; + if (tmpView.startsWith("/")) { + newUrl = `${tmpView}`; + } + window.location.pathname = newUrl; + return; + } + + if (responseJson.tutorials !== undefined && responseJson.tutorials !== null) { + const welcome = responseJson.tutorials.find((element) => element.name === "welcome"); + if (welcome === undefined || welcome === null) { + setTimeout(() => { + window.location.pathname = "/welcome"; + }, 3000); + } + } + + setTimeout(() => { + window.location.pathname = "/workflows"; + }, 3000); + } else { + toast("Failed to setup 2fa. Please try again."); + } + }) + .catch((error) => { + console.error("Error:", error); + }); + }; + + return ( +
    + + + Multi-Factor Authentication Setup + +
    + +
    + + Enter the code from your authenticator app below. + + setCode(e.target.value)} + onKeyPress={(e) => { + if (e.key === "Enter" && code !== null && code !== "" && code.length === 6) { + handleVerify2FA(mfaCode, code, true); + } + }} + /> + + +
    +
    + ); +}; + +const QRCodeSection = ({ secret2FA, image2FA }) => { + return ( +
    + {secret2FA && image2FA ? ( +
    + + Scan the image below with the two-factor authentication app on your phone. If you can’t use a QR code, use the code {secret2FA} instead. + + 2FA QR code +
    + ) : ( + + )} +
    + ); +}; + +export default MFASetup; diff --git a/frontend/src/views/ApiExplorerWrapper.jsx b/frontend/src/views/ApiExplorerWrapper.jsx new file mode 100644 index 00000000..0b9143c4 --- /dev/null +++ b/frontend/src/views/ApiExplorerWrapper.jsx @@ -0,0 +1,1787 @@ +import React, { memo, useCallback } from "react"; +import { useState, useEffect, useContext, Suspense } from "react"; +import { useNavigate, Link, useLocation } from "react-router-dom"; +import { toast } from "react-toastify"; +import { Context } from "../context/ContextApi.jsx"; +import { + Box, + IconButton, + MenuItem, + Select, + Skeleton, + Stack, + Collapse, + ListItem, + Typography, + Tab, + Button, + Dialog, + Tooltip, + DialogContent, + DialogTitle, + DialogActions, + TextField, + Divider, +} from "@mui/material"; + +import { validateJson, } from "../views/Workflows.jsx"; +import { isMobile } from "react-device-detect" +import theme from "../theme.jsx"; +import PaperComponent from "../components/PaperComponent.jsx"; +import { CodeHandler, Img, OuterLink, } from '../views/Docs.jsx' +import { v4 as uuidv4} from "uuid"; + +import { + ExpandLess as ExpandLessIcon, + ExpandMore as ExpandMoreIcon, + DragIndicator as DragIndicatorIcon, + Close as CloseIcon, + Edit as EditIcon, + LockOpen as LockOpenIcon, + Delete as DeleteIcon, + CheckCircle as CheckCircleIcon, +} from "@mui/icons-material"; + +import Markdown from "react-markdown"; +import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; +import algoliasearch from "algoliasearch/lite"; +import { green } from "../views/AngularWorkflow.jsx" + +const searchClient = algoliasearch( + "JNSS5CFDZZ", + "db08e40265e2941b9a7d8f644b6e5240" +) + +// Lazy loading of ApiExplorer component to reduce initial load time +const ApiExplorer = React.lazy(() => import("../components/ApiExplorer.jsx")); + + +const ApiExplorerWrapper = (props) => { + const { globalUrl, serverside, userdata, isLoggedIn} = props; + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" + const location = useLocation(); + const navigate = useNavigate(); + const [openapi, setOpenapi] = useState({}); + const [selectedAppData, setSelectedAppData] = useState({}) + const [selectedAuthentication, setSelectedAuthentication] = useState({}); + const [authenticationModalOpen, setAuthenticationModalOpen] = useState(false) + const [authenticationType, setAuthenticationType] = React.useState(""); + const [appAuthentication, setAppAuthentication] = useState([]); + const [selectedMeta, setSelectedMeta] = useState(undefined); + const [selectedAction, setSelectedAction] = useState( + { + "app_name": selectedAppData.name, + "app_id": selectedAppData.id, + "app_version": selectedAppData.version, + "large_image": selectedAppData.large_image, + } + ) + const [authHighlighted, setAuthHighlighted] = useState(false) + const [locations, setLocations] = React.useState([]) + const [selectedLocation, setSelectedLocation] = React.useState("") + + const appid = location.pathname.split("/")[2]; + const base64_decode = (str) => { + return decodeURIComponent( + atob(str) + .split("") + .map(function (c) { + return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join("") + ); + }; + + useEffect(() => { + if (selectedAppData !== undefined && selectedAppData !== null && Object.getOwnPropertyNames(selectedAppData).length > 0) { + HandleAppAuthentication(selectedAppData?.name) + } + }, [selectedAppData, openapi]) + + useEffect(() => { + if (appid !== undefined && appid !== null && appid.length !== 0) { + getAppData(appid) + HandleGetLocations() + } + + if (appAuthentication.length === 0 || selectedAuthentication.length === 0) { + HandleAppAuthentication() + } + }, [appid]); + + function Heading(props) { + const element = React.createElement( + `h${props.level}`, + { style: { marginTop: 40 } }, + props.children + ); + return ( + + {props.level !== 1 ? ( + + ) : null} + {element} + + ); + } + + const runAlgoliaAppSearch = (appname) => { + const index = searchClient.initIndex("appsearch"); + + console.log("Running appsearch for: ", appname); + + index + .search(appname) + .then(({ hits }) => { + + if (hits !== undefined && hits !== null && hits.length > 0) { + const appsearchname = appname.replaceAll("_", " ").toLowerCase() + var found = false + for (var key in hits) { + const hit = hits[key] + const newname = hit.name.replaceAll("_", " ").toLowerCase() + + if (newname?.includes(appsearchname)) { + found = true + getAppData(hit.objectID) + break + } + } + + if (!found) { + toast.error("Failed to get app data or App doesn't exist (1). Redirecting.."); + setTimeout(()=>{ + navigate("/search?tab=apps"); + },3000) + } + } else { + toast.error("Failed to get app data or App doesn't exist (2). Redirecting.."); + setTimeout(()=>{ + navigate("/search?tab=apps"); + },3000) + } + }) + .catch((err) => { + console.log(err); + }); + } + + // Fetch data when appid is available + const getAppData = useCallback((appid) => { + if (appid === undefined || appid === null || appid.length === 0) { + return + } + + if (appid.length !== 32) { + runAlgoliaAppSearch(appid) + return + } + + const url = `${globalUrl}/api/v1/apps/${appid}/config` + + fetch(url, { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + toast.error("Failed to get app data or App doesn't exist (3). Redirecting.."); + setTimeout(()=>{ + navigate("/search?tab=apps"); + },3000) + return; + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + handleDecodeOfOpenApiData(responseJson); + } else { + toast.error("Failed to get app data or App doesn't exist (4)"); + } + }) + .catch((error) => { + console.error("error for app is :", error); + }); + },[appid]); + + const handleDecodeOfOpenApiData = (data) => { + var appexists = false; + var parsedapp = {}; + + if (data.app !== undefined && data.app !== null) { + var parsedBaseapp = ""; + try { + parsedBaseapp = base64_decode(data.app); + } catch (e) { + parsedBaseapp = data; + } + + parsedapp = JSON.parse(parsedBaseapp); + parsedapp.name = parsedapp.name.replaceAll("_", " "); + + appexists = + parsedapp.name !== undefined && + parsedapp.name !== null && + parsedapp.name.length !== 0; + if(parsedapp?.id.length > 0){ + setSelectedAppData(parsedapp) + handleAppAuthenticationType(parsedapp) + const apptype = selectedAppData?.generated === false ? "python" : "openapi" + getAppDocs(parsedapp.name, apptype, parsedapp.version); + } + } + + if (data.openapi === undefined || data.openapi === null) { + return; + } + + var parsedDecoded = ""; + try { + parsedDecoded = base64_decode(data.openapi); + } catch (e) { + parsedDecoded = data; + } + + parsedapp = JSON.parse(parsedDecoded); + data = + parsedapp.body === undefined ? parsedapp : JSON.parse(parsedapp.body); + + setOpenapi(data); + }; + + const handleAppAuthenticationType = (selectedAppData) => { + + if (selectedAppData.authentication === undefined || selectedAppData.authentication === null) { + setAuthenticationType({ + type: "", + }) + + selectedAppData.authentication = { + type: "", + required: false, + } + } else { + setAuthenticationType( + selectedAppData.authentication.type === "oauth2-app" || (selectedAppData.authentication.type === "oauth2" && selectedAppData.authentication.redirect_uri !== undefined && selectedAppData.authentication.redirect_uri !== null) ? { + type: selectedAppData.authentication.type, + redirect_uri: selectedAppData.authentication.redirect_uri, + refresh_uri: selectedAppData.authentication.refresh_uri, + token_uri: selectedAppData.authentication.token_uri, + scope: selectedAppData.authentication.scope, + client_id: selectedAppData.authentication.client_id, + client_secret: selectedAppData.authentication.client_secret, + grant_type: selectedAppData.authentication.grant_type, + } : { + type: "", + } + ) + } + } + + const fix_url = (newUrl) => { + if (newUrl.includes("hhttp")) { + newUrl = newUrl.replace("hhttp", "http"); + } + + if (newUrl.includes("http:/") && !newUrl.includes("http://")) { + newUrl = newUrl.replace("http:/", "http://"); + } + if (newUrl.includes("https:/") && !newUrl.includes("https://")) { + newUrl = newUrl.replace("https:/", "https://"); + } + if (newUrl.includes("http:///")) { + newUrl = newUrl.replace("http:///", "http://"); + } + if (newUrl.includes("https:///")) { + newUrl = newUrl.replace("https:///", "https://"); + } + if (!newUrl.includes("http://") && !newUrl.includes("https://")) { + newUrl = `http://${newUrl}`; + } + return newUrl; + }; + + function isValidMethod(method) { + const validMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]; + method = method.toUpperCase(); + + if (validMethods.includes(method)) { + return method; + } else { + throw new Error(`Invalid HTTP method: ${method}`); + } + } + function fixHeader(headers) { + if (Array.isArray(headers)) { + return headers.reduce((acc, header) => { + if (header.key.trim() !== "" || header.value.trim() !== "") { + acc[header.key.trim()] = header.value.trim(); + } + return acc; + }, {}); + } + + const parsedHeaders = {}; + + if (typeof headers === 'string' && headers) { + const splitHeaders = headers.split("\n"); + + splitHeaders.forEach(header => { + let splitItem; + if (header.includes(":")) { + splitItem = ":"; + } else if (header.includes("=")) { + splitItem = "="; + } else { + return; + } + + const splitHeader = header.split(splitItem); + if (splitHeader.length >= 2) { + const key = splitHeader[0].trim(); + const value = splitHeader.slice(1).join(splitItem).trim(); + parsedHeaders[key] = value; + } + }); + } + + return parsedHeaders; + } + + function fixParams(queries) { + if (Array.isArray(queries)) { + return queries + .filter(query => query.key.trim() !== "" || query.value.trim() !== "") + .map(query => ({ key: query.key.trim(), value: query.value.trim() })); + } + + const parsedQueries = []; + if (typeof queries === 'string') { + if (!queries.trim()) return parsedQueries; + const cleanedQueries = queries.trim().replace(/\s+/g, " "); + const splittedQueries = cleanedQueries.split("&"); + splittedQueries.forEach(query => { + if (!query.includes("=")) { + console.info("Skipping as there is no '=' in the query"); + return; + } + const [key, value] = query.split("="); + if (!key.trim() || !value.trim()) { + console.info("Skipping because either key or value is not present in query"); + return; + } + parsedQueries.push({ key: key.trim(), value: value.trim() }); + }); + } + + return parsedQueries; + } + + const UpdateAppAuthentication = useCallback((data, appname) => { + if (data === undefined || data === null) { + return + } + + if (appname !== undefined && appname !== null && appname.length > 0) { + selectedAppData.name = appname + } + + const filteredData = data.filter((appAuth) => appAuth?.app?.id === appid || appAuth?.app?.name?.replaceAll(" ", "_").toLowerCase() === selectedAppData?.name?.replaceAll(" ", "_").toLowerCase()); + if (filteredData.length === 0) { + setAppAuthentication([]) + setSelectedAuthentication({}) + } else { + setAppAuthentication(filteredData) + setSelectedAuthentication(filteredData[0]) + } + }, [appid]) + + const HandleGetLocations = () => { + const url = `${globalUrl}/api/v1/environments`; + + fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + }).then((response) => { + if (response.status !== 200) { + return + } + + return response.json() + }).then((responseJson) => { + if (responseJson.success !== false) { + setLocations(responseJson) + } + }).catch((error) => { + console.error("Error loading locations:", error); + }) + } + + const HandleAppAuthentication = useCallback((appname) =>{ + + const url = `${globalUrl}/api/v1/apps/authentication`; + + fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + }).then((response) => { + if (response.status !== 200) { + return; + } + return response.json(); + }).then((responseJson) => { + if (responseJson.success === true) { + UpdateAppAuthentication(responseJson.data, appname) + } else { + toast.error("Failed to get app authentication data"); + } + }).catch((error) => { + console.error("error for app is :", error); + }); + }) + + const HandleApiExecution = useCallback(async (selectedMethod, url, path, RequestHeader, RequestBody, RequestParams, info, action, setCurTab, executionLocation) => { + + let validMethod; + try { + validMethod = isValidMethod(selectedMethod); + } catch (error) { + console.error(error); + toast.error(error.message); + return { error: error.message }; + } + + const headers = {}; + RequestHeader.forEach((header) => { + if (header.key.length > 0 && header.value.length > 0) { + headers[header.key] = header.value; + } + }); + + const formatArrayToString = (array) => { + return array + .map(item => (item.key.trim().length > 0 && item.value.trim().length > 0 ? `${item.key}=${item.value}` : "")) + .filter(str => str.length > 0) + .join("\n"); + }; + + var appid = ""; + + if (selectedAppData?.id?.length > 0) { + appid = selectedAppData?.id; + }else if (openapi?.id?.length > 0) { + appid = openapi?.id; + }else{ + toast.error("App id is missing. Please try again."); + return; + } + + const fullUrl = `${globalUrl}/api/v1/apps/${appid}/run`; + + var actionData = { + name: "custom_action", + app_name: info?.title, + app_version: info?.version, + app_id: appid, + authentication_id: selectedAuthentication?.id?.length > 0 ? selectedAuthentication?.id : "", + auth_not_required: false, + environment: isCloud ? "cloud" : "Shuffle", + node_type: "action", + parameters: [{ name: "url", value: fix_url(url)}], + } + + if (selectedLocation?.length > 0 && selectedLocation?.toLowerCase() !== "default") { + actionData.environment = selectedLocation + + // Find the env + for (var envkey in locations) { + const env = locations[envkey] + if (env.Name !== selectedLocation) { + continue + } + + if (env.Type === "cloud" || (env.running_ip !== undefined && env.running_ip !== null && env.running_ip.length > 0)) { + } else { + toast.warn(`Location ${env.Name} is not running and may not work as expected`) + } + break + } + } + + + const body = RequestBody; + const header = formatArrayToString(RequestHeader); + const param = fixParams(RequestParams); + + var hasBody = false + if (body.length > 0 && body !== "{}" && validMethod !== "GET" && validMethod !== "HEAD" && validMethod !== "OPTIONS" && validMethod !== "CONNECT" && validMethod !== "TRACE") { + hasBody = true + actionData.parameters.push({ + name: "body", + value: body, + }); + } + + if (header.length > 0) { + actionData.parameters.push({ + name: "headers", + value: header, + }); + } + + if (param.length > 0) { + const paramsString = new URLSearchParams(param.map(param => [param.key, param.value])).toString(); + actionData.parameters.push({ + name: "queries", + value: paramsString, + }); + } + if ( validMethod.length > 0) { + actionData.parameters.push({ + name: "method", + value: validMethod, + }); + } + + if (path.length > 0) { + actionData.parameters.push({ + name: "path", + value: path, + }); + } + + const options = { + method: "POST", + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(actionData), + credentials: 'include', + }; + + try { + const response = await fetch(fullUrl, options) + const data = await response.json() + + if (data.success === false) { + if (data.reason !== undefined && data.reason !== null && data.reason.length > 0) { + + if (data.reason.includes("authenticate")) { + toast.error("Authenticate the app first or add authentication headers"); + + setAuthHighlighted(true) + if (setCurTab !== undefined) { + if (hasBody) { + setCurTab(3) + } else { + setCurTab(2) + } + } + } + } + } else { + if (data.result !== undefined && data.result !== null && data.result.length > 0) { + const validate = validateJson(data.result) + if (validate.valid === true) { + if (validate.result.status === 401 || validate.result.status === 403) { + setAuthHighlighted(true) + + toast.info("You need to authenticate the app first, either with an API-key directly in the headers or with the Shuffle auth system") + + if (setCurTab !== undefined) { + if (hasBody) { + setCurTab(3) + } else { + setCurTab(2) + } + } + } else if (validate.result.status === 404) { + toast.error("Page not found. Please try a different URL.") + } else if (validate.result.error !== undefined && validate.result.error !== null && validate.result.error.length > 0) { + if (validate.result.error.toLowerCase().includes("max retries")) { + toast.error("Are you sure the URL is correct? It seems like the server is not responding.") + } + } + } + + if (data.result.includes("custom_action doesn't exist")) { + // No timeout error + toast.info("This API is being rebuilt due to missing functionality. Please wait a minute or two, then try again. If this persists, please report to support@shuffler.io", { + "autoClose": 90000, + }) + } else if (data.result.includes("authentication") && data.result.includes("Oauth2")) { + toast.error("Oauth2 apps require authentication") + + setAuthHighlighted(true) + if (setCurTab !== undefined) { + if (hasBody) { + setCurTab(3) + } else { + setCurTab(2) + } + } + } + + if (validate.valid === true) { + return validate.result + } + } + } + + return data + + } catch (error) { + console.error("Error during API execution:", error); + toast.error(`${error.message} Please ensure all fields are filled out correctly and try again.`); + return { error: error.message }; + } + + },[selectedAuthentication, selectedAppData, openapi]); + + + const AuthenticationList = () => { + const [openId, setOpenId] = useState(null); + const name = selectedAuthentication?.app?.name?.length > 0 ? selectedAuthentication?.label : "No Selection"; + const [authenticationName, setAuthenticationName] = useState(name) + + const toggleScope = (id, event) => { + event.stopPropagation(); + setOpenId((prevOpenId) => (prevOpenId === id ? null : id)); + }; + + return ( + + ); + }; + + const skeletonLoader = ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + + const AuthenticationData = (props) => { + const selectedApp = props.app; + + const [authenticationOption, setAuthenticationOptions] = React.useState({ + app: JSON.parse(JSON.stringify(selectedApp)), + fields: {}, + label: "", + usage: [ + { + // workflow_id: workflow.id, + }, + ], + id: uuidv4(), + active: true, + }); + + if ( + selectedApp.authentication === undefined || + selectedApp.authentication.parameters === null || + selectedApp.authentication.parameters === undefined || + selectedApp.authentication.parameters.length === 0 + ) { + return ( + + + {selectedApp.name} does not require authentication + + + ); + } + + authenticationOption.app.actions = []; + + for (let paramkey in selectedApp.authentication.parameters) { + if ( + authenticationOption.fields[ + selectedApp.authentication.parameters[paramkey].name + ] === undefined + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[paramkey].name + ] = ""; + } + } + + const setNewAppAuth = (appAuthData, refresh) => { + setSelectedAuthentication(appAuthData); + var headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + + headers["Org-Id"] = userdata?.active_org?.id + + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "PUT", + headers: headers, + body: JSON.stringify(appAuthData), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for setting app auth :O!"); + + if (response.status === 400) { + toast.error("Failed setting new auth. Please try again", { + "autoClose": true, + }) + } + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + toast.error("Error: " + responseJson.reason, { + "autoClose": false, + }) + + } else { + HandleAppAuthentication() + setAuthenticationModalOpen(false) + } + }) + .catch((error) => { + console.log("New auth error: ", error.toString()); + }); + }; + + const handleSubmitCheck = () => { + if (authenticationOption.label.length === 0) { + authenticationOption.label = `Auth for ${selectedApp.name}`; + } + for (let paramkey in selectedApp.authentication.parameters) { + if ( + authenticationOption.fields[ + selectedApp.authentication.parameters[paramkey].name + ].length === 0 + ) { + if ( + selectedApp.authentication.parameters[paramkey].value !== undefined && + selectedApp.authentication.parameters[paramkey].value !== null && + selectedApp.authentication.parameters[paramkey].value.length > 0 + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[paramkey].name + ] = selectedApp.authentication.parameters[paramkey].value; + } else { + if ( + selectedApp.authentication.parameters[paramkey].schema.type === "bool" + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[paramkey].name + ] = "false"; + } else { + toast( + "Field " + + selectedApp.authentication.parameters[paramkey].name + + " can't be empty" + ); + return; + } + } + } + } + + var newAuthOption = JSON.parse(JSON.stringify(authenticationOption)); + var newFields = []; + for (let authkey in newAuthOption.fields) { + const value = newAuthOption.fields[authkey]; + newFields.push({ + "key": authkey, + "value": value, + }); + } + + newAuthOption.fields = newFields + setNewAppAuth(newAuthOption) + } + + if (authenticationOption.label === null || authenticationOption.label === undefined) { + authenticationOption.label = selectedApp.name + " authentication"; + } + + return ( +
    + +
    + Authentication for {selectedApp.name.replaceAll("_", " ", -1)} +
    +
    + +
    + What is app authentication? + +
    + These are required fields for authenticating with {selectedApp.name} +
    + Label for you to remember + { + authenticationOption.label = event.target.value; + }} + /> + +
    + {selectedApp.authentication.parameters.map((data, index) => { + if (data.value === "" || data.value === null || data.value === undefined || data.name === "url") { + } + + + return ( +
    + + {data.name} + + {data.schema !== undefined && + data.schema !== null && + data.schema.type === "bool" ? ( + + ) : ( + { + authenticationOption.fields[data.name] = + event.target.value; + }} + /> + )} +
    + ); + })} + + + + + +
    + ); + }; + + 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) { + //toast("Successfully GOT app "+appId) + } else { + //toast("Failed getting app"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + if (responseJson.meta !== undefined && responseJson.meta !== null && Object.getOwnPropertyNames(responseJson.meta).length > 0) { + setSelectedMeta(responseJson.meta) + } + + if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) { + if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) { + // Translate into markdown ![]() + const imgRegex = / ({ + ...prevState, + documentation: newdata, + })); + } + } + } + + }) + .catch((error) => { + toast(error.toString()); + }); + }; + + const authenticationModal = authenticationModalOpen ? ( + {setSelectedMeta(undefined)}} + PaperProps={{ + style: { + pointerEvents: "auto", + color: "white", + minWidth: 1100, + minHeight: 700, + maxHeight: 700, + padding: 15, + overflow: "hidden", + zIndex: 10012, + border: theme.palette.defaultBorder, + }, + }} + > +
    + { selectedAppData.reference_info === undefined || + selectedAppData.reference_info === null || + selectedAppData.reference_info.github_url === undefined || + selectedAppData.reference_info.github_url === null || + selectedAppData.reference_info.github_url.length === 0 ? ( + + + {`Documentation + + ) : ( + + {`Documentation + + )} +
    + + + + + + { + setAuthenticationModalOpen(false); + }} + > + + +
    +
    + {authenticationType.type === "oauth2" || authenticationType.type === "oauth2-app" ? + + : + + } +
    +
    + {selectedAppData.documentation === undefined || + selectedAppData.documentation === null || + selectedAppData.documentation.length === 0 ? ( + +
    + + {selectedAppData?.description} + +
    + + +
    + + There is no Shuffle-specific documentation for this app yet outside of the general description above. Documentation is written for each api, and is a community effort. We hope to see your contribution! + + +
    + + + Want to help the making of, or improve this app?{" "} +
    + + Join the community on Discord! + +
    + + + Want to help change this app directly? + + {selectedAppData.reference_info === undefined || + selectedAppData.reference_info === null || + selectedAppData.reference_info.github_url === undefined || + selectedAppData.reference_info.github_url === null || + selectedAppData.reference_info.github_url.length === 0 ? ( + + + + Check it out on Github! + + + + ) : ( + + + + Check it out on Github! + + + + )} +
    + ) : ( +
    + {selectedMeta !== undefined && selectedMeta !== null && Object.getOwnPropertyNames(selectedMeta).length > 0 && selectedMeta.name !== undefined && selectedMeta.name !== null ? +
    +
    + {isMobile ? null : ( + + + + + + )} + {isMobile ? null : ( +
    + )} + + {selectedMeta.read_time} minute + {selectedMeta.read_time === 1 ? "" : "s"} to read + +
    +
    + {isMobile || + selectedMeta.contributors === undefined || + selectedMeta.contributors === null ? ( + "" + ) : ( +
    + {selectedMeta.contributors.slice(0, 7).map((data, index) => { + return ( + + + {data.url} + + + ); + })} +
    + )} +
    +
    + : null} + + + {selectedAppData.documentation} + +
    + )} +
    +
    +
    +) : null; + + const ConfigurationTab = memo((props) => { + return ( +
    +
    +
    + {isLoggedIn === true ? + + : + + + + } + + {appAuthentication?.length > 0 ? +
    + + or use + + +
    + +
    +
    + : null} +
    +
    + + {locations !== undefined && locations !== null && locations.length > 0 ? +
    + + Runtime location + + +
    + : null} +
    + )}); + + return ( + + + {authenticationModal} + + + + ); +}; + +export default ApiExplorerWrapper; + + +const Wrapper = ({children, userdata})=>{ + + const { leftSideBarOpenByClick } = useContext(Context); + + return( + +
    + {children} +
    + ) +} diff --git a/frontend/src/views/CodeWorkflow.jsx b/frontend/src/views/CodeWorkflow.jsx new file mode 100644 index 00000000..6959fe7d --- /dev/null +++ b/frontend/src/views/CodeWorkflow.jsx @@ -0,0 +1,466 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { toast } from 'react-toastify'; +import { Button, Box, Typography, Paper, Toolbar, Divider, CircularProgress } from '@mui/material'; + +import { + PlayArrow as PlayArrowIcon, + Save as SaveIcon, + History as HistoryIcon +} from '@mui/icons-material'; + +import ExecutionPanel from '../components/ExecutionPanel.jsx'; +import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; + +const CodeWorkflow = (defaultprops) => { + const { serverside, userdata, globalUrl, isLoaded, isLoggedIn, surfaceColor, inputColor, ...props } = defaultprops; + + const [workflow, setWorkflow] = useState({}); + + const [showExecutions, setShowExecutions] = useState(false); + // In CodeWorkflow, add this state + const [panelHeight, setPanelHeight] = useState(400); + const [executions, setExecutions] = useState([]); + const [currentExecution, setCurrentExecution] = useState(null); + const [mainAction, setMainAction] = useState(null); + const editorRef = useRef(null); + const [apiKey, setApiKey] = useState(""); + + const [editorData, setEditorData] = React.useState({ + "name": "", + "value": "", + "field_number": -1, + "actionlist": [], + "field_id": "", + }) + + const getSettings = async () => { + try { + const response = await fetch(`${globalUrl}/api/v1/getsettings`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }); + + if (response.status !== 200) { + console.log("Status not 200 for getsettings :O!"); + if (response.status >= 500) { + toast("Something went wrong while loading the settings. Please reload.") + } + return null; + } + + const responseJson = await response.json(); + setApiKey(responseJson.apikey); + console.log("API Key: ", responseJson.apikey); + + return responseJson.apikey; + } catch (error) { + console.log("Get settings error: ", error.toString()); + return null; + } + } + // Calculate editor height based on execution panel visibility + const getEditorHeight = () => { + return `calc(100vh - ${showExecutions ? `${panelHeight + 40}px` : '40px'})`; + }; + + let url = window.location.pathname; + const workflowId = url.split("/")[2]; + + const getWorkflow = async (workflow_id, sourcenode) => { + try { + const response = await fetch(`${globalUrl}/api/v1/workflows/${workflow_id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }); + + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + if (response.status >= 500) { + toast("Something went wrong while loading the workflow. Please reload.") + } + } + + const responseJson = await response.json(); + setWorkflow(responseJson); + + for (let i = 0; i < responseJson.actions.length; i++) { + if (responseJson.actions[i].app_id === "3e320a20966d33c9b7e6790b2705f0bf") { + console.log("Setting code to: ", responseJson.actions[i].parameters[0].value); + setCode(responseJson.actions[i].parameters[0].value); + setMainAction(responseJson.actions[i]); + + if (responseJson.actions[i].parameters[0].value.length === 0) { + // fetch API key of the user + const result = await getSettings(); + + console.log("accessible result: ", result); + + // await setCode(` + // from shufflepy import Shuffle + + // shuffle = Shuffle( + // "${result}", + // url='https://shuffler.io', + // ) + // ` + // ); + } + break; + } + } + } catch (error) { + console.log("Get workflows error: ", error.toString()); + } + }; + + useEffect(() => { + getWorkflow(workflowId); + }, []); + + // In CodeWorkflow component, add this effect: + useEffect(() => { + if (workflow.id) { + // Check URL for execution_id parameter + const urlParams = new URLSearchParams(window.location.search); + const executionId = urlParams.get('execution_id'); + if (executionId) { + setShowExecutions(true); // Show the panel if execution_id is present + } + } + }, [workflow]); + + const [code, setCode] = useState(""); + const [testResult, setTestResult] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + const saveLatestWorkflow = () => { + let newWorkflow = workflow; + + console.log("Workflow actions: ", newWorkflow.actions); + + // find the latest "Shuffle tools fork" node + for (let i = 0; i < newWorkflow.actions.length; i++) { + console.log("Actios: ", newWorkflow.actions[i]); + if (newWorkflow.actions[i].app_id === "3e320a20966d33c9b7e6790b2705f0bf") { + // update the code + console.log("Updating code: ", code); + newWorkflow.actions[i].parameters[0].value = code; + break; + } + } + + fetch(`${globalUrl}/api/v1/workflows/${workflow.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(newWorkflow), + }).then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + toast("Something went wrong while saving the workflow. Please try again.", { type: "error" }); + } else if (response.status === 200) { + toast("Workflow saved successfully!"); + } + }); + }; + + const getParents = async () => { + return [ + { + "label": "Execution Argument", + "type": "INTERNAL" + } + ] + } + + const handleRunCode = async () => { + saveLatestWorkflow(); + setLoading(true); + setError(""); + + let start_node = ""; + + for (let i = 0; i < workflow.actions.length; i++) { + if (workflow.actions[i].isStartNode) { + start_node = workflow.actions[i].id; + } + } + + try { + const response = await fetch(`${globalUrl}/api/v1/workflows/${workflow.id}/execute`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify({ + "start": start_node, + "execution_arguments": "", + }), + }); + + if (response.ok) { + const responseJson = await response.json(); + const newExecution = { + execution_id: responseJson.execution_id, + authorization: responseJson.authorization, + status: 'EXECUTING', + started_at: new Date().toISOString() + }; + setShowExecutions(true); + setCurrentExecution(newExecution); + } + + } catch (error) { + console.log("Error: ", error); + setError(error.toString()); + } finally { + setLoading(false); + } + }; + + + const handleSaveCode = async () => { + saveLatestWorkflow(); + }; + + const handleEditorDidMount = (editor, monaco) => { + if (monaco.languages && monaco.languages.python) { + if (monaco.languages.python.pythonDefaults) { + monaco.languages.python.pythonDefaults.setCompilerOptions({ + target: monaco.languages.typescript.ScriptTarget.ES2020, + allowNonTsExtensions: true + }); + } + } + + monaco.languages.setLanguageConfiguration('python', { + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" } + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" } + ] + }); + }; + + const handleEditorChange = (value) => { + setCode(value); + }; + + return ( + + {/* IDE-like toolbar */} + + + {workflow.name || 'Untitled Workflow'} + + + + + + + + + {/* Editor container */} + + {workflow && mainAction ? ( + { }} + toolsAppId={mainAction.app_id} + codedata={code} + setcodedata={setCode} + parameterName={editorData.name} + fieldCount={editorData.field_number} + actionlist={editorData.actionlist} + fieldname={editorData.field_id} + changeActionParameterCodeMirror={() => { }} + activeDialog={() => { }} + setActiveDialog={() => { }} + fullScreenMode={true} + /> + ) : ( + + + + )} + + {/* */} + + + {/* Create an input element called "copy_element_shuffle" that is not visible */} + + + {/* Results Panel */} + {(error || testResult) && ( + + {error && ( + + Error: +
    {error}
    +
    + )} + {testResult && ( + + Test Result: +
    {JSON.stringify(testResult, null, 2)}
    +
    + )} +
    + )} + + {/* In CodeWorkflow component */} + {showExecutions && ( + setShowExecutions(false)} + currentExecution={currentExecution} + height={panelHeight} + onHeightChange={setPanelHeight} // Add this prop to handle height updates + mainAction={mainAction} + /> + )} + +
    + ); +}; + +export default CodeWorkflow; From c3e4ea017b349f4f6bafee1340eefb4aba83ec71 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 30 Nov 2024 16:02:50 +0100 Subject: [PATCH 302/336] Workflow rebuild --- frontend/src/views/AngularWorkflow.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index a5685474..a88e4deb 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -9444,6 +9444,10 @@ const releaseToConnectLabel = "Release to Connect" return null } + if (trigger.trigger_type == "PIPELINE") { + return null + } + const imagesize = isMobile ? 40 : trigger.large_image.includes("svg") ? 50 : 50 var imageline = trigger.large_image.length === 0 ? : From c07088e5cde5d08ed61ca013693c51aca64900c1 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Sat, 30 Nov 2024 20:39:23 +0530 Subject: [PATCH 303/336] use swarm by default --- docker-compose.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 64155cc9..4bca9b95 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,11 +49,12 @@ services: - ORG_ID=Shuffle - BASE_URL=http://${OUTER_HOSTNAME}:5001 - DOCKER_API_VERSION=1.40 - - HTTP_PROXY=${HTTP_PROXY} - - HTTPS_PROXY=${HTTPS_PROXY} - SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY} - SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY} - SHUFFLE_STATS_DISABLED=true + - SHUFFLE_SWARM_CONFIG=run + - SHUFFLE_LOGS_DISABLED=true + - SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:nightly env_file: .env restart: unless-stopped security_opt: From 8f0b5029ab68de40036a319d4cfef92c95600f63 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Sat, 30 Nov 2024 20:41:46 +0530 Subject: [PATCH 304/336] added back the http --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 4bca9b95..12e1a011 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,6 +49,8 @@ services: - ORG_ID=Shuffle - BASE_URL=http://${OUTER_HOSTNAME}:5001 - DOCKER_API_VERSION=1.40 + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} - SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY} - SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY} - SHUFFLE_STATS_DISABLED=true From 2f8995e8447e85b0e1eb6b9d68d6c93899899808 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 30 Nov 2024 16:18:18 +0100 Subject: [PATCH 305/336] Force shuffle-database folder to exist --- shuffle-database/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 shuffle-database/README.md diff --git a/shuffle-database/README.md b/shuffle-database/README.md new file mode 100644 index 00000000..614e8b6b --- /dev/null +++ b/shuffle-database/README.md @@ -0,0 +1 @@ +TMP From e1a19c3ae07024319c5c011e2210923d916187f7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 30 Nov 2024 16:22:14 +0100 Subject: [PATCH 306/336] Fixed worker responses --- functions/onprem/worker/worker.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index ff0fb8ae..1bf6249d 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -3836,7 +3836,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { body, err := ioutil.ReadAll(request.Body) if err != nil { log.Printf("[WARNING] Failed reading body for stream result queue") - resp.WriteHeader(401) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -3846,7 +3846,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { err = json.Unmarshal(body, &execRequest) if err != nil { log.Printf("[WARNING] Failed shuffle.WorkflowExecution unmarshaling: %s", err) - resp.WriteHeader(401) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -3905,9 +3905,10 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { streamResultUrl, bytes.NewBuffer([]byte(fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, execRequest.ExecutionId, execRequest.Authorization))), ) + if err != nil { log.Printf("[ERROR][%s] Failed to create a new request", execRequest.ExecutionId) - resp.WriteHeader(401) + resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -3916,7 +3917,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { newresp, err := client.Do(req) if err != nil { log.Printf("[ERROR] Failed making request (2): %s", err) - resp.WriteHeader(401) + resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -3925,7 +3926,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { body, err = ioutil.ReadAll(newresp.Body) if err != nil { log.Printf("[ERROR][%s] Failed reading body (2): %s", execRequest.ExecutionId, err) - resp.WriteHeader(401) + resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -3938,7 +3939,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { //shutdown(workflowExecution, "", "", true) } - resp.WriteHeader(401) + resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad statuscode: %d"}`, newresp.StatusCode))) return } @@ -3946,7 +3947,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { err = json.Unmarshal(body, &workflowExecution) if err != nil { log.Printf("[ERROR] Failed workflowExecution unmarshal: %s", err) - resp.WriteHeader(401) + resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -3981,7 +3982,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { if workflowExecution.Status != "EXECUTING" { log.Printf("[WARNING] Exiting as worker execution has status %s!", workflowExecution.Status) log.Printf("[DEBUG] Shutting down (38)") - resp.WriteHeader(401) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad status %s for the workflow execution %s"}`, workflowExecution.Status, workflowExecution.ExecutionId))) return } @@ -4002,7 +4003,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) { err = executionInit(workflowExecution) if err != nil { log.Printf("[DEBUG][%s] Shutting down (30) - Workflow setup failed: %s", workflowExecution.ExecutionId, err) - resp.WriteHeader(401) + resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error in execution init: %s"}`, err))) return //shutdown(workflowExecution, "", "", true) From 565676f7de937406d193470a3883b9371f728848 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 30 Nov 2024 16:30:17 +0100 Subject: [PATCH 307/336] Removed compose version --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 12e1a011..1299bf8b 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3' services: frontend: image: ghcr.io/shuffle/shuffle-frontend:nightly From 3b2184957ee85a7f34207e7bcf1ac0c141e9bc89 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Sat, 30 Nov 2024 22:28:08 +0530 Subject: [PATCH 308/336] fixed lot of small issues when deployed in docker --- functions/onprem/orborus/orborus.go | 52 +++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index fdc7e539..803bf538 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -38,6 +38,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/network" @@ -340,7 +341,7 @@ func deployServiceWorkers(image string) { log.Printf("[ERROR] Memcached is not running. Will try to deploy it.") deployMemcached(dockercli) } - ip := getLocalIP() + ip := "shuffle-cache" os.Setenv("SHUFFLE_MEMCACHED", fmt.Sprintf("%s:11211", ip)) @@ -1973,6 +1974,12 @@ func main() { deployK8sWorker(workerImage, "shuffle-workers", []string{}) runString = "Run: \"kubectl get pods\" for more info" } + + err := setBackendToSwarmNetwork(ctx) + if err != nil { + log.Printf("[WARNING] Failed setting backend to swarm network: %s", err) + } + log.Printf("[DEBUG] Waiting 45 seconds to ensure workers are deployed. %s", runString) time.Sleep(time.Duration(45) * time.Second) @@ -2786,11 +2793,11 @@ func createAndStartTenzirNode(ctx context.Context, containerName, imageName stri }, } - if isKubernetes != "true" { + if isKubernetes != "true" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" { hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) } - _, err := dockercli.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName) + resp, err := dockercli.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName) if err != nil { if strings.Contains(err.Error(), "path does not exist") { log.Printf("[ERROR] Not using permanent pipeline storage as storage folder %s does not exist. If you want permanent storage, create the %s folder then restart Orborus (1). Raw: %s", tenzirStorageFolder, tenzirStorageFolder, err) @@ -2802,6 +2809,14 @@ func createAndStartTenzirNode(ctx context.Context, containerName, imageName stri return err } + if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" { + networkName := "shuffle_swarm_executions" + err = dockercli.NetworkConnect(ctx, networkName, resp.ID, nil) + if err != nil { + log.Printf("[ERROR] Error connecting tenzir container to network: %s", err) + } + } + err = dockercli.ContainerStart(ctx, containerName, containerStartOptions) if err != nil { if strings.Contains(err.Error(), "path does not exist") { @@ -3971,6 +3986,11 @@ func deployMemcached(dockercli *dockerclient.Client) error { log.Printf("[ERROR] Error starting memcached continer: %s", err) return err } + networkName := "shuffle_swarm_executions" + err = dockercli.NetworkConnect(ctx, networkName, resp.ID, nil) + if err != nil { + log.Printf("[ERROR] Error connecting memcached container to network: %s", err) + } log.Printf("[INFO] Memcached container started successfully at port 11211") @@ -4076,3 +4096,29 @@ func collectMetrics(ctx context.Context, dockerClient *dockerclient.Client) (int return len(executionRequests.Data), nil } + +func setBackendToSwarmNetwork(ctx context.Context) error { + containerId := "" + filterArgs := filters.NewArgs() + filterArgs.Add("name", "shuffle-backend") + + containers, err := dockercli.ContainerList(ctx, container.ListOptions{ + All: true, + Filters: filterArgs, + }) + if err != nil { + return err + } + if len(containers) == 0 { + return errors.New("No containers found with name shuffle-backend") + } + + containerId = containers[0].ID + networkName := "shuffle_swarm_executions" + err = dockercli.NetworkConnect(ctx, networkName, containerId, nil) + if err != nil { + log.Printf("[ERROR] Error connecting backend container to network: %s", err) + } + + return nil +} From 294840a9a0d5c3253e1f91a795018fb3ddaf6d9c Mon Sep 17 00:00:00 2001 From: lalitdeore Date: Sat, 30 Nov 2024 22:42:19 +0530 Subject: [PATCH 309/336] fix left side bar alignment issue --- frontend/public/icons/copyIcon.svg | 5 + frontend/public/icons/deleteIcon.svg | 4 + frontend/public/icons/detection.svg | 4 + frontend/public/icons/docker copy.svg | 5 + frontend/public/icons/documentation.svg | 4 + frontend/public/icons/downloadIcon.svg | 6 + frontend/public/icons/editIcon.svg | 3 + frontend/public/icons/expandMoreIcon.svg | 4 + frontend/src/App.jsx | 5 +- frontend/src/components/LeftSideBar.jsx | 239 +++++++++++++++-------- frontend/src/views/AngularWorkflow.jsx | 27 ++- frontend/src/views/Docs.jsx | 10 +- frontend/src/views/Search.jsx | 2 +- frontend/src/views/Usecases2.jsx | 2 +- 14 files changed, 213 insertions(+), 107 deletions(-) create mode 100644 frontend/public/icons/copyIcon.svg create mode 100644 frontend/public/icons/deleteIcon.svg create mode 100644 frontend/public/icons/detection.svg create mode 100644 frontend/public/icons/docker copy.svg create mode 100644 frontend/public/icons/documentation.svg create mode 100644 frontend/public/icons/downloadIcon.svg create mode 100644 frontend/public/icons/editIcon.svg create mode 100644 frontend/public/icons/expandMoreIcon.svg diff --git a/frontend/public/icons/copyIcon.svg b/frontend/public/icons/copyIcon.svg new file mode 100644 index 00000000..3efb3e78 --- /dev/null +++ b/frontend/public/icons/copyIcon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/public/icons/deleteIcon.svg b/frontend/public/icons/deleteIcon.svg new file mode 100644 index 00000000..41e0cce0 --- /dev/null +++ b/frontend/public/icons/deleteIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/public/icons/detection.svg b/frontend/public/icons/detection.svg new file mode 100644 index 00000000..a751f83d --- /dev/null +++ b/frontend/public/icons/detection.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/public/icons/docker copy.svg b/frontend/public/icons/docker copy.svg new file mode 100644 index 00000000..297bb83f --- /dev/null +++ b/frontend/public/icons/docker copy.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/public/icons/documentation.svg b/frontend/public/icons/documentation.svg new file mode 100644 index 00000000..28242959 --- /dev/null +++ b/frontend/public/icons/documentation.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/public/icons/downloadIcon.svg b/frontend/public/icons/downloadIcon.svg new file mode 100644 index 00000000..d9ed0beb --- /dev/null +++ b/frontend/public/icons/downloadIcon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/public/icons/editIcon.svg b/frontend/public/icons/editIcon.svg new file mode 100644 index 00000000..e2eb0660 --- /dev/null +++ b/frontend/public/icons/editIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/public/icons/expandMoreIcon.svg b/frontend/public/icons/expandMoreIcon.svg new file mode 100644 index 00000000..9bef6b01 --- /dev/null +++ b/frontend/public/icons/expandMoreIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e2c7a41d..9f6c7655 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -205,7 +205,7 @@ const App = (message, props) => { /> } -
    + {curpath.includes("/workflows") && curpath.includes("/run") ?
    : @@ -214,6 +214,7 @@ const App = (message, props) => {
    : +
    { {...props} /> +
    } -
    {/*
    diff --git a/frontend/src/components/LeftSideBar.jsx b/frontend/src/components/LeftSideBar.jsx index 1d0de5d5..72ec534c 100644 --- a/frontend/src/components/LeftSideBar.jsx +++ b/frontend/src/components/LeftSideBar.jsx @@ -795,7 +795,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { - {!leftSideBarOpenByClick && setExpandLeftNav(true);}} onMouseLeave={()=>{!leftSideBarOpenByClick && setExpandLeftNav(false);setOpenAutocomplete(false);}}> + {!leftSideBarOpenByClick && setExpandLeftNav(true);}} onMouseLeave={()=>{!leftSideBarOpenByClick && setExpandLeftNav(false);setOpenAutocomplete(false);}}> { marginTop: 2.5, }} > - + + - + + { setOpenSecurityTab((prev) => !prev); @@ -1106,82 +1133,126 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { - - - - - - - - - + + + + + + + + + + + + +
    - {/*selectedAction.id === workflow.start ? null : - - - - - */} {selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 1 ? ( @@ -1646,7 +1691,6 @@ const ParsedAction = (props) => {
    Name { style={{ backgroundColor: theme.palette.inputColor, color: "white", - height: 50, + height: 35, maxWidth: rightsidebarStyle.maxWidth - 80, borderRadius: theme.palette?.borderRadius, }} @@ -2109,7 +2153,7 @@ const ParsedAction = (props) => { ) : null} {selectedAction.authentication_id === "authgroups" && (authGroups === undefined || authGroups === null || authGroups.length === 0) ? - + Create your first Authentication group @@ -2336,7 +2380,7 @@ const ParsedAction = (props) => { fullWidth style={{ backgroundColor: theme.palette.inputColor, - height: 50, + height: 35, borderRadius: theme.palette?.borderRadius, }} onChange={(event, newValue) => { @@ -2491,43 +2535,6 @@ const ParsedAction = (props) => { /> ) : null} - {/*setNewSelectedAction !== undefined ? - - : null*/}
    { title={"Click to learn more about this action"} placement="top" > +
    + {/* + */} } @@ -2726,7 +2742,7 @@ const ParsedAction = (props) => { fullWidth style={{ backgroundColor: theme.palette.inputColor, - height: 50, + height: 35, borderRadius: theme.palette?.borderRadius, }} onChange={(event, newValue) => { @@ -2905,13 +2921,9 @@ const ParsedAction = (props) => { data.value = data.value.join(",") } - if ( - data.value !== undefined && - data.value !== null && - data.value.startsWith("{") && - data.value.endsWith("}") - ) { - multiline = true; + if (data.value !== undefined && data.value !== null && + data.value.startsWith("{") && data.value.endsWith("}")) { + multiline = true } var placeholder = "Value"; @@ -3013,6 +3025,91 @@ const ParsedAction = (props) => { textAlign: "center", }} > + + { + // Set localstorage + localStorage.setItem("hideBody", "true") + + setHideBody(false) + const updatedParameters = selectedActionParameters.map((param) => { + if (param.name === "body") { + return { + ...param, + id: "UNTOGGLED", + } + } + + if (param.description === openApiFieldDesc) { + // Check required fields here + if (selectedAction.required_body_fields !== undefined && selectedAction.required_body_fields !== null && selectedAction.required_body_fields.length > 0) { + // Look for the field name in the required_body_fields + if (selectedAction.required_body_fields.includes(param.name)) { + param.required = true + } else { + param.required = false + } + } + + return { ...param, field_active: true } + } + + return param + }) + + setSelectedActionParameters(updatedParameters) + }} + /> + { + localStorage.setItem("hideBody", "false") + setHideBody(true) + // Make sure the body field is shown + const updatedParameters = selectedActionParameters.map((param) => { + if (param.name === "body") { + return { + ...param, + id: "TOGGLED", + } + } + + if (param.description === openApiFieldDesc) { + return { ...param, field_active: false } + } + + return param + }) + + setSelectedActionParameters(updatedParameters) + }} + /> + + {/* { + */}
    - ); + ) var showButtonField = false if (selectedApp.generated === true && data.name === "body") { @@ -3159,7 +3257,7 @@ const ParsedAction = (props) => { description: openApiFieldDesc, example: "", id: "", - multiline: true, + multiline: false, name: tmpitem, options: null, required: isRequired, @@ -3170,7 +3268,7 @@ const ParsedAction = (props) => { variant: "STATIC_VALUE", field_active: true, - autocompleted: true, + autocompleted: false, }); } @@ -3230,7 +3328,8 @@ const ParsedAction = (props) => { tmpitem = "Password" } - multiline = data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline + // No longer multiline for new fields + //multiline = data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline if (data.name === "body") { //console.log("BODY: ", data) @@ -3293,7 +3392,7 @@ const ParsedAction = (props) => { localStorage.setItem("disabled_ui_box", "true") setUiBox("closed") }}> - + Don't show again
    @@ -3330,16 +3429,20 @@ const ParsedAction = (props) => { style={{ backgroundColor: theme.palette.inputColor, borderRadius: theme.palette?.borderRadius, + width: "100%", + maxHeight: multiline === true ? undefined : 40, + minHeight: 40, + border: selectedActionParameters[count].required || selectedActionParameters[count].configuration - ? "2px solid #f85a3e" + ? "1px solid #FF8544" : "", - color: "white", - width: "100%", - fontSize: "1em", }} InputProps={{ + style: { + maxHeight: multiline === true ? undefined : 40, + }, disableUnderline: true, endAdornment: hideExtraTypes ? null : ( @@ -3392,7 +3495,7 @@ const ParsedAction = (props) => { ), }} - multiline={data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline} + multiline={multiline} onClick={() => { /* setExpansionModalOpen(false); @@ -3802,7 +3905,7 @@ const ParsedAction = (props) => { ); } else if (data.variant === "STATIC_VALUE") { - staticcolor = "#f85a3e"; + staticcolor = "#FF8544"; } if (data.field_active === false) { @@ -3938,7 +4041,7 @@ const ParsedAction = (props) => { ); if (exec_text_field !== null) { if (inside) { - exec_text_field.style.border = "2px solid #f85a3e"; + exec_text_field.style.border = "2px solid #FF8544"; } else { exec_text_field.style.border = ""; } @@ -4228,7 +4331,7 @@ const ParsedAction = (props) => { >
    @@ -4240,65 +4343,12 @@ const ParsedAction = (props) => { flex: "10", marginTop: "auto", marginBottom: "auto", + color: "#C5C5C5", }} > - {tmpitem} + {tmpitem} {selectedActionParameters[count].required || selectedActionParameters[count].configuration ? "*" : ""}
    - {/*selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null : -
    - -
    { - e.preventDefault() - changeActionParameterVariant("STATIC_VALUE", count) - }}> - -
    -
    -  |  - -
    { - e.preventDefault() - changeActionParameterVariant("ACTION_RESULT", count) - }}> - -
    -
    -  |  - -
    { - e.preventDefault() - changeActionParameterVariant("WORKFLOW_VARIABLE", count) - }}> - -
    -
    -
    - */} - {/*(selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 && selectedActionParameters[count].required === true && selectedActionParameters[count].unique_toggled !== undefined) || hideExtraTypes ? null : -
    - -
    {}}> - { - //console.log("CHECKED!: ", selectedActionParameters[count]) - selectedActionParameters[count].unique_toggled = !selectedActionParameters[count].unique_toggled - selectedAction.parameters[count].unique_toggled = selectedActionParameters[count].unique_toggled - setSelectedActionParameters(selectedActionParameters) - setSelectedAction(selectedAction) - setUpdate(Math.random()) - }} - name="requires_unique" - /> -
    -
    -
    - */}
    {datafield} {/*shufflecode*/} @@ -4338,7 +4388,7 @@ const ParsedAction = (props) => { open={showAutocomplete} style={{ color: "white", - height: 50, + height: 35, marginTop: 2, borderRadius: theme.palette?.borderRadius, }} diff --git a/frontend/src/theme.jsx b/frontend/src/theme.jsx index 54e7a790..87aa89e1 100644 --- a/frontend/src/theme.jsx +++ b/frontend/src/theme.jsx @@ -4,13 +4,13 @@ import { createTheme, adaptV4Theme } from "@mui/material/styles"; const theme = createTheme(adaptV4Theme({ palette: { theme: "dark", - main: "#F86743", + main: "#FF8544", primary: { - main: "#F86743", + main: "#FF8544", contrastText: "#ffffff", }, secondary: { - main: "#e8eaf6", + main: "rgba(255,255,255,0.7)", contrastText: "#000000", }, text: { @@ -21,7 +21,8 @@ const theme = createTheme(adaptV4Theme({ inputColor: "rgba(39,41,45,1)", surfaceColor: "#27292d", - platformColor: "#1c1c1d", + //platformColor: "#1c1c1d", + platformColor: "#212121", backgroundColor: "#1a1a1a", green: "#5cc879", @@ -45,10 +46,13 @@ const theme = createTheme(adaptV4Theme({ overflowX: "auto", }, textFieldStyle: { - backgroundColor: "#383B40", + backgroundColor: "#212121", borderRadius: 5, }, innerTextfieldStyle: { + height: 40, + fontSize: 16, + backgroundColor: "#212121", // Removed since upgrading to mui 18 //color: "white", //minHeight: 50, diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 0b713135..c3e8d89d 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -360,11 +360,11 @@ export function removeParam(key, sourceURL) { const useStyles = makeStyles({ notchedOutline: { - borderColor: "#f85a3e !important", + borderColor: "#FF8544 !important", }, root: { "& .MuiAutocomplete-listbox": { - border: "2px solid #f85a3e", + border: "2px solid #FF8544", color: "white", fontSize: 18, "& li:nth-child(even)": { @@ -4463,7 +4463,7 @@ const releaseToConnectLabel = "Release to Connect" const elementMouseIsOver = document.elementFromPoint(x, y); if (elementMouseIsOver !== undefined && elementMouseIsOver !== null) { - // Color for #f85a3e translated to rgb + // Color for #FF8544 translated to rgb const newBorder = "3px solid rgb(248, 90, 62)"; if ( elementMouseIsOver.style.border !== newBorder && @@ -8739,6 +8739,11 @@ const releaseToConnectLabel = "Release to Connect" if (cy.edgehandles !== undefined) { cy.edgehandles({ handleNodes: (el) => { + // Check of length of el.data() is 1 + if (el.data() === undefined || Object.keys(el.data()).length === 1) { + return false + } + if (el.isNode() && el.data("buttonType") != "ACTIONSUGGESTION" && el.data("name") != "switch" && @@ -9231,7 +9236,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" href="https://shuffler.io/docs/workflows#workflow_variables" target="_blank" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > Workflow variables? @@ -9281,7 +9286,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" href="https://shuffler.io/docs/workflows#execution_variables" target="_blank" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > Runtime variables? @@ -11049,8 +11054,6 @@ const releaseToConnectLabel = "Release to Connect" results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input") results.push({ label: "Execution Argument", type: "INTERNAL" }) - console.log(results) - return results } @@ -11383,7 +11386,7 @@ const releaseToConnectLabel = "Release to Connect" width: "17px", height: "17px", borderRadius: 17 / 2, - backgroundColor: "#f85a3e", + backgroundColor: "#FF8544", marginRight: "10px", }} /> @@ -11420,7 +11423,7 @@ const releaseToConnectLabel = "Release to Connect" open={showAutocomplete} fullWidth style={{ - borderBottom: `1px solid #f85a3e`, + borderBottom: `1px solid #FF8544`, color: "white", height: 50, marginTop: 2, @@ -11450,7 +11453,7 @@ const releaseToConnectLabel = "Release to Connect" ); if (exec_text_field !== null) { if (inside) { - exec_text_field.style.border = "2px solid #f85a3e"; + exec_text_field.style.border = "2px solid #FF8544"; } else { exec_text_field.style.border = ""; } @@ -11598,6 +11601,9 @@ const releaseToConnectLabel = "Release to Connect" color: "white", minWidth: isMobile ? "90%" : 800, border: theme.palette.defaultBorder, + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} onClose={() => { @@ -11719,6 +11725,9 @@ const releaseToConnectLabel = "Release to Connect" color: "white", minWidth: isMobile ? "90%" : 650, border: theme.palette.defaultBorder, + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} onClose={() => { @@ -11930,6 +11939,7 @@ const releaseToConnectLabel = "Release to Connect" padding: 50, paddingBottom: 70, + borderRadius: theme.palette.borderRadius, borderImage: "linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) 1", }, }} @@ -12034,6 +12044,9 @@ const releaseToConnectLabel = "Release to Connect" color: "white", minWidth: isMobile ? "90%" : 800, border: theme.palette.defaultBorder, + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} onClose={() => { @@ -12055,7 +12068,7 @@ const releaseToConnectLabel = "Release to Connect" href="/docs/workflows#conditions" style={{ textDecoration: "none", - color: "#f85a3e", + color: "#FF8544", }} > Learn more @@ -12539,7 +12552,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" target="_blank" href="https://shuffler.io/docs/workflows#conditions" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > What are conditions? @@ -13292,7 +13305,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" target="_blank" href="https://shuffler.io/docs/triggers#subflow" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > What are subflows? @@ -13414,7 +13427,7 @@ const releaseToConnectLabel = "Release to Connect" target="_blank" style={{ textDecoration: "none", - color: "#f85a3e", + color: "#FF8544", marginLeft: 5, marginTop: 10, }} @@ -13707,7 +13720,7 @@ const releaseToConnectLabel = "Release to Connect" }} open={!!menuPosition} style={{ - border: `2px solid #f85a3e`, + border: `2px solid #FF8544`, color: "white", marginTop: 2, }} @@ -13729,7 +13742,7 @@ const releaseToConnectLabel = "Release to Connect" ); if (exec_text_field !== null) { if (inside) { - exec_text_field.style.border = "2px solid #f85a3e"; + exec_text_field.style.border = "2px solid #FF8544"; } else { exec_text_field.style.border = ""; } @@ -14089,7 +14102,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" target="_blank" href="https://shuffler.io/docs/workflows#comments" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > What are comments? @@ -14356,7 +14369,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" target="_blank" href="https://shuffler.io/docs/triggers#webhook" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > What are webhooks? @@ -14600,7 +14613,7 @@ const releaseToConnectLabel = "Release to Connect" width: "17px", height: "17px", borderRadius: 17 / 2, - backgroundColor: "#f85a3e", + backgroundColor: "#FF8544", marginRight: "10px", }} /> @@ -15084,7 +15097,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" target="_blank" href="https://shuffler.io/docs/triggers#user_input" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > What is the user input trigger? @@ -15471,7 +15484,7 @@ const releaseToConnectLabel = "Release to Connect" })}
    : -
    { +
    { setEditWorkflowModalOpen(true) toast.info("Expand and scroll down to add input-questions") }}> @@ -15498,7 +15511,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" target="_blank" href="https://shuffler.io/docs/triggers#pipelines" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > What are pipelines? @@ -15760,7 +15773,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" target="_blank" href="https://shuffler.io/docs/triggers#schedule" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > What are schedules? @@ -15864,12 +15877,12 @@ const releaseToConnectLabel = "Release to Connect" width: "17px", height: "17px", borderRadius: 17 / 2, - backgroundColor: "#f85a3e", + backgroundColor: "#FF8544", marginRight: "10px", }} />
    - When to start: {isCloud || selectedTrigger?.environment === "cloud" ? Cron formatting : "every X second"} + When to start: {isCloud || selectedTrigger?.environment === "cloud" ? Cron formatting : "every X second"}
    @@ -15990,7 +16003,7 @@ const releaseToConnectLabel = "Release to Connect" const cytoscapeViewWidths = isMobile ? 50 : 950; const bottomBarStyle = { - position: "fixed", + position: "absolute", transition: "all 0.3s ease", minWidth: windowWidth < 1600 && leftSideBarOpenByClick ? 800 : cytoscapeViewWidths, maxWidth: windowWidth < 1600 && leftSideBarOpenByClick ? 800 : cytoscapeViewWidths, @@ -15999,14 +16012,14 @@ const releaseToConnectLabel = "Release to Connect" zIndex: 10, transform: isMobile ? `translateX(20px)` - : `translateX(${leftSideBarOpenByClick ? 340 : 330}px)`, + : `translateX(280px)`, top: isMobile ? appBarSize + 55 : undefined, bottom: isMobile ? undefined : 0, }; const topBarStyle = { position: "fixed", - top: isMobile ? 30 : appBarSize - 20, + top: isMobile ? 30 : 35, pointerEvents: "none", transition: "transform 0.3s ease", transform: isMobile @@ -16032,7 +16045,7 @@ const releaseToConnectLabel = "Release to Connect" return (
    - + {/* + */}

    Warning: Change { toast("Changing to correct organisation. Please wait a few seconds.") @@ -16761,12 +16775,12 @@ const releaseToConnectLabel = "Release to Connect" style={{ border: "1px solid rgba(255,255,255,0.1)", position: "absolute", - bottom: 140, - left: leftSideBarOpenByClick ? 620 : 435, + bottom: 100, + left: leftSideBarOpenByClick ? 630 : 445, color: "white", padding: 10, borderRadius: theme.palette?.borderRadius, - transition: "left 0.3s ease, top 0.3s ease", + transition: "left 0.3s ease, top 0.3s ease", }} > @@ -16787,9 +16801,9 @@ const releaseToConnectLabel = "Release to Connect" - + {/**/} - Workflow Issues: {workflow.errors.length} + {workflow.errors.length} Workflow Issue{workflow.errors.length > 1 ? "s" : ""} { // Find it in cytoscape if (cy === undefined || cy === null) { @@ -16976,7 +16990,7 @@ const releaseToConnectLabel = "Release to Connect" top: "40%", width: 70, height: 235, - border: "1px solid #f85a3e", + border: "1px solid #FF8544", cursor: "pointer", borderRadius: theme.palette?.borderRadius, padding: 10, @@ -17084,7 +17098,8 @@ const releaseToConnectLabel = "Release to Connect" return null; } - const boxSize = isMobile ? 50 : 100; + const buttonHeights = 45 + const boxSize = buttonHeights+5 const executionButton = executionRunning ? ( @@ -17096,7 +17111,7 @@ const releaseToConnectLabel = "Release to Connect" abortExecution(); }} > - + @@ -17107,24 +17122,23 @@ const releaseToConnectLabel = "Release to Connect" workflow.public || executionRequestStarted } - style={{ height: boxSize, width: boxSize }} + style={{ height: boxSize, width: boxSize, backgroundColor: green, }} color="primary" variant="contained" onClick={() => { executeWorkflow(executionText, workflow.start, lastSaved); }} > - + ) return (
    - {executionButton}
    - {isMobile || workflow.public ? null : ( + + {executionButton} { setExecutionText(e.target.value); }} + // Start adornment + inputProps={{ + style: { + height: 18, + }, + }} /> - )} + {/*userdata.avatar === creatorProfile.github_avatar ? null :*/} - + + - {workflow.public || userdata.support == true ? ( + {workflow.public || userdata.support == true ? - ) : null} + : null} + + {/* + */} - - - - - + + {workflow.configuration !== null && workflow.configuration !== undefined && workflow.configuration.exit_on_error !== undefined ? ( @@ -17353,9 +17366,9 @@ const releaseToConnectLabel = "Release to Connect" + + + + + + + +
    @@ -18483,6 +18519,9 @@ const releaseToConnectLabel = "Release to Connect" color: "white", fontSize: 18, borderLeft: theme.palette.defaultBorder, + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} > @@ -18804,7 +18843,7 @@ const releaseToConnectLabel = "Release to Connect" {lastExecution === data.execution_id ? ( Env      - { + { window.open("/admin?tab=locations", "_blank") }}> {executionData.workflow.actions[0].environment} @@ -19131,7 +19170,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" href={`/admin?tab=app_auth`} target="_blank" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > Auth Group '{executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0 ? `${executionData.authgroup}` : null}' @@ -19141,7 +19180,7 @@ const releaseToConnectLabel = "Release to Connect" executionData.execution_parent.length > 0 ? ( executionData.execution_source === props.match.params.key ? { getWorkflowExecution( props.match.params.key, @@ -19156,7 +19195,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" href={`/workflows/${executionData.execution_source}?view=executions&execution_id=${executionData.execution_parent}`} target="_blank" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > Parent Workflow @@ -19167,7 +19206,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" href={`/forms/${executionData.workflow.id}`} target="_blank" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > Form @@ -19613,7 +19652,7 @@ const releaseToConnectLabel = "Release to Connect" data.action.parameters[0].value === props.match.params.key ? ( { getWorkflowExecution( props.match.params.key, @@ -19630,7 +19669,7 @@ const releaseToConnectLabel = "Release to Connect" target="_blank" style={{ textDecoration: "none", - color: "#f85a3e", + color: "#FF8544", }} onClick={(event) => { }} > @@ -19758,7 +19797,7 @@ const releaseToConnectLabel = "Release to Connect" Action Logs - Logs for an action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode. + Logs for an action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode.
    ) @@ -19839,7 +19878,7 @@ const releaseToConnectLabel = "Release to Connect" variant="body2" style={{ whiteSpace: 'pre-line', - color: showlink ? "#f85a3e" : "white", + color: showlink ? "#FF8544" : "white", cursor: showlink ? "pointer" : "default", }} onClick={(e) => { @@ -20005,7 +20044,7 @@ const releaseToConnectLabel = "Release to Connect" PaperComponent={PaperComponent} aria-labelledby="draggable-dialog-title" disableEnforceFocus={true} - style={{ pointerEvents: "none", zIndex : activeDialog === "result" ? 1200 : 1100 }} + style={{ pointerEvents: "none", zIndex : activeDialog === "result" ? 1200 : 1100, }} hideBackdrop={true} open={codeModalOpen} PaperProps={{ @@ -20019,6 +20058,9 @@ const releaseToConnectLabel = "Release to Connect" overflowY: "auto", overflowX: "hidden", border: theme.palette.defaultBorder, + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} > @@ -20198,7 +20240,7 @@ const releaseToConnectLabel = "Release to Connect" -
    +
    {curapp === null ? null : ( )} @@ -20574,7 +20617,10 @@ const releaseToConnectLabel = "Release to Connect" color: "white", border: theme.palette.defaultBorder, maxWidth: isMobile ? bodyWidth - 100 : 800, - minWidth: isMobile ? bodyWidth - 100 : 800, + minWidth: isMobile ? bodyWidth - 100 : 800, + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} > @@ -20589,7 +20635,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" href="https://shuffler.io/docs/workflows#execution_variables" target="_blank" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > here @@ -20751,6 +20797,9 @@ const releaseToConnectLabel = "Release to Connect" color: "white", border: theme.palette.defaultBorder, maxWidth: isMobile ? bodyWidth - 100 : "100%", + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} > @@ -21027,7 +21076,7 @@ const releaseToConnectLabel = "Release to Connect" target="_blank" rel="noopener noreferrer" href="https://shuffler.io/docs/apps#authentication" - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} > What is app authentication? @@ -21195,6 +21244,9 @@ const releaseToConnectLabel = "Release to Connect" color: "white", minWidth: 650, border: theme.palette.defaultBorder, + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} > @@ -21262,6 +21314,9 @@ const releaseToConnectLabel = "Release to Connect" overflow: "hidden", zIndex: 10012, border: theme.palette.defaultBorder, + + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} > @@ -21548,7 +21603,7 @@ const releaseToConnectLabel = "Release to Connect" rel="noopener noreferrer" target="_blank" href={selectedMeta.link} - style={{ textDecoration: "none", color: "#f85a3e" }} + style={{ textDecoration: "none", color: "#FF8544" }} >
    ); - console.log("docs render") - // Padding and zIndex etc set because of footer in cloud. const loadedCheck = ( From 6753410525244d1416a2dae842acbbec2c6e0890 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sat, 30 Nov 2024 21:39:45 +0530 Subject: [PATCH 312/336] Made some UI changes --- frontend/src/components/AppModal.jsx | 1 - frontend/src/views/Workflows2.jsx | 130 +++++++++++++++++++-------- 2 files changed, 94 insertions(+), 37 deletions(-) diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index a4bd272f..239cec7c 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -293,7 +293,6 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { sx: { borderRadius: 3, border: "1px solid var(--Container-Stroke, #494949)", - backgroundColor: "var(--Container, #212121)", minWidth: '440px', fontFamily: "Inter" } diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx index 3937345e..506574e0 100644 --- a/frontend/src/views/Workflows2.jsx +++ b/frontend/src/views/Workflows2.jsx @@ -2086,9 +2086,9 @@ const Workflows2 = (props) => { continue } - if (parsedAction.name === "Shuffle Tools" || parsedAction.id === "bc78f35c6c6351b07a09b7aed5d29652") { - continue - } + // if (parsedAction.name === "Shuffle Tools" || parsedAction.id === "bc78f35c6c6351b07a09b7aed5d29652") { + // continue + // } if (appsFound.findIndex(data => data.name === parsedAction.name) < 0) { appsFound.push(parsedAction) @@ -3287,11 +3287,11 @@ const Workflows2 = (props) => { {editingWorkflow.id !== undefined ? "Edit Workflow" : "Create New Workflow"} -
    +
    upload.click()} style={{ backgroundColor: "rgba(255,255,255,0.08)" }} > @@ -3299,7 +3299,7 @@ const Workflows2 = (props) => { setDialogModalOpen(false)} style={{ backgroundColor: "rgba(255,255,255,0.08)" }} > @@ -3532,7 +3532,7 @@ const Workflows2 = (props) => { setDefaultReturnValue(""); setEditingWorkflow({}); setNewWorkflowTags([]); - setModalOpen(false); + setDialogModalOpen(false); setSelectedUsecases([]) }} > @@ -4035,6 +4035,7 @@ const Workflows2 = (props) => {
    + { setFilters(chips); findWorkflow(chips); }} - style={{ flex: 0.9, borderRadius: 8 }} - // style={searchStyle} - //onAdd={(chip) => { - // console.log("ADd: ", chip); - // addFilter(chip); - //}} - //onDelete={(_, index) => { - // console.log("Remove: ", index); - // removeFilter(index); - //}} + style={{ flex: 0.9 }} + InputProps={{ + style: { + color: "white", + borderRadius: 8, + }, + }} + sx={{ + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.2)', + }, + '&:hover fieldset': { + borderColor: 'rgba(255, 255, 255, 0.3)', + }, + '&.Mui-focused fieldset': { + borderColor: '#f85a3e', + }, + }, + '& .MuiChip-root': { + backgroundColor: 'rgba(255, 255, 255, 0.1)', + color: 'white', + '& .MuiChip-deleteIcon': { + color: 'rgba(255, 255, 255, 0.7)', + '&:hover': { + color: 'white', + }, + }, + }, + }} /> - - From 6da219f53b42c73331ad2d2208372ab620391c8b Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sun, 1 Dec 2024 14:49:19 +0530 Subject: [PATCH 313/336] Create workflow form change to previous year --- frontend/src/views/Workflows2.jsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx index 506574e0..b0dded3c 100644 --- a/frontend/src/views/Workflows2.jsx +++ b/frontend/src/views/Workflows2.jsx @@ -724,7 +724,15 @@ const Workflows2 = (props) => { }; const handleCreateWorkflow = () => { - setDialogModalOpen(true) + setModalOpen(true) + setIsEditing(false) + setNewWorkflowName("") + setNewWorkflowDescription("") + setDefaultReturnValue("") + setEditingWorkflow({}) + setNewWorkflowTags([]) + setSelectedUsecases([]) + }; @@ -3232,7 +3240,7 @@ const Workflows2 = (props) => { { - setDialogModalOpen(true) + setModalOpen(true) setIsEditing(false) }} > @@ -4690,7 +4698,7 @@ const Workflows2 = (props) => { */} - {modalView} + {/* {modalView} */} {deleteModal} {exportVerifyModal} {publishModal} From e652d0039c84fe6dd73272ac7bce54adab3915ec Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sun, 1 Dec 2024 19:12:28 +0530 Subject: [PATCH 314/336] Done with the revamp of apps2 page : New look --- frontend/src/views/Apps2.jsx | 816 ++++++++++++++++++++--------------- 1 file changed, 468 insertions(+), 348 deletions(-) diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 7011be79..0f395471 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -15,6 +15,7 @@ import { } from "@mui/material"; import { Context } from "../context/ContextApi.jsx"; import Add from '@mui/icons-material/Add'; +import EditIcon from '@mui/icons-material/Edit'; import InputAdornment from '@mui/material/InputAdornment'; import Search from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; @@ -34,20 +35,23 @@ const searchClient = algoliasearch( ); // AppCard Component -const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab, handleAppClick }) => { +const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab, handleAppClick, leftSideBarOpenByClick, userdata }) => { const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000"; const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`; + var canEditApp = userdata.admin === "true" || userdata.id === data?.owner || data?.owner === "" || (userdata.admin === "true" && userdata.active_org.id === data?.reference_org) || !data?.generated const paperStyle = { - backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#1A1A1A", + backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#212121", color: "rgba(241, 241, 241, 1)", cursor: "pointer", - position: "relative", - width: 365, + fontFamily: "Inter", + // position: "relative", + width: "100%", height: 96, borderRadius: 8, boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)", marginBottom: 20, + transition: "width 0.3s ease", }; return ( @@ -63,7 +67,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, fontSize: 16, overflow: "hidden", display: "flex", - alignItems: "flex-start", + fontFamily: "Inter", width: '100%', backgroundColor: mouseHoverIndex === index ? "#2F2F2F" : "#212121" }} @@ -92,13 +96,14 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, fontWeight: '400', overflow: "hidden", margin: "12px 0", - fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" + fontFamily: "Inter" }}>
    -
    +
    {data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => ( {tag} @@ -134,44 +140,57 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
    {/* Deactivate button */} {currTab === 0 && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true && ( - + ) + } + + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success === false) { + toast.error(responseJson.reason); + } else { + toast.success("App Deactivated Successfully. Reload UI to see updated changes."); + } + }) + .catch(error => { + console.log("app error: ", error.toString()); + }); + }} + > + Deactivate + +
    )}
    @@ -192,6 +211,7 @@ const Hits = ({ globalUrl, isLoading, isLoggedIn, + leftSideBarOpenByClick }) => { const [hoverEffect, setHoverEffect] = useState(-1); const [allActivatedAppIds, setAllActivatedAppIds] = useState(userdata?.active_apps); @@ -330,7 +350,7 @@ const Hits = ({ color: "rgba(241, 241, 241, 1)", cursor: "pointer", position: "relative", - width: 365, + width: leftSideBarOpenByClick ? 325 : 365, height: 96, borderRadius: 8, boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)", @@ -374,7 +394,6 @@ const Hits = ({ fontWeight: '400', overflow: "hidden", margin: "12px 0", - fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} >
    {data.categories !== null @@ -514,46 +533,7 @@ const Hits = ({ ); } -// Custom Category Dropdown Component -const CategoryDropdown = ({ items, currentRefinement, refine }) => { - const handleChange = (event) => { - const value = event.target.value; - refine(value); - }; - return ( - - ); -}; - -const CustomCategoryDropdown = connectRefinementList(CategoryDropdown); // Custom SearchBox Component const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { @@ -608,10 +588,11 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { event.preventDefault(); } }} - style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} + style={{ borderRadius: 8, height: 45, fontFamily: "Inter", flex: 1 }} InputProps={{ style: { borderRadius: 8, + height: 45 }, endAdornment: ( @@ -637,6 +618,38 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { const CustomSearchBox = connectSearchBox(SearchBox); const CustomHits = connectHits(Hits); +// Custom Category Dropdown Component +const CategoryDropdown = ({ items, currentRefinement, refine }) => { + const handleChange = (event) => { + const value = event.target.value; + refine(value); + }; + + return ( + + ); +}; +const CustomCategoryDropdown = connectRefinementList(CategoryDropdown); + // Custom Label Dropdown Component const LabelDropdown = ({ items, currentRefinement, refine }) => { const handleChange = (event) => { @@ -652,15 +665,7 @@ const LabelDropdown = ({ items, currentRefinement, refine }) => { onChange={handleChange} displayEmpty multiple - style={{ - width: '100%', - maxWidth: '300px', - borderRadius: '7px', - fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - }} + style={{ borderRadius: 8, height: 45, fontFamily: "Inter", flex: 1 }} renderValue={(selected) => selected.length ? selected.join(', ') : 'All Labels'} > @@ -675,9 +680,44 @@ const LabelDropdown = ({ items, currentRefinement, refine }) => { ); }; - const CustomLabelDropdown = connectRefinementList(LabelDropdown); + + +// New filter function +const filterApps = (apps, searchQuery, selectedCategory, selectedLabel) => { + if (!Array.isArray(apps)) return []; + + return apps.filter((app) => { + const matchesSearchQuery = ( + searchQuery === "" || // If searchQuery is empty, match all apps + app.name.toLowerCase().includes(searchQuery.toLowerCase()) || + (app.tags && app.tags.some(tag => + tag.toLowerCase().includes(searchQuery.toLowerCase()) + )) || + (app.categories && app.categories.some((category) => + category.toLowerCase().includes(searchQuery.toLowerCase()) + )) + ); + + const matchesSelectedCategories = ( + selectedCategory.length === 0 || // If no category is selected, match all apps + (app.categories && app.categories.some(category => + selectedCategory.includes(category) + )) + ); + + const matchesSelectedTags = ( + selectedLabel.length === 0 || // If no label is selected, match all apps + (app.tags && app.tags.some(tag => + selectedLabel.includes(tag) + )) + ); + + return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags; + }); +}; + // Main Apps Component const Apps2 = (props) => { const { globalUrl, isLoaded, serverside, userdata, isLoggedIn, checkLogin } = props; @@ -803,14 +843,16 @@ const Apps2 = (props) => { } }, [currTab, globalUrl, userdata?.id]); // Remove location.search dependency - useEffect(() => { - setSearchQuery(""); - }, [currTab]) + // useEffect(() => { + // // setSearchQuery(""); + // setSelectedCategory([]); + // setSelectedLabel([]); + // }, [currTab]) // Find top categories and tags based on the current tab useEffect(() => { - if (currTab === 0) { + if (currTab === 0 || currTab === 1) { setCategories(findTopCategories()); setLabels(findTopTags()); } @@ -881,76 +923,81 @@ const Apps2 = (props) => { const handleCreateApp = (e) => { e.preventDefault(); - setOpenModal(true); + // setOpenModal(true); }; useEffect(() => { const apps = currTab === 1 ? userApps : orgApps; - // Search app based on app name, category, and tag - const filteredUserAppdata = Array.isArray(apps) ? apps.filter((app) => { - - const matchesSearchQuery = ( - searchQuery === "" || // If searchQuery is empty, match all apps - app.name.toLowerCase().includes(searchQuery.toLowerCase()) || - (app.tags && app.tags.some(tag => - tag.toLowerCase().includes(searchQuery.toLowerCase()) - )) || - (app.categories && app.categories.some((category) => - category.toLowerCase().includes(searchQuery.toLowerCase()) - )) - ); - - const matchesSelectedCategories = ( - selectedCategory.length === 0 || // If no category is selected, match all apps - (app.categories && app.categories.some(category => - selectedCategory.includes(category) - )) - ); - - const matchesSelectedTags = ( - selectedLabel.length === 0 || // If no label is selected, match all apps - (app.tags && app.tags.some(tag => - selectedLabel.includes(tag) - )) - ); - - return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags; - }) : []; - + const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel); setAppsToShow(filteredUserAppdata); - }, [searchQuery, selectedCategory, selectedLabel]); + }, [searchQuery, selectedCategory, selectedLabel, currTab]); const handleTabChange = (newTab) => { setCurrTab(newTab); + // Apply filters immediately when changing tabs if (newTab === 0) { - setAppsToShow(orgApps) - const categories = findTopCategories(); - const labels = findTopTags(); - setCategories(categories); - setLabels(labels); - } - if (newTab === 1) { - setAppsToShow(userApps) + const filteredOrgApps = filterApps(orgApps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredOrgApps); + } else if (newTab === 1) { + const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredUserApps); } + + // Update URL query params const newQueryParam = newTab === 0 ? 'org_apps' : newTab === 1 ? 'my_apps' : 'all_apps'; const queryParams = new URLSearchParams(location.search); queryParams.set('tab', newQueryParam); - queryParams.delete('q'); + // Only remove 'q' param if search is empty + if (!searchQuery) { + queryParams.delete('q'); + } window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); }; + // Update useEffect to handle initial load and URL search params + useEffect(() => { + const queryParams = new URLSearchParams(location.search); + const searchParam = queryParams.get('q'); + if (searchParam) { + setSearchQuery(searchParam); + } + }, []); + // Update useEffect for filtering to handle both tabs + useEffect(() => { + if (currTab === 2) return; // Skip for "Discover Apps" tab as it uses Algolia + const apps = currTab === 1 ? userApps : orgApps; + const filteredApps = filterApps(apps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredApps); + + // Update URL with search query + const queryParams = new URLSearchParams(location.search); + if (searchQuery) { + queryParams.set('q', searchQuery); + } else { + queryParams.delete('q'); + } + window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); + }, [searchQuery, selectedCategory, selectedLabel, currTab, userApps, orgApps]); + + // Update search input handler to maintain state across tabs + const handleSearchChange = (event) => { + const newSearchQuery = event.target.value; + setSearchQuery(newSearchQuery); + }; const boxStyle = { color: "white", display: "flex", flexDirection: "column", + height: "100%", width: "100%", margin: "auto", - maxWidth: "60%", + maxWidth: "70%", + fontFamily: "Inter", // padding: '20px 380px', }; @@ -975,214 +1022,287 @@ const Apps2 = (props) => { } return ( - - -
    - - Apps - -
    - handleTabChange(newTab)} - TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }} - > - - - - -
    -
    -
    +
    + + +
    + + Apps + +
    + handleTabChange(newTab)} + TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }} + style={{ fontFamily: "Inter" }} + > + + + + +
    +
    +
    + { + (currTab === 0 || currTab === 1) && + { + if (event.key === "Enter") { + event.preventDefault(); + } + }} + limit={5} + InputProps={{ + style: { + borderRadius: 8, + height: 45 + }, + endAdornment: ( + + {searchQuery.length === 0 ? ( + + ) : ( + { + setSearchQuery(""); + }} + /> + )} + + ), + }} + /> + } + { + currTab === 2 && + + } +
    +
    + {currTab === 2 ? ( + + ) : ( + <> + + {selectedCategory.length > 0 && ( + setSelectedCategory([])} + /> + )} + + )} +
    +
    + {currTab === 2 ? ( + + ) : ( + <> + + {selectedLabel.length > 0 && ( + setSelectedLabel([])} + /> + )} + + )} +
    +
    + +
    +
    +
    + { - (currTab === 0 || currTab === 1) && - { - setSearchQuery(event.target.value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - } - }} - limit={5} - style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} - InputProps={{ - style: { - borderRadius: 8, - }, - endAdornment: ( - - { - searchQuery.length === 0 ? : - } - - ), - }} - /> + currTab === 0 && ( +
    + {isLoading ? ( +
    + +
    + ) : ( + <> + {appsToShow?.length > 0 && appsToShow !== undefined && !isLoading ? ( +
    + {appsToShow.map((data, index) => ( + + ))} +
    + ) : ( +
    + +
    + )} + + )} +
    + ) } + { + currTab === 1 && ( +
    + {isLoading ? ( +
    + +
    + ) : ( + <> + {appsToShow?.length > 0 && appsToShow !== undefined ? ( +
    + {appsToShow.map((data, index) => ( + + ))} +
    + ) : ( +
    + No Apps Found +
    + )} + + )} +
    + ) + } + { currTab === 2 && - + }
    -
    - {currTab === 2 ? ( - - ) : ( - - )} -
    -
    - {currTab === 2 ? ( - - ) : ( - - )} -
    -
    - -
    -
    -
    - - { - currTab === 0 && ( -
    - {isLoading ? ( -
    - -
    - ) : ( - <> - {appsToShow?.length > 0 && appsToShow !== undefined && !isLoading ? ( -
    - {appsToShow.map((data, index) => ( - - ))} -
    - ) : ( -
    - -
    - )} - - )} -
    - ) - } - { - currTab === 1 && ( -
    - {isLoading ? ( -
    - -
    - ) : ( - <> - {appsToShow?.length > 0 && appsToShow !== undefined ? ( -
    - {appsToShow.map((data, index) => ( - - ))} -
    - ) : ( -
    - No Apps Found -
    - )} - - )} -
    - ) - } - - { - currTab === 2 && - - } -
    -
    - - +
    + + +
    ); }; From 02db40aa17c3fc276ecc8f07b65784b2666fae0f Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sun, 1 Dec 2024 19:51:51 +0530 Subject: [PATCH 315/336] Done with the discover apps tab revamp : New look --- frontend/src/components/AppModal.jsx | 2 +- frontend/src/views/Apps2.jsx | 552 ++++++++++++++------------- 2 files changed, 296 insertions(+), 258 deletions(-) diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index 239cec7c..0f0a3173 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -309,7 +309,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { }} > - About Gmail + About {app?.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())} No Apps Found
    ) : ( - -
    -
    - {hits?.map((data, index) => { - const appUrl = - isCloud - ? `/apps/${data.objectID}?queryID=${data.__queryID}` - : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; +
    + {hits?.map((data, index) => { + const appUrl = + isCloud + ? `/apps/${data.objectID}?queryID=${data.__queryID}` + : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; - return ( - + { + setHoverEffect(index); + }} + onMouseLeave={() => { + setHoverEffect(-1); + }} + > + { + handleAppClick(data); }} > -
    { - handleAppClick(data); - } - } + {data.name} +
    - { - setHoverEffect(index); - }} - onMouseLeave={() => { - setHoverEffect(-1); + display: 'flex', + flexDirection: "row", + overflow: "hidden", + gap: 8, + fontWeight:600, + textOverflow: "ellipsis", + whiteSpace: "nowrap", + color: '#F1F1F1' }} > - - {data.name} -
    -
    - {(allActivatedAppIds && allActivatedAppIds.includes(data.objectID)) && } - {normalizedString(data.name)} -
    + {(allActivatedAppIds && allActivatedAppIds.includes(data.objectID)) && } + {normalizedString(data.name)} +
    -
    - {data.categories !== null - ? normalizedString(data.categories).join(", ") - : "NA"} -
    -
    -
    - {hoverEffect === index && isCloud ? ( -
    - {data.tags && ( - - - {data.tags.slice(0, 1).map((tag, tagIndex) => ( - - {normalizedString(tag)} - {tagIndex < 1 ? ", " : ""} - - ))} - - - )} -
    - ) : ( -
    - {data.tags && - data.tags.map((tag, tagIndex) => ( - - {normalizedString(tag)} - {tagIndex < data.tags.length - 1 ? ", " : ""} - - ))} -
    - )} -
    -
    - {hoverEffect === index && isCloud && ( -
    - {allActivatedAppIds && allActivatedAppIds.includes(data.objectID) ? ( -
    +
    +
    + {hoverEffect === index && isCloud ? ( +
    + {data.tags && ( + { - handleActivateButton(event, data, "deactivate"); - }}> - Deactivate - - ) : ( - - )} -
    - )} -
    + width: "auto", + height: "auto", + fontSize: 16, + border: "1px solid rgba(73, 73, 73, 1)", + } + } + }} + > + + {data.tags.slice(0, 1).map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < 1 ? ", " : ""} + + ))} + + + )}
    -
    - - + ) : ( +
    + {data.tags && + data.tags.map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < data.tags.length - 1 ? ", " : ""} + + ))} +
    + )} +
    +
    + {hoverEffect === index && isCloud && ( +
    + {allActivatedAppIds && allActivatedAppIds.includes(data.objectID) ? ( + + ) : ( + + )} +
    + )} +
    +
    -
    - ); - })} -
    -
    - + + + + ); + })} +
    )}
    ) : (
    - )} -
    + ) + } +
    ); } @@ -540,6 +528,14 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { const inputRef = useRef(null); const [localQuery, setLocalQuery] = useState(searchQuery); + // Initialize search when component mounts or when switching to Discover tab + useEffect(() => { + if (searchQuery) { + setLocalQuery(searchQuery); + refine(searchQuery); // This will trigger the Algolia search + } + }, [searchQuery, refine]); + // Debounced function to refine search const debouncedRefine = useRef( debounce((value) => { @@ -600,7 +596,6 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { { setLocalQuery(''); @@ -626,26 +621,43 @@ const CategoryDropdown = ({ items, currentRefinement, refine }) => { }; return ( - selected.length ? selected.join(', ') : 'All Categories'} + > + + All Categories - ))} - + {items.map(item => ( + + + {item.label} ({item.count}) + + ))} + + {currentRefinement.length > 0 && ( + refine([])} + /> + )} +
    ); }; const CustomCategoryDropdown = connectRefinementList(CategoryDropdown); @@ -658,26 +670,46 @@ const LabelDropdown = ({ items, currentRefinement, refine }) => { }; return ( - selected.length ? selected.join(', ') : 'All Labels'} + > + + All Labels - ))} - + {items.map(item => ( + + + {item.label} ({item.count}) + + ))} + + {currentRefinement.length > 0 && ( + refine([])} + /> + )} +
    ); }; const CustomLabelDropdown = connectRefinementList(LabelDropdown); @@ -936,6 +968,7 @@ const Apps2 = (props) => { const handleTabChange = (newTab) => { setCurrTab(newTab); + // Apply filters immediately when changing tabs if (newTab === 0) { const filteredOrgApps = filterApps(orgApps, searchQuery, selectedCategory, selectedLabel); @@ -944,15 +977,20 @@ const Apps2 = (props) => { const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel); setAppsToShow(filteredUserApps); } + // Note: We don't clear the search when switching to tab 2 (Discover Apps) anymore // Update URL query params const newQueryParam = newTab === 0 ? 'org_apps' : newTab === 1 ? 'my_apps' : 'all_apps'; const queryParams = new URLSearchParams(location.search); queryParams.set('tab', newQueryParam); - // Only remove 'q' param if search is empty - if (!searchQuery) { + + // Maintain search query in URL regardless of tab + if (searchQuery) { + queryParams.set('q', searchQuery); + } else { queryParams.delete('q'); } + window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); }; @@ -1110,7 +1148,7 @@ const Apps2 = (props) => { All Categories {categories?.map((category) => ( - + {category.category} @@ -1153,7 +1191,7 @@ const Apps2 = (props) => { All Labels {labels?.map((tag) => ( - + {tag.tag} From cdad00152c318a31e1421a6210c6a01027562a85 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sun, 1 Dec 2024 20:01:54 +0530 Subject: [PATCH 316/336] Minor fixes --- frontend/src/views/Apps2.jsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 3a9ad2d2..87fefcfd 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -966,7 +966,7 @@ const Apps2 = (props) => { }, [searchQuery, selectedCategory, selectedLabel, currTab]); - const handleTabChange = (newTab) => { + const handleTabChange = (event, newTab) => { setCurrTab(newTab); // Apply filters immediately when changing tabs @@ -977,12 +977,16 @@ const Apps2 = (props) => { const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel); setAppsToShow(filteredUserApps); } - // Note: We don't clear the search when switching to tab 2 (Discover Apps) anymore - // Update URL query params - const newQueryParam = newTab === 0 ? 'org_apps' : newTab === 1 ? 'my_apps' : 'all_apps'; + // Update URL query params based on tab index + const tabMapping = { + 0: 'org_apps', + 1: 'my_apps', + 2: 'all_apps' + }; + const queryParams = new URLSearchParams(location.search); - queryParams.set('tab', newQueryParam); + queryParams.set('tab', tabMapping[newTab]); // Maintain search query in URL regardless of tab if (searchQuery) { @@ -991,7 +995,7 @@ const Apps2 = (props) => { queryParams.delete('q'); } - window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); + navigate(`${location.pathname}?${queryParams.toString()}`); }; // Update useEffect to handle initial load and URL search params @@ -1076,7 +1080,7 @@ const Apps2 = (props) => {
    handleTabChange(newTab)} + onChange={(event, newTab) => handleTabChange(event, newTab)} TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }} style={{ fontFamily: "Inter" }} > From 9e1d1e8ad7db0b5028a1601875f723cca300ffe0 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sun, 1 Dec 2024 20:32:13 +0530 Subject: [PATCH 317/336] Added the skeleton loading --- frontend/src/views/Apps2.jsx | 87 ++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 87fefcfd..3b8fd0e8 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -12,6 +12,7 @@ import { Box, CircularProgress, Checkbox, + Skeleton, } from "@mui/material"; import { Context } from "../context/ContextApi.jsx"; import Add from '@mui/icons-material/Add'; @@ -280,7 +281,6 @@ const Hits = ({ const [showNoAppFound, setShowNoAppFound] = useState(false); - //show some delay to show the "App Not Found." so it doesn't not show while changing tab. useEffect(() => { const timer = setTimeout(() => { setShowNoAppFound(true); @@ -514,7 +514,7 @@ const Hits = ({ )}
    ) : ( -
    + ) }

    @@ -750,6 +750,81 @@ const filterApps = (apps, searchQuery, selectedCategory, selectedLabel) => { }); }; +// Add this new component for the app skeleton +const AppSkeleton = () => { + return ( + +
    + +
    + + + +
    +
    +
    + ); +}; + +// Replace the loading sections in the main component with this +const LoadingGrid = () => { + return ( +
    + {[...Array(7)].map((_, index) => ( + + ))} +
    + ); +}; + // Main Apps Component const Apps2 = (props) => { const { globalUrl, isLoaded, serverside, userdata, isLoggedIn, checkLogin } = props; @@ -1237,9 +1312,7 @@ const Apps2 = (props) => { currTab === 0 && (
    {isLoading ? ( -
    - -
    + ) : ( <> {appsToShow?.length > 0 && appsToShow !== undefined && !isLoading ? ( @@ -1292,9 +1365,7 @@ const Apps2 = (props) => { currTab === 1 && (
    {isLoading ? ( -
    - -
    + ) : ( <> {appsToShow?.length > 0 && appsToShow !== undefined ? ( From c9cba51d50ccc1430cab37534e04b450ebe0f160 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sun, 1 Dec 2024 21:07:42 +0530 Subject: [PATCH 318/336] Changed the app modal UI --- frontend/src/components/AppModal.jsx | 125 +++++++++++++++++---------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index 0f0a3173..17cf2918 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -291,10 +291,23 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { fullWidth PaperProps={{ sx: { - borderRadius: 3, - border: "1px solid var(--Container-Stroke, #494949)", + borderRadius: 2, + border: "1px solid #494949", minWidth: '440px', - fontFamily: "Inter" + fontFamily: "Inter", + backgroundColor: "#212121", + '& .MuiDialogContent-root': { + backgroundColor: "#212121", + }, + '& .MuiDialogTitle-root': { + backgroundColor: "#212121", + }, + '& .MuiTypography-root': { + fontFamily: 'Inter, sans-serif', + }, + '& .MuiButton-root': { + fontFamily: 'Inter, sans-serif', + }, } }} > @@ -303,27 +316,29 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { display: 'flex', justifyContent: 'space-between', alignItems: 'center', - pb: 1, + pb: 2, pt: 2, - px: 3 + pl: 3, + pr: 2, + fontFamily: "Inter" }} > - + About {app?.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())} - - + +
    { boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.2)" }} /> -
    +
    { @@ -420,14 +438,26 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { textAlign: "start", flex: 1, }}> - + 20 - + Public Workflow
    @@ -438,8 +468,9 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { paddingLeft: "10px", height: "100%", }}> - @@ -455,7 +486,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { paddingLeft: "10px", paddingTop: "5px" }}> -
    +
    { app?.collection ? ( <> @@ -473,7 +504,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { }
    - + Part of a collection
    @@ -488,9 +519,9 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => {
    { (foundAppUsecase?.srcapp !== undefined && foundAppUsecase?.dstapp !== undefined) ? ( @@ -502,9 +533,9 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => {
    { ) } - + {foundAppUsecase?.name || "Search for a Usecase"} @@ -559,24 +590,24 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => {
    - + ); }; From 675707cb4992bc6ea2e2446ae7f59eab4a6107ea Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 2 Dec 2024 12:08:21 +0530 Subject: [PATCH 319/336] Added the UI for download from github and added two CTAs --- frontend/src/components/AppModal.jsx | 4 +- frontend/src/views/Apps2.jsx | 624 +++++++++++++++++++++++++-- 2 files changed, 596 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index 17cf2918..17189887 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -323,7 +323,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { fontFamily: "Inter" }} > - + About {app?.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())} { display: "flex", flexDirection: "row", }}> - + {newAppname} { diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 3b8fd0e8..59037947 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -13,13 +13,21 @@ import { CircularProgress, Checkbox, Skeleton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + IconButton, } from "@mui/material"; import { Context } from "../context/ContextApi.jsx"; import Add from '@mui/icons-material/Add'; +import CachedIcon from '@mui/icons-material/Cached'; +import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; import EditIcon from '@mui/icons-material/Edit'; import InputAdornment from '@mui/material/InputAdornment'; import Search from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; +import CloseIcon from '@mui/icons-material/Close'; import { ClearRefinements, connectHits, connectSearchBox, connectStateResults, InstantSearch, RefinementList, connectRefinementList, Configure } from "react-instantsearch-dom"; import { removeQuery } from "../components/ScrollToTop.jsx"; @@ -147,7 +155,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, }}> { canEditApp && ( - ) @@ -158,7 +166,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, // marginLeft: 15, width: 102, height: 35, - borderRadius: 6, + borderRadius: 3, backgroundColor: "rgba(73, 73, 73, 1)", color: "rgba(241, 241, 241, 1)", textTransform: "none", @@ -391,7 +399,7 @@ const Hits = ({ flexDirection: "row", overflow: "hidden", gap: 8, - fontWeight:600, + fontWeight: 600, textOverflow: "ellipsis", whiteSpace: "nowrap", color: '#F1F1F1' @@ -765,14 +773,14 @@ const AppSkeleton = () => { width: "100%", height: "100%" }}> -
    { flex: 1, gap: 6 }}> - - -
    @@ -827,7 +835,7 @@ const LoadingGrid = () => { // Main Apps Component const Apps2 = (props) => { - const { globalUrl, isLoaded, serverside, userdata, isLoggedIn, checkLogin } = props; + const { globalUrl, isLoaded, serverside, userdata, isLoggedIn, checkLogin, isCloud } = props; let navigate = useNavigate(); const { leftSideBarOpenByClick } = useContext(Context); const location = useLocation(); @@ -850,6 +858,20 @@ const Apps2 = (props) => { const [selectedApp, setSelectedApp] = useState(null); const [appFramework, setAppFramework] = useState(undefined); const [defaultSearch, setDefaultSearch] = useState(""); + + const [apps, setApps] = useState([]); + const [filteredApps, setFilteredApps] = useState([]); + const [appSearchLoading, setAppSearchLoading] = useState(false); + const [creatorProfile, setCreatorProfile] = useState({}); + const [openApi, setOpenApi] = React.useState(""); + const [loadAppsModalOpen, setLoadAppsModalOpen] = useState(false); + const [downloadBranch, setDownloadBranch] = useState("master"); + const [field1, setField1] = useState(""); + const [field2, setField2] = useState(""); + const [validation, setValidation] = useState(null); + + const baseRepository = "https://github.com/frikky/shuffle-apps"; + // Set the current tab based on the query parameter useEffect(() => { const queryParams = new URLSearchParams(location.search); @@ -965,6 +987,40 @@ const Apps2 = (props) => { } }, [currTab, appsToShow]) + const getUserProfile = (username) => { + if (serverside === true || !isCloud) { + setCreatorProfile({}) + return; + } + + 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("creator profile: ", responseJson) + setCreatorProfile(responseJson); + } else { + setCreatorProfile({}) + } + }) + .catch((error) => { + console.log(error); + setCreatorProfile({}) + }); + }; useEffect(() => { if (serverside) { @@ -972,6 +1028,426 @@ const Apps2 = (props) => { } }, [serverside]); + const getApps = () => { + // Get apps from localstorage + var storageApps = [] + try { + const appstorage = localStorage.getItem("apps") + storageApps = JSON.parse(appstorage) + if (storageApps === null || storageApps === undefined || storageApps.length === 0) { + storageApps = [] + } else { + setApps(storageApps) + setFilteredApps(storageApps) + setAppSearchLoading(false) + } + } catch (e) { + //console.log("Failed to get apps from localstorage: ", e) + } + + fetch(globalUrl + "/api/v1/apps", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + setIsLoading(false); + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + + //if (isCloud) { + // window.location.pathname = "/search"; + //} + } + + return response.json(); + }) + .then((responseJson) => { + //responseJson = sortByKey(responseJson, "large_image") + //responseJson = sortByKey(responseJson, "is_valid") + //setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name) && !(!app.activated && app.generated))) + console.log("responseJson: from getApps ", responseJson) + var privateapps = []; + var valid = []; + var invalid = []; + for (var key in responseJson) { + const app = responseJson[key]; + if (app.is_valid && !(!app.activated && app.generated)) { + privateapps.push(app); + } else if ( + app.private_id !== undefined && + app.private_id.length > 0 + ) { + valid.push(app); + } else { + invalid.push(app); + } + } + + //console.log(privateapps) + //console.log(valid) + //console.log(invalid) + //console.log(privateapps) + //privateapps.reverse() + privateapps.push(...valid); + privateapps.push(...invalid); + console.log("privateapps: setting apps ", privateapps) + setApps(privateapps); + // setCursearch(""); + + //handleSearchChange(event.target.value) + //setCursearch(event.target.value) + setFilteredApps(privateapps); + if (privateapps.length > 0) { + if (selectedApp.id === undefined || selectedApp.id === null) { + if (privateapps[0].owner !== undefined && privateapps[0].owner !== null) { + getUserProfile(privateapps[0].owner); + } + + // setContact(privateapps[0].contact_info) + + // setSelectedApp(privateapps[0]); + // setSharingConfiguration(privateapps[0].sharing === true ? "public" : "you") + } + + // if ( + // privateapps[0].actions !== null && + // privateapps[0].actions.length > 0 + // ) { + // setSelectedAction(privateapps[0].actions[0]); + // } else { + // setSelectedAction({}); + // } + } + + if (privateapps.length > 0 && storageApps.length === 0) { + try { + localStorage.setItem("apps", JSON.stringify(privateapps)) + } catch (e) { + console.log("Failed to set apps in localstorage: ", e) + } + } + + //setTimeout(() => { + // setFirstLoad(false) + //}, 5000) + }) + .catch((error) => { + toast(error.toString()); + setIsLoading(false); + }); + }; + + + // Locally hotloads app from folder + const hotloadApps = () => { + toast("Hotloading apps from location in .env"); + setIsLoading(true); + fetch(globalUrl + "/api/v1/apps/run_hotload", { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + setIsLoading(false); + if (response.status === 200) { + //toast("Hotloaded apps!") + getApps(); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + toast("Successfully finished hotload"); + } else { + toast("Failed hotload: ", responseJson.reason); + //(responseJson.reason !== undefined && responseJson.reason.length > 0) { + } + }) + .catch((error) => { + toast(error.toString()); + }); + }; + + + // Load data e.g. from github + const getSpecificApps = (url, forceUpdate) => { + setValidation(true); + + setIsLoading(true); + //start() + + const parsedData = { + url: url, + branch: downloadBranch || "master", + }; + + if (field1.length > 0) { + parsedData["field_1"] = field1; + } + + if (field2.length > 0) { + parsedData["field_2"] = field2; + } + + parsedData["force_update"] = forceUpdate; + + toast("Getting specific apps from your URL."); + var cors = "cors"; + fetch(globalUrl + "/api/v1/apps/get_existing", { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(parsedData), + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + toast("Loaded existing apps!"); + } + + //stop() + setIsLoading(false); + setValidation(false); + return response.json(); + }) + .then((responseJson) => { + console.log("DATA: ", responseJson); + if (responseJson.reason !== undefined) { + toast("Failed loading: " + responseJson.reason); + } + }) + .catch((error) => { + console.log("ERROR: ", error.toString()); + //toast(error.toString()); + //stop() + + setIsLoading(false); + setValidation(false); + }); + }; + + const handleGithubValidation = (forceUpdate) => { + getSpecificApps(openApi, forceUpdate); + setLoadAppsModalOpen(false); + }; + + + const appsModalLoad = loadAppsModalOpen ? ( + { + setOpenApi(""); + setLoadAppsModalOpen(false); + setField1(""); + setField2(""); + }} + maxWidth="md" + fullWidth + PaperProps={{ + sx: { + borderRadius: 2, + border: "1px solid #494949", + minWidth: '440px', + fontFamily: "Inter", + backgroundColor: "#212121", + zIndex: 1000, + '& .MuiDialogContent-root': { + backgroundColor: "#212121", + }, + '& .MuiDialogTitle-root': { + backgroundColor: "#212121", + }, + '& .MuiTypography-root': { + fontFamily: 'Inter, sans-serif', + }, + '& .MuiButton-root': { + fontFamily: 'Inter, sans-serif', + }, + } + }} + > + + + Load from Github Repository + + { + setOpenApi(""); + setLoadAppsModalOpen(false); + setField1(""); + setField2(""); + }} + sx={{ + color: 'rgba(255, 255, 255, 0.7)', + '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' } + }} + > + + + + + + Repository (supported: github, gitlab, bitbucket) + + setOpenApi(e.target.value)} + sx={{ + mb: 3, + '& .MuiOutlinedInput-root': { + color: 'white', + height: '50px', + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.23)', + }, + }, + }} + /> + + + Branch (default value is "master") + + setDownloadBranch(e.target.value)} + placeholder="master" + sx={{ + mb: 3, + '& .MuiOutlinedInput-root': { + color: 'white', + height: '50px', + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.23)', + }, + }, + }} + /> + + + Authentication (optional - private repos etc) + +
    + setField1(e.target.value)} + sx={{ + '& .MuiOutlinedInput-root': { + color: 'white', + height: '50px', + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.23)', + }, + }, + }} + /> + setField2(e.target.value)} + sx={{ + '& .MuiOutlinedInput-root': { + color: 'white', + height: '50px', + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.23)', + }, + }, + }} + /> +
    +
    + + + {!isCloud && ( + + )} + + +
    + ) : null; + const findTopCategories = () => { const categoryCountMap = {}; @@ -1043,7 +1519,7 @@ const Apps2 = (props) => { const handleTabChange = (event, newTab) => { setCurrTab(newTab); - + // Apply filters immediately when changing tabs if (newTab === 0) { const filteredOrgApps = filterApps(orgApps, searchQuery, selectedCategory, selectedLabel); @@ -1059,17 +1535,17 @@ const Apps2 = (props) => { 1: 'my_apps', 2: 'all_apps' }; - + const queryParams = new URLSearchParams(location.search); queryParams.set('tab', tabMapping[newTab]); - + // Maintain search query in URL regardless of tab if (searchQuery) { queryParams.set('q', searchQuery); } else { queryParams.delete('q'); } - + navigate(`${location.pathname}?${queryParams.toString()}`); }; @@ -1148,10 +1624,98 @@ const Apps2 = (props) => { userdata={userdata} globalUrl={globalUrl} /> + {appsModalLoad}
    - - Apps - +
    + + Apps + + {isCloud ? null : ( + + {userdata === undefined || userdata === null || isLoading ? null : ( + + + + )} + + {userdata === undefined || userdata === null || userdata.admin === "false" ? null : + + + + } + + )} +
    Date: Mon, 2 Dec 2024 12:29:40 +0530 Subject: [PATCH 320/336] Buttons on right : hotlaod and download from github --- frontend/src/views/Apps2.jsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 59037947..011df1ad 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -1037,9 +1037,11 @@ const Apps2 = (props) => { if (storageApps === null || storageApps === undefined || storageApps.length === 0) { storageApps = [] } else { + setAppsToShow(storageApps) + setOrgApps(storageApps) setApps(storageApps) - setFilteredApps(storageApps) - setAppSearchLoading(false) + // setFilteredApps(storageApps) + // setAppSearchLoading(false) } } catch (e) { //console.log("Failed to get apps from localstorage: ", e) @@ -1095,12 +1097,14 @@ const Apps2 = (props) => { privateapps.push(...valid); privateapps.push(...invalid); console.log("privateapps: setting apps ", privateapps) + setAppsToShow(privateapps); + setOrgApps(privateapps); setApps(privateapps); // setCursearch(""); //handleSearchChange(event.target.value) //setCursearch(event.target.value) - setFilteredApps(privateapps); + // setFilteredApps(privateapps); if (privateapps.length > 0) { if (selectedApp.id === undefined || selectedApp.id === null) { if (privateapps[0].owner !== undefined && privateapps[0].owner !== null) { @@ -1141,7 +1145,6 @@ const Apps2 = (props) => { }); }; - // Locally hotloads app from folder const hotloadApps = () => { toast("Hotloading apps from location in .env"); @@ -1167,12 +1170,13 @@ const Apps2 = (props) => { if (responseJson.success === true) { toast("Successfully finished hotload"); } else { - toast("Failed hotload: ", responseJson.reason); + console.log("failed hotload: ", responseJson) + // toast(`Failed hotload: ${responseJson.reason}`); //(responseJson.reason !== undefined && responseJson.reason.length > 0) { } }) .catch((error) => { - toast(error.toString()); + toast(`Failed hotload: ${error.toString()}`); }); }; From 899bde4947088a7ee998807112ef0c960bb54dc6 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 2 Dec 2024 12:49:23 +0530 Subject: [PATCH 321/336] added the app library link when searched app not found in org --- frontend/src/components/AppSelection.jsx | 74 ++++++++++++++++-------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/AppSelection.jsx b/frontend/src/components/AppSelection.jsx index df78af8e..76d38d88 100644 --- a/frontend/src/components/AppSelection.jsx +++ b/frontend/src/components/AppSelection.jsx @@ -428,30 +428,56 @@ const AppSelection = props => { />
    ) : null} - - Find your apps - - - Select the apps you work with and we will connect them for you. - + { + !isAppPage && ( + <> + + Find your apps + + + Select the apps you work with and we will connect them for you. + + + ) + } + { + isAppPage && ( +
    + + Your organization has no apps yet, select your starting apps here + or discover more apps using the { + navigate("/apps2?tab=all_apps") + }} + style={{ color: "#FF8444", fontWeight: "medium", fontSize: 16, cursor:"pointer" }}>App Library + +
    + ) + } {appButtons.map((appData, index) => { // This is here due to a memory issue with setting apps properly From ed71bec7dae354cda5f57b877bfe80dced1c89c4 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 2 Dec 2024 14:39:13 +0100 Subject: [PATCH 322/336] Added admin2 page and new workflow design --- frontend/src/App.jsx | 2 +- frontend/src/components/EditWorkflow.jsx | 30 ++- frontend/src/components/LeftSideBar.jsx | 5 +- frontend/src/components/ParsedAction.jsx | 13 +- frontend/src/views/Admin2.jsx | 243 +++++++++++++++++++++++ frontend/src/views/AngularWorkflow.jsx | 220 +++++++++++++------- frontend/src/views/Workflows.jsx | 9 + 7 files changed, 429 insertions(+), 93 deletions(-) create mode 100644 frontend/src/views/Admin2.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9f6c7655..d6a3de2d 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -210,7 +210,7 @@ const App = (message, props) => {
    : isLoggedIn ? -
    +
    : diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index dc0a324e..e26ae7c5 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -205,6 +205,8 @@ const EditWorkflow = (props) => { paddingLeft: 50, //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, + borderRadius: theme.palette.borderRadius, + backgroundColor: "black", }, }} > @@ -280,22 +282,18 @@ const EditWorkflow = (props) => {
    -
    - {/* - - */} +
    - + - +