diff --git a/.env b/.env index 07deaf4a..f8be5532 100755 --- a/.env +++ b/.env @@ -55,29 +55,28 @@ SHUFFLE_PASS_APP_PROXY=FALSE TZ=Europe/Amsterdam # Timezone-handler in Orborus, Worker and Apps ORBORUS_CONTAINER_NAME= # Used to FIND the containername. cgroup v2: issue 501 SHUFFLE_ORBORUS_STARTUP_DELAY= # Used for setting up a startup delay for Orborus +IS_KUBERNETES=false # Used for controlling if the environment should run in kubernetes or not SHUFFLE_BASE_IMAGE_NAME=shuffle SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.1.0" -## shuffle_memcached (for distributed caching) -## shuffle_SWARM_CONFIG (run vs not run) -## shuffle_Scale_Replicas (workers/node) -## shuffle_App_Replicas (apps/node) - -SHUFFLE_SWARM_BRIDGE_DEFAULT_MTU=1500 # 1500 by default # The eth0 interface inside a container corresponds # to the virtual Ethernet interface that connects # the container to the docker0 SHUFFLE_SWARM_BRIDGE_DEFAULT_INTERFACE=eth0 +SHUFFLE_SWARM_BRIDGE_DEFAULT_MTU=1500 # 1500 by default -# Used for auto-cleanup of containers. REALLY important at scale. -SHUFFLE_CONTAINER_AUTO_CLEANUP=false +# Used for auto-cleanup of containers. REALLY important at scale. Set to false to see all container info. +SHUFFLE_MEMCACHED= +SHUFFLE_CONTAINER_AUTO_CLEANUP=true +SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY=3 # The amount of concurrent executions Orborus can handle. This is a soft limit, but it's recommended to keep it low. +SHUFFLE_HEALTHCHECK_DISABLED=false SHUFFLE_ELASTIC=true SHUFFLE_LOGS_DISABLED=false SHUFFLE_CHAT_DISABLED=false # Controls support chat -SHUFFLE_RERUN_SCHEDULE=300 SHUFFLE_DISABLE_RERUN_AND_ABORT=false +SHUFFLE_RERUN_SCHEDULE=300 SHUFFLE_WORKER_SERVER_URL= # Definition in case Worker & Orborus is talking to the wrong server SHUFFLE_ORBORUS_PULL_TIME= # Definition in case Orborus is pulling too often/not often enough @@ -93,3 +92,5 @@ SHUFFLE_OPENSEARCH_CLOUDID= SHUFFLE_OPENSEARCH_PROXY= SHUFFLE_OPENSEARCH_INDEX_PREFIX= SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true + +DEBUG_MODE=false \ No newline at end of file diff --git a/.github/install-guide.md b/.github/install-guide.md index 68d1345b..23f9b75a 100755 --- a/.github/install-guide.md +++ b/.github/install-guide.md @@ -8,7 +8,7 @@ The Docker setup is done with docker-compose **PS: if you're setting up Shuffle on Windows, go to the next step (Windows Docker setup)** -1. Make sure you have [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) installed. +1. Make sure you have [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) installed, and that you have a minimum of **2Gb of RAM** available. 2. Download Shuffle ```bash git clone https://github.com/Shuffle/Shuffle diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index 6c7787eb..df2cc7d2 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -33,22 +33,24 @@ jobs: experimental: true steps: - name: Checkout - uses: actions/checkout@v2 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: actions/checkout@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - + uses: docker/setup-buildx-action@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: 'amd64,arm64,arm' + - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to Ghcr - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -56,7 +58,7 @@ jobs: - name: Ghcr Build and push id: docker_build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 env: BUILDX_NO_DEFAULT_LOAD: true with: @@ -68,8 +70,8 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache tags: | - ghcr.io/shuffle/shuffle-${{ matrix.app }}:nightly - ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:nightly + ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ matrix.version }} + ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ matrix.version }} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/backend/Dockerfile b/backend/Dockerfile index 5bc61f64..cb287e8f 100755 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -41,4 +41,4 @@ COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certifica WORKDIR /app EXPOSE 5001 -CMD ["./webapp"] +CMD ["./webapp"] \ No newline at end of file diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index b0ffee23..f19b0f2c 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -1,11 +1,12 @@ import os import ast -import copy import sys import re +import copy import time import base64 import json +import random import liquid import logging import urllib3 @@ -22,8 +23,7 @@ import dateutil import threading import concurrent.futures -from io import StringIO as StringBuffer -from io import BytesIO +from io import StringIO as StringBuffer, BytesIO from liquid import Liquid, defaults runtime = os.getenv("SHUFFLE_SWARM_CONFIG", "") @@ -105,9 +105,12 @@ def base64_encode(a): def base64_decode(a): a = str(a) try: - return base64.b64decode(a).decode() + return base64.b64decode(a).decode("unicode_escape") except: - return base64.b64decode(a) + try: + return base64.b64decode(a).decode() + except: + return base64.b64decode(a) @shuffle_filters.register def json_parse(a): @@ -502,10 +505,12 @@ class AppBase: except Exception as e: print(f"[WARNING] Failed adding parameter for logs: {e}") - # FIXME: Adding retries here. try: finished = False for i in range (0, 10): + # Random sleeptime between 0 and 1 second, with 0.1 increments + sleeptime = float(random.randint(0, 10) / 10) + try: ret = requests.post(url, headers=headers, json=action_result, timeout=10, verify=False) @@ -514,29 +519,29 @@ class AppBase: finished = True break else: - self.logger.info(f"[ERROR] RESP: {ret.text}") + self.logger.info(f"[ERROR] Bad resp {ret.status_code}: {ret.text}") except requests.exceptions.RequestException as e: self.logger.info(f"[DEBUG] Request problem: {e}") - time.sleep(0.1) + time.sleep(sleeptime) #time.sleep(5) continue except TimeoutError as e: self.logger.info(f"[DEBUG] Timeout or request: {e}") - time.sleep(0.1) + time.sleep(sleeptime) #time.sleep(5) continue except requests.exceptions.ConnectionError as e: self.logger.info(f"[DEBUG] Connectionerror: {e}") - time.sleep(0.1) + time.sleep(sleeptime) #time.sleep(5) continue except http.client.RemoteDisconnected as e: self.logger.info(f"[DEBUG] Remote: {e}") - time.sleep(0.1) + time.sleep(sleeptime) #time.sleep(5) continue @@ -553,8 +558,11 @@ class AppBase: # Not sure why this would work tho :) action_result["status"] = "FAILURE" action_result["result"] = json.dumps({"success": False, "reason": "POST error: Failed connecting to %s over 10 retries to the backend" % url}) - self.logger.info(f"[DEBUG] Before typeerror stream result - NOT finished after 10 requests") - ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result, verify=False) + self.logger.info(f"[ERROR] Before typeerror stream result - NOT finished after 10 requests") + + #ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result, verify=False) + self.send_result(action_result, {"Content-Type": "application/json", "Authorization": "Bearer %s" % self.authorization}, "/api/v1/streams") + return self.logger.info(f"""[DEBUG] Successful request result request: Status= {ret.status_code} & Response= {ret.text}. Action status: {action_result["status"]}""") except requests.exceptions.ConnectionError as e: @@ -1291,6 +1299,27 @@ class AppBase: else: return returns + def delete_cache(self, key): + org_id = self.full_execution["workflow"]["execution_org"]["id"] + url = "%s/api/v1/orgs/%s/delete_cache" % (self.url, org_id) + + data = { + "workflow_id": self.full_execution["workflow"]["id"], + "execution_id": self.current_execution_id, + "authorization": self.authorization, + "org_id": org_id, + "key": key, + } + + response = requests.post(url, json=data, verify=False) + try: + allvalues = response.json() + return json.dumps(allvalues) + except Exception as e: + self.logger.info("[ERROR} Failed to parse response from delete_cache: %s" % e) + #return response.json() + return json.dumps({"success": False, "reason": f"Failed to delete cache for key '{key}'"}) + def set_cache(self, key, value): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) @@ -1309,8 +1338,8 @@ class AppBase: allvalues["key"] = key allvalues["value"] = str(value) return allvalues - except: - self.logger.info("Value couldn't be parsed") + except Exception as e: + self.logger.info("[ERROR} Failed to parse response from set cache: %s" % e) #return response.json() return {"success": False} @@ -2277,10 +2306,13 @@ class AppBase: #if len(template) > 100: # self.logger.info("[DEBUG] Running liquid with data of length %d" % len(template)) #self.logger.info(f"[DEBUG] Data: {template}") - run = Liquid(template, mode="wild", from_file=False, filters=shuffle_filters.filters) - # Can't handle self yet (?) - ret = run.render(**globals()) + all_globals = globals() + all_globals["self"] = self + run = Liquid(template, mode="wild", from_file=False, filters=shuffle_filters.filters, globals=all_globals) + + # Add locals that are missing to globals + ret = run.render() return ret except jinja2.exceptions.TemplateNotFound as e: self.logger.info(f"[ERROR] Liquid Template error: {e}") @@ -3487,7 +3519,7 @@ class AppBase: if self.action["app_name"].lower() == "shuffle tools": timeout = 55 - timeout = 30 + #timeout = 30 try: executor = concurrent.futures.ThreadPoolExecutor() @@ -3556,15 +3588,11 @@ class AppBase: if "the JSON object must be" in errorstring: self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (0)? the JSON object must be in...") - try: - e = json.loads(f"{e}") - except: - e = f"{e}" newres = json.dumps({ "success": False, "reason": "An exception occurred while running this function (1). See exception for more details and contact support if this persists (support@shuffler.io)", - "exception": e, + "exception": f"{type(e).__name__} - {e}", }) break elif "got an unexpected keyword argument" in errorstring: @@ -3587,15 +3615,16 @@ class AppBase: except Exception as e: self.logger.info(f"[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (1)? err: {e}") - try: - e = json.loads(f"{e}") - except: - e = f"{e}" + #try: + # e = json.loads(f"{e}") + #except: + # e = f"{e}" newres = json.dumps({ "success": False, "reason": "An exception occurred while running this function (2). See exception for more details and contact support if this persists (support@shuffler.io)", - "exception": e, + "exception": f"{type(e).__name__} - {e}", + }) break diff --git a/backend/app_sdk/requirements.txt b/backend/app_sdk/requirements.txt index 03bbc1f5..cd36ec7c 100644 --- a/backend/app_sdk/requirements.txt +++ b/backend/app_sdk/requirements.txt @@ -1,7 +1,7 @@ urllib3==1.26.18 requests==2.31.0 MarkupSafe==2.0.1 -liquidpy==0.7.6 +liquidpy==0.8.1 flask[async]==2.0.2 waitress==2.1.0 #flask==1.1.2 diff --git a/backend/go-app/README.md b/backend/go-app/README.md new file mode 100644 index 00000000..158fb213 --- /dev/null +++ b/backend/go-app/README.md @@ -0,0 +1,20 @@ +# Run +go run main.go walkoff.go docker.go + +## Modify +- Make sure it's connected with the latest version of the shuffle-shared library, which is used to get resources from Shuffle + +## Database +- The database is Opensearch and can be modified with the SHUFFLE_OPENSEARCH_URL environment variable. See .env in the root directory for more. This requires Opensearch to be running (typically started from docker-compose.yml) +``` +docker-compose up -d +docker stop shuffle-backend +docker stop shuffle-frontend +docker stop shuffle-orborus +``` + +## Caching +- To handle caching, it by default runs it in memory of the application itself. If you want to offload this, it can be done using the SHUFFLE_MEMCACHED environment variable, connecting to a memcached instance. +``` +docker run --name shuffle-cache -p 11211:11211 -d memcached -m 1024 +``` diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go index a6324faf..a2dc9fd1 100755 --- a/backend/go-app/docker.go +++ b/backend/go-app/docker.go @@ -33,6 +33,15 @@ import ( "net/http" "os" "strings" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "time" + // "k8s.io/client-go/tools/clientcmd" + // "k8s.io/client-go/util/homedir" ) // Parses a directory with a Dockerfile into a tar for Docker images.. @@ -318,70 +327,220 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin return nil } +func getK8sClient() (*kubernetes.Clientset, error) { + config, err := rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("[ERROR] failed to get in-cluster config: %v", err) + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("[ERROR] failed to create Kubernetes client: %v", err) + } + + return clientset, nil +} + +func deleteJob(client *kubernetes.Clientset, jobName, namespace string) error { + deletePolicy := metav1.DeletePropagationForeground + return client.BatchV1().Jobs(namespace).Delete(context.TODO(), jobName, metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }) +} + func buildImage(tags []string, dockerfileFolder string) error { - ctx := context.Background() - client, err := client.NewEnvClient() - if err != nil { - log.Printf("Unable to create docker client: %s", err) - return err + + isKubernetes := false + if os.Getenv("IS_KUBERNETES") == "true" { + isKubernetes = true } - log.Printf("[INFO] Docker Tags: %s", tags) - dockerfileSplit := strings.Split(dockerfileFolder, "/") + if isKubernetes { + // log.Printf("K8S ###################") + // log.Print("dockerfileFolder: ", dockerfileFolder) + // log.Print("tags: ", tags) + // log.Print("only tag: ", tags[1]) - // Create a buffer - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - defer tw.Close() - baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/") - - // Builds the entire folder into buf - err = getParsedTar(tw, baseDir, "") - if err != nil { - log.Printf("Tar issue: %s", err) - } - - dockerFileTarReader := bytes.NewReader(buf.Bytes()) - buildOptions := types.ImageBuildOptions{ - Remove: true, - Tags: tags, - BuildArgs: map[string]*string{}, - } - //NetworkMode: "host", - - httpProxy := os.Getenv("HTTP_PROXY") - if len(httpProxy) > 0 { - buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy - } - httpsProxy := os.Getenv("HTTPS_PROXY") - if len(httpProxy) > 0 { - buildOptions.BuildArgs["https_proxy"] = &httpsProxy - } - - // Build the actual image - imageBuildResponse, err := client.ImageBuild( - ctx, - dockerFileTarReader, - buildOptions, - ) - - if err != nil { - return err - } - - // Read the STDOUT from the build process - defer imageBuildResponse.Body.Close() - buildBuf := new(strings.Builder) - _, err = io.Copy(buildBuf, imageBuildResponse.Body) - if err != nil { - return err - } else { - if strings.Contains(buildBuf.String(), "errorDetail") { - log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n")) - return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ","))) + registryName := "" + if len(os.Getenv("REGISTRY_URL")) > 0 { + registryName = os.Getenv("REGISTRY_URL") } - } + log.Printf("[INFO] registry name: %s", registryName) + + contextDir := strings.Replace(dockerfileFolder, "Dockerfile", "", -1) + contextDir = "/app/" + contextDir + log.Print("contextDir: ", contextDir) + dockerFile := "./Dockerfile" + + client, err := getK8sClient() + if err != nil { + fmt.Printf("Unable to authencticate : %v\n", err) + return err + } + + BackendPodLabel := "io.kompose.service=backend" + + backendPodList, podListErr := client.CoreV1().Pods("shuffle").List(context.TODO(), metav1.ListOptions{ + LabelSelector: BackendPodLabel, + }) + + if podListErr != nil || len(backendPodList.Items) == 0 { + fmt.Println("Error getting backend pod or no pod found:", podListErr) + return podListErr + } + + backendNodeName := backendPodList.Items[0].Spec.NodeName + log.Printf("[INFO] Backend running on: %s", backendNodeName) + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "shuffle-app-builder", + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "kaniko", + Image: "gcr.io/kaniko-project/executor:latest", + Args: []string{ + "--verbosity=debug", + "--dockerfile=" + dockerFile, + "--context=dir://" + contextDir, + "--skip-tls-verify", + "--destination=" + registryName + "/" + tags[1], + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "kaniko-workspace", + MountPath: "/app/generated", + }, + }, + }, + }, + NodeSelector: map[string]string{ + "node": backendNodeName, + }, + RestartPolicy: corev1.RestartPolicyNever, + Volumes: []corev1.Volume{ + { + Name: "kaniko-workspace", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "backend-apps-claim", + }, + }, + }, + }, + }, + }, + }, + } + + createdJob, err := client.BatchV1().Jobs("shuffle").Create(context.TODO(), job, metav1.CreateOptions{}) + if err != nil { + log.Printf("Failed to start image builder job: %s", err) + return err + } + + timeout := time.After(5 * time.Minute) + tick := time.Tick(5 * time.Second) + + for { + select { + case <-timeout: + return fmt.Errorf("job didn't complete within the expected time") + case <-tick: + currentJob, err := client.BatchV1().Jobs("shuffle").Get(context.TODO(), createdJob.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("[ERROR] failed to fetch %s status: %v", createdJob.Name, err) + } + + if currentJob.Status.Succeeded > 0 { + log.Printf("[INFO] Job %s completed successfully!", createdJob.Name) + log.Printf("[INFO] Cleaning up the job %s", createdJob.Name) + err := deleteJob(client, createdJob.Name, "shuffle") + if err != nil { + return fmt.Errorf("[ERROR] failed deleting job %s with error: %s", createdJob.Name, err) + } + log.Println("Job deleted successfully!") + return nil + } else if currentJob.Status.Failed > 0 { + log.Printf("[ERROR] %s job failed with error: %s", createdJob.Name, err) + err := deleteJob(client, createdJob.Name, "shuffle") + if err != nil { + return fmt.Errorf("[ERROR] failed deleting job %s with error: %s", createdJob.Name, err) + } + } + } + } + } else { + + ctx := context.Background() + client, err := client.NewEnvClient() + if err != nil { + log.Printf("Unable to create docker client: %s", err) + return err + } + + log.Printf("[INFO] Docker Tags: %s", tags) + dockerfileSplit := strings.Split(dockerfileFolder, "/") + + // Create a buffer + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + defer tw.Close() + baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/") + + // Builds the entire folder into buf + err = getParsedTar(tw, baseDir, "") + if err != nil { + log.Printf("Tar issue: %s", err) + } + + dockerFileTarReader := bytes.NewReader(buf.Bytes()) + buildOptions := types.ImageBuildOptions{ + Remove: true, + Tags: tags, + BuildArgs: map[string]*string{}, + } + //NetworkMode: "host", + + httpProxy := os.Getenv("HTTP_PROXY") + if len(httpProxy) > 0 { + buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy + } + httpsProxy := os.Getenv("HTTPS_PROXY") + if len(httpProxy) > 0 { + buildOptions.BuildArgs["https_proxy"] = &httpsProxy + } + + // Build the actual image + imageBuildResponse, err := client.ImageBuild( + ctx, + dockerFileTarReader, + buildOptions, + ) + + if err != nil { + return err + } + + // Read the STDOUT from the build process + defer imageBuildResponse.Body.Close() + buildBuf := new(strings.Builder) + _, err = io.Copy(buildBuf, imageBuildResponse.Body) + if err != nil { + return err + } else { + if strings.Contains(buildBuf.String(), "errorDetail") { + log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n")) + return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ","))) + } + } + + } return nil } diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index dd6bd5c8..91464105 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -1,6 +1,6 @@ module shuffle-shared -replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared +//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared go 1.19 @@ -19,13 +19,16 @@ require ( github.com/gorilla/mux v1.8.0 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.4.19 - golang.org/x/crypto v0.14.0 + github.com/shuffle/shuffle-shared v0.4.66 + golang.org/x/crypto v0.9.0 google.golang.org/api v0.125.0 google.golang.org/appengine v1.6.7 google.golang.org/grpc v1.55.0 gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.22.5 + k8s.io/apimachinery v0.22.5 + k8s.io/client-go v0.22.5 ) require ( @@ -45,11 +48,13 @@ require ( github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.6.18 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-logr/logr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/swag v0.19.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -58,18 +63,23 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-github/v28 v28.1.1 // indirect github.com/google/go-querystring v1.0.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.10.0 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.11.13 // indirect github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect github.com/moby/patternmatcher v0.5.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect @@ -91,15 +101,22 @@ require ( golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum new file mode 100644 index 00000000..d3fd1d01 --- /dev/null +++ b/backend/go-app/go.sum @@ -0,0 +1,887 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds= +cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.4.0/go.mod h1:d18825/a9bICdAIJy2EkHs9joU4RlIZ1t6l8WDdbdY0= +cloud.google.com/go/datastore v1.11.0 h1:iF6I/HaLs3Ado8uRKMvZRvF/ZLkWaWE9i8AiHzbC774= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/iam v1.0.1 h1:lyeCAU6jpnVNrE9zGQkTl3WgNgK/X+uWwaw0kynZJMU= +cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBalRE8= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.31.0 h1:aXdyyJz90kA+bor9+6+xHAciMD5mj8v15WqFZ5E0sek= +cloud.google.com/go/pubsub v1.31.0/go.mod h1:dYmJ3K97NCQ/e4OwZ20rD4Ym3Bu8Gu9m/aJdWQjdcks= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= +github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ= +github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= +github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4= +github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= +github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw= +github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 h1:hjXJeBcAMS1WGENGqDpzvmgS43oECTx8UXq31UBu0Jw= +github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= +github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 h1:9bAydALqAjBfPHd/eAiJBHnMZUYov8m2PkXVr+YGQeI= +github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82/go.mod h1:tyA14J0sA3Hph4dt+AfCjPrYR13+vVodshQSM7km9qw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns= +github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg= +github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frikky/go-elasticsearch/v8 v8.13.1/go.mod h1:RPq0JXPQVVSFHTlPwj/go8BZ1hegRf+StaSpT2iGIoQ= +github.com/frikky/kin-openapi v0.41.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= +github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0= +github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= +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.9.7 h1:FlIrT71E62zwKgRvCvWGdxRD+a/pIy+miY/n3MXgfuw= +github.com/fsouza/go-dockerclient v1.9.7/go.mod h1:vx9C32kE2D15yDSOMCDaAEIARZpDQDFBHeqL3MgQy/U= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= +github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= +github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.10.0 h1:ebSgKfMxynOdxw8QQuFOKMgomqeLGPqNLQox2bo42zg= +github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= +github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +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.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +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= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= +github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M= +github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= +github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ= +github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +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.4.56 h1:Z29GZB/eipt2aA4i+6heO1V2mrUmHPdw4QN4X4b1HdU= +github.com/shuffle/shuffle-shared v0.4.56/go.mod h1:X613gbo0dT3fnYvXDRwjQZyLC+T49T2nSQOrCV5QMlI= +github.com/shuffle/shuffle-shared v0.4.59 h1:5Sv8aorgQJFZr3cCKltfycdXzp9v5zlF2l3GZXjrTEo= +github.com/shuffle/shuffle-shared v0.4.59/go.mod h1:X613gbo0dT3fnYvXDRwjQZyLC+T49T2nSQOrCV5QMlI= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= +github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= +github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= +go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.125.0 h1:7xGvEY4fyWbhWMHf3R2/4w7L4fXyfpRGE9g6lp8+DCk= +google.golang.org/api v0.125.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.34.1/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.22.5 h1:xk7C+rMjF/EGELiD560jdmwzrB788mfcHiNbMQLIVI8= +k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= +k8s.io/apimachinery v0.22.5 h1:cIPwldOYm1Slq9VLBRPtEYpyhjIm1C6aAMAoENuvN9s= +k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= +k8s.io/client-go v0.22.5 h1:I8Zn/UqIdi2r02aZmhaJ1hqMxcpfJ3t5VqvHtctHYFo= +k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/backend/go-app/main.go b/backend/go-app/main.go index ee4622d9..3fbd4f70 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -27,36 +27,22 @@ import ( "os/exec" "path/filepath" - //"regexp" + // import httptest + "net/http/httptest" + "strings" "time" - // Google cloud - "cloud.google.com/go/datastore" - "cloud.google.com/go/pubsub" - "cloud.google.com/go/storage" - "google.golang.org/appengine/mail" - "github.com/frikky/kin-openapi/openapi2" "github.com/frikky/kin-openapi/openapi2conv" "github.com/frikky/kin-openapi/openapi3" - /* - "github.com/frikky/kin-openapi/openapi2" - "github.com/frikky/kin-openapi/openapi2conv" - "github.com/frikky/kin-openapi/openapi3" - */ - "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/storage/memory" - //cv "github.com/nirasan/go-oauth-pkce-code-verifier" - - //githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http" - // Random xj "github.com/basgys/goxml2json" newscheduler "github.com/carlescere/scheduler" @@ -69,8 +55,6 @@ import ( // Web "github.com/gorilla/mux" - "google.golang.org/api/option" - "google.golang.org/grpc" http2 "gopkg.in/src-d/go-git.v4/plumbing/transport/http" ) @@ -84,61 +68,6 @@ var registryName = "registry.hub.docker.com" var runningEnvironment = "onprem" var syncUrl = "https://shuffler.io" -var syncSubUrl = "https://shuffler.io" - -var dbclient *datastore.Client - -type Userapi struct { - Username string `datastore:"username"` - ApiKey string `datastore:"apikey"` -} - -type ExecutionInfo struct { - TotalApiUsage int64 `json:"total_api_usage" datastore:"total_api_usage"` - TotalWorkflowExecutions int64 `json:"total_workflow_executions" datastore:"total_workflow_executions"` - TotalAppExecutions int64 `json:"total_app_executions" datastore:"total_app_executions"` - TotalCloudExecutions int64 `json:"total_cloud_executions" datastore:"total_cloud_executions"` - TotalOnpremExecutions int64 `json:"total_onprem_executions" datastore:"total_onprem_executions"` - DailyApiUsage int64 `json:"daily_api_usage" datastore:"daily_api_usage"` - DailyWorkflowExecutions int64 `json:"daily_workflow_executions" datastore:"daily_workflow_executions"` - DailyAppExecutions int64 `json:"daily_app_executions" datastore:"daily_app_executions"` - DailyCloudExecutions int64 `json:"daily_cloud_executions" datastore:"daily_cloud_executions"` - DailyOnpremExecutions int64 `json:"daily_onprem_executions" datastore:"daily_onprem_executions"` -} - -// "Execution by status" -// Execution history -//type GlobalStatistics struct { -// BackendExecutions int64 `json:"backend_executions" datastore:"backend_executions"` -// WorkflowCount int64 `json:"workflow_count" datastore:"workflow_count"` -// ExecutionCount int64 `json:"execution_count" datastore:"execution_count"` -// ExecutionSuccessCount int64 `json:"execution_success_count" datastore:"execution_success_count"` -// ExecutionAbortCount int64 `json:"execution_abort_count" datastore:"execution_abort_count"` -// ExecutionFailureCount int64 `json:"execution_failure_count" datastore:"execution_failure_count"` -// ExecutionPendingCount int64 `json:"execution_pending_count" datastore:"execution_pending_count"` -// AppUsageCount int64 `json:"app_usage_count" datastore:"app_usage_count"` -// TotalAppsCount int64 `json:"total_apps_count" datastore:"total_apps_count"` -// SelfMadeAppCount int64 `json:"self_made_app_count" datastore:"self_made_app_count"` -// WebhookUsageCount int64 `json:"webhook_usage_count" datastore:"webhook_usage_count"` -// Baseline map[string]int64 `json:"baseline" datastore:"baseline"` -//} - -type ParsedOpenApi struct { - Body string `datastore:"body,noindex" json:"body"` - ID string `datastore:"id" json:"id"` - Success bool `datastore:"success,omitempty" json:"success,omitempty"` -} - -// Limits set for a user so that they can't do a shitload -type UserLimits struct { - DailyApiUsage int64 `json:"daily_api_usage" datastore:"daily_api_usage"` - DailyWorkflowExecutions int64 `json:"daily_workflow_executions" datastore:"daily_workflow_executions"` - DailyCloudExecutions int64 `json:"daily_cloud_executions" datastore:"daily_cloud_executions"` - DailyTriggers int64 `json:"daily_triggers" datastore:"daily_triggers"` - DailyMailUsage int64 `json:"daily_mail_usage" datastore:"daily_mail_usage"` - MaxTriggers int64 `json:"max_triggers" datastore:"max_triggers"` - MaxWorkflows int64 `json:"max_workflows" datastore:"max_workflows"` -} type retStruct struct { Success bool `json:"success"` @@ -148,43 +77,6 @@ type retStruct struct { Reason string `json:"reason"` } -// Saves some data, not sure what to have here lol -type UserAuth struct { - Description string `json:"description" datastore:"description,noindex" yaml:"description"` - Name string `json:"name" datastore:"name" yaml:"name"` - Workflows []string `json:"workflows" datastore:"workflows"` - Username string `json:"username" datastore:"username"` - Fields []UserAuthField `json:"fields" datastore:"fields"` -} - -type UserAuthField struct { - Key string `json:"key" datastore:"key"` - Value string `json:"value" datastore:"value,noindex"` -} - -// Not environment, but execution environment -//type Environment struct { -// Name string `datastore:"name"` -// Type string `datastore:"type"` -// Registered bool `datastore:"registered"` -// Default bool `datastore:"default" json:"default"` -// Archived bool `datastore:"archived" json:"archived"` -// Id string `datastore:"id" json:"id"` -// OrgId string `datastore:"org_id" json:"org_id"` -//} - -// timeout maybe? idk -type session struct { - Username string `datastore:"Username,noindex"` - Id string `datastore:"Id,noindex"` - Session string `datastore:"session,noindex"` -} - -type loginStruct struct { - Username string `json:"username"` - Password string `json:"password"` -} - type Contact struct { Firstname string `json:"firstname"` Lastname string `json:"lastname"` @@ -374,64 +266,6 @@ type Hook struct { Environment string `json:"environment" datastore:"environment"` } -func createFileFromFile(ctx context.Context, bucket *storage.BucketHandle, remotePath, localPath string) error { - // [START upload_file] - f, err := os.Open(localPath) - if err != nil { - return err - } - defer f.Close() - - wc := bucket.Object(remotePath).NewWriter(ctx) - if _, err = io.Copy(wc, f); err != nil { - return err - } - if err := wc.Close(); err != nil { - return err - } - // [END upload_file] - return nil -} - -func createFileFromBytes(ctx context.Context, bucket *storage.BucketHandle, remotePath string, data []byte) error { - wc := bucket.Object(remotePath).NewWriter(ctx) - - byteReader := bytes.NewReader(data) - if _, err := io.Copy(wc, byteReader); err != nil { - return err - } - - if err := wc.Close(); err != nil { - return err - } - - // [END upload_file] - return nil -} - -func readFile(ctx context.Context, bucket *storage.BucketHandle, object string) ([]byte, error) { - // [START download_file] - rc, err := bucket.Object(object).NewReader(ctx) - if err != nil { - return nil, err - } - defer rc.Close() - - data, err := ioutil.ReadAll(rc) - if err != nil { - return nil, err - } - return data, nil - // [END download_file] -} - -func IndexHandler(entrypoint string) func(w http.ResponseWriter, r *http.Request) { - fn := func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, entrypoint) - } - - return http.HandlerFunc(fn) -} func GetUsersHandler(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{ @@ -480,29 +314,6 @@ func authenticate(request *http.Request) bool { return false } -func publishPubsub(ctx context.Context, topic string, data []byte, attributes map[string]string) error { - client, err := pubsub.NewClient(ctx, gceProject) - if err != nil { - return err - } - - t := client.Topic(topic) - result := t.Publish(ctx, &pubsub.Message{ - Data: data, - Attributes: attributes, - }) - // Block until the result is returned and a server-generated - // ID is returned for the published message. - id, err := result.Get(ctx) - if err != nil { - return err - } - - log.Printf("Published message for topic %s; msg ID: %v\n", topic, id) - - return nil -} - func checkError(cmdName string, cmdArgs []string) error { cmd := exec.Command(cmdName, cmdArgs...) cmdReader, err := cmd.StdoutPipe() @@ -710,10 +521,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) } } - //err = increaseStatisticsField(ctx, "successful_register", username, 1, org.Id) - //if err != nil { - // log.Printf("Failed to increase total apps loaded stats: %s", err) - //} return nil } @@ -1064,7 +871,6 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { } org, err := shuffle.GetOrg(ctx, item) - _ = err if len(org.Id) > 0 { userOrgs = append(userOrgs, shuffle.OrgMini{ Id: org.Id, @@ -1073,7 +879,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { Image: org.Image, }) } else { - log.Printf("[WARNING] Failed to get org %s for user %s", item, userInfo.Username) + log.Printf("[WARNING] Failed to get org %s (%s) for user %s. Error: %#v", org.Name, item, userInfo.Username, err) } } @@ -1103,7 +909,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { userOrgs = shuffle.SortOrgList(userOrgs) orgPriorities := org.Priorities if len(org.Priorities) < 10 { - log.Printf("[WARNING] Should find and add priorities as length is less than 10 for org %s", userInfo.ActiveOrg.Id) + //log.Printf("[WARNING] Should find and add priorities as length is less than 10 for org %s", userInfo.ActiveOrg.Id) newPriorities, err := shuffle.GetPriorities(ctx, userInfo, org) if err != nil { log.Printf("[WARNING] Failed getting new priorities for org %s: %s", org.Id, err) @@ -1171,110 +977,6 @@ type passwordReset struct { Reference string `json:"reference"` } -// This might be... a bit off, but that's fine :) -// This might also be stupid, as we want timelines and such -// Anyway, these are super basic stupid stats. -func increaseStatisticsField(ctx context.Context, fieldname, id string, amount int64, orgId string) error { - - // 1. Get current stats - // 2. Increase field(s) - // 3. Put new stats - statisticsId := "global_statistics" - nameKey := fieldname - key := datastore.NameKey(statisticsId, nameKey, nil) - - statisticsItem := shuffle.StatisticsItem{} - newData := shuffle.StatisticsData{ - Timestamp: int64(time.Now().Unix()), - Amount: amount, - Id: id, - } - - if err := dbclient.Get(ctx, key, &statisticsItem); err != nil { - // Should init - if strings.Contains(fmt.Sprintf("%s", err), "entity") { - statisticsItem = shuffle.StatisticsItem{ - Total: amount, - OrgId: orgId, - Fieldname: fieldname, - Data: []shuffle.StatisticsData{ - newData, - }, - } - - if _, err := dbclient.Put(ctx, key, &statisticsItem); err != nil { - log.Printf("Error setting base stats: %s", err) - return err - } - - return nil - } - //log.Printf("STATSERR: %s", err) - - return err - } - - statisticsItem.Total += amount - statisticsItem.Data = append(statisticsItem.Data, newData) - - // New struct, to not add body, author etc - // FIXME - reintroduce - //if _, err := dbclient.Put(ctx, key, &statisticsItem); err != nil { - // log.Printf("Error stats to %s: %s", fieldname, err) - // return err - //} - - //log.Printf("Stats: %#v", statisticsItem) - - return nil -} - -// FIXME - forward this to emails or whatever CRM system in use -func handleContact(resp http.ResponseWriter, request *http.Request) { - cors := shuffle.HandleCors(resp, request) - if cors { - return - } - - body, err := ioutil.ReadAll(request.Body) - if err != nil { - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - - var t Contact - err = json.Unmarshal(body, &t) - if err != nil { - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - - if len(t.Email) < 3 || len(t.Message) == 0 { - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Please fill a valid email and message"}`))) - return - } - - ctx := context.Background() - mailContent := fmt.Sprintf("Firsname: %s\nLastname: %s\nTitle: %s\nCompanyname: %s\nPhone: %s\nEmail: %s\nMessage: %s", t.Firstname, t.Lastname, t.Title, t.Companyname, t.Phone, t.Email, t.Message) - log.Printf("Sending contact from %s", t.Email) - - msg := &mail.Message{ - Sender: "Shuffle ", - To: []string{"frikky@shuffler.io"}, - Subject: "Shuffler.io - New contact form", - Body: mailContent, - } - - if err := mail.Send(ctx, msg); err != nil { - log.Printf("Couldn't send email: %v", err) - } - - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true, "message": "Thanks for reaching out. We will contact you soon!"}`))) -} func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) @@ -3067,8 +2769,8 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s // FIXME: Check whether it's in use. if user.Id != app.Owner && user.Role != "admin" { log.Printf("[WARNING] Wrong user (%s) for app %s when verifying swagger", user.Username, app.Name) - resp.WriteHeader(400) - resp.Write([]byte(`{"success": false}`)) + resp.WriteHeader(403) + resp.Write([]byte(`{"success": false, "reason": "You don't have permissions to edit this app. Contact support@shuffler.io if this persists."}`)) return } @@ -3500,6 +3202,7 @@ func handleCloudExecutionOnprem(workflowId, startNode, executionSource, executio } func handleCloudJob(job shuffle.CloudSyncJob) error { + ctx := context.Background() // May need authentication in all of these..? log.Printf("[INFO] Handle job with type %s and action %s", job.Type, job.Action) shuffle.IncrementCache(ctx, job.OrgId, "org_sync_actions") @@ -3750,16 +3453,69 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error { return nil } + func remoteOrgJobHandler(org shuffle.Org, interval int) error { + + // Check if it's 1 in 10 (10% chance random) + backupJob := shuffle.BackupJob{} + + // Check if workflow backup is active + // Check if app backup is active + ctx := context.Background() + + foundUser := org.Users[0] + for _, user := range org.Users { + if user.Role == "admin" { + foundUser = user + break + } + } + + if org.SyncConfig.WorkflowBackup { + workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser) + if err != nil { + log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err) + } else { + backupJob.Workflows = workflows + } + } + + if org.SyncConfig.AppBackup && len(org.Users) > 0 { + + apps, err := shuffle.GetPrioritizedApps(ctx, foundUser) + if err != nil { + log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err) + } else { + backupJob.Apps = apps + } + } + + info, err := shuffle.GetOrgStatistics(ctx, org.Id) + if err != nil { + log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err) + } else { + backupJob.Stats = *info + } + + backupJobData, err := json.Marshal(backupJob) + if err != nil { + log.Printf("[ERROR] Failed marshalling backup job: %s", err) + backupJobData = []byte{} + } + + client := &http.Client{} syncUrl := fmt.Sprintf("%s/api/v1/cloud/sync", syncUrl) req, err := http.NewRequest( - "GET", + "POST", syncUrl, - nil, + bytes.NewBuffer(backupJobData), ) req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) + + //log.Printf("[INFO] Sending org sync with autho %s", org.SyncConfig.Apikey) + newresp, err := client.Do(req) if err != nil { //log.Printf("Failed request in org sync: %s", err) @@ -3775,7 +3531,7 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { //log.Printf("Remote Data: %s", respBody) err = remoteOrgJobController(org, respBody) if err != nil { - log.Printf("[ERROR] Failed job controller run for %s: %s", respBody, err) + //log.Printf("[ERROR] Failed cloud sync job controller run for '%s': %s", respBody, err) return err } return nil @@ -3819,7 +3575,7 @@ func runInitEs(ctx context.Context) { activeOrgs, err := shuffle.GetAllOrgs(ctx) setUsers := false - //log.Printf("ORGS: %d", len(activeOrgs)) + _ = setUsers if err != nil { if fmt.Sprintf("%s", err) == "EOF" { time.Sleep(7 * time.Second) @@ -3874,7 +3630,7 @@ func runInitEs(ctx context.Context) { if len(activeOrgs) == 1 { if len(activeOrgs[0].Users) == 0 { - log.Printf("ORG doesn't have any users??") + log.Printf("[ERROR] Main Org doesn't have any user. Creating.") users, err := shuffle.GetAllUsers(ctx) if err != nil && len(users) == 0 { @@ -3907,10 +3663,9 @@ func runInitEs(ctx context.Context) { if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") { log.Printf("[INFO] Waiting during init to make sure the opensearch instance is up and running with security features properly") - time.Sleep(30 * time.Second) + time.Sleep(15 * time.Second) } - _ = setUsers schedules, err := shuffle.GetAllSchedules(ctx, "ALL") if err != nil { log.Printf("[WARNING] Failed getting schedules during service init: %s", err) @@ -4054,17 +3809,17 @@ func runInitEs(ctx context.Context) { continue } - log.Printf("[DEBUG] Should start schedule for org %s (%s)", org.Name, org.Id) + log.Printf("[DEBUG] Should start cloud schedule for org %s (%s)", org.Name, org.Id) job := func() { err := remoteOrgJobHandler(org, interval) if err != nil { - log.Printf("[ERROR] Failed request with remote org setup (2): %s", err) + log.Printf("[ERROR] Failed request with remote org sync for org %s (2): %s", org.Id, err) } } jobret, err := newscheduler.Every(int(interval)).Seconds().NotImmediately().Run(job) if err != nil { - log.Printf("[CRITICAL] Failed to schedule org: %s", err) + log.Printf("[ERROR] Failed to schedule org: %s", err) } else { log.Printf("[INFO] Started sync on interval %d for org %s (%s)", interval, org.Name, org.Id) scheduledOrgs[org.Id] = jobret @@ -4186,8 +3941,8 @@ func runInitEs(ctx context.Context) { if err != nil && len(workflowapps) == 0 { log.Printf("[WARNING] Failed getting apps (runInit): %s", err) - } else if err == nil { - log.Printf("[DEBUG] Downloading default apps") + } else if err == nil && len(workflowapps) < 10 { + log.Printf("[DEBUG] Downloading default apps as %d were found", len(workflowapps)) fs := memfs.New() storer := memory.NewStorage() @@ -4243,6 +3998,8 @@ func runInitEs(ctx context.Context) { if len(location) != 0 { handleAppHotload(ctx, location, false) } + } else { + log.Printf("[DEBUG] Skipping download of default apps as %d were found", len(workflowapps)) } log.Printf("[INFO] Downloading OpenAPI data for search - EXTRA APPS") @@ -4258,7 +4015,7 @@ func runInitEs(ctx context.Context) { _, err = git.Clone(storer, fs, cloneOptions) if err != nil { log.Printf("[WARNING] Failed loading repo %s into memory: %s", apis, err) - } else { + } else if err == nil && len(workflowapps) < 10 { log.Printf("[INFO] Finished git clone. Looking for updates to the repo.") dir, err := fs.ReadDir("") if err != nil { @@ -4267,699 +4024,39 @@ func runInitEs(ctx context.Context) { iterateOpenApiGithub(fs, dir, "", "") log.Printf("[INFO] Finished downloading extra API samples") + } else { + log.Printf("[INFO] Skipping download of extra API samples as %d were found", len(workflowapps)) + } + + + if os.Getenv("SHUFFLE_HEALTHCHECK_DISABLED") != "true" { + healthcheckInterval := 15 + log.Printf("[INFO] Starting healthcheck job every %d minute. Stats available on /api/v1/health/stats. Disable with SHUFFLE_HEALTHCHECK_DISABLED=true", healthcheckInterval) + job := func() { + // Prepare a fake http.responsewriter + resp := httptest.NewRecorder() + + request := http.Request{} + // Add the "force=true" query to the fake request + request.URL, err = url.Parse("/api/v1/health/stats?force=true") + if err != nil { + log.Printf("[ERROR] Failed to parse test url for healthstats: %s", err) + } + + shuffle.RunOpsHealthCheck(resp, &request) + } + + _, err := newscheduler.Every(int(healthcheckInterval)).Minutes().Run(job) + if err != nil { + log.Printf("[ERROR] Failed to schedule healthcheck: %s", err) + } else { + log.Printf("[DEBUG] Successfully started healthcheck interval of %d minutes", healthcheckInterval) + } } log.Printf("[INFO] Finished INIT (ES)") } -// Handles configuration items during Shuffle startup -func runInit(ctx context.Context) { - // Setting stats for backend starts (failure count as well) - //err := increaseStatisticsField(ctx, "backend_executions", "", 1, "") - //if err != nil { - // log.Printf("Failed increasing local stats: %s", err) - //} - //log.Printf("[DEBUG] Finalized init statistics update") - - log.Printf("[DEBUG] Starting INIT setup (NOT Opensearch/Elasticsearch!)") - httpProxy := os.Getenv("HTTP_PROXY") - if len(httpProxy) > 0 { - log.Printf("Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy) - } - httpsProxy := os.Getenv("HTTPS_PROXY") - if len(httpsProxy) > 0 { - log.Printf("Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy) - } - - //requestCache = cache.New(5*time.Minute, 10*time.Minute) - - /* - proxyUrl, err := url.Parse(httpProxy) - if err != nil { - log.Printf("Failed setting up proxy: %s", err) - } else { - // accept any certificate (might be useful for testing) - customClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - Proxy: http.ProxyURL(proxyUrl), - }, - - // 15 second timeout - Timeout: 15 * 15time.Second, - - // don't follow redirect - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - } - - // Override http(s) default protocol to use our custom client - client.InstallProtocol("http", githttp.NewClient(customClient)) - client.InstallProtocol("https", githttp.NewClient(customClient)) - } - } - - httpsProxy := os.Getenv("SHUFFLE_HTTPS_PROXY") - if len(httpsProxy) > 0 { - log.Printf("Running with HTTPS proxy %s", httpsProxy) - } - */ - - setUsers := false - log.Printf("[DEBUG] Getting organizations") - orgQuery := datastore.NewQuery("Organizations") - var activeOrgs []shuffle.Org - _, err := dbclient.GetAll(ctx, orgQuery, &activeOrgs) - if err != nil { - log.Printf("Error getting organizations!") - } else { - // Add all users to it - if len(activeOrgs) == 1 { - setUsers = true - } - - log.Printf("Organizations exist!") - if len(activeOrgs) == 0 { - log.Printf(`[DEBUG] No orgs. Setting org "default"`) - orgSetupName := "default" - orgId := uuid.NewV4().String() - newOrg := shuffle.Org{ - Name: orgSetupName, - Id: orgId, - Org: orgSetupName, - Users: []shuffle.User{}, - Roles: []string{"admin", "user"}, - CloudSync: false, - } - - err = shuffle.SetOrg(ctx, newOrg, newOrg.Id) - if err != nil { - log.Printf("[WARNING] Failed setting organization: %s", err) - } else { - log.Printf("[WARNING] Successfully created the default org!") - setUsers = true - } - } else { - log.Printf("[DEBUG] There are %d org(s).", len(activeOrgs)) - - if len(activeOrgs) == 1 { - if len(activeOrgs[0].Users) == 0 { - log.Printf("[WARNING] ORG doesn't have any users??") - - q := datastore.NewQuery("Users") - var users []shuffle.User - _, err = dbclient.GetAll(ctx, q, &users) - if err != nil && len(users) == 0 { - log.Printf("Failed getting users in org fix") - } else { - // Remapping everyone to admin. This should never happen. - - for _, user := range users { - user.ActiveOrg = shuffle.OrgMini{ - Id: activeOrgs[0].Id, - Name: activeOrgs[0].Name, - Role: "admin", - } - - activeOrgs[0].Users = append(activeOrgs[0].Users, user) - } - - err = shuffle.SetOrg(ctx, activeOrgs[0], activeOrgs[0].Id) - if err != nil { - log.Printf("Failed setting org: %s", err) - } else { - log.Printf("Successfully updated org to have users!") - } - } - - } - } - } - } - - // Adding the users to the base organization since only one exists (default) - if setUsers && len(activeOrgs) > 0 { - activeOrg := activeOrgs[0] - - q := datastore.NewQuery("Users") - var users []shuffle.User - _, err = dbclient.GetAll(ctx, q, &users) - if err == nil { - setOrgBool := false - usernames := []string{} - for _, user := range users { - usernames = append(usernames, user.Username) - newUser := shuffle.User{ - Username: user.Username, - Id: user.Id, - ActiveOrg: shuffle.OrgMini{ - Id: activeOrg.Id, - }, - Orgs: []string{activeOrg.Id}, - Role: user.Role, - } - - found := false - for _, orgUser := range activeOrg.Users { - if user.Id == orgUser.Id { - found = true - } - } - - if !found && len(user.Username) > 0 { - log.Printf("Adding user %s to org %s", user.Username, activeOrg.Name) - activeOrg.Users = append(activeOrg.Users, newUser) - setOrgBool = true - } - } - - log.Printf("Users found: %s", strings.Join(usernames, ", ")) - - if setOrgBool { - err = shuffle.SetOrg(ctx, activeOrg, activeOrg.Id) - if err != nil { - log.Printf("Failed setting org %s: %s!", activeOrg.Name, err) - } else { - log.Printf("UPDATED org %s!", activeOrg.Name) - } - } - } - - log.Printf("Should add %d users to organization default", len(users)) - } - - if len(activeOrgs) == 0 { - orgQuery := datastore.NewQuery("Organizations") - _, err = dbclient.GetAll(ctx, orgQuery, &activeOrgs) - if err != nil { - log.Printf("Failed getting orgs the second time around") - } - } - - // Fix active users etc - q := datastore.NewQuery("Users").Filter("active =", true) - var activeusers []shuffle.User - _, err = dbclient.GetAll(ctx, q, &activeusers) - if err != nil && len(activeusers) == 0 { - log.Printf("Error getting users during init: %s", err) - } else { - log.Printf("Parsing all users and setting them to active.") - q := datastore.NewQuery("Users") - var users []shuffle.User - _, err := dbclient.GetAll(ctx, q, &users) - //log.Printf("User ret: %s", err) - - if len(activeusers) == 0 && len(users) > 0 { - log.Printf("No active users found - setting ALL to active") - if err == nil { - for _, user := range users { - user.Active = true - if len(user.Username) == 0 { - shuffle.DeleteKey(ctx, "Users", strings.ToLower(user.Username)) - continue - } - - if len(user.Role) > 0 { - user.Roles = append(user.Roles, user.Role) - } - - if len(user.Orgs) == 0 { - defaultName := "default" - user.Orgs = []string{defaultName} - user.ActiveOrg = shuffle.OrgMini{ - Name: defaultName, - Role: "admin", - } - } - - err = shuffle.SetUser(ctx, &user, true) - if err != nil { - log.Printf("Failed to reset user") - } else { - log.Printf("Remade user %s with ID", user.Id) - err = shuffle.DeleteKey(ctx, "Users", strings.ToLower(user.Username)) - if err != nil { - log.Printf("Failed to delete old user by username") - } - } - } - } - } else if len(users) == 0 { - log.Printf("Trying to set up user based on environments SHUFFLE_DEFAULT_USERNAME & SHUFFLE_DEFAULT_PASSWORD") - username := os.Getenv("SHUFFLE_DEFAULT_USERNAME") - password := os.Getenv("SHUFFLE_DEFAULT_PASSWORD") - if len(username) == 0 || len(password) == 0 { - log.Printf("SHUFFLE_DEFAULT_USERNAME and SHUFFLE_DEFAULT_PASSWORD not defined as environments. Running without default user.") - } else { - apikey := os.Getenv("SHUFFLE_DEFAULT_APIKEY") - - tmpOrg := shuffle.OrgMini{ - Name: "default", - } - - err = createNewUser(username, password, "admin", apikey, tmpOrg) - if err != nil { - log.Printf("Failed to create default user %s: %s", username, err) - } else { - log.Printf("Successfully created user %s", username) - } - } - } else { - if len(users) < 10 && len(users) > 0 { - for _, user := range users { - log.Printf("[INFO] Username: %s, role: %s", user.Username, user.Role) - } - } else { - log.Printf("[INIT] Found %d users.", len(users)) - } - - if len(activeOrgs) == 1 && len(users) > 0 { - for _, user := range users { - if user.ActiveOrg.Id == "" && len(user.Username) > 0 { - user.ActiveOrg = shuffle.OrgMini{ - Id: activeOrgs[0].Id, - Name: activeOrgs[0].Name, - } - - err = shuffle.SetUser(ctx, &user, true) - if err != nil { - log.Printf("Failed updating user %s with org", user.Username) - } else { - log.Printf("Updated user %s to have org", user.Username) - } - } - } - } - //log.Printf(users[0].Username) - } - } - - // Gets environments and inits if it doesn't exist - count, err := shuffle.GetEnvironmentCount() - if count == 0 && err == nil && len(activeOrgs) == 1 { - log.Printf("[INFO] Setting up environment with org %s", activeOrgs[0].Id) - - defaultEnv := os.Getenv("ORG_ID") - if len(defaultEnv) == 0 { - defaultEnv = "Shuffle" - log.Printf("[DEBUG] Setting default environment for org to %s", defaultEnv) - } - - item := shuffle.Environment{ - Name: defaultEnv, - Type: "onprem", - OrgId: activeOrgs[0].Id, - Default: true, - Id: uuid.NewV4().String(), - } - - err = shuffle.SetEnvironment(ctx, &item) - if err != nil { - log.Printf("[WARNING] Failed setting up new environment") - } - } else if len(activeOrgs) == 1 { - log.Printf("[INFO] Setting up all environments with org %s", activeOrgs[0].Id) - var environments []shuffle.Environment - q := datastore.NewQuery("Environments") - _, err = dbclient.GetAll(ctx, q, &environments) - if err == nil { - existingEnv := []string{} - _ = existingEnv - for _, item := range environments { - //if shuffle.ArrayContains(existingEnv, item.Name) { - // log.Printf("[WARNING] Env %s already exists - deleting it. %#v", item.Name, item) - // err = DeleteKey(ctx, "Environments", item.Name) - // if err != nil { - // log.Printf("[WARNING] Env deletion error: %s", err) - // } - - // continue - //} - - //existingEnv = append(existingEnv, item.Name) - - if item.OrgId == activeOrgs[0].Id && len(item.Id) > 0 { - continue - } - - if len(item.Id) == 0 { - item.Id = uuid.NewV4().String() - } - - item.OrgId = activeOrgs[0].Id - err = shuffle.SetEnvironment(ctx, &item) - if err != nil { - log.Printf("[WARNING] Failed adding environment to org %s", activeOrgs[0].Id) - } - } - } - } - - // Fixing workflows to have real activeorg IDs - //workflowQ := datastore.NewQuery("workflow") - //ret, err := dbclient.GetAll(ctx, workflowQ, &workflows) - //log.Printf("[INFO] Found %d workflows during startup", workflowCount) - //log.Printf("%#v, %s", ret, err) - - var workflows []shuffle.Workflow - if len(activeOrgs) == 1 { - q := datastore.NewQuery("workflow").Limit(35) - _, err = dbclient.GetAll(ctx, q, &workflows) - if err != nil && len(workflows) == 0 { - log.Printf("Error getting workflows in runinit: %s", err) - } else { - updated := 0 - timeNow := time.Now().Unix() - for _, workflow := range workflows { - setLocal := false - if workflow.ExecutingOrg.Id == "" || len(workflow.OrgId) == 0 { - workflow.OrgId = activeOrgs[0].Id - workflow.ExecutingOrg = shuffle.OrgMini{ - Id: activeOrgs[0].Id, - Name: activeOrgs[0].Name, - } - - setLocal = true - } else if workflow.Edited == 0 { - workflow.Edited = timeNow - setLocal = true - } - - if setLocal { - err = shuffle.SetWorkflow(ctx, workflow, workflow.ID) - if err != nil { - log.Printf("Failed setting workflow in init: %s", err) - } else { - log.Printf("Fixed workflow %s to have the right info.", workflow.ID) - updated += 1 - } - } - } - - if updated > 0 { - log.Printf("Set workflow orgs for %d workflows", updated) - } - } - - /* - fileq := datastore.NewQuery("Files").Limit(1) - count, err := dbclient.Count(ctx, fileq) - log.Printf("FILECOUNT: %d", count) - if err == nil && count < 10 { - basepath := "." - filename := "testfile.txt" - fileId := uuid.NewV4().String() - log.Printf("Creating new file reference %s because none exist!", fileId) - workflowId := "2cf1169d-b460-41de-8c36-28b2092866f8" - downloadPath := fmt.Sprintf("%s/%s/%s/%s", basepath, activeOrgs[0].Id, workflowId, fileId) - - timeNow := time.Now().Unix() - newFile := File{ - Id: fileId, - CreatedAt: timeNow, - UpdatedAt: timeNow, - Description: "Created by system for testing", - Status: "active", - Filename: filename, - OrgId: activeOrgs[0].Id, - WorkflowId: workflowId, - DownloadPath: downloadPath, - } - - err = setFile(ctx, newFile) - if err != nil { - log.Printf("Failed setting file: %s", err) - } else { - log.Printf("Created file %s in init", newFile.DownloadPath) - } - } - */ - - var allworkflowapps []shuffle.AppAuthenticationStorage - q = datastore.NewQuery("workflowappauth") - _, err = dbclient.GetAll(ctx, q, &allworkflowapps) - if err == nil { - log.Printf("Setting up all app auths with org %s", activeOrgs[0].Id) - for _, item := range allworkflowapps { - if item.OrgId != "" { - continue - } - - //log.Printf("Should update auth for %#v!", item) - item.OrgId = activeOrgs[0].Id - err = shuffle.SetWorkflowAppAuthDatastore(ctx, item, item.Id) - if err != nil { - log.Printf("Failed adding AUTH to org %s", activeOrgs[0].Id) - } - } - } - - var schedules []shuffle.ScheduleOld - q = datastore.NewQuery("schedules") - _, err = dbclient.GetAll(ctx, q, &schedules) - if err == nil { - log.Printf("Setting up all schedules with org %s", activeOrgs[0].Id) - for _, item := range schedules { - if item.Org != "" { - continue - } - - if item.Environment == "cloud" { - log.Printf("Skipping cloud schedule") - continue - } - - item.Org = activeOrgs[0].Id - err = shuffle.SetSchedule(ctx, item) - if err != nil { - log.Printf("Failed adding schedule to org %s", activeOrgs[0].Id) - } - } - } - } - - log.Printf("Starting cloud schedules for orgs!") - type requestStruct struct { - ApiKey string `json:"api_key"` - } - for _, org := range activeOrgs { - if !org.CloudSync { - log.Printf("Skipping org %s because sync isn't set (1).", org.Id) - continue - } - - //interval := int(org.SyncConfig.Interval) - interval := 15 - if interval == 0 { - log.Printf("Skipping org %s because sync isn't set (0).", org.Id) - continue - } - - log.Printf("[DEBUG] Should start schedule for org %s (%s)", org.Name, org.Id) - job := func() { - err := remoteOrgJobHandler(org, interval) - if err != nil { - log.Printf("[ERROR] Failed request with remote org setup (2): %s", err) - } - } - - jobret, err := newscheduler.Every(int(interval)).Seconds().NotImmediately().Run(job) - if err != nil { - log.Printf("[CRITICAL] Failed to schedule org: %s", err) - } else { - log.Printf("Started sync on interval %d for org %s", interval, org.Name) - scheduledOrgs[org.Id] = jobret - } - } - - // Gets schedules and starts them - log.Printf("Relaunching schedules") - schedules, err := shuffle.GetAllSchedules(ctx, "ALL") - if err != nil { - log.Printf("Failed getting schedules during service init: %s", err) - } else { - log.Printf("Setting up %d schedule(s)", len(schedules)) - url := &url.URL{} - for _, schedule := range schedules { - if schedule.Environment == "cloud" { - log.Printf("Skipping cloud schedule") - continue - } - - //log.Printf("Schedule: %#v", schedule) - job := func() { - //log.Printf("[INFO] Running schedule %s with interval %d.", schedule.Id, schedule.Seconds) - //log.Printf("ARG: %s", schedule.WrappedArgument) - - request := &http.Request{ - URL: url, - Method: "POST", - Body: ioutil.NopCloser(strings.NewReader(schedule.WrappedArgument)), - } - - orgId := "" - if len(activeOrgs) > 0 { - orgId = activeOrgs[0].Id - } - - _, _, err := handleExecution(schedule.WorkflowId, shuffle.Workflow{}, request, orgId) - if err != nil { - log.Printf("[WARNING] Failed to execute %s: %s", schedule.WorkflowId, err) - } - } - - //log.Printf("Schedule time: every %d seconds", schedule.Seconds) - jobret, err := newscheduler.Every(schedule.Seconds).Seconds().NotImmediately().Run(job) - if err != nil { - log.Printf("Failed to schedule workflow: %s", err) - } - - scheduledJobs[schedule.Id] = jobret - } - } - - // form force-flag to download workflow apps - forceUpdateEnv := os.Getenv("SHUFFLE_APP_FORCE_UPDATE") - forceUpdate := false - if len(forceUpdateEnv) > 0 && forceUpdateEnv == "true" { - log.Printf("Forcing to rebuild apps") - forceUpdate = true - } - - // Getting apps to see if we should initialize a test - workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000, 0) - log.Printf("[INFO] Getting and validating workflowapps. Got %d with err %s", len(workflowapps), err) - if err != nil && len(workflowapps) == 0 { - log.Printf("[WARNING] Failed getting apps (runInit): %s", err) - } else if err == nil && len(workflowapps) > 0 { - var allworkflowapps []shuffle.WorkflowApp - q := datastore.NewQuery("workflowapp") - _, err := dbclient.GetAll(ctx, q, &allworkflowapps) - if err == nil { - for _, workflowapp := range allworkflowapps { - if workflowapp.Edited == 0 { - err = shuffle.SetWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID) - if err == nil { - log.Printf("[INFO] Updating time for workflowapp %s:%s", workflowapp.Name, workflowapp.AppVersion) - } - } - } - } - - } else if err == nil && len(workflowapps) == 0 { - log.Printf("Downloading default workflow apps") - fs := memfs.New() - storer := memory.NewStorage() - - url := os.Getenv("SHUFFLE_APP_DOWNLOAD_LOCATION") - if len(url) == 0 { - url = "https://github.com/shuffle/shuffle-apps" - } - - username := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_USERNAME") - password := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_PASSWORD") - - cloneOptions := &git.CloneOptions{ - URL: url, - } - - if len(username) > 0 && len(password) > 0 { - cloneOptions.Auth = &http2.BasicAuth{ - Username: username, - Password: password, - } - } - branch := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_BRANCH") - if len(branch) > 0 && branch != "master" && branch != "main" { - cloneOptions.ReferenceName = plumbing.ReferenceName(branch) - } - - log.Printf("[DEBUG] Getting apps from URL '%s'", url) - - r, err := git.Clone(storer, fs, cloneOptions) - - if err != nil { - log.Printf("Failed loading repo into memory (init): %s", err) - } - - dir, err := fs.ReadDir("") - if err != nil { - log.Printf("Failed reading folder: %s", err) - } - _ = r - //iterateAppGithubFolders(fs, dir, "", "testing") - - // FIXME: Get all the apps? - _, _, err = IterateAppGithubFolders(ctx, fs, dir, "", "", forceUpdate) - if err != nil { - log.Printf("[WARNING] Error from app load in init: %s", err) - } - //_, _, err = iterateAppGithubFolders(fs, dir, "", "", forceUpdate) - - // Hotloads locally - location := os.Getenv("SHUFFLE_APP_HOTLOAD_FOLDER") - if len(location) != 0 { - handleAppHotload(ctx, location, false) - } - } - - log.Printf("[INFO] Downloading OpenAPI data for search - EXTRA APPS") - apis := "https://github.com/shuffle/security-openapis" - - // FIXME: This part gets memory problems. Fix in the future to load these apps too. - //apis := "https://github.com/APIs-guru/openapi-directory" - fs := memfs.New() - storer := memory.NewStorage() - cloneOptions := &git.CloneOptions{ - URL: apis, - } - _, err = git.Clone(storer, fs, cloneOptions) - if err != nil { - log.Printf("Failed loading repo %s into memory: %s", apis, err) - } else { - log.Printf("[INFO] Finished git clone. Looking for updates to the repo.") - dir, err := fs.ReadDir("") - if err != nil { - log.Printf("Failed reading folder: %s", err) - } - - iterateOpenApiGithub(fs, dir, "", "") - log.Printf("[INFO] Finished downloading extra API samples") - } - - workflowLocation := os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION") - if len(workflowLocation) > 0 { - log.Printf("[INFO] Downloading WORKFLOWS from %s if no workflows - EXTRA workflows", workflowLocation) - q := datastore.NewQuery("workflow").Limit(35) - var workflows []shuffle.Workflow - _, err = dbclient.GetAll(ctx, q, &workflows) - if err != nil && len(workflows) == 0 { - log.Printf("Error getting workflows: %s", err) - } else { - if len(workflows) == 0 { - username := os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_USERNAME") - password := os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_PASSWORD") - orgId := "" - if len(activeOrgs) > 0 { - orgId = activeOrgs[0].Id - } - - err = loadGithubWorkflows(workflowLocation, username, password, "", os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_BRANCH"), orgId) - if err != nil { - log.Printf("Failed to upload workflows from github: %s", err) - } else { - log.Printf("[INFO] Finished downloading workflows from github!") - } - } else { - log.Printf("[INFO] Skipping because there are %d workflows already", len(workflows)) - } - - } - } - - log.Printf("[INFO] Finished INIT") -} func handleVerifyCloudsync(orgId string) (shuffle.SyncFeatures, error) { ctx := context.Background() @@ -5244,7 +4341,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { b, err := json.Marshal(requestData) if err != nil { - log.Printf("Failed marshaling api key data: %s", err) + log.Printf("[ERROR] Failed marshaling api key data: %s", err) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync: %s"}`, err))) return @@ -5271,7 +4368,8 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { return } - //log.Printf("Respbody: %s", string(respBody)) + log.Printf("[DEBUG] Respbody from sync: %s", string(respBody)) + responseData := retStruct{} err = json.Unmarshal(respBody, &responseData) if err != nil { @@ -5309,13 +4407,13 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { job := func() { err := remoteOrgJobHandler(*org, interval) if err != nil { - log.Printf("[ERROR] Failed request with remote org setup (1): %s", err) + log.Printf("[ERROR] Failed request with remote org sync (1): %s", err) } } jobret, err := newscheduler.Every(int(interval)).Seconds().NotImmediately().Run(job) if err != nil { - log.Printf("[CRITICAL] Failed to schedule org: %s", err) + log.Printf("[ERROR] Failed to schedule org: %s", err) } else { log.Printf("[INFO] Started sync on interval %d for org %s", interval, org.Name) scheduledOrgs[org.Id] = jobret @@ -5391,251 +4489,6 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { resp.Write(respBody) } -// Runs DB migration from Datastore to Opensearch -// If the function has "ALL" in it, that means it's intended to be used for Orgs -// but that we've added a function to grab everything -func migrateDatabase(resp http.ResponseWriter, request *http.Request) { - cors := shuffle.HandleCors(resp, request) - if cors { - return - } - - user, userErr := shuffle.HandleApiAuthentication(resp, request) - if userErr != nil { - log.Printf("[WARNING] Api authentication failed in make workflow public: %s", userErr) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - if user.Role != "admin" { - log.Printf("[WARNING] Failed to migrate because you're not admin") - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - if strings.ToLower(os.Getenv("SHUFFLE_ELASTIC")) != "false" { - log.Printf("[WARNING] Failed to migrate because main DB is Elastic. Set SHUFFLE_ELASTIC=false in .env") - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - ctx := context.Background() - //es := shuffle.GetEsConfig() - _, err := shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", false, "") - if err != nil { - log.Printf("[WARNING] Failed to start migration because of init issues: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - log.Printf("\n\n------- STARTING MIGRATION TO OPENSEARCH --------") - users, err := shuffle.GetAllUsers(ctx) - if err != nil { - log.Printf("[ERROR] Failed getting users: %#v", err) - } else { - log.Printf("[DEBUG] Found %d user(s) to be migrated", len(users)) - } - - orgs, err := shuffle.GetAllOrgs(ctx) - if err != nil { - log.Printf("[ERROR] Failed getting orgs: %#v", err) - } else { - log.Printf("[DEBUG] Found %d org(s) to be migrated", len(orgs)) - } - - workflows, err := shuffle.GetAllWorkflows(ctx, "ALL") - if err != nil { - log.Printf("[ERROR] Failed getting workflows: %#v", err) - } else { - log.Printf("[DEBUG] Found %d workflows(s) to be migrated", len(workflows)) - } - - apps, err := shuffle.GetAllWorkflowApps(ctx, 0, 0) - if err != nil { - log.Printf("[ERROR] Failed getting apps: %#v", err) - } else { - log.Printf("[DEBUG] Found %d app(s) to be migrated", len(apps)) - } - - openapiApps, err := shuffle.GetAllOpenApi(ctx) - if err != nil { - log.Printf("[ERROR] Failed getting openapi apps: %#v", err) - } else { - log.Printf("[DEBUG] Found %d openapi(s) to be migrated", len(openapiApps)) - } - - workflowappauth, err := shuffle.GetAllWorkflowAppAuth(ctx, "ALL") - if err != nil { - log.Printf("[ERROR] Failed getting app auth: %#v", err) - } else { - log.Printf("[DEBUG] Found %d appauth(s) to be migrated", len(workflowappauth)) - } - - environments, err := shuffle.GetEnvironments(ctx, "ALL") - if err != nil { - log.Printf("[ERROR] Failed getting environments: %#v", err) - } else { - log.Printf("[DEBUG] Found %d environment(s) to be migrated", len(environments)) - } - - hooks, err := shuffle.GetAllHooks(ctx) - if err != nil { - log.Printf("[ERROR] Failed getting hooks: %#v", err) - } else { - log.Printf("[DEBUG] Found %d hook(s) to be migrated", len(hooks)) - } - - schedules, err := shuffle.GetAllSchedules(ctx, "ALL") - if err != nil { - log.Printf("[ERROR] Failed getting schedules: %#v", err) - } else { - log.Printf("[DEBUG] Found %d schedule(s) to be migrated", len(schedules)) - } - - log.Printf("\n\n------- SWAPPING TO OPENSEARCH DB WITH ACQUIRED INFO ---------") - userSuccess := 0 - orgSuccess := 0 - workflowSuccess := 0 - appSuccess := 0 - openapiSuccess := 0 - authSuccess := 0 - envSuccess := 0 - hookSuccess := 0 - scheduleSuccess := 0 - _, err = shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", false, "elasticsearch") - - for _, item := range orgs { - err = shuffle.SetOrg(ctx, item, item.Id) - if err != nil { - //log.Printf("[WARNING] Failed to update org in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set org %s (%s) in opensearch", item.Name, item.Id) - orgSuccess += 1 - } - } - - log.Printf("----- ORGS FOUND: %d - success: %d - failed: %d", len(orgs), orgSuccess, len(orgs)-orgSuccess) - - for _, item := range workflowappauth { - err = shuffle.SetWorkflowAppAuthDatastore(ctx, item, item.Id) - if err != nil { - //log.Printf("[WARNING] Failed to update app auth in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set app auth %s in opensearch", item.Id) - authSuccess += 1 - } - } - - log.Printf("----- AUTH FOUND: %d - success: %d - failed: %d", len(workflowappauth), authSuccess, len(workflowappauth)-authSuccess) - - for _, item := range environments { - err = shuffle.SetEnvironment(ctx, &item) - if err != nil { - //log.Printf("[WARNING] Failed to update env in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set env %s in opensearch", item.Id) - envSuccess += 1 - } - } - - log.Printf("----- ENVS FOUND: %d - success: %d - failed: %d", len(environments), envSuccess, len(environments)-envSuccess) - - for _, item := range hooks { - err = shuffle.SetHook(ctx, item) - if err != nil { - //log.Printf("[WARNING] Failed to update hooks in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set hook %s in opensearch", item.Id) - hookSuccess += 1 - } - } - - log.Printf("---- HOOKS FOUND: %d - success: %d - failed: %d", len(hooks), hookSuccess, len(hooks)-hookSuccess) - - for _, item := range schedules { - err = shuffle.SetSchedule(ctx, item) - if err != nil { - //log.Printf("[WARNING] Failed to update schedule in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set schedule %s in opensearch", item.Id) - scheduleSuccess += 1 - } - } - - log.Printf(" SCHEDULES FOUND: %d - success: %d - failed: %d", len(schedules), scheduleSuccess, len(schedules)-scheduleSuccess) - - for _, item := range users { - err = shuffle.SetUser(ctx, &item, false) - if err != nil { - //log.Printf("[WARNING] Failed to update user in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set user %s (%s) in opensearch", item.Username, item.Id) - userSuccess += 1 - } - } - - log.Printf("---- USERS FOUND: %d - success: %d - failed: %d", len(users), userSuccess, len(users)-userSuccess) - - for _, item := range workflows { - err = shuffle.SetWorkflow(ctx, item, item.ID) - if err != nil { - //log.Printf("[WARNING] Failed to update workflow in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set workflow %s (%s) in opensearch", item.Name, item.ID) - workflowSuccess += 1 - } - } - - log.Printf(" WORKFLOWS FOUND: %d - success: %d - failed: %d", len(workflows), workflowSuccess, len(workflows)-workflowSuccess) - - for _, item := range openapiApps { - err = shuffle.SetOpenApiDatastore(ctx, item.ID, item) - if err != nil { - //log.Printf("[WARNING] Failed to update openapi app in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set openapi %s in opensearch", item.ID) - openapiSuccess += 1 - } - } - - log.Printf("-- OpenAPI FOUND: %d - success: %d - failed: %d", len(openapiApps), openapiSuccess, len(openapiApps)-openapiSuccess) - - for _, item := range apps { - err = shuffle.SetWorkflowAppDatastore(ctx, item, item.ID) - if err != nil { - //log.Printf("[WARNING] Failed to update app in opensearch: %s", err) - } else { - //log.Printf("[DEBUG] Set app %s (%s) in opensearch", item.Name, item.ID) - appSuccess += 1 - } - } - - log.Printf("----- APPS FOUND: %d - success: %d - failed: %d", len(apps), appSuccess, len(apps)-appSuccess) - - // Handle users - // 1. Get users - // 2. Get organizations - // 4. Get workflows - // 5. Get apps - // 6. Get workflowappauth - // 7. Get workflowexecution - // 9. Get Environments - // 10. Get hooks - // 11. Get openapi3 - // 12. Get schedules - - //log.Printf("[INFO] Successfully published workflow %s (%s) TO CLOUD", workflow.Name, workflow.ID) - log.Printf("\n\n[DEBUG] Successfully updated ran migration from Datastore to Opensearch!") - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) - log.Printf("[DEBUG] Panicing to force-restart Shuffle post-migration. Stop Shuffle and change database. Docs: https://shuffler.io/docs/configuration#database_migration") - os.Exit(0) -} - func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { @@ -5783,6 +4636,8 @@ func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) } + + func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { @@ -5841,6 +4696,8 @@ func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte("OK")) } + + func initHandlers() { var err error ctx := context.Background() @@ -5854,22 +4711,8 @@ func initHandlers() { elasticConfig = "" } - dbclient, err = datastore.NewClient(ctx, gceProject, option.WithGRPCDialOption(grpc.WithNoProxy())) - if err != nil { - if elasticConfig == "" { - log.Printf("[ERROR] Database client error during init: %s. Env: SHUFFLE_ELASTIC=false", err) - } else { - if !strings.Contains(fmt.Sprintf("%s", err), "find default credentials") { - log.Printf("[DEBUG] Database client error info during init: %s. Here for backwards compatibility: not critical.", err) - } - dbclient = &datastore.Client{} - } - } else { - //log.Printf("Database client initiated: %s", dbclient) - } - for { - _, err = shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", true, elasticConfig) + _, err = shuffle.RunInit(*shuffle.GetDatastore(), *shuffle.GetStorage(), gceProject, "onprem", true, elasticConfig) if err != nil { log.Printf("[ERROR] Error in initial database connection. Retrying in 5 seconds. %s", err) time.Sleep(5 * time.Second) @@ -5885,11 +4728,15 @@ func initHandlers() { time.Sleep(5 * time.Second) go runInitEs(ctx) } else { - go runInit(ctx) + //go shuffle.runInit(ctx) + log.Printf("[ERROR] Opensearch is the only viable option. Please set SHUFFLE_ELASTIC=true") + os.Exit(1) } r := mux.NewRouter() r.HandleFunc("/api/v1/_ah/health", shuffle.HealthCheckHandler) + r.HandleFunc("/api/v1/health", shuffle.RunOpsHealthCheck).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/health/stats", shuffle.GetOpsDashboardStats).Methods("GET", "OPTIONS") // Make user related locations // Fix user changes with org @@ -5962,10 +4809,6 @@ func initHandlers() { r.HandleFunc("/api/v1/apps/authentication/{appauthId}/config", shuffle.SetAuthenticationConfig).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps/authentication/{appauthId}", shuffle.DeleteAppAuthentication).Methods("DELETE", "OPTIONS") - // Related to NFT things - r.HandleFunc("/api/v1/workflows/collections/load", shuffle.LoadCollections).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/workflows/collections/{key}", shuffle.HandleGetCollection).Methods("GET", "OPTIONS") - // Related to use-cases that are not directly workflows. r.HandleFunc("/api/v1/workflows/usecases/{key}", shuffle.HandleGetUsecase).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/workflows/usecases", shuffle.LoadUsecases).Methods("GET", "OPTIONS") @@ -5995,6 +4838,9 @@ func initHandlers() { r.HandleFunc("/api/v1/workflows/{key}", shuffle.GetSpecificWorkflow).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/workflows/recommend", shuffle.HandleActionRecommendation).Methods("POST", "OPTIONS") + // First v2 API + r.HandleFunc("/api/v2/workflows/{key}/executions", shuffle.GetWorkflowExecutionsV2).Methods("GET", "OPTIONS") + // New for recommendations in Shuffle r.HandleFunc("/api/v1/recommendations/get_actions", shuffle.HandleActionRecommendation).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/recommendations/modify", shuffle.HandleRecommendationAction).Methods("POST", "OPTIONS") @@ -6045,6 +4891,8 @@ func initHandlers() { r.HandleFunc("/api/v1/orgs/{orgId}/create_sub_org", shuffle.HandleCreateSubOrg).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/change", shuffle.HandleChangeUserOrg).Methods("POST", "OPTIONS") // Swaps to the org + r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleDeleteOrg).Methods("DELETE", "OPTIONS") + // This is a new API that validates if a key has been seen before. // Not sure what the best course of action is for it. r.HandleFunc("/api/v1/environments/{key}/stop", shuffle.HandleStopExecutions).Methods("GET", "POST", "OPTIONS") @@ -6053,14 +4901,20 @@ func initHandlers() { r.HandleFunc("/api/v1/orgs/{orgId}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "PUT", "OPTIONS") - r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/orgs/{orgId}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/statistics", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS") + + r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/datastore/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS") + // Docker orborus specific - downloads an image r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/migrate_database", migrateDatabase).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("GET", "POST", "OPTIONS") r.HandleFunc("/api/v1/login_openid", shuffle.HandleOpenId).Methods("GET", "POST", "OPTIONS") @@ -6092,11 +4946,13 @@ func initHandlers() { r.HandleFunc("/api/v1/dashboards/{key}/widgets", shuffle.HandleNewWidget).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/dashboards/{key}/widgets/{widget_id}", shuffle.HandleGetWidget).Methods("GET", "OPTIONS") + r.Use(shuffle.RequestMiddleware) http.Handle("/", r) } // Had to move away from mux, which means Method is fucked up right now. func main() { + initHandlers() hostname, err := os.Hostname() if err != nil { diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 75c979dd..aa5d5e97 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -561,7 +561,7 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { } if len(workflowExecution.ExecutionOrg) > 0 && user.ActiveOrg.Id == workflowExecution.ExecutionOrg && user.Role == "admin" { - log.Printf("[DEBUG] User %s is in correct org. Allowing org continuation for execution!", user.Username) + //log.Printf("[DEBUG] User %s is in correct org. Allowing org continuation for execution!", user.Username) } else { log.Printf("[WARNING] Bad authorization key when getting stream results %s.", actionResult.ExecutionId) resp.WriteHeader(401) @@ -700,64 +700,6 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { } } - /* - // Removed as UserInput is now handled as an app - if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" { - log.Printf("[INFO] SHOULD WAIT A BIT AND RUN USER INPUT! WAITING!") - - var trigger shuffle.Trigger - err = json.Unmarshal([]byte(actionResult.Result), &trigger) - if err != nil { - log.Printf("[WARNING] Failed unmarshaling actionresult for user input: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - orgId := workflowExecution.ExecutionOrg - if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 { - orgId = workflowExecution.Workflow.OrgId - } - - err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId) - if err != nil { - log.Printf("[WARNING] Failed userinput handler: %s", err) - - actionResult.Result = fmt.Sprintf(`{"success": false, "reason": "%s"}`, err) - - workflowExecution.Results = append(workflowExecution.Results, actionResult) - workflowExecution.Status = "ABORTED" - err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) - if err != nil { - log.Printf("[WARNING] Failed to set execution during wait: %s", err) - } else { - log.Printf("[INFO] Successfully set the execution %s to waiting.", workflowExecution.ExecutionId) - } - - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err))) - return - } else { - log.Printf("[INFO] Successful userinput handler") - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`))) - - actionResult.Result = `{"success": True, "reason": "Waiting for user feedback based on configuration"}` - - workflowExecution.Results = append(workflowExecution.Results, actionResult) - workflowExecution.Status = actionResult.Status - err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) - if err != nil { - log.Printf("[WARNING] Failed setting userinput: %s", err) - } else { - log.Printf("[DEBUG] Successfully set the execution to waiting.") - } - } - - return - } - */ - runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp) } @@ -992,6 +934,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { cacheKey := fmt.Sprintf("%s_workflows", user.Id) shuffle.DeleteCache(ctx, cacheKey) + shuffle.DeleteCache(ctx, fmt.Sprintf("%s_workflows", user.ActiveOrg.Id)) log.Printf("[DEBUG] Cleared workflow cache for %s (%s)", user.Username, user.Id) resp.WriteHeader(200) @@ -1187,7 +1130,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request var execution shuffle.ExecutionRequest err = json.Unmarshal(body, &execution) if err != nil { - log.Printf("[WARNING] Failed execution POST unmarshaling - continuing anyway: %s", err) + log.Printf("[WARNING] Failed execution POST unmarshalling for execution %s - continuing anyway: %s", execution.ExecutionId, err) //return shuffle.WorkflowExecution{}, "", err } @@ -1390,13 +1333,10 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request childNodes := shuffle.FindChildNodes(workflowExecution, workflowExecution.Start, []string{}, []string{}) - //topic := "workflows" startFound := false - // FIXME - remove this? newActions := []shuffle.Action{} defaultResults := []shuffle.ActionResult{} - allAuths := []shuffle.AppAuthenticationStorage{} for _, action := range workflowExecution.Workflow.Actions { //action.LargeImage = "" if action.ID == workflowExecution.Start { @@ -1408,98 +1348,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request return shuffle.WorkflowExecution{}, fmt.Sprintf("Environment is not defined for %s", action.Name), errors.New("Environment not defined!") } - // FIXME: Authentication parameters - if len(action.AuthenticationId) > 0 { - if len(allAuths) == 0 { - allAuths, err = shuffle.GetAllWorkflowAppAuth(ctx, workflow.ExecutingOrg.Id) - if err != nil { - log.Printf("Api authentication failed in get all app auth: %s", err) - return shuffle.WorkflowExecution{}, fmt.Sprintf("Api authentication failed in get all app auth: %s", err), err - } - } - - curAuth := shuffle.AppAuthenticationStorage{Id: ""} - for _, auth := range allAuths { - if auth.Id == action.AuthenticationId { - curAuth = auth - break - } - } - - if len(curAuth.Id) == 0 { - return shuffle.WorkflowExecution{}, fmt.Sprintf("Auth ID %s doesn't exist", action.AuthenticationId), errors.New(fmt.Sprintf("Auth ID %s doesn't exist", action.AuthenticationId)) - } - - if curAuth.Encrypted { - setField := true - newFields := []shuffle.AuthenticationStore{} - for _, field := range curAuth.Fields { - parsedKey := fmt.Sprintf("%s_%d_%s_%s", curAuth.OrgId, curAuth.Created, curAuth.Label, field.Key) - newValue, err := shuffle.HandleKeyDecryption([]byte(field.Value), parsedKey) - if err != nil { - log.Printf("[WARNING] Failed decryption for %s: %s", field.Key, err) - setField = false - break - } - - field.Value = string(newValue) - newFields = append(newFields, field) - } - - if setField { - curAuth.Fields = newFields - } - } else { - log.Printf("[INFO] AUTH IS NOT ENCRYPTED - attempting encrypting!") - err = shuffle.SetWorkflowAppAuthDatastore(ctx, curAuth, curAuth.Id) - if err != nil { - log.Printf("[WARNING] Failed running encryption during execution: %s", err) - } - } - - newParams := []shuffle.WorkflowAppActionParameter{} - if strings.ToLower(curAuth.Type) == "oauth2" { - log.Printf("[DEBUG] Should replace auth parameters (Oauth2)") - - for _, param := range curAuth.Fields { - if param.Key == "expiration" { - continue - } - - newParams = append(newParams, shuffle.WorkflowAppActionParameter{ - Name: param.Key, - Value: param.Value, - }) - } - - for _, param := range action.Parameters { - //log.Printf("Param: %#v", param) - if param.Configuration { - continue - } - - newParams = append(newParams, param) - } - } else { - // Rebuild params with the right data. This is to prevent issues on the frontend - for _, param := range action.Parameters { - - for _, authparam := range curAuth.Fields { - if param.Name == authparam.Key { - param.Value = authparam.Value - //log.Printf("Name: %s - value: %s", param.Name, param.Value) - //log.Printf("Name: %s - value: %s\n", param.Name, param.Value) - break - } - } - - newParams = append(newParams, param) - } - } - - action.Parameters = newParams - } - action.LargeImage = "" if len(action.Label) == 0 { action.Label = action.ID @@ -1844,6 +1692,8 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request return shuffle.WorkflowExecution{}, "Cloud not implemented yet", errors.New("Cloud not implemented yet") } + shuffle.IncrementCache(ctx, workflowExecution.OrgId, "workflow_executions_cloud") + // What it needs to know: // 1. Parameters if len(workflowExecution.Workflow.Actions) == 1 { @@ -1857,13 +1707,11 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request // If worker, should this backend be a proxy? I think so. return shuffle.WorkflowExecution{}, "Cloud not implemented yet (2)", errors.New("Cloud not implemented yet") } + } else { + shuffle.IncrementCache(ctx, workflowExecution.OrgId, "workflow_executions_onprem") } - //err = increaseStatisticsField(ctx, "workflow_executions", workflow.ID, 1, workflowExecution.ExecutionOrg) - //if err != nil { - // log.Printf("Failed to increase stats execution stats: %s", err) - //} - + shuffle.IncrementCache(ctx, workflowExecution.OrgId, "workflow_executions") return workflowExecution, "", nil } diff --git a/docker-compose.yml b/docker-compose.yml index 9e66eb53..c35a6138 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: @@ -57,12 +57,13 @@ services: security_opt: - seccomp:unconfined opensearch: - image: opensearchproject/opensearch:2.5.0 + image: opensearchproject/opensearch:2.11.0 hostname: shuffle-opensearch container_name: shuffle-opensearch environment: - - bootstrap.memory_lock=true - "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM + - bootstrap.memory_lock=true + - DISABLE_PERFORMANCE_ANALYZER_AGENT_CLI=true - cluster.initial_master_nodes=shuffle-opensearch - cluster.routing.allocation.disk.threshold_enabled=false - cluster.name=shuffle-cluster @@ -83,9 +84,17 @@ services: networks: - shuffle restart: unless-stopped + + #memcached: + # image: docker.io/bitnami/memcached:1 + # container_name: shuffle-cache + # hostname: shuffle-cache + # ports: + # - 11211:11211 + #docker-socket-proxy: # image: tecnativa/docker-socket-proxy - # container_name: shuffle-frontend + # container_name: docker-socket-proxy # hostname: docker-socket-proxy # privileged: true # environment: @@ -110,6 +119,7 @@ services: # - /var/run/docker.sock:/var/run/docker.sock # networks: # - shuffle + # networks: shuffle: driver: bridge diff --git a/frontend/Dockerfile b/frontend/Dockerfile index b2d4f2ce..2a45fb5f 100755 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,5 @@ # Build environment -FROM node:14 as builder +FROM node:18 as builder RUN mkdir /usr/src/app @@ -8,9 +8,12 @@ ENV PATH /usr/src/app/node_modules/.bin:$PATH COPY package.json /usr/src/app/package.json +# Nocache yarn install RUN yarn config set "strict-ssl" false -g RUN yarn install --network-timeout 1000000 +#RUN npm install + # copy only required files to not trigger rebuilding every time COPY ./certs /usr/src/app/certs/ COPY ./public /usr/src/app/public/ @@ -18,7 +21,7 @@ COPY ./src /usr/src/app/src/ COPY ./*.sh /usr/src/app/ COPY ./*.json /usr/src/app/ -RUN rm -rf /usr/src/app/node_modules/webpack +#RUN rm -rf /usr/src/app/node_modules/webpack RUN yarn build # Production environment diff --git a/frontend/confd/templates/nginx.conf b/frontend/confd/templates/nginx.conf index bd2219a4..6d1d6081 100755 --- a/frontend/confd/templates/nginx.conf +++ b/frontend/confd/templates/nginx.conf @@ -70,11 +70,11 @@ http { try_files $uri /index.html; } - location /api/v1 { + location ~ /api/v(1|2) { proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001; proxy_buffering off; proxy_http_version 1.1; - + proxy_connect_timeout 900; proxy_send_timeout 900; proxy_read_timeout 900; diff --git a/frontend/package.json b/frontend/package.json index 5082a3b8..eb3cfe90 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,88 +1,88 @@ { "name": "shuffler", "homepage": "https://shuffler.io", - "version": "1.2.0", + "version": "1.3.0", "private": true, "dependencies": { - "@codemirror/commands": "^6.2.2", + "@codemirror/commands": "^6.2.4", "@emotion/is-prop-valid": "^1.1.1", - "@emotion/react": "^11.7.0", - "@emotion/styled": "^11.6.0", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", "@lezer/highlight": "^1.1.3", - "@material-ui/core": "^4.5.2", - "@material-ui/icons": "^4.5.1", - "@material-ui/lab": "^4.0.0-alpha.58", - "@material-ui/styles": "^4.5.2", - "@material-ui/utils": "^4.11.2", - "@metamask/detect-provider": "^1.2.0", - "@mui/icons-material": "^5.2.1", - "@mui/material": "^5.2.3", + "@mui/icons-material": "^5.14.0", + "@mui/material": "^5.14.0", + "@mui/styles": "^5.14.0", "@mui/x-data-grid": "^5.17.11", - "@uiw/codemirror-themes": "^4.19.9", - "@uiw/react-codemirror": "^3.2.1", + "@mui/x-date-pickers": "^6.11.1", + "@uiw/codemirror-themes": "^4.21.9", + "@uiw/react-codemirror": "^4.21.9", "@use-it/interval": "^1.0.0", "algoliasearch": "^4.13.1", + "calculate-size": "^1.1.1", "class-transformer": "^0.4.0", - "create-react-app": "^4.0.3", + "create-react-app": "^5.0.1", "cytoscape": "^3.15.1", - "cytoscape-clipboard": "^2.2.1", - "cytoscape-cxtmenu": "^3.1.1", "cytoscape-edgehandles": "^3.6.0", - "cytoscape-grid-guide": "~2.3.3", "cytoscape-node-html-label": "^1.1.5", - "cytoscape-panzoom": "^2.5.2", - "cytoscape-undo-redo": "^1.3.2", "d3": "^7.1.1", + "dayjs": "^1.11.9", "dotenv": "^6.1.0", "downshift": "^3.3.5", "github-markdown-css": "^3.0.1", "import": "0.0.6", "interweave": "^11.2.0", - "material-icons": "^0.7.7", - "material-icons-react": "^1.0.4", - "material-ui-chip-input": "^2.0.0-beta.2", - "material-ui-nested-menu-item": "^1.0.2", + "jss": "^10.10.0", + "jss-camel-case": "^6.1.0", + "jss-default-unit": "^8.0.2", + "jss-global": "^3.0.0", + "jss-nested": "^6.0.1", + "jss-props-sort": "^6.0.0", + "jss-vendor-prefixer": "^8.0.1", "md5-file": "^4.0.0", "mdbreact": "^4.21.1", + "mime": "^3.0.0", "moment": "^2.29.1", - "react": "^16.14.0", - "react-alert": "^5.5.0", + "mui-chips-input": "^2.1.3", + "mui-nested-menu": "^3.2.1", + "process": "^0.11.10", + "react": "^18.2.0", + "react-alert": "^7.0.3", "react-alert-template-basic": "^1.0.0", "react-alice-carousel": "^2.6.4", "react-avatar-editor": "^11.1.0", "react-beforeunload": "^2.2.1", "react-chartjs-2": "^2.11.1", "react-cookie": "^4.0.1", - "react-cytoscapejs": "^1.2.0", - "react-device-detect": "^1.9.10", - "react-dom": "^16.14.0", + "react-cytoscapejs": "^2.0.0", + "react-device-detect": "^2.2.3", + "react-dom": "^18.2.0", "react-draggable": "^3.3.2", "react-driftjs": "^1.2.2", - "react-dropzone": "^10.1.10", + "react-dropzone": "^14.2.3", "react-ga4": "^2.0.0", - "react-iframe": "^1.8.0", "react-instantsearch-dom": "^6.28.0", "react-json-pretty": "^2.2.0", - "react-json-view": "^1.19.1", - "react-markdown": "^4.2.2", + "react-json-view": "^1.21.3", + "react-markdown": "^8.0.7", "react-markdown-github": "^3.3.1", - "react-powerhooks": "0.0.7", - "react-router": "6.2.1", - "react-router-dom": "6.2.1", - "react-scripts": "^4.0.1", - "react-shepherd": "^3.3.6", - "reactstrap": "^7.1.0", - "reaviz": "^12.1.0", + "react-powerhooks": "^0.0.7", + "react-router": "^6.14.1", + "react-router-dom": "^6.14.1", + "react-scripts": "^5.0.1", + "react-toastify": "^9.1.3", + "reaviz": "^14.9.4", + "remark-gfm": "^3.0.1", "search-insights": "^2.2.1", "shellwords": "^0.1.1", "simplebar": "^4.2.3", "styled-components": "^4.4.0", "yaml": "^1.7.2", "yamljs": "^0.3.0", - "zone.js": "~0.11.4" + "zone.js": "^0.13.1", + "webpack": "^5.88.2" }, "scripts": { - "start": "HTTPS=false&&PORT=3000 react-scripts --openssl-legacy-provider start", + "start": "HTTPS=false&&PORT=3000 GENERATE_SOURCEMAP=false react-scripts --openssl-legacy-provider start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", @@ -109,6 +109,8 @@ "babel-eslint": "^10.1.0", "prettier": "2.4.1", "promise-window": "^1.2.1", - "webpack": "^4.44.2" + "react-16": "npm:react@16.13.1", + "react-dom-16": "npm:react-dom@16.13.1", + "react-error-overlay": "6.0.9" } } diff --git a/frontend/public/images/workflows/UserInput.svg b/frontend/public/images/workflows/UserInput.svg new file mode 100644 index 00000000..edd09fd1 --- /dev/null +++ b/frontend/public/images/workflows/UserInput.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/images/workflows/UserInput2.svg b/frontend/public/images/workflows/UserInput2.svg new file mode 100644 index 00000000..f1d8c40e --- /dev/null +++ b/frontend/public/images/workflows/UserInput2.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/run.sh b/frontend/run.sh index 0ba2beea..9a608216 100755 --- a/frontend/run.sh +++ b/frontend/run.sh @@ -10,9 +10,9 @@ docker tag ghcr.io/frikky/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-front echo "Starting server" # Rerun build locally for it to update :) -#docker run -it \ -# -p 3001:80 \ -# -p 3002:443 \ -# -v $(pwd)/build:/usr/share/nginx/html:ro \ -# --rm \ -# nginx +docker run -it \ + -p 3001:80 \ + -p 3002:443 \ + -v $(pwd)/build:/usr/share/nginx/html:ro \ + --rm \ + ghcr.io/frikky/shuffle-frontend:nightly diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1ab5b924..4d96250a 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,8 +6,7 @@ import { removeCookies, useCookies } from "react-cookie"; import Workflows from "./views/Workflows"; import GettingStarted from "./views/GettingStarted"; -import EditWebhook from "./views/EditWebhook"; -import AngularWorkflow from "./views/AngularWorkflow"; +import AngularWorkflow from "./views/AngularWorkflow.jsx"; import Header from "./components/Header.jsx"; import theme from "./theme"; @@ -19,31 +18,31 @@ import Dashboard from "./views/Dashboard.jsx"; import DashboardView from "./views/DashboardViews.jsx"; import AdminSetup from "./views/AdminSetup"; import Admin from "./views/Admin"; -import Docs from "./views/Docs"; -import Introduction from "./views/Introduction"; +import Docs from "./views/Docs.jsx"; +//import Introduction from "./views/Introduction"; import SetAuthentication from "./views/SetAuthentication"; import SetAuthenticationSSO from "./views/SetAuthenticationSSO"; import Search from "./views/Search.jsx"; import RunWorkflow from "./views/RunWorkflow.jsx"; -import LandingPageNew from "./views/LandingpageNew"; import LoginPage from "./views/LoginPage"; import SettingsPage from "./views/SettingsPage"; import KeepAlive from "./views/KeepAlive.jsx"; -import MyView from "./views/MyView"; - -import { createMuiTheme, MuiThemeProvider } from "@material-ui/core/styles"; +import { ThemeProvider } from "@mui/material/styles"; +import CssBaseline from '@mui/material/CssBaseline'; +import UpdateAuthentication from "./views/UpdateAuthentication.jsx"; import FrameworkWrapper from "./views/FrameworkWrapper.jsx"; import ScrollToTop from "./components/ScrollToTop"; import AlertTemplate from "./components/AlertTemplate"; import { useAlert, positions, Provider } from "react-alert"; import { isMobile } from "react-device-detect"; -import detectEthereumProvider from "@metamask/detect-provider"; +import { ToastContainer, toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + import Drift from "react-driftjs"; -import DashboardPage from "./views/TempDashboard.jsx"; // Production - backend proxy forwarding in nginx var globalUrl = window.location.origin; @@ -147,120 +146,6 @@ const App = (message, props) => { } // Handling Ethereum update - {/* - detectEthereumProvider().then((provider) => { - if ( - provider && - userInfo.eth_info !== undefined && - userInfo.eth_info !== null - ) { - if ( - userInfo.eth_info.account !== undefined && - userInfo.eth_info.account !== null && - userInfo.eth_info.account.length === 0 - ) { - userInfo.eth_info = {}; - var method = "eth_requestAccounts"; - var params = []; - provider - .request({ - method: method, - params, - }) - .then((result) => { - if ( - result !== undefined && - result !== null && - result.length > 0 - ) { - userInfo.eth_info.account = result[0]; - - // Getting and setting balance for the current user - method = "eth_getBalance"; - params = [userInfo.eth_info.account, "latest"]; - provider - .request({ - method: method, - params, - }) - .then((result) => { - if ( - result !== undefined && - result !== null && - result.length > 0 - ) { - userInfo.parsed_balance = - result / 1000000000000000000; - } else { - alert.error("Couldn't find balance: ", result); - } - // The result varies by RPC method. - // For example, this method will return a transaction hash hexadecimal string on success. - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error( - "Failed getting info from ethereum API: " + error - ); - }); - } else { - alert.error("Couldn't find any user: ", result); - } - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error( - "Failed getting info from ethereum API: " + error - ); - }); - } - - // Register hooks here - provider.on("message", (event) => { - alert.info("Message from MetaMask: ", event); - }); - - provider.on("chainChanged", (chainId) => { - console.log("Changed chain to: ", chainId); - - method = "eth_getBalance"; - params = [userInfo.eth_info.account, "latest"]; - provider - .request({ - method: method, - params, - }) - .then((result) => { - console.log("Got result: ", result); - if (result !== undefined && result !== null) { - userInfo.eth_info.balance = result; - userInfo.eth_info.parsed_balance = - result / 1000000000000000000; - console.log("INFO: ", userInfo); - setUserData(userInfo); - } else { - alert.error("Couldn't find balance: ", result); - } - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error( - "Failed getting info from ethereum API: " + error - ); - }); - }); - } - }); - - if ( - userInfo.eth_info !== undefined && - userInfo.eth_info.balance !== undefined - ) { - //console.log(userInfo.eth_info.balance) - userInfo.eth_info.parsed_balance = - userInfo.eth_info.balance / 1000000000000000000; - } - */} //console.log("USER: ", userInfo) setUserData(userInfo); @@ -283,21 +168,9 @@ const App = (message, props) => { } const includedData = - window.location.pathname === "/home" || - window.location.pathname === "/features" ? ( -
- - } - /> - -
- ) : (
{ path="/usecases" element={ { /> } /> - } /> + } /> { path="/workflows" element={ { /> } /> - } /> - } /> + } /> + } /> { /> } /> - - } - /> - - } - /> { {...props} /> } - /> - - } /> { /> } /> - - } + + } + /> { />
- ); - //
- // backgroundColor: "#213243", - // This is a mess hahahah return ( - + + {includedData} + - + ); }; diff --git a/frontend/src/components/AlertTemplate.js b/frontend/src/components/AlertTemplate.js index 4dfa06d0..3b320a6b 100755 --- a/frontend/src/components/AlertTemplate.js +++ b/frontend/src/components/AlertTemplate.js @@ -1,9 +1,14 @@ import React from "react"; -import InfoIcon from "@material-ui/icons/Info"; -import CheckIcon from "@material-ui/icons/Check"; -import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline"; -import CloseIcon from "@material-ui/icons/Close"; -import Typography from "@material-ui/core/Typography"; +import { + Info as InfoIcon, + Check as CheckIcon, + ErrorOutline as ErrorOutlineIcon, + Close as CloseIcon, +} from "@mui/icons-material"; + +import { + Typography +} from "@mui/material"; const alertStyle = { backgroundColor: "rgba(0,0,0,0.9)", diff --git a/frontend/src/components/AppFramework.jsx b/frontend/src/components/AppFramework.jsx index e6be25f1..13f85fb1 100644 --- a/frontend/src/components/AppFramework.jsx +++ b/frontend/src/components/AppFramework.jsx @@ -1,10 +1,9 @@ import React, { useState, useEffect } from 'react'; +import theme from '../theme.jsx'; import CytoscapeComponent from 'react-cytoscapejs'; import frameworkStyle from '../frameworkStyle.jsx'; import { v4 as uuidv4 } from "uuid"; -import theme from '../theme.jsx'; -import { useAlert } from "react-alert"; import AppSearch from '../components/Appsearch.jsx'; import PaperComponent from "../components/PaperComponent.jsx" @@ -18,30 +17,159 @@ import { Divider, IconButton, Badge, - CircularProgress, + CircularProgress, Tooltip, Dialog, Chip, Avatar, -} from "@material-ui/core"; + Button, +} from "@mui/material"; -import { - Button -} from '@material-ui/core'; import { Close as CloseIcon, Delete as DeleteIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; import * as edgehandles from "cytoscape-edgehandles"; import * as cytoscape from "cytoscape"; +import { toast } from 'react-toastify'; cytoscape.use(edgehandles); +export const findSpecificApp = (framework, inputcategory) => { + // Get the frameworkinfo for the org and fill in + // + if (framework === undefined || framework === null) { + console.log("findSpecificApp: framework is null") + return null + } + + if (inputcategory === undefined || inputcategory === null) { + console.log("findSpecificApp: category is null") + return null + } + + const category = inputcategory.toLowerCase().split(":")[0].trim() + + console.log("findSpecificApp: ", category, framework) + if (category === "edr" || category === "eradication" || category === "edr & av") { + if (framework["EDR & AV"] !== undefined && framework["EDR & AV"].name !== undefined) { + return framework["EDR & AV"] + } + + return { + name: "EDR :default", + large_image: parsedDatatypeImages["EDR & AV"], + count: 0, + description: "", + id: "", + } + } else if (category === "communication") { + if (framework["Comms"] !== undefined && framework["Comms"].name !== undefined) { + return framework["Comms"] + } + + return { + name: "COMMS :default", + large_image: parsedDatatypeImages["COMMS"], + count: 0, + description: "", + id: "", + } + } else if (category === "email") { + if (framework["Email"] !== undefined && framework["Email"].name !== undefined) { + return framework["Email"] + } + + return { + name: "COMMS :default", + large_image: parsedDatatypeImages["COMMS"], + count: 0, + description: "", + id: "", + } + } else if (category === "assets") { + if (framework["Assets"] !== undefined && framework["Assets"].name !== undefined) { + return framework["Assets"] + } + + return { + name: "ASSETS :default", + large_image: parsedDatatypeImages["ASSETS"], + count: 0, + description: "", + id: "", + } + } else if (category === "cases") { + if (framework["Cases"] !== undefined && framework["Cases"].name !== undefined) { + return framework["Cases"] + } + + return { + name: "CASES :default", + large_image: parsedDatatypeImages["CASES"], + count: 0, + description: "", + id: "", + } + } else if (category === "iam") { + if (framework["IAM"] !== undefined && framework["IAM"].name !== undefined) { + return framework["IAM"] + } + + return { + name: "EDR :default", + large_image: parsedDatatypeImages["EDR & AV"], + count: 0, + description: "", + id: "", + } + } else if (category === "network") { + if (framework["Network"] !== undefined && framework["Network"].name !== undefined) { + return framework["Network"] + } + + return { + name: "Network :default", + large_image: parsedDatatypeImages["NETWORK"], + count: 0, + description: "", + id: "", + } + } else if (category === "intel") { + if (framework["Intel"] !== undefined && framework["Intel"].name !== undefined) { + return framework["Intel"] + } + + return { + name: "INTEL :default", + large_image: parsedDatatypeImages["INTEL"], + count: 0, + description: "", + id: "", + } + } else if (category === "siem") { + if (framework["SIEM"] !== undefined && framework["SIEM"].name !== undefined) { + return framework["SIEM"] + } + + return { + name: "SIEM :default", + large_image: parsedDatatypeImages["SIEM"], + count: 0, + description: "", + id: "", + } + } else { + console.log("findSpecificApp: unknown category: ", category) + } + + return null +} const svgSize = "40px" -const parsedDatatypeImages = { +export const parsedDatatypeImages = { "SIEM": encodeURI(`data:image/svg+xml;utf-8,`), "CASES": encodeURI(`data:image/svg+xml;utf-8,`), @@ -523,7 +651,7 @@ const AppFramework = (props) => { const scale = size === undefined ? 1 : size > 5 ? 3 : size - const alert = useAlert() + //const alert = useAlert() const handleLoadNextSuggestion = (frameworkData) => { @@ -716,7 +844,7 @@ const AppFramework = (props) => { } useEffect(() => { - console.log("DISCWRAP CHANG: ", discoveryWrapper) + //console.log("DISCWRAP CHANG: ", discoveryWrapper) if (discoveryWrapper === undefined || discoveryWrapper.id === "SHUFFLE" || discoveryWrapper.id === undefined || cy === undefined) { setDiscoveryData({}) @@ -786,16 +914,16 @@ const AppFramework = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Failed updating: " + responseJson.reason) + toast("Failed updating: " + responseJson.reason) } else { - alert.error("Failed to update framework for your org.") + toast("Failed to update framework for your org.") } } else { - alert.info("Updated usecase.") + toast("Updated usecase.") } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); //setFrameworkLoaded(true) }) } @@ -818,13 +946,13 @@ const AppFramework = (props) => { }) .then((responseJson) => { if (responseJson.success === false) { - alert.error("Failed to activate the app") + toast("Failed to activate the app") } else { - //alert.success("App activated for your organization! Refresh the page to use the app.") + //toast("App activated for your organization! Refresh the page to use the app.") } }) .catch(error => { - //alert.error(error.toString()) + //toast(error.toString()) console.log("Activate app error: ", error.toString()) }); } @@ -854,9 +982,9 @@ const AppFramework = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Failed updating: " + responseJson.reason) + toast("Failed updating: " + responseJson.reason) } else { - alert.error("Failed to update framework for your org.") + toast("Failed to update framework for your org.") } } @@ -865,7 +993,7 @@ const AppFramework = (props) => { //setFrameworkData(responseJson) }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); //setFrameworkLoaded(true) }) } @@ -877,7 +1005,7 @@ const AppFramework = (props) => { }, []) useEffect(() => { - console.log("New selected app: ", newSelectedApp, discoveryData) + //console.log("New selected app: ", newSelectedApp, discoveryData) if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) { return } @@ -1859,7 +1987,6 @@ const AppFramework = (props) => { //autounselectify={true} var usecasediff = -100 const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color - return (
@@ -1995,11 +2122,11 @@ const AppFramework = (props) => { { Object.getOwnPropertyNames(discoveryData).length > 0 ? - + {paperTitle.length > 0 ? - {paperTitle} + {paperTitle.replace("_", " ", -1)} @@ -2069,8 +2196,8 @@ const AppFramework = (props) => { const foundelement = cy.getElementById(discoveryData.id) if (foundelement !== undefined && foundelement !== null) { - console.log("element: ", foundelement) - console.log("DISC: ", discoveryData) + //console.log("element: ", foundelement) + //console.log("DISC: ", discoveryData) foundelement.data("large_image", parsedDatatypeImages[discoveryData.id.toUpperCase()]) foundelement.data("text_margin_y", "14px") foundelement.data("margin_x", "32px") @@ -2129,7 +2256,7 @@ const AppFramework = (props) => { ? - Click an app below to select it + Search to find your app : @@ -2164,7 +2291,7 @@ const AppFramework = (props) => { elements={elements} minZoom={0.35} maxZoom={2.00} - style={{width: 560*scale, height: 560*scale, backgroundColor: "transparent", margin: "auto",}} + style={{width: 560*scale, height: 560*scale, backgroundColor: theme.palette.backgroundColor, margin: "auto",}} stylesheet={frameworkStyle} boxSelectionEnabled={false} panningEnabled={false} diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index af7ddae5..2d0e9d3d 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -1,10 +1,15 @@ import React, {useEffect, useState} from 'react'; +import theme from '../theme.jsx'; import ReactGA from 'react-ga4'; -import { useTheme } from '@material-ui/core/styles'; import {Link} from 'react-router-dom'; +import { removeQuery } from '../components/ScrollToTop.jsx'; -import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; +import { + Search as SearchIcon, + CloudQueue as CloudQueueIcon, + Code as CodeIcon +} from '@mui/icons-material'; import algoliasearch from 'algoliasearch/lite'; import { InstantSearch, Configure, connectSearchBox, connectHits, connectHitInsights } from 'react-instantsearch-dom'; @@ -21,7 +26,7 @@ import { Typography, Button, Tooltip -} from '@material-ui/core'; +} from '@mui/material'; const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") @@ -34,7 +39,6 @@ const AppGrid = props => { const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 2 : parsedXs - const theme = useTheme(); //const [apps, setApps] = React.useState([]); //const [filteredApps, setFilteredApps] = React.useState([]); const [formMail, setFormMail] = React.useState(""); @@ -71,7 +75,7 @@ const AppGrid = props => { .then(response => { if (response.success === true) { setFormMessage(response.reason) - //alert.info("Thanks for submitting!") + //toast("Thanks for submitting!") } else { setFormMessage(errorMessage) } @@ -86,21 +90,24 @@ const AppGrid = props => { } const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { - useEffect(() => { - if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { - const urlSearchParams = new URLSearchParams(window.location.search) - const params = Object.fromEntries(urlSearchParams.entries()) - const foundQuery = params["q"] - if (foundQuery !== null && foundQuery !== undefined) { - console.log("Got query: ", foundQuery) - refine(foundQuery) - } + var defaultSearch = "" + //useEffect(() => { + if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { + const urlSearchParams = new URLSearchParams(window.location.search) + const params = Object.fromEntries(urlSearchParams.entries()) + const foundQuery = params["q"] + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery) + refine(foundQuery) + defaultSearch = foundQuery } - }, []) + } + //}, []) return (
{ autoComplete='off' type="search" color="primary" - defaultValue={currentRefinement} placeholder="Find Apps..." id="shuffle_search_field" onChange={(event) => { + // Remove "q" from URL + removeQuery("q") + refine(event.currentTarget.value) }} limit={5} @@ -148,8 +157,6 @@ const AppGrid = props => { // setInnerHits(hits) //} - console.log("In appgrid") - return ( {hits.map((data, index) => { diff --git a/frontend/src/components/AppGrid1.jsx b/frontend/src/components/AppGrid1.jsx deleted file mode 100644 index b64610c5..00000000 --- a/frontend/src/components/AppGrid1.jsx +++ /dev/null @@ -1,377 +0,0 @@ -import React, {useEffect, useState} from 'react'; - -import ReactGA from 'react-ga4'; -import { useTheme } from '@material-ui/core/styles'; -import {Link} from 'react-router-dom'; - -import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; - -import algoliasearch from 'algoliasearch/lite'; -import { InstantSearch, Configure, connectSearchBox, connectHits, connectHitInsights } from 'react-instantsearch-dom'; - -import aa from 'search-insights' - -import { - Zoom, - Grid, - Paper, - TextField, - ButtonBase, - InputAdornment, - Typography, - Button, - Tooltip -} from '@material-ui/core'; - -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") -//const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") -const AppGrid1 = props => { - const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, searchValue } = props - - const isCloud = - window.location.host === "localhost:3000" || - window.location.host === "shuffler.io"; - - const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows - const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 2 : parsedXs - const theme = useTheme(); - //const [apps, setApps] = React.useState([]); - //const [filteredApps, setFilteredApps] = React.useState([]); - const [formMail, setFormMail] = React.useState(""); - const [message, setMessage] = React.useState(""); - const [formMessage, setFormMessage] = React.useState(""); - - const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,} - - const innerColor = "rgba(255,255,255,0.65)" - const borderRadius = 3 - window.title = "Shuffle | Apps | Find and integrate any app" - - const submitContact = (email, message) => { - const data = { - "firstname": "", - "lastname": "", - "title": "", - "companyname": "", - "email": email, - "phone": "", - "message": message, - } - - const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly." - - fetch(globalUrl+"/api/v1/contact", { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }) - .then(response => response.json()) - .then(response => { - if (response.success === true) { - setFormMessage(response.reason) - //alert.info("Thanks for submitting!") - } else { - setFormMessage(errorMessage) - } - - setFormMail("") - setMessage("") - }) - .catch(error => { - setFormMessage(errorMessage) - console.log(error) - }); - } - - const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { - - useEffect(() => { - if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { - const urlSearchParams = new URLSearchParams(window.location.search) - const params = Object.fromEntries(urlSearchParams.entries()) - const foundQuery = {searchValue} - if (foundQuery !== null && foundQuery !== undefined) { - console.log("Got query: ", foundQuery) - refine(foundQuery) - } - } - }, []) - - return ( - - - - - ), - }} - autoComplete='off' - type="hidden" - color="primary" - defaultValue={currentRefinement} - placeholder="Find Apps..." - id="shuffle_search_field" - onChange={(event) => { - refine(event.currentTarget.value) - }} - limit={5} - /> - {/*isSearchStalled ? 'My search is stalled' : ''*/} - - ) - } - - var workflowDelay = -50 - const Hits = ({ hits, insights }) => { - const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) - var counted = 0 - - //console.log(hits) - //var curhits = hits - //if (hits.length > 0 && defaultApps.length === 0) { - // setDefaultApps(hits) - //} - - //const [defaultApps, setDefaultApps] = React.useState([]) - //console.log(hits) - //if (hits.length > 0 && hits.length !== innerHits.length) { - // setInnerHits(hits) - //} - - return ( - - {hits.map((data, index) => { - - workflowDelay += 50 - - const paperStyle = { - backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : theme.palette.inputColor, - color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)", - border: `1px solid ${innerColor}`, - padding: 15, - cursor: "pointer", - position: "relative", - minHeight: 116, - } - - if (counted === 12/xs*rowHandler) { - return null - } - - counted += 1 - var parsedname = "" - for (var key = 0; key < data.name.length; key++) { - var character = data.name.charAt(key) - if (character === character.toUpperCase()) { - //console.log(data.name[key], data.name[key+1]) - if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) { - } else { - parsedname += " " - } - } - - parsedname += character - } - - parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ") - - return ( - - - - { - setMouseHoverIndex(index) - /* - ReactGA.event({ - category: "app_grid_view", - action: `search_bar_click`, - label: "", - }) - */ - }} onMouseOut={() => { - setMouseHoverIndex(-1) - }} onClick={() => { - if (isCloud) { - ReactGA.event({ - category: "app_grid_view", - action: `app_${parsedname}_${data.id}_click`, - label: "", - }) - } - - //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") - console.log(searchClient) - aa('init', { - appId: searchClient.appId, - apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] - }) - - const timestamp = new Date().getTime() - aa('sendEvents', [ - { - eventType: 'click', - eventName: 'Product Clicked', - index: 'appsearch', - objectIDs: [data.objectID], - timestamp: timestamp, - queryID: data.__queryID, - positions: [data.__position], - userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, - } - ]) - - }}> - - {data.name} - -
- {index === mouseHoverIndex || showName === true ? - parsedname - : - null - } - {data.generated ? - - {data.invalid ? - - : - - } - - : - - - - } - - - - - ) - })} - - ) - } - - const CustomSearchBox = connectSearchBox(SearchBox) - const CustomHits = connectHits(Hits) - //const CustomHits = connectHitInsights(aa)(Hits) - const selectButtonStyle = { - minWidth: 150, - maxWidth: 150, - minHeight: 50, - } - - return ( -
- {/* -
- -
- */} -
- -
- -
- - -
- {showSuggestion === true ? -
- - Can't find what you're looking for? - -
- setFormMail(e.target.value)} - /> - setMessage(e.target.value)} - /> -
- - {formMessage} -
- : null - } - - - - Search by - - - Algolia logo - - -
-
- ) -} - -export default AppGrid1; diff --git a/frontend/src/components/AppSearchButtons.jsx b/frontend/src/components/AppSearchButtons.jsx new file mode 100644 index 00000000..d739b96c --- /dev/null +++ b/frontend/src/components/AppSearchButtons.jsx @@ -0,0 +1,403 @@ +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 { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon, Close as CloseIcon, Folder as FolderIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material'; +import aa from 'search-insights' +import DeleteIcon from '@mui/icons-material/Delete'; +import ShowChartIcon from '@mui/icons-material/ShowChart'; +import ExploreIcon from '@mui/icons-material/Explore'; +import LightbulbIcon from "@mui/icons-material/Lightbulb"; +import NewReleasesIcon from "@mui/icons-material/NewReleases"; +import ExtensionIcon from "@mui/icons-material/Extension"; +import EmailIcon from "@mui/icons-material/Email"; +import FingerprintIcon from '@mui/icons-material/Fingerprint'; +import AppSearch from "../components/Appsearch.jsx"; +import { toast } from 'react-toastify'; +import { + Zoom, + Grid, + Paper, + TextField, + Collapse, + IconButton, + Avatar, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip, + List, + ListItem, + ListItemAvatar, + ListItemText, +} from '@mui/material'; + +const AppSearchButtons = (props) => { + const { userdata, globalUrl, appFramework, defaultSearch, finishedApps, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData } = props + const ref = useRef() + const [moreButton, setMoreButton] = useState(false); + let navigate = useNavigate(); + + const sizing = moreButton ? 510 : 480; + const buttonWidth = 450; + const buttonMargin = 10; + const bottomButtonStyle = { + borderRadius: 200, + marginTop: moreButton ? 44 : "", + height: 51, + width: 510, + fontSize: 16, + // background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)", + background: "linear-gradient(90deg, #F86744 0%, #F34475 100%)", + padding: "16px 24px", + // top: 20, + // margin: "auto", + textTransform: 'capitalize', + itemAlign: "center", + // marginTop: 25 + // marginLeft: "65px", + }; + const buttonStyle = { + flex: 1, + width: 224, + padding: 25, + margin: buttonMargin, + color: "var(--White-text, #F1F1F1)", + fontWeight: 400, + fontSize: 17, + background: "rgba(33, 33, 33, 1)", + textTransform: 'capitalize', + border: "1px solid rgba(33, 33, 33, 1)", + borderRadius: 8, + marginRight: 8, + }; + + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + + const mouseOver = (e) => { + e.target.style.border = "1px solid #f85a3e"; + } + const mouseOut = (e) => { + e.target.style.border = "1px solid rgb(33, 33, 33)"; + } + + return ( + + + {/*Find your integrations!*/} + {/*
+ +
+
+ + +
+
+ + +
+ {moreButton ? ( +
+ + + +
+ ) + : +
+ +
} */} +
+
} + onClick={(event) => { + onNodeSelect("CASES"); + setDefaultSearch(discoveryData.label) + }} + > +
+ {AppImage === undefined || AppImage === undefined || + AppImage === null || AppImage === null || AppImage.length === 0 ? +
+ +
+ : } +
+ Case Management + {appName === undefined || appName === undefined || + appName === null || appName === null || appName.length === 0 ? + "" + : + {appName}} +
+ +
+
+
+
+ ) +} +export default AppSearchButtons diff --git a/frontend/src/components/AppSelection.jsx b/frontend/src/components/AppSelection.jsx new file mode 100644 index 00000000..d5f212df --- /dev/null +++ b/frontend/src/components/AppSelection.jsx @@ -0,0 +1,433 @@ +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 { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon, Close as CloseIcon, Folder as FolderIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material'; +import aa from 'search-insights' +import DeleteIcon from '@mui/icons-material/Delete'; +import ShowChartIcon from '@mui/icons-material/ShowChart'; +import ExploreIcon from '@mui/icons-material/Explore'; +import LightbulbIcon from "@mui/icons-material/Lightbulb"; +import NewReleasesIcon from "@mui/icons-material/NewReleases"; +import ExtensionIcon from "@mui/icons-material/Extension"; +import EmailIcon from "@mui/icons-material/Email"; +import FingerprintIcon from '@mui/icons-material/Fingerprint'; +import AppSearch from "../components/Appsearch.jsx"; +import AppSearchButtons from "../components/AppSearchButtons.jsx"; +import { toast } from 'react-toastify'; +import { + Zoom, + Grid, + Paper, + TextField, + Collapse, + IconButton, + Avatar, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip, + List, + ListItem, + ListItemAvatar, + ListItemText, +} from '@mui/material'; + +const AppSelection = props => { + const { + userdata, + globalUrl, + appFramework, + setActiveStep, + defaultSearch, + setDefaultSearch, + checkLogin, + } = props; + const [discoveryData, setDiscoveryData] = React.useState({}) + const [selectionOpen, setSelectionOpen] = React.useState(false) + const [newSelectedApp, setNewSelectedApp] = React.useState({}) + const [finishedApps, setFinishedApps] = React.useState([]) + const [appButtons, setAppButtons] = useState([]) + const [apps, setApps] = useState([]) + const [appName, setAppName] = React.useState(); + const [moreButton, setMoreButton] = useState(false); + + // const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + const ref = useRef() + let navigate = useNavigate(); + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + + + const setFrameworkItem = (data) => { + console.log("Setting framework item: ", data, isCloud) + // if (!isCloud) { + // activateApp(data.id) + // } + + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + if (checkLogin !== undefined) { + checkLogin() + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Failed updating: " + responseJson.reason) + } else { + toast("Failed to update framework for your org.") + + } + } + //setFrameworkLoaded(true) + //setFrameworkData(responseJson) + }) + .catch((error) => { + if (checkLogin !== undefined) { + checkLogin() + } + + toast(error.toString()); + //setFrameworkLoaded(true) + }) + } + const GetApps = (data) => { + console.log("Setting framework item: ", data, isCloud) + // if (!isCloud) { + // activateApp(data.id) + // } + + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((responseJson) => { + if (responseJson === null) { + console.log("null-response from server") + const pretend_apps = [{ + "description": "TBD", + "id": "TBD", + "large_image": "", + "name": "TBD", + "type": "TBD" + }] + + setApps(pretend_apps) + return + } + + if (responseJson.success === false) { + console.log("error loading apps: ", responseJson) + return + } + + setApps(responseJson); + }) + .catch((error) => { + console.log("App loading error: " + error.toString()); + }) + } + + const onNodeSelect = (label) => { + // if (setDiscoveryWrapper !== undefined) { + // setDiscoveryWrapper({ id: label }); + // } + + if (isCloud) { + ReactGA.event({ + category: "welcome", + action: `click_${label}`, + label: "", + }); + } + setDiscoveryData(label) + setSelectionOpen(true) + setNewSelectedApp({}) + setDefaultSearch(label.charAt(0).toUpperCase() + (label.substring(1)).toLowerCase()) + }; + + useEffect(() => { + var tempApps = [] + if (tempApps.length === 0) { + const tempApps = + [{ + "description": newSelectedApp.description, + "id": newSelectedApp.objectID, + "large_image": newSelectedApp.image_url, + "name": newSelectedApp.name, + "type": discoveryData + }, + //{ + // // description: newSelectedApp.siem.description, + // id: newSelectedApp.siem.objectID, + // large_image: newSelectedApp.siem.image_url, + // name: newSelectedApp.siem.name, + // type: discoveryData.siem + // },{ + // // description: newSelectedApp.edr.description, + // id: newSelectedApp.edr.objectID, + // large_image: newSelectedApp.edr.image_url, + // name: newSelectedApp.edr.name, + // type: discoveryData.edr + // } + ] + setAppButtons(tempApps) + GetApps() + } + }, []) + + useEffect(() => { + if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) { + return + } + const submitNewApp = { + description: newSelectedApp.description, + id: newSelectedApp.objectID, + large_image: newSelectedApp.image_url, + name: newSelectedApp.name, + type: discoveryData + } + if (discoveryData === "CASES") { + appFramework.cases = submitNewApp + } + else if (discoveryData === "SIEM") { + appFramework.siem = submitNewApp + } + else if (discoveryData === "ERADICATION") { + appFramework.edr = submitNewApp + } + else if (discoveryData === "INTEL") { + appFramework.intel = submitNewApp + } + else if (discoveryData === "EMAIL") { + appFramework.communication = submitNewApp + } + else if (discoveryData === "NETWORK") { + appFramework.network = submitNewApp + } + else if (discoveryData === "ASSETS") { + appFramework.assets = submitNewApp + } + else if (discoveryData === "IAM") { + appFramework.iam = submitNewApp + } + setFrameworkItem(submitNewApp); + setSelectionOpen(false); + console.log("Selected app changed (effect)"); + }, [newSelectedApp]); + + const sizing = moreButton ? 510 : 480; + const buttonWidth = 450; + const buttonMargin = 10; + const bottomButtonStyle = { + borderRadius: 200, + marginTop: moreButton ? 44 : "", + height: 51, + width: 510, + fontSize: 16, + // background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)", + background: "linear-gradient(90deg, #F86744 0%, #F34475 100%)", + padding: "16px 24px", + // top: 20, + // margin: "auto", + textTransform: 'capitalize', + itemAlign: "center", + // marginTop: 25 + // marginLeft: "65px", + }; + const buttonStyle = { + flex: 1, + width: 224, + padding: 25, + margin: buttonMargin, + color: "var(--White-text, #F1F1F1)", + fontWeight: 400, + fontSize: 17, + background: "rgba(33, 33, 33, 1)", + textTransform: 'capitalize', + border: "1px solid rgba(33, 33, 33, 1)", + borderRadius: 8, + marginRight: 8, + }; + // console.log("appFramework",appFramework.cases.name) + return ( + +
+ {selectionOpen ? ( +
+
+
+ {discoveryData} +
+
+ + { + setSelectionOpen(false) + }} + > + + + + + { + e.preventDefault(); + setSelectionOpen(false) + setDefaultSearch("") + const submitDeletedApp = { + "description": "", + "id": "remove", + "name": "", + "type": discoveryData + } + setFrameworkItem(submitDeletedApp) + setNewSelectedApp({}) + setTimeout(() => { + setDiscoveryData({}) + setFrameworkItem(submitDeletedApp) + setNewSelectedApp({}) + }, 1000) + //setAppName(discoveryData.cases.name) + }} + > + + + +
+
+
+ +
+ ) : null} + + Find your apps + + + Select the apps you work with and we will connect the for you. + + {appButtons.map((appData, index) => { + + const appName = appData.name + const AppImage = appData.large_image + const appType = appData.type + + return ( + + + ) + })} +
+
+ +
+ + ) +} + +export default AppSelection; diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx index 9da69a70..1879d0ef 100644 --- a/frontend/src/components/Appsearch.jsx +++ b/frontend/src/components/Appsearch.jsx @@ -1,24 +1,33 @@ import React, { useState, useEffect } from 'react'; import ReactGA from 'react-ga4'; -import { useTheme } from '@material-ui/core/styles'; +import theme from '../theme.jsx'; import {Link} from 'react-router-dom'; -import { useAlert } from "react-alert"; -import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; +import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material'; +import { toast } from 'react-toastify'; //import algoliasearch from 'algoliasearch/lite'; import algoliasearch from 'algoliasearch'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; -import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core'; +import { + Grid, + Paper, + TextField, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip +} from '@mui/material'; + import aa from 'search-insights' const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const Appsearch = props => { - const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList} = props + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - const alert = useAlert(); const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs - const theme = useTheme(); + //const theme = useTheme(); //const [apps, setApps] = React.useState([]); //const [filteredApps, setFilteredApps] = React.useState([]); const [formMail, setFormMail] = React.useState(""); @@ -31,82 +40,6 @@ const Appsearch = props => { const borderRadius = 3 window.title = "Shuffle | Apps | Find and integration any app" - const setUserSpecialzedApp = (user, data) => { - // var data = newfields] - console.log("data value", data) - const appData = {"user_id":user,"specialized_apps":[{}]} - console.log("User Check for appdata:", user) - appData["specialized_apps"][0]["name"] = data["name"] - appData["specialized_apps"][0]["image"] = data["image_url"] - appData["specialized_apps"][0]["category"] = data["categories"].toString() - console.log("AppData:",appData) - console.log("setActionImageList",setActionImageList) - console.log("actionImageList",actionImageList) - - const finalData = actionImageList.concat(appData["specialized_apps"]) - appData["specialized_apps"]=finalData - fetch(globalUrl + "/api/v1/users/updateuser", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(appData), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for set creator :O!"); - } - alert.success("Sucessfully updated specialzed app.") - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed updating user: " + responseJson.reason); - } - }) - .catch((error) => { - console.log(error); - }); - }; - const submitContact = (email, message) => { - const data = { - "firstname": "", - "lastname": "", - "title": "", - "companyname": "", - "email": email, - "phone": "", - "message": message, - } - - const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly." - - fetch(globalUrl+"/api/v1/contact", { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }) - .then(response => response.json()) - .then(response => { - if (response.success === true) { - setFormMessage(response.reason) - //alert.info("Thanks for submitting!") - } else { - setFormMessage(errorMessage) - } - - setFormMail("") - setMessage("") - }) - .catch(error => { - setFormMessage(errorMessage) - console.log(error) - }); - } // value={currentRefinement} const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { @@ -121,14 +54,14 @@ const Appsearch = props => {
@@ -138,7 +71,8 @@ const Appsearch = props => { type="search" color="primary" defaultValue={defaultSearch} - placeholder={`Find ${defaultSearch} Apps...`} + // placeholder={`Find ${defaultSearch} Apps...`} + placeholder= {defaultSearch ? `${defaultSearch}` : "Search Cases "} id="shuffle_workflow_search_field" onChange={(event) => { refine(event.currentTarget.value) @@ -159,9 +93,9 @@ const Appsearch = props => { {hits.map((data, index) => { const paperStyle = { - backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : theme.palette.inputColor, + backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : "#2F2F2F", color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)", - border: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e", + // border: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e", textAlign: "left", padding: 10, cursor: "pointer", @@ -207,13 +141,8 @@ const Appsearch = props => { setMouseHoverIndex(-1) }} onClick={() => { if(isCreatorPage === true){ - console.log("data:",data) - console.log("userdata.id",userdata.id) - console.log("is creator", isCreatorPage) - if (setNewSelectedApp !== undefined) { - // setUserSpecialzedApp = data + if (setNewSelectedApp !== undefined && setUserSpecialzedApp !== undefined) { setUserSpecialzedApp(userdata.id, data) - //setActionImageList(userdata.id, data) } } if (setNewSelectedApp !== undefined) { @@ -255,7 +184,7 @@ const Appsearch = props => { } }}>
- {data.name} + {data.name} {parsedname} @@ -272,7 +201,7 @@ const Appsearch = props => { const CustomHits = connectHits(InputHits) return ( -
+
{/* showSearch === false ? null :
diff --git a/frontend/src/components/AppsearchPopout.jsx b/frontend/src/components/AppsearchPopout.jsx index 25ea719c..1e76206b 100644 --- a/frontend/src/components/AppsearchPopout.jsx +++ b/frontend/src/components/AppsearchPopout.jsx @@ -12,12 +12,12 @@ import { CircularProgress, Tooltip, Button, -} from "@material-ui/core"; +} from "@mui/material"; import { Close as CloseIcon, Delete as DeleteIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; const AppSearchPopout = (props) => { const { diff --git a/frontend/src/components/AuthenticationItem.jsx b/frontend/src/components/AuthenticationItem.jsx index eef9806c..eae08e69 100644 --- a/frontend/src/components/AuthenticationItem.jsx +++ b/frontend/src/components/AuthenticationItem.jsx @@ -1,7 +1,8 @@ import React, { useState, useEffect } from "react"; import theme from '../theme.jsx'; -import { useAlert } from "react-alert"; +import { toast } from 'react-toastify'; + import { Tooltip, IconButton, @@ -17,15 +18,14 @@ import { Grid, Paper, Typography, - TextField, Zoom, -} from "@material-ui/core"; +} from "@mui/material"; import { Edit as EditIcon, Delete as DeleteIcon, SelectAll as SelectAllIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; const AuthenticationItem = (props) => { const { data, index, globalUrl, getAppAuthentication } = props @@ -34,7 +34,7 @@ const AuthenticationItem = (props) => { const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false); const [authenticationFields, setAuthenticationFields] = React.useState([]); - const alert = useAlert(); + //const alert = useAlert(); var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; @@ -63,7 +63,7 @@ const AuthenticationItem = (props) => { } const deleteAuthentication = (data) => { - alert.info("Deleting auth " + data.label); + toast("Deleting auth " + data.label); // Just use this one? const url = globalUrl + "/api/v1/apps/authentication/" + data.id; @@ -79,13 +79,13 @@ const AuthenticationItem = (props) => { response.json().then((responseJson) => { console.log("RESP: ", responseJson); if (responseJson["success"] === false) { - alert.error("Failed deleting auth"); + toast("Failed deleting auth"); } else { // Need to wait because query in ES is too fast setTimeout(() => { getAppAuthentication(); }, 1000); - //alert.success("Successfully deleted authentication!") + //toast("Successfully deleted authentication!") } }) ) @@ -115,9 +115,9 @@ const AuthenticationItem = (props) => { .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - alert.error("Failed overwriting appauth in workflows"); + toast("Failed overwriting appauth in workflows"); } else { - alert.success("Successfully updated auth everywhere!"); + toast("Successfully updated auth everywhere!"); //setSelectedUserModalOpen(false); setTimeout(() => { getAppAuthentication(); @@ -126,7 +126,7 @@ const AuthenticationItem = (props) => { }) ) .catch((error) => { - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; diff --git a/frontend/src/components/AuthenticationNormal.jsx b/frontend/src/components/AuthenticationNormal.jsx index 5d3a4fd3..1140de10 100644 --- a/frontend/src/components/AuthenticationNormal.jsx +++ b/frontend/src/components/AuthenticationNormal.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import theme from '../theme.jsx'; import { v4 as uuidv4 } from "uuid"; - +import { toast } from 'react-toastify'; import { Button, @@ -13,11 +13,11 @@ import { DialogTitle, DialogContent, Typography, -} from "@material-ui/core"; +} from "@mui/material"; import { LockOpen as LockOpenIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; const AuthenticationData = (props) => { const { @@ -54,7 +54,7 @@ const AuthenticationData = (props) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to set app auth: " + responseJson.reason); + toast("Failed to set app auth: " + responseJson.reason); } else { if (getAppAuthentication !== undefined) { getAppAuthentication() @@ -65,11 +65,11 @@ const AuthenticationData = (props) => { } // Needs a refresh with the new authentication.. - //alert.success("Successfully saved new app auth") + //toast("Successfully saved new app auth") } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("New auth error: ", error.toString()); }); } @@ -146,7 +146,7 @@ const AuthenticationData = (props) => { selectedApp.authentication.parameters[key].name ] = "false"; } else { - alert.info( + toast( "Field " + selectedApp.authentication.parameters[key].name + " can't be empty" diff --git a/frontend/src/components/AuthenticationWindow.jsx b/frontend/src/components/AuthenticationWindow.jsx new file mode 100755 index 00000000..7d32f597 --- /dev/null +++ b/frontend/src/components/AuthenticationWindow.jsx @@ -0,0 +1,469 @@ +import React, { useState, useEffect } from "react"; + +import theme from '../theme.jsx'; +import { v4 as uuidv4 } from "uuid"; +import { toast } from 'react-toastify'; + +import { + Divider, + MenuItem, + Button, + Dialog, + DialogTitle, + DialogActions, + DialogContent, + Textfield, + TextField, + Typography, + Select, + IconButton, +} from "@mui/material"; + +import { + LockOpen as LockOpenIcon, + Close as CloseIcon, +} from "@mui/icons-material"; + +import PaperComponent from "../components/PaperComponent.jsx" +import { useParams, useNavigate, Link } from "react-router-dom"; + +const AuthenticationData = (props) => { + const { + globalUrl, + selectedApp, + getAppAuthentication, + authenticationModalOpen, + setAuthenticationModalOpen, + + configureWorkflowModalOpen, + workflow, + setUpdate, + selectedAction, + setSelectedAction, + isLoggedIn, + authFieldsOnly, + } = props + + //const alert = useAlert() + let navigate = useNavigate(); + const [submitSuccessful, setSubmitSuccessful] = useState(false) + const [authenticationOption, setAuthenticationOptions] = React.useState({ + app: JSON.parse(JSON.stringify(selectedApp)), + fields: {}, + label: "", + usage: [ + { + workflow_id: workflow === undefined ? "" : workflow.id, + }, + ], + id: uuidv4(), + active: true, + }); + + useEffect(() => { + if (isLoggedIn === false && authFieldsOnly !== true) { + navigate(`/login?view=${window.location.pathname}&message=Log in to authenticate this app`) + } + }, []) + + const setNewAppAuth = (appAuthData) => { + var headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + + // Find org_id and authorization from queries and add to headers + if (window.location.search !== "") { + const params = new URLSearchParams(window.location.search) + const org_id = params.get("org_id") + const authorization = params.get("authorization") + if (org_id !== null && authorization !== null) { + headers["Org-Id"] = org_id + headers["Authorization"] = "Bearer " + authorization + } + } + + 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!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + if (responseJson.reason === undefined) { + toast("Failed to set app auth. Are you logged in?") + } else { + toast("Failed to set app auth: " + responseJson.reason); + } + } else { + setSubmitSuccessful(true) + if (getAppAuthentication !== undefined) { + getAppAuthentication(true, false); + } + + if (setAuthenticationModalOpen !== undefined) { + setAuthenticationModalOpen(false) + } + } + }) + .catch((error) => { + //toast(error.toString()); + console.log("New auth error: ", error.toString()); + }); + }; + + 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 handleSubmitCheck = () => { + if (authenticationOption.label.length === 0) { + authenticationOption.label = `Auth for ${selectedApp.name}`; + } + + // Automatically mapping fields that already exist (predefined). + // Warning if fields are NOT filled + 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; + } + } + } + } + + console.log("Action: ", selectedAction); + + if (selectedAction !== undefined) { + selectedAction.authentication_id = authenticationOption.id; + selectedAction.selectedAuthentication = authenticationOption; + if ( + selectedAction.authentication === undefined || + selectedAction.authentication === null + ) { + selectedAction.authentication = [authenticationOption]; + } else { + selectedAction.authentication.push(authenticationOption); + } + + setSelectedAction(selectedAction); + } + + var newAuthOption = JSON.parse(JSON.stringify(authenticationOption)); + var newFields = []; + console.log("Fields: ", newAuthOption.fields) + for (let authkey in newAuthOption.fields) { + const value = newAuthOption.fields[authkey]; + newFields.push({ + "key": authkey, + "value": value, + }); + } + + newAuthOption.fields = newFields; + setNewAppAuth(newAuthOption); + + if (configureWorkflowModalOpen === true) { + setSelectedAction({}); + } + + if (setUpdate !== undefined) { + setUpdate(authenticationOption.id); + } + }; + + if (authenticationOption.label === null || authenticationOption.label === undefined) { + authenticationOption.label = selectedApp.name + " authentication"; + } + + const authenticationParameters = selectedApp.authentication.parameters.map((data, index) => { + return ( +
+
+ + + {data.name.replace("_basic", "", -1).replace("_", " ", -1)} + +
+ + {data.schema !== undefined && + data.schema !== null && + data.schema.type === "bool" ? ( + + ) : ( + { + authenticationOption.fields[data.name] = + event.target.value; + }} + /> + )} +
+ ); + }) + + const authenticationButtons = + + {authFieldsOnly === true ? null : + + } + + + // Check if only the auth items should show + if (authFieldsOnly === true) { + return ( +
+ {submitSuccessful === true ? + + App succesfully configured! You may close this window. + + : + + {authenticationParameters} + {authenticationButtons} + + } +
+ ) + } + + return ( + { + //if (configureWorkflowModalOpen) { + // setSelectedAction({}); + //} + setAuthenticationModalOpen(false); + }} + PaperProps={{ + style: { + pointerEvents: "auto", + color: "white", + minWidth: 600, + minHeight: 600, + maxHeight: 600, + padding: 15, + overflow: "hidden", + zIndex: 10012, + border: theme.palette.defaultBorder, + }, + }} + > + { + setAuthenticationModalOpen(false); + if (configureWorkflowModalOpen === true) { + setSelectedAction({}); + } + }} + > + + + +
+ Authentication for {selectedApp.name} +
+
+ + + What is app authentication? + +
+ These are required fields for authenticating with {selectedApp.name} +
+ Name - what is this used for? + { + authenticationOption.label = event.target.value; + }} + /> + +
+ {authenticationParameters} + + + + {authenticationButtons} + +
+ ); +}; + +export default AuthenticationData; diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index b59b5e4a..91f353bd 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -1,317 +1,1011 @@ -import React, { useState, useEffect } from "react"; -import ReactGA from 'react-ga4'; - -import { useTheme } from "@material-ui/core/styles"; -import { - Paper, - Typography, - Divider, - Button, - Grid, - Card, -} from "@material-ui/core"; - -import { useAlert } from "react-alert"; -import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; - -const Billing = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; - console.log("Billing: ", billingInfo); - const theme = useTheme(); - const alert = useAlert(); - - const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : "" - console.log("Stripe: ", stripe) - - const paperStyle = { - padding: 20, - height: "100%", - width: "100%", - backgroundColor: theme.palette.surfaceColor, - border: "1px solid rgba(255,255,255,0.3)", - marginRight: 10, - } - - const isCloud = - window.location.host === "localhost:3002" || - window.location.host === "shuffler.io"; - - billingInfo.subscription = { - "active": true, - "name": "Pay as you go", - "price": typecost_single, - "currency": "USD", - "currency_text": "$", - "interval": "app run / month", - "description": "Pay as you go", - "features": [ - "Includes 10.000 app run/month for free. ", - "Pay for what you use with no minimum commitment and cancel anytime.", - ], - "limit": 10000, - } - - - const handleStripeRedirect = () => { - //var priceItem = "price_1MRNF1DzMUgUjxHSfFTUb2Xh" - if (stripe == "") { - console.log("Stripe not loaded") - return - } - - var priceItem = "price_1MROFrDzMUgUjxHShcSxgHO1" - - const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success` - const failUrl = `${window.location.origin}/admin?admin_tab=billing&payment=failure` - var checkoutObject = { - lineItems: [ - { - price: priceItem, - quantity: 1 - }, - ], - mode: "subscription", - billingAddressCollection: "auto", - successUrl: successUrl, - cancelUrl: failUrl, - clientReferenceId: props.userdata.active_org.id, - } - //submitType: "donate", - - stripe.redirectToCheckout(checkoutObject) - .then(function (result) { - console.log("SUCCESS STRIPE?: ", result) - - ReactGA.event({ - category: "pricing", - action: "add_card_success", - label: "", - }) - }) - .catch(function(error) { - console.error("STRIPE ERROR: ", error) - - 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 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); - } - - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success !== undefined && responseJson.success) { - alert.success("Successfully stopped subscription!"); - } else { - alert.error("Failed stopping subscription. Please contact us."); - } - }) - .catch(function (error) { - console.log("Error: ", error); - alert.error("Failed stopping subscription. Please contact us."); - }); - }; - - const SubscriptionObject = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, } = props; - - console.log("Sub: ", subscription) - var top_text = "Base Access" - if (subscription.limit === undefined && subscription.level !== undefined) { - - subscription.name = "Enterprise" - subscription.currency_text = "$" - 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", - "And all other features from /pricing", - ] - } - - if (subscription.name === "Enterprise" && subscription.active === true) { - top_text = "Current Plan" - } - - return ( - -
- - {top_text} - -
- -
- - {subscription.name} - -
- - {subscription.currency_text}{subscription.price} - - - / {subscription.interval} - -
- - Features - -
    - {subscription.features !== undefined && subscription.features !== null ? - subscription.features.map((feature, index) => { - return ( -
  • - - {feature} - -
  • - ) - }) - : null} -
-
- {/*subscription.name === "Pay as you go" && subscription.limit <= 10000 ? - - - You are not subscribed to any plan and are using the free plan with max 10,000 apps per month. Activate billing to de-activate this limit. - - - - : null*/} -
- ) - } - - - return ( -
- - Billing - - - We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below. - -
- {billingInfo.subscription !== undefined && billingInfo.subscription !== null ? - - : null} - {isCloud && - selectedOrganization.subscriptions !== undefined && - selectedOrganization.subscriptions !== null && - selectedOrganization.subscriptions.length > 0 ? - - selectedOrganization.subscriptions - .reverse() - .map((sub, index) => { - return ( - - ) - }) - : null} - {/* - - - Quantity: {sub.level} -
- Recurrence: {sub.recurrence} -
- {sub.active ? ( -
- Started:{" "} - {new Date(sub.startdate * 1000).toISOString()} -
- -
- ) : ( -
- Cancelled:{" "} - {new Date( - sub.cancellationdate * 1000 - ).toISOString()} -
- - Status: Deactivated - -
- )} - - - */} -
-
- ) -} - -export default Billing; +import React, { useState, useEffect } from "react"; +import ReactGA from 'react-ga4'; + +import theme from "../theme.jsx"; +import { useTheme } from "@mui/styles"; +import countries from "../components/Countries.jsx"; +import { + Box, + Paper, + Typography, + Divider, + Button, + Grid, + Card, + List, + ListItemText, + ListItem, + Dialog, + DialogTitle, + DialogContent, + TextField, +} from "@mui/material"; + +import { useNavigate, Link } from "react-router-dom"; +import { Autocomplete } from "@mui/material"; +import { toast } from "react-toastify" + +import { + Cached as CachedIcon, +} from "@mui/icons-material"; + +//import { useAlert +import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; +import BillingStats from "../components/BillingStats.jsx"; + +const Billing = (props) => { + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = 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 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: "" }, + ]; + + const handleGetDeals = (orgId) => { + 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; + } + + 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." + ); + }); + }; + + useEffect(() => { + if (isCloud && selectedOrganization.partner_info !== undefined && selectedOrganization.partner_info.reseller === true) { + handleGetDeals(selectedOrganization.id); + } + }, []) + + const paperStyle = { + padding: 20, + height: "100%", + minHeight: 280, + maxWidth: 400, + width: "100%", + backgroundColor: theme.palette.surfaceColor, + borderRadius: theme.palette.borderRadius, + border: "1px solid rgba(255,255,255,0.3)", + marginRight: 10, + } + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + billingInfo.subscription = { + "active": true, + "name": "Pay as you go", + "price": typecost_single, + "currency": "USD", + "currency_text": "$", + "interval": "app run / month", + "description": "Pay as you go", + "features": [ + "Includes 10.000 app run/month for free. ", + "Pay for what you use with no minimum commitment and cancel anytime.", + ], + "limit": 10000, + } + + + const handleStripeRedirect = () => { + //var priceItem = "price_1MRNF1DzMUgUjxHSfFTUb2Xh" + if (stripe == "") { + console.log("Stripe not loaded") + return + } + + var priceItem = "price_1MROFrDzMUgUjxHShcSxgHO1" + + const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success` + const failUrl = `${window.location.origin}/admin?admin_tab=billing&payment=failure` + var checkoutObject = { + lineItems: [ + { + price: priceItem, + quantity: 1 + }, + ], + mode: "subscription", + billingAddressCollection: "auto", + successUrl: successUrl, + cancelUrl: failUrl, + clientReferenceId: props.userdata.active_org.id, + } + //submitType: "donate", + + stripe.redirectToCheckout(checkoutObject) + .then(function (result) { + console.log("SUCCESS STRIPE?: ", result) + + ReactGA.event({ + category: "pricing", + action: "add_card_success", + label: "", + }) + }) + .catch(function(error) { + console.error("STRIPE ERROR: ", error) + + 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 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); + } + + 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 SubscriptionObject = (props) => { + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, highlight, } = props; + + var top_text = "Base 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.interval = subscription.recurrence + subscription.features = [ + "Includes " + subscription.limit + " app runs/month. ", + "Multi-Tenancy and Region-Selection", + "And all other features from /pricing", + ] + } + + var newPaperstyle = JSON.parse(JSON.stringify(paperStyle)) + if (subscription.name === "Enterprise" && subscription.active === true) { + top_text = "Current Plan" + + newPaperstyle.border = "1px solid #f85a3e" + } + + var showSupport = false + if (subscription.name.includes("default")) { + top_text = "Custom Contract" + newPaperstyle.border = "1px solid #f85a3e" + showSupport = true + } + + if (subscription.name.includes("App Run Units")) { + top_text = "Cloud Access" + showSupport = true + } + + if (subscription.name.includes("Open Source")) { + top_text = "Open Source" + showSupport = true + } + + if (subscription.name.includes("Scale")) { + top_text = "Scale access" + } + + if (highlight === true) { + // Add an "Upgrade now" button + newPaperstyle.border = "1px solid #f85a3e" + } + + return ( + +
+ + {top_text} + +
+ +
+ + {subscription.name} + + + {subscription.currency_text !== undefined ? +
+ + {subscription.currency_text}{subscription.price} + + + / {subscription.interval} + +
+ : null} + + + 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("Licensed Worker: ")) { + parsedFeature = + + Download the licensed worker + + } + + return ( +
  • + + {parsedFeature} + +
  • + ) + }) + : null} +
+
+ {(highlight === true && subscription.name === "Pay as you go" && subscription.limit <= 10000) || subscription.name.includes("Scale") ? + + + {subscription.name.includes("Scale") ? + "" + : + "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." + } + + + + : null} + {showSupport ? + + : 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} +
+ + +
+
+
+ ); + + 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 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." + ); + + 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); + }); + }; + + return ( +
+ {addDealModal} + + Billing + + + {isCloud ? + "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 to use it. You may however activate Cloud Sync, get our Scale license, get help with Kubernetes, or talk to Shuffle's Support team to get automation help." + } + +
+ {isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? + + : !isCloud ? + + + + + : null} + {isCloud && + selectedOrganization.subscriptions !== undefined && + selectedOrganization.subscriptions !== null && + selectedOrganization.subscriptions.length > 0 ? + + selectedOrganization.subscriptions + .reverse() + .map((sub, index) => { + return ( + + ) + }) + : null} + {/* + + + Quantity: {sub.level} +
+ Recurrence: {sub.recurrence} +
+ {sub.active ? ( +
+ Started:{" "} + {new Date(sub.startdate * 1000).toISOString()} +
+ +
+ ) : ( +
+ Cancelled:{" "} + {new Date( + sub.cancellationdate * 1000 + ).toISOString()} +
+ + Status: Deactivated + +
+ )} + + + */} +
+ {isCloud && + selectedOrganization.partner_info !== undefined && + selectedOrganization.partner_info.reseller === true ? ( +
+ + Reseller dashboard + + + + + + + + + + + + + + + + + + {dealList.length === 0 ? ( + + No deals registered yet. Click "Add deal" to register one + + ) : ( + dealList.map((deal, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + return ( + + + + + + + + + + + + + ); + }) + )} + + + + +
+ ) : null} +
+ + Shuffle Utilization + +
+ +
+ ) +} + +export default Billing; diff --git a/frontend/src/components/BillingStats.jsx b/frontend/src/components/BillingStats.jsx new file mode 100644 index 00000000..3f7a7516 --- /dev/null +++ b/frontend/src/components/BillingStats.jsx @@ -0,0 +1,280 @@ +import React, { useState, useEffect } from 'react'; + +import classNames from "classnames"; +import theme from '../theme.jsx'; + +import { + Tooltip, + TextField, + IconButton, + Button, + Typography, + Grid, + Paper, + Chip, + Checkbox, +} from "@mui/material"; + +import { + BarChart, + RadialBarChart, + RadialAreaChart, + RadialAxis, + StackedBarSeries, + TooltipArea, + ChartTooltip, + TooltipTemplate, + RadialAreaSeries, + RadialPointSeries, + RadialArea, + RadialLine, + TreeMap, + TreeMapSeries, + TreeMapLabel, + TreeMapRect, + Line, + LineChart, + LineSeries, + LinearYAxis, + LinearXAxis, + LinearYAxisTickSeries, + LinearXAxisTickSeries, + Area, + AreaChart, + AreaSeries, + AreaSparklineChart, + PointSeries, + GridlineSeries, + Gridline, + Stripes, + Gradient, + GradientStop, + LinearXAxisTickLabel, +} from 'reaviz'; + +const LineChartWrapper = ({keys, inputname, height, width}) => { + const [hovered, setHovered] = useState(""); + const inputdata = keys.data === undefined ? keys : keys.data + + return ( +
+ + {inputname} + + } /> + } + /> +
+ ) +} + + +const AppStats = (defaultprops) => { + const { globalUrl, selectedOrganization, userdata, } = defaultprops; + const [keys, setKeys] = useState([]) + const [searches, setSearches] = useState([]); + const [clickData, setClickData] = useState(undefined); + const [conversionData, setConversionData] = useState(undefined); + const [statistics, setStatistics] = useState(undefined); + const [appRuns, setAppruns] = useState(undefined); + const [workflowRuns, setWorkflowRuns] = useState(undefined); + const [subflowRuns, setSubflowRuns] = useState(undefined); + + const handleDataSetting = (inputdata, grouping) => { + if (inputdata === undefined || inputdata === null) { + return + } + + const dailyStats = inputdata.daily_statistics + if (dailyStats === undefined || dailyStats === null) { + return + } + + console.log("Looking at daily data: ", inputdata) + + var appRuns = { + "key": "App Runs", + "data": [] + } + + var workflowRuns = { + "key": "Workflow Runs (includes subflows)", + "data": [] + } + + var subflowRuns = { + "key": "Subflow Runs", + "data": [] + } + + for (let key in dailyStats) { + // Always skips first one as it has accumulated data in it + if (key === 0) { + continue + } + + const item = dailyStats[key] + + if (item["date"] === undefined) { + console.log("No date: ", item) + continue + } + + // Check if app_executions key in item + if (item["app_executions"] !== undefined && item["app_executions"] !== null) { + appRuns["data"].push({ + key: new Date(item["date"]), + data: item["app_executions"] + }) + } + + // Check if workflow_executions key in item + if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) { + workflowRuns["data"].push({ + key: new Date(item["date"]), + data: item["workflow_executions"] + }) + } + + if (item["subflow_executions"] !== undefined && item["subflow_executions"] !== null) { + subflowRuns["data"].push({ + key: new Date(item["date"]), + data: item["subflow_executions"] + }) + } + } + + // Adds data for today + console.log("Inputdata: ", inputdata) + if (inputdata["daily_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) { + appRuns["data"].push({ + key: new Date(), + data: inputdata["daily_app_executions"] + }) + } + + if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) { + workflowRuns["data"].push({ + key: new Date(), + data: inputdata["daily_workflow_executions"] + }) + } + + if (inputdata["daily_subflow_executions"] !== undefined && inputdata["daily_subflow_executions"] !== null) { + subflowRuns["data"].push({ + key: new Date(), + data: inputdata["daily_subflow_executions"] + }) + } + + setSubflowRuns(subflowRuns) + setWorkflowRuns(workflowRuns) + setAppruns(appRuns) + } + + const getStats = () => { + fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`, { + 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!: ", response.status); + return; + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson["success"] === false) { + return + } + + setStatistics(responseJson) + handleDataSetting(responseJson, "day") + }) + .catch((error) => { + console.log("error: ", error) + }); + } + + useEffect(() => { + getStats() + }, []) + + const paperStyle = { + textAlign: "center", + padding: 40, + margin: 5, + backgroundColor: theme.palette.surfaceColor, + maxWidth: 300, + } + + const data = ( +
+ + All Stat widgets are monthly and gathered from Your Organization Statistics. + This is a feature to help give you more insight into Shuffle, and will be populating over time. + + {statistics !== undefined ? +
+ + + {statistics.monthly_workflow_executions} + + + Workflow Runs + + + + + {statistics.monthly_app_executions} + + + App Runs + + +
+ : null} + + {appRuns === undefined ? + null + : + + } + + {workflowRuns === undefined ? + null + : + + } + + {subflowRuns === undefined ? + null + : + + } +
+ ) + + const dataWrapper = ( +
{data}
+ ); + + return dataWrapper; +} + +export default AppStats; diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index 40254bbb..9dd40791 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -1,8 +1,8 @@ import React, { useState, useEffect } from "react"; import ReactGA from 'react-ga4'; import theme from "../theme.jsx"; +import { ToastContainer, toast } from "react-toastify" -import { useTheme } from "@material-ui/core/styles"; import { Paper, Typography, @@ -10,27 +10,92 @@ import { Button, Grid, Card, -} from "@material-ui/core"; +} from "@mui/material"; -import { useAlert } from "react-alert"; +//import { useAlert const Branding = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; - const alert = useAlert(); - const [publishingInfo, setPublishingInfo] = useState(""); + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; + //const alert = useAlert(); + const [publishingInfo, setPublishingInfo] = useState(""); + const [publishRequirements, setPublishRequirements] = useState([]) - // Should enable / disable org branding - const handleChangePublishing = () => { - console.log("Handle change publishing"); - } + + const handleEditOrg = (joinStatus) => { + const data = { + "org_id": selectedOrganization.id, + "creator_config": joinStatus, + }; + + 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 { + if (joinStatus == "join") { + setPublishingInfo("Your organization is now part of the Creator Incentive Program. You can now create and publish content to your organization's page. You can also create a creator account to manage your organization's content.") + } else { + setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.") + } + handleGetOrg(selectedOrganization.id); + } + }) + ) + .catch((error) => { + toast("Err: " + error.toString()); + }); + }; + + // Should enable / disable org branding + const handleChangePublishing = () => { + console.log("Handle change publishing"); + + if (selectedOrganization.creator_id == "") { + handleEditOrg("join") + } else { + handleEditOrg("leave") + } + } const isOrganizationReady = () => { + console.log("Is organization ready?") + // A simple checklist to ensure the button shows up properly if (selectedOrganization.name === selectedOrganization.org) { + const comment = "Change the name of your organization" + if (!publishRequirements.includes(comment)) { + setPublishRequirements([...publishRequirements, comment]) + } + + return false; + } + + // Check if it's a suborg + if (selectedOrganization.creator_org !== "") { + const comment = "Child orgs can't become creators" + if (!publishRequirements.includes(comment)) { + setPublishRequirements([...publishRequirements, comment]) + } return false; } if (selectedOrganization.large_image === "" || selectedOrganization.large_image === theme.palette.defaultImage) { + const comment = "Add a logo for your organization" + if (!publishRequirements.includes(comment)) { + setPublishRequirements([...publishRequirements, comment]) + } return false; } @@ -39,40 +104,58 @@ const Branding = (props) => { return (
- - Branding - +

+ Branding +

You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more.

- Creator Network + Creator Incentive Program

-
+
- By changing publishing settings, you agree to our Terms of Service, and acknowledge that your organization's non-sensitive data will be turned into a creator account. Support: support@shuffler.io - + By changing publishing settings, you agree to our Terms of Service, and acknowledge that your organization's non-sensitive data will be added as a creator account. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization is reversible.
Support: support@shuffler.io + {selectedOrganization.creator_id == "" ? + +   + + : + + + Modify your creator organization + + } + - + {publishingInfo} + + {publishRequirements.map((item) => { + return ( +
+ Required: {item} +
+ ) + })} +
diff --git a/frontend/src/components/CacheView.jsx b/frontend/src/components/CacheView.jsx index 12ec0c1c..83bb0608 100644 --- a/frontend/src/components/CacheView.jsx +++ b/frontend/src/components/CacheView.jsx @@ -1,5 +1,7 @@ import React, { useState, useEffect } from "react"; import theme from "../theme.jsx"; +import { toast } from 'react-toastify'; + import { Tooltip, Divider, @@ -15,8 +17,7 @@ import { Dialog, DialogTitle, DialogActions, -} from "@material-ui/core"; -import { useAlert } from "react-alert"; +} from "@mui/material"; import { Edit as EditIcon, @@ -40,8 +41,7 @@ import { Business as BusinessIcon, Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon, -} from "@material-ui/icons"; -import data from "./frameworkStyle.jsx"; +} from "@mui/icons-material"; const scrollStyle1 = { height: 100, @@ -73,7 +73,7 @@ const CacheView = (props) => { const [dataValue, setDataValue] = React.useState({}); const [editCache, setEditCache] = React.useState(false); const [show, setShow] = useState({}); - const alert = useAlert(); + useEffect(() => { listOrgCache(orgId); console.log("orgid", orgId); @@ -106,7 +106,7 @@ const CacheView = (props) => { } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -149,7 +149,7 @@ const CacheView = (props) => { const deleteCache = (orgId, key) => { - alert.info("Attempting to delete Cache"); + toast("Attempting to delete Cache"); fetch(globalUrl + `/api/v1/orgs/${orgId}/cache/${key}`, { method: "DELETE", headers: { @@ -159,16 +159,16 @@ const CacheView = (props) => { }) .then((response) => { if (response.status === 200) { - alert.success("Successfully deleted Cache"); + toast("Successfully deleted Cache"); setTimeout(() => { listOrgCache(orgId); }, 1000); } else { - alert.error("Failed deleting Cache. Does it still exist?"); + toast("Failed deleting Cache. Does it still exist?"); } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -198,12 +198,12 @@ const CacheView = (props) => { }) .then((responseJson) => { setAddCache(responseJson); - alert.success("Cache Edited Successfully!"); + toast("Cache Edited Successfully!"); listOrgCache(orgId); setModalOpen(false); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -232,12 +232,12 @@ const CacheView = (props) => { }) .then((responseJson) => { setAddCache(responseJson); - alert.success("New Cache Added Successfully!"); + toast("New Cache Added Successfully!"); listOrgCache(orgId); setModalOpen(false); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index b7b9f2b0..52a3ac1b 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; import { useInterval } from "react-powerhooks"; +import { toast } from 'react-toastify'; import { InputAdornment, @@ -14,12 +15,18 @@ import { List, ListItem, ListItemText, - Fade, -} from "@material-ui/core"; + Collapse, + IconButton, +} from "@mui/material"; + import { FavoriteBorder as FavoriteBorderIcon, Error as ErrorIcon, CheckCircleRounded as CheckCircleRoundedIcon, + ExpandMore as ExpandMoreIcon, + ExpandLess as ExpandLessIcon, + Check as CheckIcon, + Visibility as VisibilityIcon, } from "@mui/icons-material"; import { FixName } from "../views/Apps.jsx"; import aa from 'search-insights' @@ -33,27 +40,26 @@ import aa from 'search-insights' // Specifically used for UNSAVED workflows only? const ConfigureWorkflow = (props) => { const { - userdata, - globalUrl, + apps, theme, + isCloud, workflow, + userdata, + globalUrl, + newWebhook, + referenceUrl, + saveWorkflow, + showTriggers, + submitSchedule, + setSelectedApp, + selectedAction, appAuthentication, setSelectedAction, - setAuthenticationModalOpen, - setSelectedApp, - apps, - selectedAction, - setConfigureWorkflowModalOpen, - saveWorkflow, - newWebhook, - submitSchedule, - referenceUrl, - isCloud, + workflowExecutions, + getWorkflowExecution, setAuthenticationType, - alert, - showTriggers, - workflowExecutions, - getWorkflowExecution, + setAuthenticationModalOpen, + setConfigureWorkflowModalOpen, } = props; const [requiredActions, setRequiredActions] = React.useState([]); @@ -63,9 +69,39 @@ const ConfigureWorkflow = (props) => { const [itemChanged, setItemChanged] = React.useState(false); const [firstLoad, setFirstLoad] = React.useState(""); const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false); + const [loopRunning, setLoopRunning] = useState(false) - const [checkStarted, setCheckStarted] = React.useState(false); + const [checkStarted, setCheckStarted] = React.useState(false); + const stop = () => { + setLoopRunning(false) + } + + const start = () => { + setLoopRunning(true) + } + + useEffect(() => { + if (loopRunning) { + const intervalId = setInterval(() => { + if (!loopRunning) { + clearInterval(intervalId); + } + + + if (getWorkflowExecution !== undefined && workflowExecutions !== undefined) { + const paramkey = workflow.id + getWorkflowExecution(paramkey) + } else { + console.log("Executions or getWorkflowExecutions not defined") + } + }, 3000) + + return () => clearInterval(intervalId); + } + }, [loopRunning]) + + /* const { start, stop } = useInterval({ duration: 3000, startImmediate: false, @@ -78,6 +114,7 @@ const ConfigureWorkflow = (props) => { } }, }); + */ // ONLY when component is being unloaded, run stop() function // This is to prevent the interval from running when the component is not being used @@ -91,16 +128,18 @@ const ConfigureWorkflow = (props) => { */ // Where is this from? - if (workflow === undefined || workflow === null) { + if (workflow === undefined || workflow === null || workflow.id === undefined) { return null; } if (apps === undefined || apps === null) { - return null; + console.log("Apps is undefined or null: ", apps) + return null; } if (appAuthentication === undefined || appAuthentication === null) { - return null; + console.log("App authentication is undefined or null: ", appAuthentication) + return null; } const getApp = (actionId, appId) => { @@ -112,9 +151,9 @@ const ConfigureWorkflow = (props) => { }) .then((response) => { if (response.status === 200) { - //alert.success("Successfully GOT app "+appId) + //toast("Successfully GOT app "+appId) } else { - alert.error("Failed getting app"); + toast("Failed getting app"); } return response.json(); @@ -128,20 +167,26 @@ const ConfigureWorkflow = (props) => { } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; if (firstLoad.length === 0 || firstLoad !== workflow.id) { if (apps === undefined || apps === null || apps.length === 0) { console.log("No apps loaded: ", apps); - setConfigureWorkflowModalOpen(false); + + if (setConfigureWorkflowModalOpen !== undefined) { + setConfigureWorkflowModalOpen(false); + } + return null; } setFirstLoad(workflow.id) + const newactions = []; for (let [key, keyval] in Object.entries(workflow.actions)) { + const action = workflow.actions[key]; var newaction = { large_image: action.large_image, @@ -153,34 +198,36 @@ const ConfigureWorkflow = (props) => { auth_done: false, action_ids: [], action: action, - update_version: action.app_version, + update_version: action.app_version, app: {}, - steps: [], - show_steps: false, + steps: [], + show_steps: false, } - //console.log("Action: ", key, keyval) + if (action.app_name.toLowerCase().endsWith("_api")) { + action.app_name = action.app_name.slice(0, -4) + } - const app = apps.find((app) => - app.id === action.app_id || - (app.name === action.app_name && - (app.app_version === action.app_version || (app.loop_versions !== null && app.loop_versions.includes(action.app_version)))) - ) - - //console.log("FOUND APP: ", app) + // ID match OR name match + version match + //const app = apps.find((app) => app.id === action.app_id || (app.name === action.app_name && (app.app_version === action.app_version || (app.loop_versions !== null && app.loop_versions.includes(action.app_version))))) + // + // without version match + const newappname = action.app_name.toLowerCase().replaceAll(" ", "_") + const app = apps.find((app) => app.id === action.app_id || app.name.toLowerCase().replaceAll(" ", "_") === newappname) if (app === undefined || app === null) { + const subapp = apps.find(app => app.name === action.app_name) - if (subapp !== undefined && subapp !== null) { - newaction.update_version = "1.1.0" - } + if (subapp !== undefined && subapp !== null) { + newaction.update_version = "1.1.0" + } newaction.must_activate = true; - newaction.steps.push({ - "title": "Activate app", - "type": "activate", - "required": true, - }) + newaction.steps.push({ + "title": "Activate app", + "type": "activate", + "required": true, + }) } else { if (action.authentication_id === "" && app.authentication.required === true && action.parameters !== undefined && action.parameters !== null) { // Check if configuration is filled or not @@ -198,20 +245,19 @@ const ConfigureWorkflow = (props) => { } } - newaction.steps.push({ - "title": "Authenticate app", - "type": "authenticate", - "required": true, - }) + newaction.steps.push({ + "title": "Authenticate app", + "type": "authenticate", + "required": true, + }) if (!filled) { newaction.must_authenticate = true; newaction.action_ids.push(action.id); } } else if (action.authentication_id !== "" && app.authentication.required === true) { - console.log("Should verify authentication ID ", action.authentication_id) - - } + console.log("Should verify authentication ID ", action.authentication_id) + } newaction.app = app; } @@ -275,80 +321,78 @@ const ConfigureWorkflow = (props) => { } if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length !== 0) { - for (let [key,keyval] in Object.entries(workflow.workflow_variables)) { - const variable = workflow.workflow_variables[key]; - if (variable.value === undefined || variable.value === undefined || variable.value.length === 0) { - variable.value = ""; - requiredVariables.push(variable); - } + for (let [key,keyval] in Object.entries(workflow.workflow_variables)) { + const variable = workflow.workflow_variables[key]; - variable.index = key; - } + if (variable.value === undefined || variable.value === undefined || variable.value.length === 0) { + variable.value = ""; + requiredVariables.push(variable); + } + + variable.index = key; + } } if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length !== 0) { - for (let [key,keyval] in Object.entries(workflow.triggers)) { - var trigger = workflow.triggers[key]; - trigger.index = key; + for (let [key,keyval] in Object.entries(workflow.triggers)) { + var trigger = workflow.triggers[key]; + trigger.index = key; - if (trigger.trigger_type === "WEBHOOK") { - 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) + if (trigger.trigger_type === "WEBHOOK") { + console.log("Found webhook: ", trigger) - // Adding webhook to start of it - if (foundindex >= 0) { - const tmpsteps = newactions[foundindex].steps - newactions[foundindex].steps = [ - { - "title": "Configure Webhook", - "type": "webhook", - "required": true, - } - ] + 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) - for (let [subkey,subkeyval] in Object.entries(tmpsteps)) { - newactions[foundindex].steps.push(tmpsteps[subkey]) - } - - newactions[foundindex].show_steps = true + // Adding webhook to start of it + if (foundindex >= 0) { + const tmpsteps = newactions[foundindex].steps + newactions[foundindex].steps = [ + { + "title": "Configure Webhook", + "type": "webhook", + "required": true, + } + ] - console.log("CHANGED ACTION: ", newactions[foundindex]) - //console.log("Index: ", newactions[foundindex]) + for (let [subkey,subkeyval] in Object.entries(tmpsteps)) { + newactions[foundindex].steps.push(tmpsteps[subkey]) + } + + newactions[foundindex].show_steps = true - continue - } - } + console.log("CHANGED ACTION: ", newactions[foundindex]) + //console.log("Index: ", newactions[foundindex]) + + continue + } + } + } + + if (trigger.status === "running") { + continue; + } + + if ( + trigger.trigger_type === "SUBFLOW" || + trigger.trigger_type === "USERINPUT" + ) { + continue; + } + + requiredTriggers.push(trigger); } + } - if (trigger.status === "running") { - continue; - } + if (requiredTriggers.length === 0 && requiredVariables.length === 0 && newactions.length === 0 && setConfigureWorkflowModalOpen !== undefined) { + setConfigureWorkflowModalOpen(false); + } - if ( - trigger.trigger_type === "SUBFLOW" || - trigger.trigger_type === "USERINPUT" - ) { - continue; - } - - requiredTriggers.push(trigger); - } -} - - if ( - requiredTriggers.length === 0 && - requiredVariables.length === 0 && - newactions.length === 0 - ) { - setConfigureWorkflowModalOpen(false); - } - - setRequiredTriggers(requiredTriggers); - setRequiredVariables(requiredVariables); - setRequiredActions(newactions); + setRequiredTriggers(requiredTriggers); + setRequiredVariables(requiredVariables); + setRequiredActions(newactions); } if (appAuthentication !== undefined && previousAuth !== undefined && appAuthentication.length !== previousAuth.length) { @@ -380,7 +424,7 @@ const ConfigureWorkflow = (props) => { const { trigger } = props return ( - + { {trigger.status !== "running" ? "Start" : "Running"} ) : null} - {/* - - - ) - }} - fullWidth - color="primary" - type={"text"} - placeholder={`New value for ${trigger.name}`} - onChange={(event) => { - console.log("NEW VALUE ON INDEX", trigger.value) - }} - onBlur={(event) => { - //workflow.variables[variable.index] = event.target.value - }} - /> - } - style={{}} - /> - */} ); }; @@ -487,7 +498,7 @@ const ConfigureWorkflow = (props) => { //Name: {variable.name} - {variable.value}. return ( - + @@ -506,13 +517,6 @@ const ConfigureWorkflow = (props) => { borderRadius: 5, }} InputProps={{ - style: { - color: "white", - minHeight: 50, - marginLeft: 5, - maxWidth: "95%", - fontSize: "1em", - }, endAdornment: , }} fullWidth @@ -573,7 +577,7 @@ const ConfigureWorkflow = (props) => { .then((response) => { if (response.status !== 200) { //window.location.pathname = "/search" - //alert.error("Failed to find this app. Is it public?") + //toast("Failed to find this app. Is it public?") } return response.json(); @@ -581,102 +585,326 @@ const ConfigureWorkflow = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Failed to activate the app: "+responseJson.reason); + toast("Failed to activate the app: "+responseJson.reason); } else { - alert.error("Failed to activate the app"); + toast("Failed to activate the app"); } } else { - alert.success("App activated for your organization!"); + toast("App activated for your organization!"); } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; + const AppSectionSelfcontained = (props) => { + const { action } = props; + + const [opened, setOpened] = useState(false); + const [filled, setFilled] = useState(false); + const [submitted, setSubmitted] = useState(false); + const [finalized, setFinalized] = useState(false); + + const [authFields, setAuthFields] = useState([]) + const [sensitiveFields, setSensitiveFields] = useState([]) + + if (authFields.length === 0 && opened === true) { + // Loop through fields of the action + + var newfields = [] + const params = action.action.parameters + + var sensitiveIndexes = [] + var index = 0 + for (let key in params) { + const param = params[key] + + if (param.configuration === true) { + if (param.name.toLowerCase().includes("key") || param.name.toLowerCase().includes("token") || param.name.toLowerCase().includes("password")) { + sensitiveIndexes.push(index) + } + + newfields.push({ + "key": param.name, + "example": param.example === undefined ? "" : param.example, + "value": param.name === "url" ? param.example : "", + }) + + index += 1 + } + } + + if (newfields.length > 0) { + setSensitiveFields(sensitiveIndexes) + setAuthFields(newfields) + } + } + + + const submitLocalAuth = (app, fields) => { + const appAuthData = { + active: true, + app: app, + fields: fields, + label: "Authentication for " + app.name, + usage: [{"workflow_id": workflow.id}], + auto_distribute: true, + } + + + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(appAuthData), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for setting app auth :O!"); + } + + setSubmitted(false) + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + toast("Failed to set app auth: " + responseJson.reason); + } else { + toast("App auth set for app " + app.name.replace("_", " ")); + setFinalized(true) + setOpened(false) + } + }) + .catch((error) => { + setSubmitted(false) + //toast(error.toString()); + console.log("New auth error: ", error.toString()); + }); + } + + var parsedName = action.app_name.replaceAll("_", " "); + if (action.app_name.toLowerCase().endsWith("_api")) { + parsedName = parsedName.substring(0, parsedName.length - 4); + } + + // Remove _basic at the end if it exists + if (parsedName.toLowerCase().endsWith("_basic")) { + parsedName = parsedName.substring(0, parsedName.length - 6); + } + + parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " "); + + return ( + +
+
{ + setOpened(!opened); + }} + > +
+ + {!opened ? : } + + {parsedName} + + {finalized ? "Authenticated" : `Configure ${parsedName}`} + +
+ {filled ? + + : null} +
+ {opened ? +
+ {authFields.map((field, index) => { + var parsedName = field.key + // Remove _basic at the end if it exists + if (parsedName.toLowerCase().endsWith("_basic")) { + parsedName = parsedName.substring(0, parsedName.length - 6); + } + + parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " "); + + return ( +
+ + {parsedName} + + { + event.preventDefault(); + authFields[index].value = event.target.value; + setAuthFields(authFields); + + var allFilled = true; + authFields.forEach((field) => { + if (field.value.length === 0) { + allFilled = false; + } else { + //console.log("Field is not filled: "+field.key) + } + }) + + if (allFilled) { + console.log("Should test the fields, and submit them") + setFilled(true); + } else { + if (filled) { + setFilled(false); + } + } + }} + + endAdornment={ + // Show item that can show field value if password + //field.name.toLowerCase().includes("key") || field.name.toLowerCase().includes("token") || field.name.toLowerCase().includes("password") ? + field.key.toLowerCase().includes("key") || field.key.toLowerCase().includes("token") || field.key.toLowerCase().includes("password") ? + + { + setSensitiveFields(sensitiveFields.filter((item) => item !== index)) + }} + onMouseDown={(event) => { + event.preventDefault(); + }} + > + + + + : null + } + fullWidth + color="primary" + type={sensitiveFields.includes(index) ? "password" : "text"} + placeholder={field.example ? field.example : `Enter your ${field.key}`} + data-lpignore="true" + dataLPIgnore="true" + autocomplete="off" + /> +
+ ) + })} + +
+ : null} +
+
+ ) + } const AppSection = (props) => { const { action } = props; + var parsedName = action.app_name.replaceAll("_", " "); + if (action.app_name.toLowerCase().endsWith("_api")) { + parsedName = parsedName.substring(0, parsedName.length - 4); + } + return ( - {/* - - - {action.app_name} - - - - */} {action.must_authenticate ? - + if (setAuthenticationModalOpen !== undefined) { + setAuthenticationModalOpen(true); + } + }} + > + {action.app_name} + + {action.auth_done ? "Authenticated" : `Authenticate ${action.app_name.replaceAll("_", " ")}`} + + : null} {action.update_version !== action.app_version ? -
: null} @@ -929,10 +1159,10 @@ const ConfigureWorkflow = (props) => {
{clicked === true ? data.steps.map((step, index) => { - var finished = false + var filled = false if (step.type === "activate") { if (data.activation_done === true) { - finished = true + filled = true if (index === activeStep && firstRun === true) { setActiveStep(activeStep+1) @@ -947,10 +1177,10 @@ const ConfigureWorkflow = (props) => { if (step.type === "authenticate") { console.log("AUTH STEP: ", step) if (data.must_authenticate === true ) { - finished = false + filled = false } else { if (data.activation_done === true && data.auth_done === true) { - finished = true + filled = true if (firstRun) { setFinishCount(finishCount+1) @@ -969,7 +1199,7 @@ const ConfigureWorkflow = (props) => { if (exec.execution_argument !== undefined && exec.execution_argument !== null && exec.execution_argument.length > 0 && exec.execution_source === "webhook") { //console.log("Done: ", exec) - finished = true + filled = true if (index === activeStep && firstRun === true) { setActiveStep(activeStep+1) @@ -997,7 +1227,7 @@ const ConfigureWorkflow = (props) => { } return ( - + ) }) : null} @@ -1008,36 +1238,48 @@ const ConfigureWorkflow = (props) => { const topColor = "#f86a3e, #fc3922" return (
-
-
-
- {workflow.name} - - The following configuration makes the workflow ready immediately. + {setConfigureWorkflowModalOpen !== undefined ? +
+ : null} +
+ + + {setConfigureWorkflowModalOpen !== undefined ? + {workflow.name} + : null + } + + + Please configure the following apps for automatic startup of automation: {requiredActions.length > 0 ? ( - - Required Actions - + {setConfigureWorkflowModalOpen !== undefined ? + + Required Actions + + : null} - + {requiredActions.map((data, index) => { return ( -
- {data.steps !== undefined && data.steps !== null && data.show_steps === true ? - - : - - } -
- ) +
+ {data.steps !== undefined && data.steps !== null && data.show_steps === true && setConfigureWorkflowModalOpen !== undefined ? + + : + setConfigureWorkflowModalOpen !== undefined ? + + : + + } +
+ ) })}
) : null} - {requiredVariables.length > 0 ? ( + {setConfigureWorkflowModalOpen !== undefined && requiredVariables.length > 0 ? ( Variables @@ -1050,7 +1292,7 @@ const ConfigureWorkflow = (props) => { ) : null} - {requiredTriggers.length > 0 && showTriggers !== false ? ( + {setConfigureWorkflowModalOpen !== undefined && requiredTriggers.length > 0 && showTriggers !== false ? ( Triggers @@ -1062,52 +1304,52 @@ const ConfigureWorkflow = (props) => { ) : null} - -
- {showFinalizeAnimation ? - finalize workflow animation { - console.log("Img loaded.") + {setConfigureWorkflowModalOpen !== undefined ? +
+ {showFinalizeAnimation ? + finalize workflow animation { + console.log("Img loaded.") + setTimeout(() => { + console.log("Img closing.") + setConfigureWorkflowModalOpen(false); + }, 1250) + + }}/> + : + + {/* + + */} + - */} - - - } -
-
+ } else { + } + }, 1000) + }} + > + Finalize + + + } +
+ : null} +
); }; diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx index 45e1961f..56b80786 100644 --- a/frontend/src/components/CreatorGrid.jsx +++ b/frontend/src/components/CreatorGrid.jsx @@ -1,10 +1,16 @@ import React, { useEffect, useState } from 'react'; import ReactGA from 'react-ga4'; -import { useTheme } from '@material-ui/core/styles'; import {Link} from 'react-router-dom'; +import theme from '../theme.jsx'; +import { removeQuery } from '../components/ScrollToTop.jsx'; -import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; +import { + SkipNext as SkipNextIcon, + SkipPrevious as SkipPreviousIcon, + PlayArrow as PlayArrowIcon, + VerifiedUser as VerifiedUserIcon, + Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material'; import algoliasearch from 'algoliasearch/lite'; import { InstantSearch, Configure, connectSearchBox, connectHits } from 'react-instantsearch-dom'; @@ -24,27 +30,18 @@ import { Zoom, CardMedia, CardActionArea, -} from '@material-ui/core'; +} from '@mui/material'; import { Avatar, AvatarGroup, } from "@mui/material" -import { - SkipNext as SkipNextIcon, - SkipPrevious as SkipPreviousIcon, - PlayArrow as PlayArrowIcon, - VerifiedUser as VerifiedUserIcon, -} from "@material-ui/icons"; - - const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const CreatorGrid = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs } = props const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs - const theme = useTheme(); //const [apps, setApps] = React.useState([]); //const [filteredApps, setFilteredApps] = React.useState([]); const [formMail, setFormMail] = React.useState(""); @@ -85,7 +82,7 @@ const CreatorGrid = props => { .then(response => { if (response.success === true) { setFormMessage(response.reason) - //alert.info("Thanks for submitting!") + //toast("Thanks for submitting!") } else { setFormMessage(errorMessage) } @@ -101,20 +98,21 @@ const CreatorGrid = props => { // value={currentRefinement} const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { - useEffect(() => { - if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { - const urlSearchParams = new URLSearchParams(window.location.search) - const params = Object.fromEntries(urlSearchParams.entries()) - const foundQuery = params["q"] - if (foundQuery !== null && foundQuery !== undefined) { - refine(foundQuery) - } + var defaultSearch = "" + if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { + const urlSearchParams = new URLSearchParams(window.location.search) + const params = Object.fromEntries(urlSearchParams.entries()) + const foundQuery = params["q"] + if (foundQuery !== null && foundQuery !== undefined) { + refine(foundQuery) + defaultSearch = foundQuery } - }, []) + } return ( { autoComplete='off' type="search" color="primary" - value={currentRefinement} placeholder="Find Creators..." id="shuffle_search_field" onChange={(event) => { + removeQuery("q") refine(event.currentTarget.value) }} /> diff --git a/frontend/src/components/DocsGrid.jsx b/frontend/src/components/DocsGrid.jsx index ea20cd0e..06469251 100644 --- a/frontend/src/components/DocsGrid.jsx +++ b/frontend/src/components/DocsGrid.jsx @@ -1,10 +1,11 @@ import React, {useEffect, useState} from 'react'; +import theme from '../theme.jsx'; import ReactGA from 'react-ga4'; -import { useTheme } from '@material-ui/core/styles'; import {Link} from 'react-router-dom'; +import { removeQuery } from '../components/ScrollToTop.jsx'; -import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; +import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon, Close as CloseIcon, Folder as FolderIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material'; import aa from 'search-insights' import algoliasearch from 'algoliasearch/lite'; @@ -24,16 +25,15 @@ import { ListItem, ListItemAvatar, ListItemText, -} from '@material-ui/core'; +} from '@mui/material'; -import {Close as CloseIcon, Folder as FolderIcon, Polymer as PolymerIcon, LibraryBooks as LibraryBooksIcon} from '@material-ui/icons' + const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const DocsGrid = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, } = props const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 2 : parsedXs - const theme = useTheme(); //const [apps, setApps] = React.useState([]); //const [filteredApps, setFilteredApps] = React.useState([]); const [formMail, setFormMail] = React.useState(""); @@ -70,7 +70,7 @@ const DocsGrid = props => { .then(response => { if (response.success === true) { setFormMessage(response.reason) - //alert.info("Thanks for submitting!") + //toast("Thanks for submitting!") } else { setFormMessage(errorMessage) } @@ -85,21 +85,22 @@ const DocsGrid = props => { } const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { - useEffect(() => { - if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { - const urlSearchParams = new URLSearchParams(window.location.search) - const params = Object.fromEntries(urlSearchParams.entries()) - const foundQuery = params["q"] - if (foundQuery !== null && foundQuery !== undefined) { - console.log("Got query: ", foundQuery) - refine(foundQuery) - } + var defaultSearch = "" + if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { + const urlSearchParams = new URLSearchParams(window.location.search) + const params = Object.fromEntries(urlSearchParams.entries()) + const foundQuery = params["q"] + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery) + refine(foundQuery) + defaultSearch = foundQuery } - }, []) + } return ( { autoComplete='off' type="search" color="primary" - defaultValue={currentRefinement} placeholder="Search our Documentation..." id="shuffle_search_field" onChange={(event) => { + removeQuery("q") refine(event.currentTarget.value) }} limit={5} @@ -182,7 +183,7 @@ const DocsGrid = props => { //const secondaryText = data.data !== undefined ? data.data.slice(0, 100)+"..." : "" const secondaryText = data.data !== undefined ? data.data.slice(0, 100)+"..." : "" - const baseImage = + const baseImage = const avatar = data.image_url === undefined ? baseImage : diff --git a/frontend/src/components/Dropzone.jsx b/frontend/src/components/Dropzone.jsx index 6ea9f0de..35b5eb01 100755 --- a/frontend/src/components/Dropzone.jsx +++ b/frontend/src/components/Dropzone.jsx @@ -1,6 +1,8 @@ import React, { useRef, useState } from "react"; import { useEffect } from "react"; -import BackupIcon from "@material-ui/icons/Backup"; +import { + Backup as BackupIcon +} from "@mui/icons-material"; const dragOverStyle = { backgroundColor: "rgba(0,0,0,0.8)", @@ -54,7 +56,11 @@ const Dropzone = ({ children, style, onDrop }) => { }; useEffect(() => { - if (!dropzoneRef.current) return; + if (dropzoneRef === null || dropzoneRef === undefined || dropzoneRef.current === null || dropzoneRef.current === undefined) { + return + } + + // Check if event listene exists for dropzoneRef.current dropzoneRef.current.addEventListener("dragover", handleDragOver); dropzoneRef.current.addEventListener("dragenter", handleDragEnter); @@ -62,6 +68,10 @@ const Dropzone = ({ children, style, onDrop }) => { dropzoneRef.current.addEventListener("drop", handleDrop); return () => { + if (dropzoneRef.current === null || dropzoneRef.current === undefined) { + return + } + dropzoneRef.current.removeEventListener("dragover", handleDragOver); dropzoneRef.current.removeEventListener("dragenter", handleDragEnter); dropzoneRef.current.removeEventListener("dragleave", handleDragLeave); diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index 22a9d9ea..81dacd85 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -1,8 +1,11 @@ import React, { useEffect, useContext } from "react"; import theme from '../theme.jsx'; import { isMobile } from "react-device-detect" -import ChipInput from "material-ui-chip-input"; +import { MuiChipsInput } from "mui-chips-input"; import UsecaseSearch from "../components/UsecaseSearch.jsx" +import WorkflowGrid from "../components/WorkflowGrid.jsx" +import dayjs from 'dayjs'; +import WorkflowTemplatePopup from "./WorkflowTemplatePopup.jsx"; import { Badge, @@ -25,6 +28,7 @@ import { Typography, Zoom, CircularProgress, + Drawer, Dialog, DialogTitle, DialogActions, @@ -37,28 +41,38 @@ import { FormControl, FormLabel, -} from "@material-ui/core"; +} from "@mui/material"; + +import { + DatePicker, + LocalizationProvider, +} from '@mui/x-date-pickers' + +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' import { ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, Publish as PublishIcon, OpenInNew as OpenInNewIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; const EditWorkflow = (props) => { - const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, } = props + const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, } = props + + const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove const [submitLoading, setSubmitLoading] = React.useState(false); const [showMoreClicked, setShowMoreClicked] = React.useState(false); - const [innerWorkflow, setInnerWorkflow] = React.useState(workflow) - const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove + const [innerWorkflow, setInnerWorkflow] = React.useState(workflow) + const [newWorkflowTags, setNewWorkflowTags] = React.useState(workflow.tags !== undefined && workflow.tags !== null ? JSON.parse(JSON.stringify(workflow.tags)) : []) + const [description, setDescription] = React.useState(workflow.description !== undefined ? workflow.description : "") + const [selectedUsecases, setSelectedUsecases] = React.useState(workflow.usecase_ids !== undefined && workflow.usecase_ids !== null ? JSON.parse(JSON.stringify(workflow.usecase_ids)) : []); const [foundWorkflowId, setFoundWorkflowId] = React.useState("") const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "") - const [description, setDescription] = React.useState(workflow.description !== undefined ? workflow.description : "") - + 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')) // Gets the generated workflow const getGeneratedWorkflow = (workflow_id) => { @@ -114,7 +128,7 @@ const EditWorkflow = (props) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Get workflow error: ", error.toString()); }) } @@ -130,73 +144,76 @@ const EditWorkflow = (props) => { return null } - const newWorkflow = isEditing === true ? false : true + const newWorkflow = isEditing === true ? false : true + const priority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) + console.log("PRIO: ", priority) var upload = ""; var total_count = 0 return ( - { setModalOpen(false); }} PaperProps={{ style: { - backgroundColor: theme.palette.surfaceColor, color: "white", - minWidth: isMobile ? "90%" : 550, - maxWidth: isMobile ? "90%" : 550, - minHeight: 400, + minWidth: isMobile ? "90%" : 650, + maxWidth: isMobile ? "90%" : 650, + minHeight: 400, + paddingTop: 25, + paddingLeft: 50, //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, }, }} > -
+
-
- - {newWorkflow ? "New" : "Editing"} workflow - - {newWorkflow === true ? null : -
- - - - - -
- } +
+ + {newWorkflow ? "New" : "Editing"} workflow + + {newWorkflow === true ? null : +
+ + + + +
- - 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 ? -
- - - -
- : null} + } +
+ + 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 ? +
+ + + +
+ : null}
{/*newWorkflow === true ?
@@ -211,12 +228,12 @@ const EditWorkflow = (props) => {
- -
+ +
{ - setName(event.target.value) - }} + onChange={(event) => { + setName(event.target.value) + }} InputProps={{ style: { color: "white", @@ -231,27 +248,29 @@ 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 - /> +
+ { + setDescription(event.target.value) + }} + InputProps={{ + style: { + color: "white", + }, + }} + maxRows={4} + color="primary" + defaultValue={innerWorkflow.description} + placeholder="Description" + multiline + label="Description" + margin="dense" + fullWidth + /> +
- { color="primary" fullWidth value={newWorkflowTags} + onChange={(chip) => { + console.log("Chip: ", chip) + //newWorkflowTags.push(chip); + setNewWorkflowTags(chip); + }} onAdd={(chip) => { newWorkflowTags.push(chip); setNewWorkflowTags(newWorkflowTags); }} onDelete={(chip, index) => { + console.log("Deleting: ", chip, index) newWorkflowTags.splice(index, 1); setNewWorkflowTags(newWorkflowTags); + setUpdate(Math.random()); }} /> {usecases !== null && usecases !== undefined && usecases.length > 0 ? @@ -328,26 +354,41 @@ const EditWorkflow = (props) => { {showMoreClicked === true ? +
+ + Status + { + console.log("Data: ", e.target.value) + + innerWorkflow.workflow_type = e.target.value + setInnerWorkflow(innerWorkflow) + }} + > + } label="Test" /> + } label="Production" /> - - Status - { - console.log("Data: ", e.target.value) - - innerWorkflow.workflow_type = e.target.value - setInnerWorkflow(innerWorkflow) + + + + - } label="Test" /> - } label="Production" /> - - - + value={dueDate} + label="Due Date" + format="YYYY-MM-DD" + onChange={(newValue) => { + setDueDate(newValue) + }} + /> + +
@@ -430,38 +471,28 @@ const EditWorkflow = (props) => { : null} - { - setShowMoreClicked(!showMoreClicked); - }} - > - {showMoreClicked ? : } - - -
- {/*newWorkflow === true ? -
- -
- : null*/} + { + setShowMoreClicked(!showMoreClicked); + }} + > + {showMoreClicked ? : } + + +
+ - + + + {newWorkflow === true ? + + + Relevant Workflows + + + {priority === null || priority === undefined ? null : +
+ 2 ? priority.description.split("&")[0] : ""} + img1={priority.description.split("&").length > 2 ? priority.description.split("&")[1] : ""} + + dstapp={priority.description.split("&").length > 3 ? priority.description.split("&")[2] : ""} + img2={priority.description.split("&").length > 3 ? priority.description.split("&")[3] : ""} + title={priority.name} + description={priority.description.split("&").length > 4 ? priority.description.split("&")[4] : ""} + + apps={apps} + /> +
+ } + +
+ : null} + + {newWorkflow === true && name.length > 2 ? +
+ +
+ : null} -
+ ) } diff --git a/frontend/src/components/ExploreWorkflow.jsx b/frontend/src/components/ExploreWorkflow.jsx new file mode 100644 index 00000000..95b464e7 --- /dev/null +++ b/frontend/src/components/ExploreWorkflow.jsx @@ -0,0 +1,382 @@ +import React, { useState, useEffect } from "react"; +import ReactGA from 'react-ga4'; + +import 'react-alice-carousel/lib/alice-carousel.css'; +import TrendingFlatIcon from '@mui/icons-material/TrendingFlat'; +import theme from '../theme.jsx'; +import CheckBoxSharpIcon from '@mui/icons-material/CheckBoxSharp'; +import { findSpecificApp } from "../components/AppFramework.jsx" +import { + Checkbox, + Button, + Collapse, + IconButton, + FormGroup, + FormControl, + InputLabel, + FormLabel, + FormControlLabel, + Select, + MenuItem, + Grid, + Paper, + Typography, + TextField, + Zoom, + List, + ListItem, + ListItemText, + Divider, + Tooltip, + Chip, + ButtonGroup, + Dialog, + DialogTitle, + DialogActions, + DialogContent, +} from "@mui/material"; + +import { useNavigate, Link } from "react-router-dom"; +import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx"; + +const ExploreWorkflow = (props) => { + const { userdata, globalUrl, appFramework } = props + const [activeUsecases, setActiveUsecases] = useState(0); + const [modalOpen, setModalOpen] = React.useState(false); + const [suggestedUsecases, setSuggestedUsecases] = useState([]) + const [usecasesSet, setUsecasesSet] = useState(false) + const [apps, setApps] = useState([]) + const sizing = 475 + + let navigate = useNavigate(); + + const imagestyle = { + height: 40, + borderRadius: 40, + //border: "2px solid rgba(255,255,255,0.3)", + } + + const loadApps = () => { + fetch(`${globalUrl}/api/v1/apps`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + return response.json(); + }) + .then((responseJson) => { + if (responseJson === null) { + console.log("null-response from server") + const pretend_apps = [{ + "name": "TBD", + "app_name": "TBD", + "app_version": "TBD", + "description": "TBD", + "version": "TBD", + "large_image": "", + }] + + setApps(pretend_apps) + return + } + + if (responseJson.success === false) { + console.log("error loading apps: ", responseJson) + return + } + + setApps(responseJson); + }) + .catch((error) => { + console.log("App loading error: " + error.toString()); + }) + } + + // Find priorities in userdata.priorities and check if the item.type === "usecase" + // If so, set the item.isActive to true + if (usecasesSet === false && userdata.priorities !== undefined && userdata.priorities !== null && userdata.priorities.length > 0 && suggestedUsecases.length === 0) { + + var tmpUsecases = [] + for (let i = 0; i < userdata.priorities.length; i++) { + if (userdata.priorities[i].type !== "usecase" || userdata.priorities[i].active === false) { + continue + } + + const descsplit = userdata.priorities[i].description.split("&") + if (descsplit.length === 5) { + console.log("descsplit: ", descsplit) + if (descsplit[1] === "") { + const item = findSpecificApp(appFramework, descsplit[0]) + console.log("item: ", item) + if (item !== null) { + descsplit[1] = item.large_image + } + } + + if (descsplit[3] === "") { + const item = findSpecificApp(appFramework, descsplit[2]) + console.log("item: ", item) + if (item !== null) { + descsplit[3] = item.large_image + } + } + + console.log("descsplit: ", descsplit) + userdata.priorities[i].description = descsplit.join("&") + } + + tmpUsecases.push(userdata.priorities[i]) + } + + console.log("USECASES: ", tmpUsecases) + if (tmpUsecases.length === 0) { + console.log("Add some random ones, as everything is done") + + const comms = findSpecificApp(appFramework, "communication") + const cases = findSpecificApp(appFramework, "cases") + const edr = findSpecificApp(appFramework, "edr") + const siem = findSpecificApp(appFramework, "siem") + + tmpUsecases = [{ + "name": "Suggested Usecase: Email management", + "description": comms.name+"&"+comms.large_image+"&"+cases.name+"&"+cases.large_image, + "type": "usecase", + "url": "/usecases?selected_object=Email management", + "severity": 0, + "active": false, + },{ + "name": "Suggested Usecase: EDR to ticket", + "description": edr.name+"&"+edr.large_image+"&"+cases.name+"&"+cases.large_image, + "type": "usecase", + "url": "/usecases?selected_object=EDR to ticket", + "severity": 0, + "active": false, + },{ + "name": "Suggested Usecase: SIEM to ticket", + "description": siem.name+"&"+siem.large_image+"&"+cases.name+"&"+cases.large_image, + "type": "usecase", + "url": "/usecases?selected_object=SIEM to ticket", + "severity": 0, + "active": false, + } + ] + } + + setSuggestedUsecases(tmpUsecases) + setUsecasesSet(true) + loadApps() + } + + const modalView = ( + // console.log("key:", dataValue.key), + //console.log("value:",dataValue.value), + { + setModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + +
+ + Sign Up +
+ + Setup +
+ + Explore +
+ + + Here’s a recommended workflow: + + {/*
+
+
+ + { + slidePrev() + }} + > + + + +
+ +
+ + { + slideNext() + }} + > + + + +
+
+
*/} + + + + +
+ ); + + + return ( +
+ {modalView} + + Start using workflows + + + Based on what you selected workflows, here are our recommendations! You will see more of these later. + + +
+
+
+ + {suggestedUsecases.length === 0 && usecasesSet ? + + All Workflows are already added for your current apps! + + : + suggestedUsecases.map((priority, index) => { + + const srcapp = priority.description.split("&")[0] + var image1 = priority.description.split("&")[1] + var image2 = "" + var dstapp = "" + if (priority.description.split("&").length > 3) { + dstapp = priority.description.split("&")[2] + image2 = priority.description.split("&")[3] + } + + const name = priority.name.replace("Suggested Usecase: ", "") + + var description = "" + if (priority.description.split("&").length > 4) { + description = priority.description[4] + } + + // FIXME: Should have a proper description + description = "" + + return ( + + ) + })} + +
+
+ + + +
+ + + Explore usecases + + +
+
+
+
+
+ ) +} +export default ExploreWorkflow diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index a3f4e520..75553621 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import { toast } from 'react-toastify'; import { IconButton, @@ -15,7 +16,7 @@ import { Divider, Select, MenuItem, -} from "@material-ui/core"; +} from "@mui/material"; import { OpenInNew as OpenInNewIcon, @@ -27,9 +28,9 @@ import { Publish as PublishIcon, Clear as ClearIcon, Add as AddIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; -import { useAlert } from "react-alert"; +//import { useAlert import Dropzone from "../components/Dropzone.jsx"; import CodeEditor from "../components/ShuffleCodeEditor.jsx"; import theme from "../theme.jsx"; @@ -45,7 +46,7 @@ const Files = (props) => { const [openEditor, setOpenEditor] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false); - const alert = useAlert(); + //const alert = useAlert(); const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log"] var upload = ""; @@ -113,7 +114,7 @@ const Files = (props) => { } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -139,12 +140,12 @@ const Files = (props) => { }) .then((responseJson) => { if (responseJson.success) { - alert.info("Successfully deleted file " + file.name); + toast("Successfully deleted file " + file.name); } else if ( responseJson.reason !== undefined && responseJson.reason !== null ) { - alert.error("Failed to delete file: " + responseJson.reason); + toast("Failed to delete file: " + responseJson.reason); } setTimeout(() => { getFiles(); @@ -153,7 +154,7 @@ const Files = (props) => { console.log(responseJson); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -178,7 +179,7 @@ const Files = (props) => { // console.log("respdata type ->", typeof(respdata)); if (respdata.length === 0) { - alert.error("Failed getting file. Is it deleted?"); + toast("Failed getting file. Is it deleted?"); return; } return respdata @@ -189,7 +190,7 @@ const Files = (props) => { //console.log("filecontent state ",fileContent); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -210,7 +211,7 @@ const Files = (props) => { }) .then((respdata) => { if (respdata.length === 0) { - alert.error("Failed getting file. Is it deleted?"); + toast("Failed getting file. Is it deleted?"); return; } @@ -249,7 +250,7 @@ const Files = (props) => { //setSchedules(responseJson) }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -291,11 +292,11 @@ const Files = (props) => { if (responseJson.success === true) { handleFileUpload(responseJson.id, file); } else { - alert.error("Failed to upload file ", filename); + toast("Failed to upload file ", filename); } }) .catch((error) => { - alert.error("Failed to upload file ", filename); + toast("Failed to upload file ", filename); console.log(error.toString()); }); }; @@ -312,7 +313,7 @@ const Files = (props) => { .then((response) => { if (response.status !== 200 && response.status !== 201) { console.log("Status not 200 for apps :O!"); - alert.error("File was created, but failed to upload."); + toast("File was created, but failed to upload."); return; } @@ -323,7 +324,7 @@ const Files = (props) => { //setFiles(responseJson) }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -364,7 +365,7 @@ const Files = (props) => { const files = isDropzone ? e.dataTransfer.files : e.target.files; //const reader = new FileReader(); - //alert.info("Starting fileupload") + //toast("Starting fileupload") uploadFiles(files); }; @@ -681,7 +682,7 @@ const Files = (props) => { @@ -742,7 +743,7 @@ const Files = (props) => { ) { const clipboard = navigator.clipboard; if (clipboard === undefined) { - alert.error( + toast( "Can only copy over HTTPS (port 3443)" ); return; @@ -758,7 +759,7 @@ const Files = (props) => { /* Copy the text inside the text field */ document.execCommand("copy"); - alert.info(file.id + " copied to clipboard"); + toast(file.id + " copied to clipboard"); } }} > diff --git a/frontend/src/components/FooterNew.js b/frontend/src/components/FooterNew.js deleted file mode 100755 index 482c45b7..00000000 --- a/frontend/src/components/FooterNew.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from "react"; - -//import List from '@material-ui/core/List'; -//import ListItem from '@material-ui/core/ListItem'; - -//borderTop: "1px solid #385F71" -const FooterStyle = { - right: "0", - left: "0", - bottom: "0", - height: "130px", - backgroundColor: "rgba(15, 14, 31, 1)", -}; - -const FooterInfo = { - maxWidth: "1150px", - minWidth: "768px", - textAlign: "center", - margin: "auto", -}; - -const hrefStyle = { - color: "#bdbdbd", - textDecoration: "none", -}; - -const Footer = (props) => { - return ( -
-
- -
-
- ); -}; - -const Box = (props) => { - return ( - - ); -}; - -export default Footer; diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js deleted file mode 100755 index 0934f2e6..00000000 --- a/frontend/src/components/Header.js +++ /dev/null @@ -1,883 +0,0 @@ -import React, { useState } from "react"; -import { BrowserView, MobileView } from "react-device-detect"; - -import { Link } from "react-router-dom"; - -import { useTheme } from "@material-ui/core/styles"; - -import { - Chip, - Badge, - Typography, - Paper, - Tooltip, - List, - Avatar, - Menu, - ListItem, - MenuItem, - Select, - Button, - IconButton, - Grid, -} from "@material-ui/core"; - -import { - MeetingRoom as MeetingRoomIcon, - Settings as SettingsIcon, - Notifications as NotificationsIcon, - Home as HomeIcon, - Polymer as PolymerIcon, - Apps as AppsIcon, - Description as DescriptionIcon, - HelpOutline as HelpOutlineIcon, -} from "@material-ui/icons"; - -import { - Analytics as AnalyticsIcon, - Lightbulb as LightbulbIcon, -} from "@mui/icons-material"; -//import LogoutIcon from '@mui/icons-material/Logout'; -import { useAlert } from "react-alert"; -import SearchField from '../components/Searchfield' - -const hoverColor = "#f85a3e"; -const hoverOutColor = "#e8eaf6"; - -const Header = (props) => { - const { - globalUrl, - setNotifications, - notifications, - isLoggedIn, - removeCookie, - homePage, - isLoaded, - userdata, - cookies, - } = props; - const theme = useTheme(); - - 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 [anchorEl, setAnchorEl] = React.useState(null); - const [anchorElAvatar, setAnchorElAvatar] = React.useState(null); - const alert = useAlert(); - - const hrefStyle = { - color: hoverOutColor, - textDecoration: "none", - }; - - const handleClose = () => { - setAnchorEl(null); - setAnchorElAvatar(null); - }; - - const clearNotifications = () => { - // Don't really care about the logout - fetch(`${globalUrl}/api/v1/notifications/clear`, { - credentials: "include", - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } - - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success === true) { - setNotifications([]); - handleClose(); - } else { - alert.error( - "Failed dismissing notifications. Please try again later." - ); - } - }) - .catch((error) => { - console.log("error in notification dismissal: ", error); - //removeCookie("session_token", {path: "/"}) - }); - }; - - const dismissNotification = (alert_id) => { - // Don't really care about the logout - fetch(`${globalUrl}/api/v1/notifications/${alert_id}/markasread`, { - credentials: "include", - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } - - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success === true) { - const newNotifications = notifications.filter( - (data) => data.id !== alert_id - ); - console.log("NEW NOTIFICATIONS: ", newNotifications); - setNotifications(newNotifications); - } else { - alert.error( - "Failed dismissing notification. Please try again later." - ); - } - }) - .catch((error) => { - console.log("error in notification dismissal: ", error); - //removeCookie("session_token", {path: "/"}) - }); - }; - - // DEBUG HERE - const handleClickLogout = () => { - console.log("COOKIES: ", cookies, "Remover: ", removeCookie); - // Don't really care about the logout - fetch(globalUrl + "/api/v1/logout", { - credentials: "include", - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }) - .then(() => { - // Log out anyway - //cookies.remove("session_token") - //window.location.pathname = "/" - console.log("Should've logged out"); - removeCookie("session_token", { path: "/" }); - removeCookie("session_token", { path: "/workflows" }); - window.location.reload(); - }) - .catch((error) => { - console.log("Error in logout: ", error); - removeCookie("session_token", { path: "/" }); - window.location.reload(); - //removeCookie("session_token", {path: "/"}) - }); - }; - - const handleClickChangeOrg = (orgId) => { - // Don't really care about the logout - //name: org.name, - //orgId = "asd" - const data = { - org_id: orgId, - }; - - localStorage.setItem("getting_started_sidebar", "open"); - - fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { - 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) { - setTimeout(() => { - window.location.reload(); - }, 2000); - alert.success( - "Successfully changed active organization - refreshing!" - ); - } else { - alert.error("Failed changing org: ", responseJson.reason); - } - }) - .catch((error) => { - console.log("error changing: ", error); - //removeCookie("session_token", {path: "/"}) - }); - }; - - // 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 handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - - const chipStyle = { - backgroundColor: "#3d3f43", - height: 30, - marginRight: 5, - paddingLeft: 5, - paddingRight: 5, - height: 28, - cursor: "pointer", - borderColor: "#3d3f43", - color: "white", - }; - - const notificationWidth = 350; - const NotificationItem = (props) => { - const { data } = props; - - return ( - - {/* - {new Date(data.updated_at).toISOString()} - */} - {data.reference_url !== undefined && - data.reference_url !== null && - data.reference_url.length > 0 ? ( - - {data.title} - - ) : ( - {data.title} - )} - - {data.image !== undefined && - data.image !== null && - data.image.length > 0 ? ( - {data.title} - ) : null} - {data.description} - {/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ? - data.tags.map((tag, index) => { - return ( - { - }} - variant="outlined" - color="primary" - /> - ) - }) - : null */} - {data.read === false ? ( - - ) : null} - - ); - }; - - const notificationMenu = ( - - { - setAnchorEl(event.currentTarget); - }} - > - - - - - { - handleClose(); - }} - > - -
- - Your Notifications ({notifications.length}) - - {notifications.length > 1 ? ( - - ) : null} -
- - Notifications are made by Shuffle to help you discover issues or - improvements. - -
- {notifications.map((data, index) => { - return ; - })} -
-
- ); - - // Should be based on some path - const avatarMenu = ( - - { - setAnchorElAvatar(event.currentTarget); - }} - > - - - { - handleClose(); - }} - > - { - event.preventDefault(); - handleClose(); - }} - > - - About - - - { - event.preventDefault(); - handleClose(); - }} - > - - Get Started - - - { - event.preventDefault(); - handleClose(); - }} - > - - Use Cases - - - { - event.preventDefault(); - handleClose(); - }} - > - - Settings - - - { - event.preventDefault(); - handleClose(); - handleClickLogout(); - }} - > -  Logout - - - - ); - - // Handle top bar or something - const logoCheck = !homePage ? null : null; - //
- const loginTextBrowser = !isLoggedIn ? ( -
- - - -
- About -
- -
-
- {!isLoaded ? null : - userdata.chat_disabled === true ? null : -
- -
- } -
- - - -
- Login -
- -
-
-
-
- ) : ( -
-
- - - -
- - Workflows -
- -
- - -
- - Apps -
- -
- {/* - - -
Dashboard
- -
- */} - - -
- - Docs -
- -
- {/* - - -
- - Pricing -
- -
- */} - {/* - - -
Configure
- -
- */} -
-
- {!isLoaded ? null : - userdata.chat_disabled === true ? null : -
- -
- } -
- {avatarMenu} - {notificationMenu} - {userdata === undefined || - userdata.admin === undefined || - userdata.admin === null || - !userdata.admin ? null : ( - - - - )} - {userdata === undefined || - userdata.orgs === undefined || - userdata.orgs === null || - userdata.orgs.length <= 1 ? null : ( - - )} -
-
- ); - - //console.log("USR: ", userdata.orgs) - - const loginTextMobile = !isLoggedIn ? ( -
- - - -
- - - - - -
- -
- - -
- About -
- -
-
-
- ) : ( -
-
- - - -
- Shuffle -
- -
- - -
- Workflows -
- -
- - -
- Apps -
- -
- {/* - - -
Configure
- -
- */} -
-
-
- {avatarMenu} -
-
- ); - - // - const loadedCheck = ( -
- {loginTextBrowser} - {loginTextMobile} -
- ); - //
- return ( -
- {loadedCheck} -
- ); -}; - -export default Header; diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 975df6b7..d3137674 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -1,8 +1,9 @@ import React, {useState} from 'react'; +import { toast } from 'react-toastify'; +import theme from '../theme.jsx'; import {BrowserView, MobileView} from "react-device-detect"; -import { useTheme } from '@material-ui/core/styles'; -import {Link} from 'react-router-dom'; +import { useNavigate, Link } from "react-router-dom"; import ReactGA from 'react-ga4'; import { @@ -21,7 +22,7 @@ import { IconButton, Divider, LinearProgress, -} from '@material-ui/core' +} from '@mui/material' import { MeetingRoom as MeetingRoomIcon, @@ -29,19 +30,20 @@ import { Settings as SettingsIcon, Notifications as NotificationsIcon, Home as HomeIcon, - Polymer as PolymerIcon, Apps as AppsIcon, Description as DescriptionIcon, EmojiObjects as EmojiObjectsIcon, Business as BusinessIcon, -} from '@material-ui/icons'; + + Polyline as PolylineIcon, +} from '@mui/icons-material'; import { Analytics as AnalyticsIcon, Lightbulb as LightbulbIcon, } from "@mui/icons-material"; -import { useAlert } from "react-alert"; +//import { useAlert import SearchField from '../components/Searchfield.jsx' const hoverColor = "#f85a3e" @@ -49,8 +51,8 @@ const hoverOutColor = "#e8eaf6" const Header = props => { const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, homePage, userdata, serverside, } = props; - const theme = useTheme(); - const alert = useAlert() + //const theme = useTheme(); + //const alert = useAlert() const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); @@ -61,6 +63,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho const [anchorEl, setAnchorEl] = React.useState(null); const [anchorElAvatar, setAnchorElAvatar] = React.useState(null); const [subAnchorEl, setSubAnchorEl] = React.useState(null); + let navigate = useNavigate(); const handleClick = (event) => { @@ -99,7 +102,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho setNotifications([]) handleClose() } else { - alert.error("Failed dismissing notifications. Please try again later.") + toast("Failed dismissing notifications. Please try again later.") } }) .catch(error => { @@ -129,7 +132,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho console.log("NEW NOTIFICATIONS: ", newNotifications) setNotifications(newNotifications) } else { - alert.error("Failed dismissing notification. Please try again later.") + toast("Failed dismissing notification. Please try again later.") } }) .catch(error => { @@ -209,7 +212,8 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho const notificationWidth = 300 const imagesize = 22; - const boxColor = "#86c142"; + const boxColor = "#86c142"; + const NotificationItem = (props) => { const {data} = props @@ -260,7 +264,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho {data.reference_url !== undefined && data.reference_url !== null && data.reference_url.length > 0 ? - {data.title} + {data.title} ({data.amount}) : @@ -274,7 +278,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho : null } - + {data.description} {/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ? @@ -404,9 +408,9 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho setTimeout(() => { window.location.reload() }, 2000) - alert.success("Successfully changed active organization - refreshing!") + toast("Successfully changed active organization - refreshing!") } else { - alert.error("Failed changing org: ", responseJson.reason) + toast("Failed changing org: ", responseJson.reason) } }) .catch(error => { @@ -518,6 +522,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho } // Handle top bar or something + const defaultTop = isCloud ? 0 : 7 const loginTextBrowser = !isLoggedIn ?
@@ -668,7 +673,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
: -
+
@@ -690,9 +695,9 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
{/* - + */} - Workflows + Workflows
@@ -702,7 +707,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho {/* */} - Apps + Apps
@@ -719,7 +724,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho {/* */} - Docs + Docs
@@ -793,8 +798,6 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho : null*/} - - {userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null : @@ -811,7 +814,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho }} MenuProps={{ style: { - zIndex: 10002, + zIndex: 15000, }, }} style={{ @@ -885,7 +888,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
- {regiontag} {image} {data.name} + {isCloud?{regiontag}:null} {image} {data.name}
@@ -895,13 +898,32 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho } + {/* Show on cloud, if not suborg and if not customer/pov/internal */} + {isCloud && (userdata.org_status === undefined || userdata.org_status === null || userdata.org_status.length === 0) ? + + + + + + : null} + {userdata === undefined || userdata.app_execution_limit === undefined || userdata.app_execution_usage === undefined || userdata.app_execution_usage < 1000 ? null : -
{ +
= 0.9 ? "#f86a3e" : null, }} onClick={() => { + console.log(userdata.appe_execution_usage/userdata.app_execution_limit) if (window.drift !== undefined) { window.drift.api.startInteraction({ interactionId: 326905 }) + navigate("/pricing") } else { console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) } @@ -1011,16 +1033,16 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho const loadedCheck =
- {loginTextBrowser} + {loginTextBrowser} - {loginTextMobile} + {loginTextMobile}
//
return ( -
- {loadedCheck} +
+ {loadedCheck}
) } diff --git a/frontend/src/components/LandingpageUsecases.jsx b/frontend/src/components/LandingpageUsecases.jsx index 9fd5d016..063e4a6b 100644 --- a/frontend/src/components/LandingpageUsecases.jsx +++ b/frontend/src/components/LandingpageUsecases.jsx @@ -4,7 +4,7 @@ import AppFramework, { usecases } from "../components/AppFramework.jsx"; import {Link} from 'react-router-dom'; import ReactGA from 'react-ga4'; -import { Button, LinearProgress, Typography } from '@material-ui/core'; +import { Button, LinearProgress, Typography } from '@mui/material'; export const securityFramework = [ { diff --git a/frontend/src/components/LoginPopup.js b/frontend/src/components/LoginPopup.js deleted file mode 100755 index 1807ae9a..00000000 --- a/frontend/src/components/LoginPopup.js +++ /dev/null @@ -1,176 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { useState } from "react"; - -import DialogTitle from "@material-ui/core/DialogTitle"; -import Dialog from "@material-ui/core/Dialog"; -import TextField from "@material-ui/core/TextField"; -import Button from "@material-ui/core/Button"; - -const LoginDialog = (props) => { - const { - classes, - onClose, - open, - globalUrl, - isLoggedIn, - setIsLoggedIn, - ...other - } = props; - - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - //const [selectedValue, setSelectedValue] = useState(false); - - // Used to swap from login to register. True = login, false = register - const [loginCheck, setLoginCheck] = useState(true); - - // Error messages etc - const [loginInfo, setLoginInfo] = useState(""); - - const handleValidateForm = () => { - return username.length > 1 && password.length > 8; - }; - - const onSubmit = (e) => { - e.preventDefault(); - - // Just use this one? - var data = - '{"username": "' + username + '", "password": "' + password + '"}'; - var baseurl = globalUrl; - if (loginCheck) { - var url = baseurl + "/login"; - fetch(url, { - method: "POST", - body: data, - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => - response.json().then((responseJson) => { - console.log(responseJson); - //console.log(e) - if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]); - } else { - setLoginInfo("Successful login :)"); - onClose(); - setIsLoggedIn(true); - } - }) - ) - .catch((error) => { - setLoginInfo("Error in userdata"); - }); - } else { - url = baseurl + "/register"; - fetch(url, { - method: "POST", - body: data, - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]); - } else { - setLoginInfo("Successful register :)"); - onClose(); - setIsLoggedIn(true); - } - }) - ) - .catch((error) => { - setLoginInfo("Error in userdata"); - }); - } - }; - - const onChangeUser = (e) => { - setUsername(e.target.value); - }; - - const onChangePass = (e) => { - setPassword(e.target.value); - }; - - const onClickRegister = () => { - setLoginCheck(!loginCheck); - }; - - //var loginChange = loginCheck ? (

Want to register? Click here.

) : (

Go back to login? Click here.

); - var formtitle = loginCheck ?
Login
:
Register
; - var formButton = loginCheck ? ( -
Click to Register
- ) : ( -
Click to Login
- ); - - return ( - - {formtitle} - - Username -
- -
- Password -
- -
-
- - - -
- {loginInfo} - -
- -
-
- ); -}; - -export default LoginDialog; diff --git a/frontend/src/components/NestedMenu.jsx b/frontend/src/components/NestedMenu.jsx deleted file mode 100755 index 35d770a5..00000000 --- a/frontend/src/components/NestedMenu.jsx +++ /dev/null @@ -1,213 +0,0 @@ -import React, { useState, useRef, useImperativeHandle } from "react"; -import { makeStyles } from "@material-ui/core/styles"; -import Menu, { MenuProps } from "@material-ui/core/Menu"; -import MenuItem, { MenuItemProps } from "@material-ui/core/MenuItem"; -import ArrowRight from "@material-ui/icons/ArrowRight"; -import clsx from "clsx"; - -// - -//export interface NestedMenuItemProps { -// /** -// * Open state of parent ``, used to close decendent menus when the -// * root menu is closed. -// */ -// parentMenuOpen: boolean; -// /** -// * Component for the container element. -// * @default 'div' -// */ -// component: React.ElementType; -// /** -// * Effectively becomes the `children` prop passed to the `` -// * element. -// */ -// label: React.ReactNode; -// /** -// * @default -// */ -// rightIcon: React.ReactNode; -// /** -// * Props passed to container element. -// */ -// ContainerProps: React.HTMLAttributes; -// // &React.RefAttributes -// /** -// * Props passed to sub `` element -// */ -// MenuProps: Omit; -// /** -// * @see https://material-ui.com/api/list-item/ -// */ -// button: true; -//} - -const TRANSPARENT = "rgba(0,0,0,0)"; -const useMenuItemStyles = makeStyles((theme) => ({ - root: (props: any) => ({ - backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT, - }), -})); - -/** - * Use as a drop-in replacement for `` when you need to add cascading - * menu elements as children to this component. - */ -//const NestedMenuItem = React.forwardRef( -const NestedMenuItem = (props, ref) => { - console.log(props, ref); - //function NestedMenuItem(props, ref) { - const { - parentMenuOpen, - component = "div", - label, - rightIcon = , - children, - className, - tabIndex: tabIndexProp, - MenuProps = {}, - ContainerProps: ContainerPropsProp = {}, - ...MenuItemProps - } = props; - - const [isSubMenuOpen, setIsSubMenuOpen] = useState(false); - - const { ref: containerRefProp, ...ContainerProps } = ContainerPropsProp; - - const menuItemRef = useRef < HTMLLIElement > null; - useImperativeHandle(ref, () => menuItemRef.current); - const containerRef = useRef < HTMLDivElement > null; - useImperativeHandle(containerRefProp, () => containerRef.current); - const menuContainerRef = useRef < HTMLDivElement > null; - - console.log( - "PAST THIS: ", - containerRefProp, - menuItemRef, - containerRef, - menuContainerRef, - ContainerProps - ); - - const handleMouseEnter = (event: React.MouseEvent) => { - setIsSubMenuOpen(true); - - if (ContainerProps?.onMouseEnter) { - ContainerProps.onMouseEnter(event); - } - }; - const handleMouseLeave = (event: React.MouseEvent) => { - setIsSubMenuOpen(false); - - if (ContainerProps?.onMouseLeave) { - ContainerProps.onMouseLeave(event); - } - }; - - // Check if any immediate children are active - const isSubmenuFocused = () => { - const active = containerRef.current?.ownerDocument?.activeElement; - for (const child of menuContainerRef.current?.children ?? []) { - if (child === active) { - return true; - } - } - return false; - }; - - const handleFocus = (event: React.FocusEvent) => { - if (event.target === containerRef.current) { - setIsSubMenuOpen(true); - } - - if (ContainerProps?.onFocus) { - ContainerProps.onFocus(event); - } - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Escape") { - return; - } - - if (isSubmenuFocused()) { - event.stopPropagation(); - } - - const active = containerRef.current?.ownerDocument?.activeElement; - - if (event.key === "ArrowLeft" && isSubmenuFocused()) { - containerRef.current?.focus(); - } - - if ( - event.key === "ArrowRight" && - event.target === containerRef.current && - event.target === active - ) { - console.log("MENU: ", menuContainerRef); - const firstChild = menuContainerRef.current.children[0]; - console.log("FIRST: ", firstChild); - firstChild.focus(); - } - }; - - const open = isSubMenuOpen && parentMenuOpen; - const menuItemClasses = useMenuItemStyles({ open }); - - // Root element must have a `tabIndex` attribute for keyboard navigation - let tabIndex; - if (!props.disabled) { - tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1; - } - - console.log("PAST 2! ", tabIndex); - - return ( -
- - {label} - {rightIcon} - - { - setIsSubMenuOpen(false); - }} - > -
- {children} -
-
-
- ); -}; - -export default NestedMenuItem; diff --git a/frontend/src/components/NestedMenuItem.jsx b/frontend/src/components/NestedMenuItem.jsx deleted file mode 100644 index e48c2988..00000000 --- a/frontend/src/components/NestedMenuItem.jsx +++ /dev/null @@ -1,202 +0,0 @@ -import React, {useState, useRef, useImperativeHandle} from 'react' -import {makeStyles} from '@material-ui/core/styles' -import Menu, {MenuProps} from '@material-ui/core/Menu' -import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem' -import ArrowRight from '@material-ui/icons/ArrowRight' -import clsx from 'clsx' - -export interface NestedMenuItemProps extends Omit { - /** - * Open state of parent ``, used to close decendent menus when the - * root menu is closed. - */ - parentMenuOpen: boolean - /** - * Component for the container element. - * @default 'div' - */ - component?: React.ElementType - /** - * Effectively becomes the `children` prop passed to the `` - * element. - */ - label?: React.ReactNode - /** - * @default - */ - rightIcon?: React.ReactNode - /** - * Props passed to container element. - */ - ContainerProps?: React.HTMLAttributes & - React.RefAttributes - /** - * Props passed to sub `` element - */ - MenuProps?: Omit - /** - * @see https://material-ui.com/api/list-item/ - */ - button?: true | undefined -} - -const TRANSPARENT = 'rgba(0,0,0,0)' -const useMenuItemStyles = makeStyles((theme) => ({ - root: (props: any) => ({ - backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT - }) -})) - -/** - * Use as a drop-in replacement for `` when you need to add cascading - * menu elements as children to this component. - */ -const NestedMenuItem = React.forwardRef< - HTMLLIElement | null, - NestedMenuItemProps ->(function NestedMenuItem(props, ref) { - const { - parentMenuOpen, - component = 'div', - label, - rightIcon = , - children, - className, - tabIndex: tabIndexProp, - MenuProps = {}, - ContainerProps: ContainerPropsProp = {}, - ...MenuItemProps - } = props - - const {ref: containerRefProp, ...ContainerProps} = ContainerPropsProp - - const menuItemRef = useRef(null) - useImperativeHandle(ref, () => menuItemRef.current) - - const containerRef = useRef(null) - useImperativeHandle(containerRefProp, () => containerRef.current) - - const menuContainerRef = useRef(null) - - const [isSubMenuOpen, setIsSubMenuOpen] = useState(false) - - const handleMouseEnter = (event: React.MouseEvent) => { - setIsSubMenuOpen(true) - - if (ContainerProps?.onMouseEnter) { - ContainerProps.onMouseEnter(event) - } - } - const handleMouseLeave = (event: React.MouseEvent) => { - setIsSubMenuOpen(false) - - if (ContainerProps?.onMouseLeave) { - ContainerProps.onMouseLeave(event) - } - } - - // Check if any immediate children are active - const isSubmenuFocused = () => { - const active = containerRef.current?.ownerDocument?.activeElement - for (const child of menuContainerRef.current?.children ?? []) { - if (child === active) { - return true - } - } - return false - } - - const handleFocus = (event: React.FocusEvent) => { - if (event.target === containerRef.current) { - setIsSubMenuOpen(true) - } - - if (ContainerProps?.onFocus) { - ContainerProps.onFocus(event) - } - } - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === 'Escape') { - return - } - - if (isSubmenuFocused()) { - event.stopPropagation() - } - - const active = containerRef.current?.ownerDocument?.activeElement - - if (event.key === 'ArrowLeft' && isSubmenuFocused()) { - containerRef.current?.focus() - } - - if ( - event.key === 'ArrowRight' && - event.target === containerRef.current && - event.target === active - ) { - const firstChild = menuContainerRef.current?.children[0] as - | HTMLElement - | undefined - firstChild?.focus() - } - } - - const open = isSubMenuOpen && parentMenuOpen - const menuItemClasses = useMenuItemStyles({open}) - - // Root element must have a `tabIndex` attribute for keyboard navigation - let tabIndex - if (!props.disabled) { - tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1 - } - - return ( -
- - {label} - {rightIcon} - - { - setIsSubMenuOpen(false) - }} - > -
- {children} -
-
-
- ) -}) - -export default NestedMenuItem diff --git a/frontend/src/components/Newsletter.jsx b/frontend/src/components/Newsletter.jsx index ccadf77b..e21797e2 100644 --- a/frontend/src/components/Newsletter.jsx +++ b/frontend/src/components/Newsletter.jsx @@ -1,102 +1,106 @@ -import React, {useState} from 'react'; -import { useTheme } from '@material-ui/core/styles'; -import {isMobile} from "react-device-detect"; -import ReactGA from 'react-ga4'; - -import {TextField, Typography, Button} from '@material-ui/core'; - -const Newsletter = (props) => { - const { globalUrl, } = props; - - const theme = useTheme(); - const [email, setEmail] = useState(""); - const [msg, setMsg] = useState(""); - const [buttonActive, setButtonActive] = useState(true); - const buttonStyle = {minWidth: 300, borderRadius: 30, height: 60, width: 140, margin: isMobile ? "15px auto 15px auto" : "20px 20px 20px 10px", fontSize: 18,} - - const newsletterSignup = (inemail) => { - if (inemail.length < 4) { - setMsg("Invalid email") - setButtonActive(true) - return - } - - setButtonActive(false) - const data = {"email": inemail} - const url = globalUrl+'/api/v1/functions/newsletter_signup' - fetch(url, { - method: 'POST', - body: JSON.stringify(data), - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - }) - .then(response => - response.json().then(responseJson => { - setButtonActive(true) - setMsg(responseJson["reason"]) - if (responseJson["success"] === false) { - } else { - setEmail("") - } - }), - ) - .catch(error => { - setMsg("Something went wrong: ", error.toString()) - setButtonActive(true) - }); - } - - return ( -
- - Security Automation Newsletter - - - Defensive security is 99% noise. Join us to sift through it. - -
- { - setEmail(e.target.value) - }} - placeholder="Your email" - id="standard-required" - margin="normal" - variant="outlined" - /> -
- -
- {msg} -
- ) -} - - -export default Newsletter; +import React, {useState} from 'react'; +import { useTheme } from '@mui/styles'; +import {isMobile} from "react-device-detect"; +import ReactGA from 'react-ga4'; + +import { + TextField, + Typography, + Button +} from '@mui/material'; + +const Newsletter = (props) => { + const { globalUrl, } = props; + + const theme = useTheme(); + const [email, setEmail] = useState(""); + const [msg, setMsg] = useState(""); + const [buttonActive, setButtonActive] = useState(true); + const buttonStyle = {minWidth: 300, borderRadius: 30, height: 60, width: 140, margin: isMobile ? "15px auto 15px auto" : "20px 20px 20px 10px", fontSize: 18,} + + const newsletterSignup = (inemail) => { + if (inemail.length < 4) { + setMsg("Invalid email") + setButtonActive(true) + return + } + + setButtonActive(false) + const data = {"email": inemail} + const url = globalUrl+'/api/v1/functions/newsletter_signup' + fetch(url, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => + response.json().then(responseJson => { + setButtonActive(true) + setMsg(responseJson["reason"]) + if (responseJson["success"] === false) { + } else { + setEmail("") + } + }), + ) + .catch(error => { + setMsg("Something went wrong: ", error.toString()) + setButtonActive(true) + }); + } + + return ( +
+ + Security Automation Newsletter + + + Defensive security is 99% noise. Join us to sift through it. + +
+ { + setEmail(e.target.value) + }} + placeholder="Your email" + id="standard-required" + margin="normal" + variant="outlined" + /> +
+ +
+ {msg} +
+ ) +} + + +export default Newsletter; diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 9832b990..204af1c6 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -1,8 +1,8 @@ import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; +import { toast } from 'react-toastify'; import { useParams, useNavigate, Link } from "react-router-dom"; -import { useTheme } from "@material-ui/core/styles"; import theme from '../theme.jsx'; -import { useAlert } from "react-alert"; +//import { useAlert import { v4 as uuidv4 } from "uuid"; import { @@ -38,7 +38,8 @@ import { CircularProgress, Switch, Fade, -} from "@material-ui/core"; +} from "@mui/material"; + import { LockOpen as LockOpenIcon, SupervisorAccount as SupervisorAccountIcon, @@ -71,6 +72,7 @@ const registeredApps = [ "todoist", "microsoft_sentinel", "microsoft_365_defender", + "google_chat", "google_sheets", "google_drive", "google_disk", @@ -98,7 +100,7 @@ const AuthenticationOauth2 = (props) => { } = props; let navigate = useNavigate(); - const alert = useAlert() + //const alert = useAlert() //const [update, setUpdate] = React.useState("|") const [defaultConfigSet, setDefaultConfigSet] = React.useState( @@ -216,7 +218,7 @@ const AuthenticationOauth2 = (props) => { "31cb4c84-658e-43d5-ae84-22c9142e967a", "", "https://graph.microsoft.com", - ["ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "Chat.Create", "Chat.ReadWrite", "Chat.Read", "offline_access"], + ["ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "Chat.Create", "Chat.ReadWrite", "Chat.Read", "offline_access", "Team.ReadBasic.All"], admin_consent, ) } else if (selectedApp.name.toLowerCase().includes("todoist")) { @@ -261,6 +263,16 @@ const AuthenticationOauth2 = (props) => { admin_consent, "consent", ) + } else if (selectedApp.name.toLowerCase().includes("google_chat") || selectedApp.name.toLowerCase().includes("google_hangout")) { + handleOauth2Request( + "253565968129-6pij4g6ojim4gpum0h9m9u3bc357qsq7.apps.googleusercontent.com", + "", + "https://www.googleapis.com", + ["https://www.googleapis.com/auth/chat.messages",], + admin_consent, + "consent", + ) + } else if (selectedApp.name.toLowerCase().includes("jira_service_desk") || selectedApp.name.toLowerCase().includes("jira") || selectedApp.name.toLowerCase().includes("jira_service_management")) { handleOauth2Request( "AI02egeCQh1Zskm1QAJaaR6dzjR97V2F", @@ -411,7 +423,7 @@ const AuthenticationOauth2 = (props) => { //} //while(open === true) } catch (e) { - alert.error( + toast( "Failed authentication - probably bad credentials. Try again" ); setButtonClicked(false); @@ -440,7 +452,7 @@ const AuthenticationOauth2 = (props) => { console.log("NEW AUTH: ", authenticationOption); if (authenticationOption.label.length === 0) { authenticationOption.label = `Auth for ${selectedApp.name}`; - //alert.info("Label can't be empty") + //toast("Label can't be empty") //return } @@ -468,7 +480,7 @@ const AuthenticationOauth2 = (props) => { selectedApp.authentication.parameters[key].name ] = "false"; } else { - alert.info( + toast( "Field " + selectedApp.authentication.parameters[key].name.replace("_basic", "", -1).replace("_", " ", -1) + " can't be empty" ); @@ -595,7 +607,7 @@ const AuthenticationOauth2 = (props) => {
- Authentication for {selectedApp.name} + Authenticate {selectedApp.name.replaceAll("_", " ")}
@@ -662,11 +674,6 @@ const AuthenticationOauth2 = (props) => { style={{backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius,}} InputProps={{ style:{ - color: "white", - marginLeft: "5px", - maxWidth: "95%", - height: 50, - fontSize: "1em", }, }} fullWidth @@ -751,11 +758,6 @@ const AuthenticationOauth2 = (props) => { }} InputProps={{ style: { - color: "white", - marginLeft: "5px", - maxWidth: "95%", - height: 50, - fontSize: "1em", }, }} fullWidth @@ -792,11 +794,6 @@ const AuthenticationOauth2 = (props) => { }} InputProps={{ style: { - color: "white", - marginLeft: "5px", - maxWidth: "95%", - fontSize: "1em", - height: "50px", }, }} fullWidth @@ -815,11 +812,6 @@ const AuthenticationOauth2 = (props) => { }} InputProps={{ style: { - color: "white", - marginLeft: "5px", - maxWidth: "95%", - fontSize: "1em", - height: "50px", }, }} fullWidth @@ -836,6 +828,7 @@ const AuthenticationOauth2 = (props) => { Scopes upload = ref} onChange={editHeaderImage} /> - {imageInfo} -
- -
-
- Name - { - const invalid = ["#", ":", "."] - for (var key in invalid) { - if (e.target.value.includes(invalid[key])) { - alert.error("Can't use "+invalid[key]+" in name") - return - } - } - - if (e.target.value.length > 100) { - alert.error("Choose a shorter name.") - return - } - - setOrgName(e.target.value) - }} - color="primary" - InputProps={{ - style:{ - color: "white", - height: "50px", - fontSize: "1em", - }, - classes: { - notchedOutline: classes.notchedOutline, - }, - }} - /> -
- Description -
- { - setOrgDescription(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> -
- {orgSaveButton} -
-
-
-
-
- { - setExpanded(!expanded) - }}> - {expanded ? - - : - - } - - {expanded ? - - - - - App Download URL - - { - setAppDownloadUrl(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - - - - - - App Download Branch - - { - setAppDownloadBranch(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - - - - - - Workflow Download URL - - { - setWorkflowDownloadUrl(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - - - - - - Workflow Download Branch - - { - setWorkflowDownloadBranch(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - - - - - - 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", - }, - }} - /> - - - {/* - - {expanded ? - - : - - } - - */} - - : - null - } -
-
- ) -} - -export default OrgHeader diff --git a/frontend/src/components/OrgHeader.jsx b/frontend/src/components/OrgHeader.jsx index 2f1b765e..95020bb1 100644 --- a/frontend/src/components/OrgHeader.jsx +++ b/frontend/src/components/OrgHeader.jsx @@ -1,17 +1,22 @@ import React, { useEffect } from "react"; -import { makeStyles } from "@material-ui/styles"; -import { useTheme } from "@material-ui/core/styles"; +import theme from "../theme.jsx"; +import { makeStyles } from "@mui/styles"; +import { toast } from 'react-toastify'; -import Tooltip from "@material-ui/core/Tooltip"; -import Grid from "@material-ui/core/Grid"; -import Button from "@material-ui/core/Button"; -import TextField from "@material-ui/core/TextField"; -import Typography from "@material-ui/core/Typography"; -import { useAlert } from "react-alert"; -import IconButton from "@material-ui/core/IconButton"; -import ExpandLessIcon from "@material-ui/icons/ExpandLess"; -import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; -import SaveIcon from "@material-ui/icons/Save"; +import { + Tooltip, + Grid, + Button, + TextField, + Typography, + IconButton, +} from "@mui/material"; + +import { + ExpandLess as ExpandLessIcon, + ExpandMore as ExpandMoreIcon, + Save as SaveIcon, +} from "@mui/icons-material"; const useStyles = makeStyles({ notchedOutline: { @@ -33,8 +38,6 @@ const OrgHeader = (props) => { handleEditOrg, } = props; - const theme = useTheme(); - const alert = useAlert(); const classes = useStyles(); var upload = ""; @@ -98,7 +101,7 @@ const OrgHeader = (props) => { +
); @@ -244,34 +300,149 @@ const OrgHeaderexpanded = (props) => { - Notification Workflow ID - { - setNotificationWorkflow(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> + Notification Workflow + {/* + + Add a Workflow that receives notifications from Shuffle when an error occurs in one of your workflows + + */} +
+ {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} +
+
@@ -699,4 +870,4 @@ const OrgHeaderexpanded = (props) => { ) } -export default OrgHeaderexpanded; \ No newline at end of file +export default OrgHeaderexpanded; diff --git a/frontend/src/components/PaperComponent.jsx b/frontend/src/components/PaperComponent.jsx index 76fb56ef..d1e2dc22 100644 --- a/frontend/src/components/PaperComponent.jsx +++ b/frontend/src/components/PaperComponent.jsx @@ -3,7 +3,7 @@ import React, {useState, useEffect, useLayoutEffect} from 'react'; import Draggable from "react-draggable"; import { Paper -} from "@material-ui/core"; +} from "@mui/material"; const PaperComponent = (props) => { return ( diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 1871dee7..c069499f 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1,16 +1,17 @@ import React, { useState, useEffect, useLayoutEffect } from "react"; -import { makeStyles, createStyles } from "@material-ui/core/styles"; +import { toast } from 'react-toastify'; +import { makeStyles, createStyles } from "@mui/styles"; +import theme from '../theme.jsx'; + import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; import { GetParsedPaths } from "../views/Apps.jsx"; import { sortByKey } from "../views/AngularWorkflow.jsx"; -import { useTheme } from "@material-ui/core/styles"; -import NestedMenuItem from "material-ui-nested-menu-item"; -import { useAlert } from "react-alert"; -import theme from '../theme.jsx'; +import { NestedMenuItem } from "mui-nested-menu"; +//import { useAlert import { - ButtonGroup, + ButtonGroup, Popper, TextField, TextareaAutosize, @@ -42,12 +43,9 @@ import { Breadcrumbs, CircularProgress, Switch, - Fade, -} from "@material-ui/core"; - -import { + Collapse, Autocomplete -} from "@material-ui/lab"; +} from "@mui/material"; import { HelpOutline as HelpOutlineIcon, @@ -88,13 +86,12 @@ import { Circle as CircleIcon, SquareFoot as SquareFootIcon, } from '@mui/icons-material'; -//} from "@material-ui/icons"; //import CodeMirror from "@uiw/react-codemirror"; //import "codemirror/keymap/sublime"; //import "codemirror/theme/gruvbox-dark.css"; -import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx"; +//import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx"; const useStyles = makeStyles({ notchedOutline: { @@ -115,7 +112,6 @@ const useStyles = makeStyles({ }, inputRoot: { color: "white", - // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90 "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: "#f86a3e", }, @@ -160,29 +156,30 @@ const ParsedAction = (props) => { authenticationType, appAuthentication, getAppAuthentication, - actionDelayChange, - getParents, - isCloud, - lastSaved, - setLastSaved, - setShowVideo, - toolsAppId, - aiSubmit, - //expansionModalOpen, - //setExpansionModalOpen, + actionDelayChange, + getParents, + isCloud, + lastSaved, + setLastSaved, + setShowVideo, + toolsAppId, + aiSubmit, + + expansionModalOpen, + setExpansionModalOpen, + + setEditorData, + setcodedata, } = props; - //const theme = useTheme(); const classes = useStyles(); - const alert = useAlert() - - const [expansionModalOpen, setExpansionModalOpen] = React.useState(false); + //const alert = useAlert() + const [hideBody, setHideBody] = React.useState(true); const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false); - const [codedata, setcodedata] = React.useState(""); - const [fieldCount, setFieldCount] = React.useState(0); - const [hiddenDescription, setHiddenDescription] = React.useState(true); + const [fieldCount, setFieldCount] = React.useState(0); + const [hiddenDescription, setHiddenDescription] = React.useState(true); useEffect(() => { @@ -231,9 +228,9 @@ const ParsedAction = (props) => { }) .then((response) => { if (response.status === 200) { - //alert.success("Successfully GOT app "+appId) + //toast("Successfully GOT app "+appId) } else { - alert.error("Failed getting app"); + toast("Failed getting app"); } return response.json(); @@ -294,7 +291,7 @@ const ParsedAction = (props) => { //foundparams.push(param.name) } } else { - alert.error("Couldn't find action " + selectedAction.name); + toast("Couldn't find action " + selectedAction.name); } selectedAction.errors = []; @@ -310,7 +307,7 @@ const ParsedAction = (props) => { } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -379,66 +376,57 @@ const ParsedAction = (props) => { const [menuPosition, setMenuPosition] = useState(null); useEffect(() => { - if ( - selectedActionParameters !== null && - selectedActionParameters.length === 0 + if (selectedActionParameters !== undefined && selectedActionParameters !== null && selectedActionParameters.length === 0 ) { - if ( - selectedAction.parameters !== null && - selectedAction.parameters.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 - ) { + 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 - } + 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 + } } - if (actionlist.length === 0) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: "", - }) - } + } + + if (actionlist.length === 0) { + actionlist.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: "", + }) + } actionlist.push({ type: "Shuffle DB", @@ -1143,13 +1131,13 @@ const ParsedAction = (props) => { borderRadius: theme.palette.borderRadius, }} onChange={(event, newValue) => { - console.log("SELECT: ", event, newValue) + console.log("SELECT: ", event, newValue) // Workaround with event lol //if (newValue !== undefined && newValue !== null) { // setNewSelectedAction({ target: { value: newValue.name } }); //} }} - renderOption={(data) => { + renderOption={(props, data, state) => { var newActionname = data.app_name; if ( data.label !== undefined && @@ -1162,6 +1150,8 @@ const ParsedAction = (props) => { const iconInfo = GetIconInfo({ name: data.app_name }); const useIcon = iconInfo.originalIcon; + console.log("Actionname 1: ", newActionname) + newActionname = ( newActionname.charAt(0).toUpperCase() + newActionname.substring(1) @@ -1314,26 +1304,26 @@ const ParsedAction = (props) => { data.value = data.example; } - // In case of data.example - if (data.value === undefined || data.value === null) { + // In case of data.example + if (data.value === undefined || data.value === null) { + data.value = "" + } + + if (data.value.length === 0) { + if (data.name.toLowerCase() === "headers") { + console.log("Should show headers field instead with + and -!") + + // Check if file ID exists + // + const fileFound = selectedActionParameters.find(param => param.name === "file_id") + if (fileFound === undefined || fileFound === null) { + data.value = data.example + } else { + // Purposely unset it if set by default when using files data.value = "" } - - if (data.value.length === 0) { - if (data.name.toLowerCase() === "headers") { - console.log("Should show headers field instead with + and -!") - - // Check if file ID exists - // - const fileFound = selectedActionParameters.find(param => param.name === "file_id") - if (fileFound === undefined || fileFound === null) { - data.value = data.example - } else { - // Purposely unset it if set by default when using files - data.value = "" - } - } - } + } + } /* if (data.name !== "queries" && data.name !== "key" && data.name !== "value" ) { @@ -1365,7 +1355,7 @@ const ParsedAction = (props) => { } var disabled = false; - var rows = "5"; + var rows = "3"; var openApiHelperText = "This is an OpenAPI specific field"; /* if ( @@ -1392,7 +1382,7 @@ const ParsedAction = (props) => { var hideBodyButton = ""; const hideBodyButtonValue = (
{ > { for (let paramkey in Object.entries(selectedActionParameters)) { var currentItem = selectedActionParameters[paramkey]; - if (currentItem.name === "ssl_verify") { + if (currentItem.name === "ssl_verify") { - } + } - if (currentItem.name === "body") { - // FIXME: Workaround for toggling, as actions don't have IDs. - // May screw up something in the future. - currentItem.id = tag - } + if (currentItem.name === "body") { + // FIXME: Workaround for toggling, as actions don't have IDs. + // May screw up something in the future. + currentItem.id = tag + } if (currentItem.description === openApiFieldDesc) { currentItem.field_active = !hideBody; @@ -1459,8 +1449,8 @@ const ParsedAction = (props) => { if (found === null) { setActivateHidingBodyButton(true); } else { - //console.log("In found: ", found, hideBody) - } + //console.log("In found: ", found, hideBody) + } } else { //console.log("SHOW BUTTON"); @@ -1486,6 +1476,17 @@ const ParsedAction = (props) => { } 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 + } + } + } + selectedActionParameters.push({ action_field: "", configuration: false, @@ -1495,7 +1496,7 @@ const ParsedAction = (props) => { multiline: true, name: tmpitem, options: null, - required: false, + required: isRequired, schema: { type: "string" }, skip_multicheck: false, tags: null, @@ -1519,29 +1520,6 @@ const ParsedAction = (props) => { const clickedFieldId = "rightside_field_" + count; - const shufflecode = fieldCount !== count ? null : - ( - - ) - // { } if (tmpitem === "from_shuffle") { - tmpitem = "from" - } + tmpitem = "from" + } tmpitem = ( tmpitem.charAt(0).toUpperCase() + tmpitem.substring(1) @@ -1588,48 +1566,50 @@ const ParsedAction = (props) => { fontSize: "1em", }} InputProps={{ - style: { - color: "white", - minHeight: 50, - marginLeft: 5, - maxWidth: "95%", - fontSize: "1em", - }, - disableUnderline: true, + disableUnderline: true, endAdornment: hideExtraTypes ? null : ( - - - { - event.preventDefault() - setFieldCount(count) - setcodedata(data.value) - setExpansionModalOpen(true) - }} - /> - - - { - event.preventDefault() + + + { + event.preventDefault() + setFieldCount(count) + setExpansionModalOpen(true) - // Get cursor position - // This makes it so we can put it in the right location? - setMenuPosition({ - top: event.pageY + 10, - left: event.pageX + 10, - }); - setShowDropdownNumber(count); - setShowDropdown(true); - setShowAutocomplete(true); - }} - /> - - - + //setcodedata(data.value) + + setEditorData({ + "name": data.name, + "value": data.value, + "field_number": count, + "actionlist": actionlist, + "field_id": clickedFieldId, + }) + }} + /> + + + { + event.preventDefault() + + // Get cursor position + // This makes it so we can put it in the right location? + setMenuPosition({ + top: event.pageY + 10, + left: event.pageX + 10, + }); + setShowDropdownNumber(count); + setShowDropdown(true); + setShowAutocomplete(true); + }} + /> + + + ), }} multiline={data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline} @@ -1659,11 +1639,11 @@ const ParsedAction = (props) => { */ //console.log("Clicked field: ", clickedFieldId) - if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) { - scrollConfig.selected = clickedFieldId - setScrollConfig(scrollConfig) - //console.log("Change field id!") - } + if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) { + scrollConfig.selected = clickedFieldId + setScrollConfig(scrollConfig) + //console.log("Change field id!") + } }} id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} @@ -1704,10 +1684,10 @@ const ParsedAction = (props) => { null : null } onBlur={(event) => { - baseHelperText = calculateHelpertext(event.target.value) - if (setLastSaved !== undefined) { - setLastSaved(false) - } + baseHelperText = calculateHelpertext(event.target.value) + if (setLastSaved !== undefined) { + setLastSaved(false) + } }} /> ); @@ -1943,13 +1923,6 @@ const ParsedAction = (props) => { borderRadius: theme.palette.borderRadius, }} InputProps={{ - style: { - color: "white", - minHeight: 50, - marginLeft: "5px", - maxWidth: "95%", - fontSize: "1em", - }, endAdornment: hideExtraTypes ? null : ( @@ -1972,7 +1945,7 @@ const ParsedAction = (props) => { helperText={returnHelperText(data.name, data.value)} fullWidth multiline={multiline} - rows="5" + rows={"3"} color="primary" defaultValue={data.value} type={"text"} @@ -2018,12 +1991,11 @@ const ParsedAction = (props) => { datafield = ( { } SelectDisplayProps={{ style: { - marginLeft: 10, }, }} fullWidth @@ -3481,20 +3528,20 @@ const ParsedAction = (props) => { autoHighlight value={selectedAction} classes={{ inputRoot: classes.inputRoot }} - groupBy={(option) => { - // 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} -
  • - ) - }} + groupBy={(option) => { + // 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={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"))} ListboxProps={{ style: { @@ -3502,13 +3549,13 @@ const ParsedAction = (props) => { 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)) + 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 - }} + return options + }} getOptionLabel={(option) => { if (option === undefined || option === null || option.name === undefined || option.name === null ) { return null; @@ -3530,146 +3577,136 @@ const ParsedAction = (props) => { // Workaround with event lol if (newValue !== undefined && newValue !== null) { setNewSelectedAction({ - target: { - value: newValue.name - } - }); + target: { + value: newValue.name + } + }); } }} - renderOption={(data) => { + 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) + //console.log("DESC: ", newActiondescription) if (data.description === undefined || data.description === null) { - newActiondescription = "Description: No description defined for this action" + newActiondescription = "Description: No description defined for this action" } else { - newActiondescription = "Description: "+newActiondescription - } + newActiondescription = "Description: "+newActiondescription + } const iconInfo = GetIconInfo({ name: data.name }); const useIcon = iconInfo.originalIcon; - newActionname = ( - newActionname.charAt(0).toUpperCase() + - newActionname.substring(1) - ).replaceAll("_", " "); + if (newActionname === undefined || newActionname === null) { + newActionname = "No name" + data.name = "No name" + data.label = "No name" + } - 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" - } + newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); - // 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] - } + 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" + } - //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("/") - // } + // 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 - // } - //} + // //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.length > 0) { + if (extraUrl.includes(" ")) { + extraUrl = extraUrl.split(" ")[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 :)") - } - } + extraDescription = `${method} ${extraUrl}` + } else { + //console.log("No url found. Check again :)") + } + } return ( - -
    -
    - - {useIcon} - - {newActionname} -
    - {extraDescription.length > 0 ? - - {extraDescription} - - : null} -
    -
    + ); }} 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 - } - } + 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 + } + } + } return ( - + ); }} /> diff --git a/frontend/src/components/Priorities.jsx b/frontend/src/components/Priorities.jsx index 88bea907..f003540c 100644 --- a/frontend/src/components/Priorities.jsx +++ b/frontend/src/components/Priorities.jsx @@ -9,13 +9,13 @@ import { Grid, Card, Switch, -} from "@material-ui/core"; +} from "@mui/material"; import Priority from "../components/Priority.jsx"; -import { useAlert } from "react-alert"; +//import { useAlert const Priorities = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, checkLogin, } = props; + const { globalUrl, userdata, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, } = props; const [showDismissed, setShowDismissed] = React.useState(false); const [showRead, setShowRead] = React.useState(false); @@ -60,6 +60,8 @@ const Priorities = (props) => { globalUrl={globalUrl} priority={priority} checkLogin={checkLogin} + setAdminTab={setAdminTab} + setCurTab={setCurTab} /> ) }) diff --git a/frontend/src/components/Priority.jsx b/frontend/src/components/Priority.jsx index 0123b37d..3f167982 100644 --- a/frontend/src/components/Priority.jsx +++ b/frontend/src/components/Priority.jsx @@ -1,7 +1,10 @@ import React, { useState, useEffect } from "react"; +import { toast } from 'react-toastify'; +import ReactGA from 'react-ga4'; import theme from "../theme.jsx"; import { useNavigate, Link } from "react-router-dom"; +import { findSpecificApp } from "../components/AppFramework.jsx" import { Paper, Typography, @@ -9,65 +12,97 @@ import { Button, Grid, Card, -} from "@material-ui/core"; +} from "@mui/material"; // import magic wand icon from material ui icons import { AutoFixHigh as AutoFixHighIcon, ArrowForward as ArrowForwardIcon, } from '@mui/icons-material'; -import { useAlert } from "react-alert"; +//import { useAlert const Priority = (props) => { - const { globalUrl, userdata, serverside, priority, checkLogin, } = props; - + const { globalUrl, userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props; + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; let navigate = useNavigate(); + + var realigned = false + let newdescription = priority.description + const descsplit = priority.description.split("&") + if (appFramework !== undefined && descsplit.length === 5 && priority.description.includes(":default")) { + console.log("descsplit: ", descsplit) + if (descsplit[1] === "") { + const item = findSpecificApp(appFramework, descsplit[0]) + console.log("item: ", item) + if (item !== null) { + descsplit[1] = item.large_image + descsplit[0] = descsplit[0].split(":")[0] + } + + realigned = true + } + + if (descsplit[3] === "") { + const item = findSpecificApp(appFramework, descsplit[2]) + console.log("item: ", item) + if (item !== null) { + descsplit[3] = item.large_image + descsplit[2] = descsplit[2].split(":")[0] + } + + realigned = true + } + + newdescription = descsplit.join("&") + } + const changeRecommendation = (recommendation, action) => { - const data = { - action: action, - name: recommendation.name, - }; + const data = { + action: action, + name: recommendation.name, + }; - fetch(`${globalUrl}/api/v1/recommendations/modify`, { - 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 === 200) { - } else { - } - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success === true) { - if (checkLogin !== undefined) { - checkLogin() - } - } else { - if (responseJson.success === false && responseJson.reason !== undefined) { - alert.error("Failed change recommendation: ", responseJson.reason) - } else { - alert.error("Failed change recommendation"); - } - } - }) - .catch((error) => { - alert.info("Failed dismissing alert. Please contact support@shuffler.io if this persists."); - }); + fetch(`${globalUrl}/api/v1/recommendations/modify`, { + 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 === 200) { + } else { + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + if (checkLogin !== undefined) { + checkLogin() + } + } else { + if (responseJson.success === false && responseJson.reason !== undefined) { + toast("Failed change recommendation: ", responseJson.reason) + } else { + toast("Failed change recommendation"); + } + } + }) + .catch((error) => { + toast("Failed dismissing alert. Please contact support@shuffler.io if this persists."); + }); } return ( -
    +
    {priority.type === "usecase" || priority.type == "apps" ? : null} @@ -77,17 +112,17 @@ const Priority = (props) => { {priority.type === "usecase" && priority.description.includes("&") ? - {priority.name} + {priority.name} - {priority.description.split("&")[0]} + {newdescription.split("&")[0]} - {priority.description.split("&").length > 3 ? + {newdescription.split("&").length > 3 ? - {priority.name+"2"} + {priority.name+"2"} - {priority.description.split("&")[2]} + {newdescription.split("&")[2]} : null} @@ -100,17 +135,30 @@ const Priority = (props) => { }
    - {priority.active === true ? - -
    - - - ); -}; - -export default SettingsDialog; diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx index 50b159a9..5fb9b152 100644 --- a/frontend/src/components/ShuffleCodeEditor.jsx +++ b/frontend/src/components/ShuffleCodeEditor.jsx @@ -1,4 +1,5 @@ import React, {useState, useEffect, useLayoutEffect} from 'react'; +import { toast } from 'react-toastify'; import { CircularProgress, IconButton, @@ -12,51 +13,51 @@ import { Menu, MenuItem, Button, -} from '@material-ui/core'; +} from '@mui/material'; import theme from '../theme.jsx'; import Checkbox from '@mui/material/Checkbox'; import { orange } from '@mui/material/colors'; import { isMobile } from "react-device-detect" -import NestedMenuItem from "material-ui-nested-menu-item"; +import { NestedMenuItem } from "mui-nested-menu" import { GetParsedPaths, FindJsonPath } from "../views/Apps.jsx"; import { SetJsonDotnotation } from "../views/AngularWorkflow.jsx"; import { FullscreenExit as FullscreenExitIcon, Extension as ExtensionIcon, - Apps as AppsIcon, - FavoriteBorder as FavoriteBorderIcon, - Schedule as ScheduleIcon, - FormatListNumbered as FormatListNumberedIcon, + Apps as AppsIcon, + FavoriteBorder as FavoriteBorderIcon, + Schedule as ScheduleIcon, + FormatListNumbered as FormatListNumberedIcon, SquareFoot as SquareFootIcon, - Circle as CircleIcon, - Add as AddIcon, + Circle as CircleIcon, + Add as AddIcon, PlayArrow as PlayArrowIcon, -} from '@mui/icons-material'; - -import { AutoFixHigh as AutoFixHighIcon, + Close as CloseIcon, CompressOutlined, QrCodeScannerOutlined, } from '@mui/icons-material'; + import { validateJson } from "../views/Workflows.jsx"; import ReactJson from "react-json-view"; import PaperComponent from "../components/PaperComponent.jsx"; import CodeMirror from '@uiw/react-codemirror'; -import 'codemirror/keymap/sublime'; -import 'codemirror/addon/selection/mark-selection.js' -import 'codemirror/theme/gruvbox-dark.css'; -import 'codemirror/theme/duotone-light.css'; +//import 'codemirror/keymap/sublime'; +//import 'codemirror/addon/selection/mark-selection.js' +//import 'codemirror/theme/gruvbox-dark.css'; +//import 'codemirror/theme/duotone-light.css'; import {indentWithTab} from "@codemirror/commands" import { padding, textAlign } from '@mui/system'; import data from '../frameworkStyle.jsx'; import { useNavigate, Link, useParams } from "react-router-dom"; +import { tags as t } from '@lezer/highlight'; import { createTheme } from '@uiw/codemirror-themes'; -import { tags } from '@lezer/highlight'; + const liquidFilters = [ {"name": "Size", "value": "size", "example": ""}, @@ -81,44 +82,39 @@ const pythonFilters = [ {"name": "Handle JSON", "value": `{% python %}\nimport json\njsondata = json.loads(r"""$nodename""")\n{% endpython %}`, "example": ``}, ] -//const shuffleTheme = createTheme({ -// theme: 'dark', -// settings: { -// background: '#282828', -// foreground: '#282828', -// caret: '#5d00ff', -// selection: '#036dd626', -// selectionMatch: '#036dd626', -// lineHighlight: '#8a91991a', -// gutterBackground: '#282828', -// gutterForeground: '#8a919966', -// fontSize: 18, -// borderRadius: theme.palette.borderRadius, -// border: `2px solid ${theme.palette.inputColor}`, -// }, -// styles: [ -// { tag: tags.comment, color: '#787b8099' }, -// { tag: tags.variableName, color: '#0080ff' }, -// { tag: [tags.string, tags.special(tags.brace)], color: '#5c6166' }, -// { tag: tags.number, color: '#5c6166' }, -// { tag: tags.bool, color: '#5c6166' }, -// { tag: tags.null, color: '#5c6166' }, -// { tag: tags.keyword, color: '#5c6166' }, -// { tag: tags.operator, color: '#5c6166' }, -// { tag: tags.className, color: '#5c6166' }, -// { tag: tags.definition(tags.typeName), color: '#5c6166' }, -// { tag: tags.typeName, color: '#5c6166' }, -// { tag: tags.angleBracket, color: '#5c6166' }, -// { tag: tags.tagName, color: '#5c6166' }, -// { tag: tags.attributeName, color: '#5c6166' }, -// ], -//}); +const shuffleTheme = createTheme({ + theme: 'dark', + settings: { + background: "rgba(40,40,40, 1)", + foreground: '#75baff', + caret: '#5d00ff', + selection: '#036dd626', + selectionMatch: '#036dd626', + lineHighlight: '#8a91991a', + gutterForeground: '#8a919966', + }, + styles: [ + { tag: t.comment, color: '#787b8099' }, + { tag: t.variableName, color: '#0080ff' }, + { tag: [t.string, t.special(t.brace)], color: '#5c6166' }, + { tag: t.number, color: '#5c6166' }, + { tag: t.bool, color: '#5c6166' }, + { tag: t.null, color: '#5c6166' }, + { tag: t.keyword, color: '#5c6166' }, + { tag: t.operator, color: '#5c6166' }, + { tag: t.className, color: '#5c6166' }, + { tag: t.definition(t.typeName), color: '#5c6166' }, + { tag: t.typeName, color: '#5c6166' }, + { tag: t.angleBracket, color: '#5c6166' }, + { tag: t.tagName, color: '#5c6166' }, + { tag: t.attributeName, color: '#5c6166' }, + ], +}); const CodeEditor = (props) => { const { globalUrl, fieldCount, - setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, @@ -132,6 +128,8 @@ const CodeEditor = (props) => { selectedAction , workflowExecutions, getParents, + + fieldname, } = props const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); @@ -140,7 +138,7 @@ const CodeEditor = (props) => { const [validation, setValidation] = React.useState(false); const [expOutput, setExpOutput] = React.useState(" "); const [linewrap, setlinewrap] = React.useState(true); - const [codeTheme, setcodeTheme] = React.useState("gruvbox-dark"); + //const [codeTheme, setcodeTheme] = React.useState("gruvbox-dark"); const [editorPopupOpen, setEditorPopupOpen] = React.useState(false); const [currentCharacter, setCurrentCharacter] = React.useState(-1); @@ -155,8 +153,8 @@ const CodeEditor = (props) => { const [mainVariables, setMainVariables] = React.useState([]); const [availableVariables, setAvailableVariables] = React.useState([]); - const [menuPosition, setMenuPosition] = useState(null); - const [showAutocomplete, setShowAutocomplete] = React.useState(false); + const [menuPosition, setMenuPosition] = useState(null); + const [showAutocomplete, setShowAutocomplete] = React.useState(false); const [isAiLoading, setIsAiLoading] = React.useState(false); @@ -206,6 +204,9 @@ const CodeEditor = (props) => { setAvailableVariables(allVariables) setMainVariables(tmpVariables) + + console.log("Checking local codedata: ", localcodedata) + expectedOutput(localcodedata) }, []) const aiSubmit = (value, inputAction) => { @@ -821,10 +822,6 @@ const CodeEditor = (props) => { } const executeSingleAction = (inputdata) => { - //if (serverside === true) { - // return - //} - if (validation === true) { inputdata = JSON.stringify(inputdata) } @@ -832,7 +829,10 @@ const CodeEditor = (props) => { // Shuffle Tools 1.2.0 (in most cases?) const appid = toolsAppId !== undefined && toolsAppId !== null && toolsAppId.length > 0 ? toolsAppId : "3e2bdf9d5069fe3f4746c29d68785a6a" - const actiondata = {"description":"Repeats the call parameter","id":"","name":"repeat_back_to_me","label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id": appid,"tags":null,"authentication":[],"tested":false,"parameters":[{"description":"The message to repeat","id":"","name":"call","example":"REPEATING: Hello world","value":inputdata,"multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false,"autocompleted":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}} + const actionname = selectedAction.name === "execute_python" && !inputdata.replaceAll(" ", "").includes("{%python%}") ? "execute_python" : "repeat_back_to_me" + const params = actionname === "execute_python" ? [{"name": "code", "value":inputdata}] : [{"name":"call", "value": inputdata}] + + const actiondata = {"description":"Repeats the call parameter","id":"","name":actionname,"label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id": appid,"tags":null,"authentication":[],"tested":false,"parameters": params, "execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}} setExecutionResult({ "valid": false, @@ -863,12 +863,12 @@ const CodeEditor = (props) => { var newResult = {} if (responseJson.success === true && responseJson.result !== null && responseJson.result !== undefined && responseJson.result.length > 0) { const result = responseJson.result.slice(0, 50)+"..." - //alert.info("SUCCESS: "+result) + //toast("SUCCESS: "+result) const validate = validateJson(responseJson.result) newResult = validate } else if (responseJson.success === false && responseJson.reason !== undefined && responseJson.reason !== null) { - alert.error(responseJson.reason) + toast(responseJson.reason) newResult = {"valid": false, "result": responseJson.reason} } else if (responseJson.success === true) { newResult = {"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."} @@ -884,7 +884,7 @@ const CodeEditor = (props) => { setExecuting(false) }) .catch(error => { - //alert.error("Execution error: "+error.toString()) + //toast("Execution error: "+error.toString()) console.log("error: ", error) setExecuting(false) }) @@ -919,10 +919,23 @@ const CodeEditor = (props) => { maxHeight: isMobile ? "100%" : 720, border: theme.palette.defaultBorder, padding: isMobile ? "25px 10px 25px 10px" : 25, - backgroundColor: theme.palette.surfaceColor, }, }} > + { + setExpansionModalOpen(false) + }} + > + +
    { isFileEditor ? @@ -1020,6 +1033,7 @@ const CodeEditor = (props) => { aria-controls={liquidOpen ? 'basic-menu' : undefined} aria-expanded={liquidOpen ? 'true' : undefined} variant="outlined" + color="secondary" style={{ textTransform: "none", width: 100, @@ -1055,6 +1069,7 @@ const CodeEditor = (props) => { aria-controls={mathOpen ? 'basic-menu' : undefined} aria-expanded={mathOpen ? 'true' : undefined} variant="outlined" + color="secondary" style={{ textTransform: "none", width: 100, @@ -1090,6 +1105,7 @@ const CodeEditor = (props) => { aria-controls={pythonOpen ? 'basic-menu' : undefined} aria-expanded={pythonOpen ? 'true' : undefined} variant="outlined" + color="secondary" style={{ textTransform: "none", width: 100, @@ -1125,6 +1141,7 @@ const CodeEditor = (props) => { aria-controls={!!menuPosition ? 'basic-menu' : undefined} aria-expanded={!!menuPosition ? 'true' : undefined} variant="outlined" + color="secondary" style={{ textTransform: "none", width: 130, @@ -1381,7 +1398,7 @@ const CodeEditor = (props) => { position: "relative", }}> { wordBreak: "break-word", marginTop: 0, paddingTop: 0, + backgroundColor: "rgba(40,40,40,1)", + minHeight: 470, }} onCursorActivity = {(value) => { // console.log(value.getCursor()) @@ -1398,27 +1417,22 @@ const CodeEditor = (props) => { findIndex(value.getCursor().line, value.getCursor().ch) highlight_variables(value) }} - onChange={(value) => { - //console.log("Value: '", value.getValue(), "'") + onChange={(value, viewUpdate) => { + console.log("Value: ", value, viewUpdate) + setlocalcodedata(value) + expectedOutput(value) - setlocalcodedata(value.getValue()) - expectedOutput(value.getValue()) - - if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){ - setEditorPopupOpen(true) - } - - // console.log(findIndex(value.getValue())) - // highlight_variables(value) + //if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){ + // setEditorPopupOpen(true) + //} }} - extensions={[indentWithTab]} + extensions={[]}//indentWithTab]} + theme={shuffleTheme} options={{ styleSelectedText: true, - theme: codeTheme, keyMap: 'sublime', mode: validation === true ? "json" : "python", lineWrapping: linewrap, - }} /> @@ -1553,7 +1567,7 @@ const CodeEditor = (props) => { { executeSingleAction(expOutput) }}> - + {executing ? : } @@ -1664,34 +1678,29 @@ const CodeEditor = (props) => {
    - - +
    ) } diff --git a/frontend/src/components/SuggestedWorkflows.jsx b/frontend/src/components/SuggestedWorkflows.jsx index 1b9148f1..1f01529a 100644 --- a/frontend/src/components/SuggestedWorkflows.jsx +++ b/frontend/src/components/SuggestedWorkflows.jsx @@ -13,7 +13,7 @@ import { CircularProgress, Tooltip, Dialog, -} from "@material-ui/core"; +} from "@mui/material"; import { Close as CloseIcon, diff --git a/frontend/src/components/UsecaseSearch.jsx b/frontend/src/components/UsecaseSearch.jsx index 03fd619f..bc7a6aad 100644 --- a/frontend/src/components/UsecaseSearch.jsx +++ b/frontend/src/components/UsecaseSearch.jsx @@ -1,7 +1,8 @@ import React, { useState, useEffect } from "react"; +import { toast } from 'react-toastify'; import theme from '../theme.jsx'; import { useNavigate, Link } from "react-router-dom"; -import { useAlert } from "react-alert"; +//import { useAlert import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; import PaperComponent from "../components/PaperComponent.jsx" import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; @@ -25,7 +26,7 @@ import { CircularProgress, Tooltip, Divider, -} from "@material-ui/core"; +} from "@mui/material"; const defaultValue = {"id": "", "name": "Build your own", "source": {"text": "No trigger selected", "error": ""}, @@ -349,14 +350,14 @@ const UsecaseSearch = (props) => { const [firstRequest, setFirstRequest] = React.useState(true); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - const alert = useAlert() + //const alert = useAlert() useEffect(() => { // if (firstRequest !== true && workflow.id !== undefined && autotry === true && setUsecaseSearch !== undefined && authenticationModalOpen === false && configureWorkflowModalOpen === false) { // if (autotry === true && configureWorkflowModalOpen === false && workflow.id !== undefined && setUsecaseSearch !== undefined) { console.log("Close it?") - alert.info("Workflow successfully added! Add more apps, and we will suggest more workflows") + toast("Workflow successfully added! Add more apps, and we will suggest more workflows") if (setCloseWindow !== undefined) { setCloseWindow(true) @@ -793,7 +794,7 @@ const UsecaseSearch = (props) => { console.log("Deleted workflow") }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Delete workflow error: ", error.toString()); }) } @@ -822,7 +823,7 @@ const UsecaseSearch = (props) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Get workflows error: ", error.toString()); }) } @@ -894,9 +895,9 @@ const UsecaseSearch = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Error setting workflow: ", responseJson.reason) + toast("Error setting workflow: ", responseJson.reason) } else { - alert.error("Error setting workflow.") + toast("Error setting workflow.") } return @@ -905,7 +906,7 @@ const UsecaseSearch = (props) => { return responseJson; }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); } @@ -930,7 +931,7 @@ const UsecaseSearch = (props) => { if (responseJson.success === false) { if (responseJson.reason !== null && responseJson.reason !== undefined) { - //alert.error(responseJson.reason) + //toast(responseJson.reason) } if (responseJson.source === "") { @@ -1009,13 +1010,13 @@ const UsecaseSearch = (props) => { responseJson.status, ).then((response) => { if (response !== undefined) { - alert.success("Successfully generated " + responseJson.name); + toast("Successfully generated " + responseJson.name); } }); } }) .catch((error) => { - alert.error("Generate error: " + error.toString()); + toast("Generate error: " + error.toString()); }) @@ -1057,7 +1058,7 @@ const UsecaseSearch = (props) => { .catch((error) => { setIsUploading(false) console.log("Merge err: ", error.toString()) - //alert.error("Err: " + error.toString()); + //toast("Err: " + error.toString()); }); } @@ -1207,7 +1208,7 @@ const UsecaseSearch = (props) => { } if (changed) { - //alert.error("Errors were found. Click them to sort sort them out or go to the next usecase.") + //toast("Errors were found. Click them to sort sort them out or go to the next usecase.") //setUpdate(Math.random()) //setIsUploading(false) @@ -1268,13 +1269,13 @@ const UsecaseSearch = (props) => { }) .then((responseJson) => { if (responseJson.success === false) { - alert.error("Failed to activate the app") + toast("Failed to activate the app") } else { - //alert.success("App activated for your organization! Refresh the page to use the app.") + //toast("App activated for your organization! Refresh the page to use the app.") } }) .catch(error => { - //alert.error(error.toString()) + //toast(error.toString()) console.log("Activate app error: ", error.toString()) }); } @@ -1304,9 +1305,9 @@ const UsecaseSearch = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Failed updating default app: " + responseJson.reason) + toast("Failed updating default app: " + responseJson.reason) } else { - alert.error("Failed to update framework for your org.") + toast("Failed to update framework for your org.") } } else { @@ -1319,7 +1320,7 @@ const UsecaseSearch = (props) => { //setFrameworkData(responseJson) }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); //setFrameworkLoaded(true) }) } @@ -1516,7 +1517,7 @@ const UsecaseSearch = (props) => { return (
    { if (subdata.disabled === true) { - //alert.info("Usecase not available yet.") + //toast("Usecase not available yet.") return } diff --git a/frontend/src/components/WelcomeForm2.jsx b/frontend/src/components/WelcomeForm2.jsx index da66fb08..f0b71273 100644 --- a/frontend/src/components/WelcomeForm2.jsx +++ b/frontend/src/components/WelcomeForm2.jsx @@ -1,872 +1,730 @@ -import React, { useState, useEffect } from "react"; -import ReactGA from 'react-ga4'; -import Button from "@material-ui/core/Button"; -import Checkbox from '@mui/material/Checkbox'; +import React, { useState, useEffect, useRef } from "react"; +import ReactGA from "react-ga4"; +import Checkbox from "@mui/material/Checkbox"; -import AliceCarousel from 'react-alice-carousel'; -import 'react-alice-carousel/lib/alice-carousel.css'; +import AliceCarousel from "react-alice-carousel"; +import "react-alice-carousel/lib/alice-carousel.css"; -import SearchIcon from '@mui/icons-material/Search'; -import EmailIcon from '@mui/icons-material/Email'; -import NewReleasesIcon from '@mui/icons-material/NewReleases'; -import ExtensionIcon from '@mui/icons-material/Extension'; -import LightbulbIcon from '@mui/icons-material/Lightbulb'; -import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; -import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; - -import theme from '../theme.jsx'; +import SearchIcon from "@mui/icons-material/Search"; +import EmailIcon from "@mui/icons-material/Email"; +import NewReleasesIcon from "@mui/icons-material/NewReleases"; +import ExtensionIcon from "@mui/icons-material/Extension"; +import LightbulbIcon from "@mui/icons-material/Lightbulb"; +import TrendingFlatIcon from "@mui/icons-material/TrendingFlat"; +import theme from "../theme.jsx"; +import CheckBoxSharpIcon from "@mui/icons-material/CheckBoxSharp"; import { - Fade, - IconButton, - FormGroup, - FormControl, - InputLabel, - FormLabel, - FormControlLabel, - Select, - MenuItem, - Grid, - Paper, - Typography, - TextField, - Zoom, - List, - ListItem, - ListItemText, - Divider, - Tooltip, - Chip, - ButtonGroup, -} from "@material-ui/core"; -import { useAlert } from "react-alert"; + Button, + Collapse, + IconButton, + FormGroup, + FormControl, + InputLabel, + FormLabel, + FormControlLabel, + Select, + MenuItem, + Grid, + Paper, + Typography, + TextField, + Zoom, + List, + ListItem, + ListItemText, + Divider, + Tooltip, + Chip, + ButtonGroup, +} from "@mui/material"; +//import { useAlert import { useNavigate, Link } from "react-router-dom"; -import WorkflowSearch from '../components/Workflowsearch.jsx'; -import AuthenticationItem from '../components/AuthenticationItem.jsx'; -import WorkflowPaper from "../components/WorkflowPaper.jsx" -import UsecaseSearch from "../components/UsecaseSearch.jsx" - +import WorkflowSearch from "../components/Workflowsearch.jsx"; +import AuthenticationItem from "../components/AuthenticationItem.jsx"; +import WorkflowPaper from "../components/WorkflowPaper.jsx"; +import UsecaseSearch from "../components/UsecaseSearch.jsx"; +import ExploreWorkflow from "../components/ExploreWorkflow.jsx"; +import AppSelection from "../components/AppSelection.jsx"; const responsive = { - 0: { items: 1 }, + 0: { items: 1 }, +}; + +const imagestyle = { + height: 40, + borderRadius: 40, + //border: "2px solid rgba(255,255,255,0.3)", }; const WelcomeForm = (props) => { - const { userdata, globalUrl, discoveryWrapper, setDiscoveryWrapper, appFramework, getFramework, activeStep, setActiveStep, steps, skipped, setSkipped, getApps, apps, handleSetSearch, usecaseButtons, defaultSearch, setDefaultSearch, selectionOpen, setSelectionOpen, } = props + const { + userdata, + globalUrl, + discoveryWrapper, + setDiscoveryWrapper, + appFramework, + getFramework, + activeStep, + setActiveStep, + steps, + skipped, + setSkipped, + getApps, + apps, + handleSetSearch, + usecaseButtons, + defaultSearch, + setDefaultSearch, + selectionOpen, + setSelectionOpen, + checkLogin, + } = props; + const [moreButton, setMoreButton] = useState(false); + const ref = useRef() + const [usecaseItems, setUsecaseItems] = useState([ + { + search: "Phishing", + usecase_search: undefined, + }, + { + search: "Enrichment", + usecase_search: undefined, + }, + { + search: "Enrichment", + usecase_search: "SIEM alert enrichment", + }, + { + search: "Build your own", + usecase_search: undefined, + }, + ]); + /* +
    + +
    + , +
    + +
    + , +
    + +
    + , +
    + +
    + ]) + */ + const [name, setName] = React.useState("") + const [orgName, setOrgName] = React.useState("") + const [role, setRole] = React.useState("") + const [orgType, setOrgType] = React.useState("") + const [finishedApps, setFinishedApps] = React.useState([]) + const [authentication, setAuthentication] = React.useState([]); - const [usecaseItems, setUsecaseItems] = useState([ - { - "search": "Phishing", - "usecase_search": undefined, - }, - { - "search": "Enrichment", - "usecase_search": undefined, - }, - { - "search": "Enrichment", - "usecase_search": "SIEM alert enrichment", - }, - { - "search": "Build your own", - "usecase_search": undefined, - }]) + const [thumbIndex, setThumbIndex] = useState(0); + const [thumbAnimation, setThumbAnimation] = useState(false); + const [clickdiff, setclickdiff] = useState(0); + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) - /* -
    - -
    - , -
    - -
    - , -
    - -
    - , -
    - -
    - ]) - */ + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + //const alert = useAlert(); + let navigate = useNavigate(); - const [discoveryData, setDiscoveryData] = React.useState({}) - const [name, setName] = React.useState("") - const [orgName, setOrgName] = React.useState("") - const [role, setRole] = React.useState("") - const [orgType, setOrgType] = React.useState("") - const [finishedApps, setFinishedApps] = React.useState([]) - const [authentication, setAuthentication] = React.useState([]); - const [newSelectedApp, setNewSelectedApp] = React.useState({}) - const [thumbIndex, setThumbIndex] = useState(0); - const [thumbAnimation, setThumbAnimation] = useState(false); - const [clickdiff, setclickdiff] = useState(0); + const iconStyles = { + color: "rgba(255, 255, 255, 1)", + }; - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - - const alert = useAlert(); - let navigate = useNavigate(); - - const onNodeSelect = (label) => { - if (setDiscoveryWrapper !== undefined) { - setDiscoveryWrapper( - {"id": label} - ) - } - - if (isCloud) { - ReactGA.event({ - category: "welcome", - action: `click_${label}`, - label: "", - }) - } - - setSelectionOpen(true) - setDefaultSearch(label) + useEffect(() => { + if (userdata.id === undefined) { + return; } - useEffect(() => { - if (userdata.id === undefined) { - return - } + if ( + userdata.name !== undefined && + userdata.name !== null && + userdata.name.length > 0 + ) { + setName(userdata.name); + } - if (userdata.name !== undefined && userdata.name !== null && userdata.name.length > 0) { - setName(userdata.name) - } + if ( + userdata.active_org !== undefined && + userdata.active_org.name !== undefined && + userdata.active_org.name !== null && + userdata.active_org.name.length > 0 + ) { + setOrgName(userdata.active_org.name); + } + }, [userdata]); - if (userdata.active_org !== undefined && userdata.active_org.name !== undefined && userdata.active_org.name !== null && userdata.active_org.name.length > 0) { - setOrgName(userdata.active_org.name) - } - }, [userdata]) + useEffect(() => { + if (discoveryWrapper === undefined || discoveryWrapper.id === undefined) { + setDefaultSearch(""); + var newfinishedApps = finishedApps; + newfinishedApps.push(defaultSearch); + setFinishedApps(finishedApps); + } + }, [discoveryWrapper]); - useEffect(() => { - if (discoveryWrapper === undefined || discoveryWrapper.id === undefined) { - setDefaultSearch("") - var newfinishedApps = finishedApps - newfinishedApps.push(defaultSearch) - setFinishedApps(finishedApps) - } - }, [discoveryWrapper]) - - useEffect(() => { - if ( - window.location.search !== undefined && - window.location.search !== null - ) { - const urlSearchParams = new URLSearchParams(window.location.search); - const params = Object.fromEntries(urlSearchParams.entries()); - const foundTab = params["tab"]; - if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) { - if (foundTab === 3 || foundTab === "3") { - //console.log("Set search!") - } - } else { - //navigate(`/welcome?tab=1`) - } - - const foundTemplate = params["workflow_template"]; - if (foundTemplate !== null && foundTemplate !== undefined) { - console.log("Found workflow template: ", foundTemplate) - - var sourceapp = undefined - var destinationapp = undefined - var action = undefined - const srcapp = params["source_app"]; - if (srcapp !== null && srcapp !== undefined) { - sourceapp = srcapp - } - - const dstapp = params["dest_app"]; - if (dstapp !== null && dstapp !== undefined) { - destinationapp = dstapp - } - - const act = params["action"]; - if (act !== null && act !== undefined) { - action = act - } + useEffect(() => { + if (window.location.search !== undefined && window.location.search !== null) { - //defaultSearch={foundTemplate} - // - usecaseItems[0] = { - "search": "enrichment", - "usecase_search": foundTemplate, - "sourceapp": sourceapp, - "destinationapp": destinationapp, - "autotry": action === "try", - } + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); - console.log("Adding: ", usecaseItems[0]) + const foundTemplate = params["workflow_template"]; + if (foundTemplate !== null && foundTemplate !== undefined) { + console.log("Found workflow template: ", foundTemplate); - setUsecaseItems(usecaseItems) - } - } - }, []) - - const isStepOptional = step => { - return step === 1 - } - - const sendUserUpdate = (name, role, userId) => { - const data = { - "tutorial": "welcome", - "firstname": name, - "company_role": role, - "user_id": userId, - } - - const url = `${globalUrl}/api/v1/users/updateuser` - fetch(url, { - mode: "cors", - method: "PUT", - 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) { - console.log("Update user success") - //alert.error("Failed updating org: ", responseJson.reason); - } else { - console.log("Update success!") - //alert.success("Successfully edited org!"); - } - }) - ) - .catch((error) => { - console.log("Update err: ", error.toString()) - //alert.error("Err: " + error.toString()); - }); - } - - const sendOrgUpdate = (orgname, company_type, orgId, priority) => { - var data = { - org_id: orgId, - }; - - if (orgname.length > 0) { - data.name = orgname - } - - if (company_type.length > 0) { - data.company_type = company_type - } - - if (priority.length > 0) { - data.priority = priority - } - - const url = globalUrl + `/api/v1/orgs/${orgId}`; - 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) { - console.log("Update of org failed") - //alert.error("Failed updating org: ", responseJson.reason); - } else { - //alert.success("Successfully edited org!"); - } - }) - ) - .catch((error) => { - console.log("Update err: ", error.toString()) - //alert.error("Err: " + error.toString()); - }); - } - - var workflowDelay = -50 - const NewHits = ({ hits }) => { - const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) - var counted = 0 - - const paperAppContainer = { - display: "flex", - flexWrap: "wrap", - alignContent: "space-between", - marginTop: 5, - } - - return ( - - {hits.map((data, index) => { - workflowDelay += 50 - - if (index > 3) { - return null - } - - return ( - - - - - - ) - })} - - ) - } - - const isStepSkipped = step => { - return skipped.has(step) - } - - const handleNext = () => { - setDefaultSearch("") - - if (activeStep === 0) { - console.log("Should send basic information about org (fetch)") - setclickdiff(240) - navigate(`/welcome?tab=2`) - - if (isCloud) { - ReactGA.event({ - category: "welcome", - action: "click_page_one_next", - label: "", - }) - } - - if (userdata.active_org !== undefined && userdata.active_org.id !== undefined && userdata.active_org.id !== null && userdata.active_org.id.length > 0) { - sendOrgUpdate(orgName, orgType, userdata.active_org.id, "") - } - - if (userdata.id !== undefined && userdata.id !== null && userdata.id.length > 0) { - sendUserUpdate(name, role, userdata.id) - } - - } else if (activeStep === 1) { - console.log("Should send secondary info about apps and other things") - setDiscoveryWrapper({}) - - navigate(`/welcome?tab=3`) - //handleSetSearch("Enrichment", "2. Enrich") - handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase) - getApps() - - // Make sure it's up to date - if (getFramework !== undefined) { - getFramework() - } - } else if (activeStep === 2) { - console.log("Should send third page with workflows activated and the like") - } - - - let newSkipped = skipped; - if (isStepSkipped(activeStep)) { - newSkipped = new Set(newSkipped.values()); - newSkipped.delete(activeStep); + var sourceapp = undefined; + var destinationapp = undefined; + var action = undefined; + const srcapp = params["source_app"]; + if (srcapp !== null && srcapp !== undefined) { + sourceapp = srcapp; } - setActiveStep(prevActiveStep => prevActiveStep + 1); - setSkipped(newSkipped); + const dstapp = params["dest_app"]; + if (dstapp !== null && dstapp !== undefined) { + destinationapp = dstapp; + } + + const act = params["action"]; + if (act !== null && act !== undefined) { + action = act; + } + + //defaultSearch={foundTemplate} + // + usecaseItems[0] = { + search: "enrichment", + usecase_search: foundTemplate, + sourceapp: sourceapp, + destinationapp: destinationapp, + autotry: action === "try", + }; + + console.log("Adding: ", usecaseItems[0]); + + setUsecaseItems(usecaseItems); + } } + }, []); - const handleBack = () => { - setActiveStep(prevActiveStep => prevActiveStep - 1); + const isStepOptional = (step) => { + return step === 1; + }; - if (activeStep === 2) { - setDiscoveryWrapper({}) - - if (getFramework !== undefined) { - getFramework() - } - navigate("/welcome?tab=2") - } else if (activeStep === 1) { - navigate("/welcome?tab=1") - } + const sendUserUpdate = (name, role, userId) => { + const data = { + tutorial: "welcome", + firstname: name, + company_role: role, + user_id: userId, }; - const handleSkip = () => { - setclickdiff(240) - if (!isStepOptional(activeStep)) { - throw new Error("You can't skip a step that isn't optional."); - } - setActiveStep(prevActiveStep => prevActiveStep + 1); - setSkipped(prevSkipped => { - const newSkipped = new Set(prevSkipped.values()); - newSkipped.add(activeStep); - return newSkipped; + const url = `${globalUrl}/api/v1/users/updateuser`; + fetch(url, { + mode: "cors", + method: "PUT", + 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) { + console.log("Update user success"); + //toast("Failed updating org: ", responseJson.reason); + } else { + console.log("Update success!"); + //toast("Successfully edited org!"); + } + }) + ) + .catch((error) => { + console.log("Update err: ", error.toString()); + //toast("Err: " + error.toString()); + }); + }; + + const sendOrgUpdate = (orgname, company_type, orgId, priority) => { + var data = { + org_id: orgId, + }; + + if (orgname.length > 0) { + data.name = orgname; + } + + if (company_type.length > 0) { + data.company_type = company_type; + } + + if (priority.length > 0) { + data.priority = priority; + } + + const url = globalUrl + `/api/v1/orgs/${orgId}`; + 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) { + console.log("Update of org failed"); + //toast("Failed updating org: ", responseJson.reason); + } else { + //toast("Successfully edited org!"); + } + }) + ) + .catch((error) => { + console.log("Update err: ", error.toString()); + //toast("Err: " + error.toString()); + }); + }; + + var workflowDelay = -50; + const NewHits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1); + var counted = 0; + + const paperAppContainer = { + display: "flex", + flexWrap: "wrap", + alignContent: "space-between", + marginTop: 5, + }; + + return ( + + {hits.map((data, index) => { + workflowDelay += 50; + + if (index > 3) { + return null; + } + + return ( + + + + + + ); + })} + + ); + }; + + const isStepSkipped = (step) => { + return skipped.has(step); + }; + + const handleNext = () => { + setDefaultSearch(""); + + if (activeStep === 0) { + console.log("Should send basic information about org (fetch)"); + setclickdiff(240); + navigate(`/welcome?tab=2`); + setActiveStep(1); + + if (isCloud) { + ReactGA.event({ + category: "welcome", + action: "click_page_one_next", + label: "", }); - }; + } - const handleReset = () => { - setActiveStep(0); - }; + if ( + userdata.active_org !== undefined && + userdata.active_org.id !== undefined && + userdata.active_org.id !== null && + userdata.active_org.id.length > 0 + ) { + sendOrgUpdate(orgName, orgType, userdata.active_org.id, ""); + } - useEffect(() => { - console.log("Selected app changed (effect)") - }, [newSelectedApp]) + if ( + userdata.id !== undefined && + userdata.id !== null && + userdata.id.length > 0 + ) { + sendUserUpdate(name, role, userdata.id); + } + } else if (activeStep === 1) { + console.log("Should send secondary info about apps and other things"); + setDiscoveryWrapper({}); - //const buttonWidth = 145 - const buttonWidth = 450 - const buttonMargin = 10 - const sizing = 475 - const buttonStyle = { - flex: 1, - width: "100%", - padding: 25, - margin: buttonMargin, - fontSize: 18, - } + navigate(`/welcome?tab=3`); + //handleSetSearch("Enrichment", "2. Enrich") + handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase); + getApps(); + setActiveStep(2); - const slideNext = () => { - if (!thumbAnimation && thumbIndex < usecaseItems.length - 1) { - //handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase) - setThumbIndex(thumbIndex + 1); - } else if (!thumbAnimation && thumbIndex === usecaseItems.length - 1) { - setThumbIndex(0) - } - }; + // Make sure it's up to date + if (getFramework !== undefined) { + getFramework(); + } + } else if (activeStep === 2) { + console.log( + "Should send third page with workflows activated and the like" + ); + } - const slidePrev = () => { - if (!thumbAnimation && thumbIndex > 0) { - setThumbIndex(thumbIndex - 1); - } else if (!thumbAnimation && thumbIndex === 0) { - setThumbIndex(usecaseItems.length-1) - } - }; + let newSkipped = skipped; + if (isStepSkipped(activeStep)) { + newSkipped = new Set(newSkipped.values()); + newSkipped.delete(activeStep); + } - const newButtonStyle = { - padding: 22, - flex: 1, - margin: buttonMargin, - minWidth: buttonWidth, - maxWidth: buttonWidth, - } + setActiveStep((prevActiveStep) => prevActiveStep + 1); + setSkipped(newSkipped); + }; + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); - const formattedCarousel = appFramework === undefined || appFramework === null ? [] : usecaseItems.map((item, index) => { - return ( -
    - -
    - ) - }) + if (activeStep === 2) { + setDiscoveryWrapper({}); - const getStepContent = (step) => { - switch (step) { - case 0: - return ( - - - {/*isCloud ? null : + if (getFramework !== undefined) { + getFramework(); + } + navigate("/welcome?tab=2"); + } else if (activeStep === 1) { + navigate("/welcome?tab=1"); + } + }; + + const handleSkip = () => { + setclickdiff(240); + if (!isStepOptional(activeStep)) { + throw new Error("You can't skip a step that isn't optional."); + } + setActiveStep((prevActiveStep) => prevActiveStep + 1); + setSkipped((prevSkipped) => { + const newSkipped = new Set(prevSkipped.values()); + newSkipped.add(activeStep); + return newSkipped; + }); + }; + + const handleReset = () => { + setActiveStep(0); + }; + + //const buttonWidth = 145 + const buttonWidth = 450; + const buttonMargin = 10; + const sizing = 510; + const bottomButtonStyle = { + borderRadius: 200, + marginTop: moreButton ? 44 : "", + height: 51, + width: 464, + fontSize: 16, + // background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)", + background: "linear-gradient(90deg, #F86744 0%, #F34475 100%)", + padding: "16px 24px", + // top: 20, + // margin: "auto", + itemAlign: "center", + // marginLeft: "65px", + }; + + const slideNext = () => { + if (!thumbAnimation && thumbIndex < usecaseItems.length - 1) { + //handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase) + setThumbIndex(thumbIndex + 1); + } else if (!thumbAnimation && thumbIndex === usecaseItems.length - 1) { + setThumbIndex(0); + } + }; + + const slidePrev = () => { + if (!thumbAnimation && thumbIndex > 0) { + setThumbIndex(thumbIndex - 1); + } else if (!thumbAnimation && thumbIndex === 0) { + setThumbIndex(usecaseItems.length - 1); + } + }; + + const newButtonStyle = { + padding: 22, + flex: 1, + margin: buttonMargin, + minWidth: buttonWidth, + maxWidth: buttonWidth, + }; + + const formattedCarousel = + appFramework === undefined || appFramework === null + ? [] + : usecaseItems.map((item, index) => { + return ( +
    + +
    + ); + }); + + const getStepContent = (step) => { + switch (step) { + case 0: + return ( + + + {/*isCloud ? null : This data will be used within the product and NOT be shared unless
    cloud synchronization is configured. */} - - In order to understand how we best can help you find relevant Usecases, please provide the information below. This is optional, but highly encouraged. - - - { - setName(e.target.value) - }} - /> - - - { - setOrgName(e.target.value) - }} - /> - - - - Your Role - - - - - - Company Type - - - - - - ) - case 1: - return ( - -
    - - Apps for each category are shown based on your activity and can be changed by clicking their icon. We will help you connect them later. - - {/*The app framework helps us access and authenticate the most important APIs for you. */} + + In order to understand how we best can help you find relevant + Usecases, please provide the information below. This is + optional, but highly encouraged. + + + { + setName(e.target.value); + }} + /> + + + { + setOrgName(e.target.value); + }} + /> + + + + + Your Role + + + + + + + + Company Type + + + + + + + ); + case 1: + return ( + + ) + case 2: + return ( + +
    - {/* - - - What is your development experience? - - - - */} - - {/*Find your integrations!*/} -
    - -
    -
    - - -
    -
    - - -
    - {/* - What do you want to automate first ? - - { onNodeSelect("Email") }} />} - label="Email" - labelPlacement="Email" - /> - { onNodeSelect("SIEM") }} />} - label="SIEM" - labelPlacement="SIEM" - /> - { onNodeSelect("EDR") }} />} - label="EDR" - labelPlacement="EDR" - /> - - */} -
    - {/* - - - What tools do you use? - - - - */} -
    - - ) - case 2: - return ( - -
    - - These are some of our Workflow templates, used to start new Workflows. Use the right and left buttons to find new Usecases, and click the orange button to build it. - - -
    -
    - - { - slidePrev() - }} - > - - - -
    - -
    - - { - slideNext() - }} - > - - - -
    -
    -
    -
    - ) - default: - return "unknown step" - } +
    +
    +
    + +
    +
    +
    +
    + + ) + default: + return "unknown step" } + } - const extraHeight = isCloud ? -7 : 0 - return ( -
    - {/*selectionOpen ? + + const extraHeight = isCloud ? -7 : 0; + return ( +
    + {/*selectionOpen ? : null*/} -
    - {activeStep === steps.length ? ( -
    - You Will be Redirected to getting Start Page Wait for 5-sec. - - - -
    - ) : ( -
    - {getStepContent(activeStep)} -
    - {activeStep === 2 || activeStep === 1 ? -
    - - -
    - : -
    - - {/*isStepOptional(activeStep) && ( - - )*/} - - {activeStep === 0 ? - + + +
    + ) : ( +
    + {getStepContent(activeStep)} +
    + )} +
    +
    + ); +}; - setActiveStep(1) - navigate(`/welcome?tab=2`) - }} - style={{marginLeft: 240, }} - disabled={activeStep !== 0} - > - Skip - - : null} -
    - } -
    - )} -
    -
    - ); -} - -export default WelcomeForm +export default WelcomeForm; diff --git a/frontend/src/components/WorkflowGrid.jsx b/frontend/src/components/WorkflowGrid.jsx index c086790d..589e4f7d 100644 --- a/frontend/src/components/WorkflowGrid.jsx +++ b/frontend/src/components/WorkflowGrid.jsx @@ -1,9 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { useTheme } from '@material-ui/core/styles'; import {Link} from 'react-router-dom'; +import theme from '../theme.jsx'; +import { removeQuery } from '../components/ScrollToTop.jsx'; -import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; +import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material'; import algoliasearch from 'algoliasearch/lite'; import { InstantSearch, Configure, connectSearchBox, connectHits } from 'react-instantsearch-dom'; @@ -18,28 +19,26 @@ import { Tooltip, Zoom, Chip, -} from '@material-ui/core'; +} from '@mui/material'; import WorkflowPaper from "../components/WorkflowPaper.jsx" import WorkflowPaperNew from "../components/WorkflowPaperNew.jsx" const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const AppGrid = props => { - const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, } = props - - const isCloud = - window.location.host === "localhost:3002" || - window.location.host === "shuffler.io"; + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs - const theme = useTheme(); //const [apps, setApps] = React.useState([]); //const [filteredApps, setFilteredApps] = React.useState([]); const [formMail, setFormMail] = React.useState(""); const [message, setMessage] = React.useState(""); const [formMessage, setFormMessage] = React.useState(""); - const [usecases, setUsecases] = React.useState([]); + const [usecases, setUsecases] = React.useState([]); + + const [localMessage, setLocalMessage] = React.useState(""); const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,} @@ -71,7 +70,7 @@ const AppGrid = props => { .then(response => { if (response.success === true) { setFormMessage(response.reason) - //alert.info("Thanks for submitting!") + //toast("Thanks for submitting!") } else { setFormMessage(errorMessage) } @@ -154,23 +153,25 @@ const AppGrid = props => { return response.json(); }) .then((responseJson) => { - if (responseJson.success !== false) { - console.log("Usecases: ", responseJson) - //handleKeysetting(responseJson, workflows) - } + if (responseJson.success !== false) { + //handleKeysetting(responseJson, workflows) + } }) .catch((error) => { - //alert.error("ERROR: " + error.toString()); + //toast("ERROR: " + error.toString()); console.log("ERROR: " + error.toString()); }); }; useEffect(() => { fetchUsecases() + }, []) + // value={currentRefinement} const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { + var defaultSearch = "" useEffect(() => { if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { const urlSearchParams = new URLSearchParams(window.location.search) @@ -179,20 +180,30 @@ const AppGrid = props => { if (foundQuery !== null && foundQuery !== undefined) { console.log("Got query: ", foundQuery) refine(foundQuery) + defaultSearch = foundQuery } } }, []) + if (localMessage !== inputsearch && inputsearch !== undefined && inputsearch !== null && inputsearch.length > 0) { + //setLocalMessage(inputsearch) + refine(inputsearch) + defaultSearch = inputsearch + return null + } else if (onlyResults === true) { + // Don't return anything unless refinement works + return null + } + return (
    + {onlyResults !== true ? @@ -207,12 +218,13 @@ const AppGrid = props => { placeholder="Find Workflows..." id="shuffle_search_field" onChange={(event) => { + removeQuery("q") refine(event.currentTarget.value) }} limit={5} /> - {/*isSearchStalled ? 'My search is stalled' : ''*/} - + : null} + ) } @@ -229,29 +241,33 @@ const AppGrid = props => { var counted = 0 return ( - - {hits.map((data, index) => { - workflowDelay += 50 +
    + {onlyResults === true && hits.length > 0 ? + null + : null} + + {hits.map((data, index) => { + workflowDelay += 50 - if (counted === 12/xs*rowHandler) { - return null - } + if (counted === 12/xs*rowHandler) { + return null + } - counted += 1 + counted += 1 - return ( - - + return ( + + {/**/} {alternativeView === true ? : } - - ) - })} - + ) + })} + +
    ) } @@ -332,11 +348,11 @@ const AppGrid = props => { fullWidth={true} placeholder="What apps do you want to see?" type="" - id="standard-required" + id="standard-required" margin="normal" variant="outlined" autoComplete="off" - onChange={e => setMessage(e.target.value)} + onChange={e => setMessage(e.target.value)} />
    : null } - - - - Search by - - - Algolia logo - - + {onlyResults === true ? null : + + + Search by + + + Algolia logo + + + }
    ) } diff --git a/frontend/src/components/WorkflowGridNew.jsx b/frontend/src/components/WorkflowGridNew.jsx deleted file mode 100644 index f0a06e87..00000000 --- a/frontend/src/components/WorkflowGridNew.jsx +++ /dev/null @@ -1,291 +0,0 @@ -import React, { useState, useEffect, useLayoutEffect } from "react"; -import theme from '../theme'; - -import { - Chip, - Typography, - Paper, - Avatar, - Grid, - Tooltip, -} from "@material-ui/core"; - -import { - AvatarGroup, -} from "@mui/material" - -import { - Restore as RestoreIcon, - Edit as EditIcon, - BubbleChart as BubbleChartIcon, - MoreVert as MoreVertIcon, -} from '@material-ui/icons'; - -import { useNavigate, Link, useParams } from "react-router-dom"; - -const workflowActionStyle = { - display: "flex", - width: 160, - height: 44, - justifyContent: "space-between", -} - -const paperAppStyle = { - minHeight: 130, - maxHeight: 130, - overflow: "hidden", - width: "100%", - color: "white", - backgroundColor: theme.palette.surfaceColor, - padding: "12px 12px 0px 15px", - borderRadius: 5, - display: "flex", - boxSizing: "border-box", - position: "relative", -} - -const chipStyle = { - backgroundColor: "#3d3f43", - marginRight: 5, - paddingLeft: 5, - paddingRight: 5, - height: 28, - cursor: "pointer", - borderColor: "#3d3f43", - color: "white", -} - -const WorkflowPaper = (props) => { - const { data } = props; - let navigate = useNavigate(); - - const [open, setOpen] = React.useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); - const appGroup = data.action_references === undefined || data.action_references === null ? [] : data.action_references - - //console.log("Workflow: ", data) - var boxColor = "#86c142"; - - var parsedName = data.name; - if ( - parsedName !== undefined && - parsedName !== null && - parsedName.length > 20 - ) { - parsedName = parsedName.slice(0, 21) + ".."; - } - - - const imageStyle = { - width: 24, - height: 24, - marginRight: 10, - border: "1px solid rgba(255,255,255,0.3)", - } - var image = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.image !== undefined && data.creator_info.image !== null && data.creator_info.image.length > 0 ? : - const creatorname = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.username !== undefined && data.creator_info.username !== null && data.creator_info.username.length > 0 ? data.creator_info.username : "" - var orgName = ""; - var orgId = ""; - if ((data.objectID === undefined || data.objectID === null) && data.id !== undefined && data.id !== null) { - data.objectID = data.id - } - - //console.log("IMG: ", data) - var parsedUrl = `/workflows/${data.objectID}` - if (data.__queryID !== undefined && data.__queryID !== null) { - parsedUrl += `?queryID=${data.__queryID}` - } - - return ( -
    - -
    - - - -
    { - if (data.creator_info !== undefined) { - navigate("/creators/"+data.creator_info.username) - } - }} - > - {image} -
    -
    - - - - {parsedName} - - - -
    - - {appGroup.length > 0 ? -
    - - {appGroup.map((app, index) => { - return ( -
    { - navigate("/apps/"+app.id) - }} - > - - - -
    - ) - })} -
    -
    - : - - - - - {data.actions === undefined || data.actions === null ? 1 : data.actions.length} - - - - } - - - - - {data.triggers === undefined || data.triggers === null ? 1 : data.triggers.length} - - - - - { - }} - > - - - - - {0} - - - -
    - - {data.tags !== undefined && data.tags !== null - ? data.tags.map((tag, index) => { - if (index >= 3) { - return null; - } - - return ( - - ); - }) - : null} - -
    - -
    - ) - } - -export default WorkflowPaper diff --git a/frontend/src/components/WorkflowPaper.jsx b/frontend/src/components/WorkflowPaper.jsx index 516756d2..7ab4c546 100644 --- a/frontend/src/components/WorkflowPaper.jsx +++ b/frontend/src/components/WorkflowPaper.jsx @@ -8,7 +8,7 @@ import { Avatar, Grid, Tooltip, -} from "@material-ui/core"; +} from "@mui/material"; import { AvatarGroup, @@ -19,7 +19,7 @@ import { Edit as EditIcon, BubbleChart as BubbleChartIcon, MoreVert as MoreVertIcon, -} from '@material-ui/icons'; +} from '@mui/icons-material'; import { useNavigate, Link, useParams } from "react-router-dom"; @@ -277,7 +277,7 @@ const WorkflowPaper = (props) => { > {data.tags !== undefined && data.tags !== null ? data.tags.map((tag, index) => { - if (index >= 3) { + if (index >= 2) { return null; } diff --git a/frontend/src/components/WorkflowPaperNew.jsx b/frontend/src/components/WorkflowPaperNew.jsx index 3234bdee..9d59c2a0 100644 --- a/frontend/src/components/WorkflowPaperNew.jsx +++ b/frontend/src/components/WorkflowPaperNew.jsx @@ -9,7 +9,7 @@ import { Grid, Tooltip, Button, -} from "@material-ui/core"; +} from "@mui/material"; import { AvatarGroup, @@ -20,7 +20,7 @@ import { Edit as EditIcon, BubbleChart as BubbleChartIcon, MoreVert as MoreVertIcon, -} from '@material-ui/icons'; +} from '@mui/icons-material'; import { useNavigate, Link, useParams } from "react-router-dom"; diff --git a/frontend/src/components/WorkflowTemplatePopup.jsx b/frontend/src/components/WorkflowTemplatePopup.jsx new file mode 100644 index 00000000..f1367092 --- /dev/null +++ b/frontend/src/components/WorkflowTemplatePopup.jsx @@ -0,0 +1,455 @@ +import React, { useState, useEffect } from "react"; + +import { toast } from "react-toastify" +import theme from '../theme.jsx'; +import { useNavigate, Link, useParams } from "react-router-dom"; +import { + Button, + Typography, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Drawer, + CircularProgress, + IconButton, + Tooltip, +} from "@mui/material"; + +import { + Check as CheckIcon, + TrendingFlat as TrendingFlatIcon, + Close as CloseIcon, +} from '@mui/icons-material'; + +import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup.jsx"; +import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; + +const WorkflowTemplatePopup = (props) => { + const { userdata, globalUrl, img1, srcapp, img2, dstapp, title, description, visualOnly, apps } = props; + + const [isActive, setIsActive] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const [modalOpen, setModalOpen] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [workflowLoading, setWorkflowLoading] = useState(false); + const [workflow, setWorkflow] = useState({}); + const [appAuthentication, setAppAuthentication] = React.useState(undefined); + + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + let navigate = useNavigate(); + + const imagestyleWrapper = { + height: 40, + width: 40, + borderRadius: 40, + border: "1px solid rgba(255,255,255,0.3)", + overflow: "hidden", + display: "flex", + } + + const imagestyleWrapperDefault = { + height: 40, + width: 40, + borderRadius: 40, + border: "1px solid rgba(255,255,255,0.3)", + overflow: "hidden", + display: "flex", + } + + const imagestyle = { + height: 40, + width: 40, + borderRadius: 40, + border: "1px solid rgba(255,255,255,0.3)", + overflow: "hidden", + } + + const imagestyleDefault = { + display: "block", + marginLeft: 11, + marginTop: 11, + height: 35, + width: "auto", + } + + if (title === undefined || title === null || title === "") { + console.log("No title for workflow template popup!"); + return null + } + + const getWorkflow = (workflowId) => { + fetch(`${globalUrl}/api/v1/workflows/${workflowId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + console.log("Error in workflow loading for ID ", workflowId) + } else { + setWorkflow(responseJson) + } + }) + .catch((error) => { + console.log("err in framework: ", error.toString()); + setWorkflowLoading(false) + }) + } + + const loadAppAuth = () => { + if (userdata === undefined || userdata === null) { + setErrorMessage("You need to be logged in to try usecases. Redirecting in 5 seconds...") + // Send the user to the login screen after 3 seconds + setTimeout(() => { + // Make it cancel if the state modalOpen changes + if (modalOpen === false) { + return + } + + navigate("/login?view=" + window.location.pathname + window.location.search) + }, 4500) + + return + } + + + fetch(`${globalUrl}/api/v1/apps/authentication`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for setting app auth :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + toast("Failed to get app auth: " + responseJson.reason); + return + } + + var newauth = []; + for (let authkey in responseJson.data) { + if (responseJson.data[authkey].defined === false) { + continue; + } + + newauth.push(responseJson.data[authkey]); + } + + setAppAuthentication(newauth); + }) + .catch((error) => { + //toast(error.toString()); + console.log("New auth error: ", error.toString()); + }); + } + + const getGeneratedWorkflow = () => { + // POST + // https://shuffler.io/api/v1/workflows/merge + // destination: {app_id: "b9c2feaf99b6309dabaeaa8518c61d3d", app_name: "Servicenow_API", app_version: "",…} + // id: "" + // middle:[] + // name: "Email analysis" + // source:{app_id: "accdaaf2eeba6a6ed43b2efc0112032d", app_name + + + if (srcapp.includes(":default") || dstapp.includes(":default")) { + toast("You need to select both a source and destination app before generating this workflow.") + return + } + + setWorkflowLoading(true) + + // FIXME: Remove hardcoding here after testing, and user srcapp/dstapp + const newsrcapp = srcapp + const newdstapp = dstapp + + const mergedata = { + name: title, + id: "", + source: { + app_name: newsrcapp, + }, + middle: [], + destination: { + app_name: newdstapp, + }, + } + + //fetch(globalUrl + "/api/v1/workflows/merge", { + fetch("https://shuffler.io/api/v1/workflows/merge", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(mergedata), + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + setWorkflowLoading(false) + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + console.log("Error in workflow template: ", responseJson.error); + + setErrorMessage("Failed to generate workflow for these tools - the Shuffle team has been notified. Click out of this window to continue. Contact support@shuffler.io for further assistance.") + + setIsActive(true) + //setTimeout(() => { + // setModalOpen(false) + //}, 5000) + } else { + console.log("Success in workflow template: ", responseJson); + setIsActive(true) + if (responseJson.workflow_id === "") { + console.log("Failed to build workflow for these tools. Closing in 3 seconds.") + return + } + + getWorkflow(responseJson.workflow_id) + } + }) + .catch((error) => { + console.log("err in framework: ", error.toString()); + setWorkflowLoading(false) + }) + } + + const isFinished = () => { + // Look for configuration fields being done in the current modal + // 1. Start by finding the modal + const template = document.getElementById("workflow-template") + if (template === null || template == undefined) { + return true + } + + // Find item in template with id app-config + const appconfig = template.getElementsByClassName("app-config") + if (appconfig === null || appconfig == undefined) { + return true + } + + return false + } + + const ModalView = () => { + return ( + { + setModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: "black", + color: "white", + minWidth: 700, + maxWidth: 700, + paddingTop: 75, + itemAlign: "center", + }, + }} + > + { + setModalOpen(false); + }} + > + + + + + Configure Workflow + + + Selected Workflow: + +
    + +
    + {workflowLoading ? +
    + Generating the Workflow... + + +
    + : +
    + + {errorMessage !== "" ? errorMessage : ""} + +
    + } + + {errorMessage === "" ? + + : null} +
    +
    + ) + } + + var parsedTitle = title + const maxlength = 30 + if (title.length > maxlength) { + parsedTitle = title.substring(0, maxlength) + "..." + } + + parsedTitle = parsedTitle.replaceAll("_", " ") + + const parsedDescription = description !== undefined && description !== null ? description.replaceAll("_", " ") : "" + + + return ( +
    + +
    { + setIsHovered(true) + }} + onMouseLeave={() => { + setIsHovered(false) + }} + onClick={() => { + if (visualOnly === true) { + console.log("Not showing more than visuals.") + return + } + + //setIsActive(!isActive) + if (errorMessage !== "") { + toast("Already failed to generate a workflow for this usecase. Please try again later or contact support@shuffler.io.") + + setModalOpen(true) + } else if (isActive) { + toast("Workflow already generated. Please try another workflow template!") + + // FIXME: Remove these? + loadAppAuth() + setModalOpen(true) + //getGeneratedWorkflow() + } else { + + loadAppAuth() + setModalOpen(true) + getGeneratedWorkflow() + } + }} + > +
    +
    + {img1 !== undefined && img1 !== "" && srcapp !== undefined && srcapp !== "" ? + + + + + + : + + } + {img2 !== undefined && img2 !== "" && dstapp !== undefined && dstapp !== "" ? + + + + + + + + + : + + } +
    +
    + + {parsedTitle} + + + {parsedDescription} + +
    +
    +
    + {isActive === true && errorMessage === "" ? + + : ""} +
    +
    +
    + ) +} + +export default WorkflowTemplatePopup diff --git a/frontend/src/components/Workflowsearch.jsx b/frontend/src/components/Workflowsearch.jsx index 152fdd83..b8f4c022 100644 --- a/frontend/src/components/Workflowsearch.jsx +++ b/frontend/src/components/Workflowsearch.jsx @@ -1,14 +1,14 @@ import React, { useState, useEffect } from 'react'; -import { useTheme } from '@material-ui/core/styles'; import {Link} from 'react-router-dom'; +import theme from '../theme.jsx'; -import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons'; +import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material'; //import algoliasearch from 'algoliasearch/lite'; import algoliasearch from 'algoliasearch'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; -import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core'; +import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@mui/material'; const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const WorkflowSearch = props => { @@ -16,7 +16,6 @@ const WorkflowSearch = props => { const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs - const theme = useTheme(); //const [apps, setApps] = React.useState([]); //const [filteredApps, setFilteredApps] = React.useState([]); const [formMail, setFormMail] = React.useState(""); @@ -54,7 +53,7 @@ const WorkflowSearch = props => { .then(response => { if (response.success === true) { setFormMessage(response.reason) - //alert.info("Thanks for submitting!") + //toast("Thanks for submitting!") } else { setFormMessage(errorMessage) } diff --git a/frontend/src/defaultCytoscapeStyle.js b/frontend/src/defaultCytoscapeStyle.js deleted file mode 100755 index 35c4b7f8..00000000 --- a/frontend/src/defaultCytoscapeStyle.js +++ /dev/null @@ -1,432 +0,0 @@ -const data = [ - { - selector: "node", - css: { - label: "data(label)", - "text-valign": "center", - "font-family": - "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif", - "font-weight": "lighter", - "margin-right": "10px", - "font-size": "18px", - width: "80px", - height: "80px", - color: "white", - padding: "10px", - margin: "5px", - "border-width": "1px", - "text-margin-x": "10px", - "z-index": 5001, - }, - }, - { - selector: "edge", - css: { - "target-arrow-shape": "triangle", - "target-arrow-color": "grey", - "curve-style": "unbundled-bezier", - label: "data(label)", - "text-margin-y": "-15px", - width: "5px", - color: "white", - "line-fill": "linear-gradient", - "line-gradient-stop-positions": ["0.0", "100"], - "line-gradient-stop-colors": ["grey", "grey"], - "z-index": 5001, - }, - }, - { - selector: `node[type="ACTION"]`, - css: { - shape: "roundrectangle", - "background-color": "#213243", - "border-color": "#81c784", - "background-width": "100%", - "background-height": "100%", - "border-radius": "5px", - "z-index": 5001, - }, - }, - { - selector: `node[type="COMMENT"]`, - css: { - shape: "roundrectangle", - color: "data(color)", - width: "data(width)", - height: "data(height)", - padding: "0px", - margin: "0px", - "background-color": "data(backgroundcolor)", - "background-image": "data(backgroundimage)", - "border-color": "#ffffff", - "text-margin-x": "0px", - "z-index": 4999, - "border-radius": "5px", - "background-opacity": "0.5", - "text-wrap": "wrap", - }, - }, - { - selector: `node[app_name="Shuffle Tools"]`, - css: { - width: "30px", - height: "30px", - "z-index": 5000, - "font-size": "0px", - "background-width": "75%", - "background-height": "75%", - "background-color": "data(iconBackground)", - "background-fill": "data(fillstyle)", - "background-gradient-direction": "to-right", - "background-gradient-stop-colors": "data(fillGradient)", - }, - }, - { - selector: `node[app_name="Testing"]`, - css: { - width: "30px", - height: "30px", - "z-index": 5000, - "font-size": "0px", - }, - }, - { - selector: `node[?small_image]`, - css: { - "background-image": "data(small_image)", - "text-halign": "right", - }, - }, - { - selector: `node[?large_image]`, - css: { - "background-image": "data(large_image)", - "text-halign": "right", - }, - }, - { - selector: `node[type="CONDITION"]`, - css: { - shape: "diamond", - "border-color": "##FFEB3B", - padding: "30px", - }, - }, - { - selector: `node[type="eventAction"]`, - css: { - "background-color": "#edbd21", - }, - }, - { - selector: `node[type="TRIGGER"]`, - css: { - shape: "octagon", - "border-radius": "5px", - "border-color": "orange", - "background-color": "#213243", - "background-width": "100%", - "background-height": "100%", - }, - }, - { - selector: `node[status="running"]`, - css: { - "border-color": "#81c784", - }, - }, - { - selector: `node[status="stopped"]`, - css: { - "border-color": "orange", - }, - }, - { - selector: 'node[type="mq"]', - css: { - "background-color": "#edbd21", - }, - }, - { - selector: "node[?isButton]", - css: { - shape: "ellipse", - width: "15px", - height: "15px", - "z-index": "5002", - "font-size": "0px", - border: "1px solid rgba(255,255,255,0.9)", - "background-image": "data(icon)", - "background-color": "data(iconBackground)", - }, - }, - { - selector: "node[?isSuggestion]", - css: { - shape: "ellipse", - width: "50px", - height: "50px", - "z-index": "5002", - "font-size": "0px", - border: "1px solid rgba(255,255,255,0.9)", - "background-image": "data(large_image)", - "background-color": "data(iconBackground)", - label: "data(label)", - }, - }, - { - selector: "node[?canConnect]", - css: { - "border-color": "#f86a3e", - "border-width": "10px", - "z-index": "5002", - "background-color": "#f86a3e", - }, - }, - { - selector: "node[?isDescriptor]", - css: { - shape: "ellipse", - "border-color": "#80deea", - width: "5px", - height: "5px", - "z-index": "5002", - "font-size": "10px", - "text-valign": "center", - "text-halign": "center", - border: "1px solid black", - "margin-right": "0px", - "text-margin-x": "0px", - "background-color": "data(imageColor)", - "background-image": "data(image)", - }, - }, - { - selector: "node[?isStartNode]", - css: { - shape: "ellipse", - "border-color": "#80deea", - width: "80px", - height: "80px", - "font-size": "18px", - "background-width": "100%", - "background-height": "100%", - }, - }, - { - selector: "node[!is_valid]", - css: { - "border-color": "red", - "border-width": "10px", - }, - }, - { - selector: ":selected", - css: { - "background-color": "#77b0d0", - "border-color": "#77b0d0", - "border-width": "20px", - }, - }, - { - selector: ".skipped-highlight", - css: { - "background-color": "grey", - "border-color": "grey", - "border-width": "8px", - "transition-property": "background-color", - "transition-duration": "0.5s", - }, - }, - { - selector: ".success-highlight", - css: { - "background-color": "#41dcab", - "border-color": "#41dcab", - "border-width": "5px", - "transition-property": "background-color", - "transition-duration": "0.5s", - }, - }, - { - selector: ".hover-highlight", - css: { - "background-color": "#5f9265", - "border-color": "#5f9265", - "border-width": "5px", - "transition-property": "background-color", - "transition-duration": "0.5s", - }, - }, - { - selector: ".failure-highlight", - css: { - "background-color": "#8e3530", - "border-color": "#8e3530", - "border-width": "5px", - "transition-property": "background-color", - "transition-duration": "0.5s", - }, - }, - { - selector: ".not-executing-highlight", - css: { - "background-color": "grey", - "border-color": "grey", - "border-width": "5px", - "transition-property": "#ffef47", - "transition-duration": "0.25s", - }, - }, - { - selector: ".executing-highlight", - css: { - "background-color": "#ffef47", - "border-color": "#ffef47", - "border-width": "8px", - "transition-property": "border-width", - "transition-duration": "0.25s", - }, - }, - { - selector: ".awaiting-data-highlight", - css: { - "background-color": "#f4ad42", - "border-color": "#f4ad42", - "border-width": "5px", - "transition-property": "border-color", - "transition-duration": "0.5s", - }, - }, - { - selector: ".shuffle-hover-highlight", - css: { - "background-color": "#f85a3e", - "border-color": "#f85a3e", - "border-width": "12px", - "transition-property": "border-width", - "transition-duration": "0.25s", - label: "data(label)", - "font-size": "18px", - color: "white", - }, - }, - { - selector: "$node > node", - css: { - "padding-top": "10px", - "padding-left": "10px", - "padding-bottom": "10px", - "padding-right": "10px", - }, - }, - { - selector: "edge.executing-highlight", - css: { - width: "5px", - "target-arrow-color": "#ffef47", - "line-color": "#ffef47", - "transition-property": "line-color, width", - "transition-duration": "0.25s", - }, - }, - { - selector: `edge[?decorator]`, - css: { - width: "1px", - "line-style": "dashed", - "line-fill": "linear-gradient", - "target-arrow-color": "#f34079", - "line-gradient-stop-positions": ["0.0", "100"], - "line-gradient-stop-colors": ["#f86a3e", "#f34079"], - }, - }, - { - selector: "edge.success-highlight", - css: { - width: "5px", - "target-arrow-color": "#41dcab", - "line-color": "#41dcab", - "transition-property": "line-color, width", - "transition-duration": "0.5s", - "line-fill": "linear-gradient", - "line-gradient-stop-positions": ["0.0", "100"], - "line-gradient-stop-colors": ["#41dcab", "#41dcab"], - }, - }, - { - selector: ".eh-handle", - style: { - "background-color": "#337ab7", - width: "1px", - height: "1px", - shape: "circle", - "border-width": "1px", - "border-color": "black", - }, - }, - { - selector: ".eh-source", - style: { - "border-width": "3", - "border-color": "#337ab7", - }, - }, - { - selector: ".eh-target", - style: { - "border-width": "3", - "border-color": "#337ab7", - }, - }, - { - selector: ".eh-preview, .eh-ghost-edge", - style: { - "background-color": "#337ab7", - "line-color": "#337ab7", - "target-arrow-color": "#337ab7", - "source-arrow-color": "#337ab7", - }, - }, - { - selector: "edge:selected", - css: { - "target-arrow-color": "#f85a3e", - }, - }, - { - selector: `edge[?source_workflow]`, - css: { - "background-opacity": "1", - "font-size": "0px", - }, - }, - { - selector: `node[?source_workflow]`, - css: { - "background-opacity": "0", - "font-size": "0px", - }, - }, - { - selector: "node:selected", - css: { - "border-color": "#f86a3e", - "border-width": "7px", - }, - }, -]; - -//{ -// selector: 'edge[?hasErrors]', -// css: { -// 'target-arrow-color': '#991818', -// 'line-color': '#991818', -// 'line-style': 'dashed', -// "line-fill": "linear-gradient", -// "line-gradient-stop-positions": ["0.0", "100"], -// "line-gradient-stop-colors": ["#991818", "#991818"], -// }, -//}, - -export default data; diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx index f9e09f22..01c389f8 100644 --- a/frontend/src/defaultCytoscapeStyle.jsx +++ b/frontend/src/defaultCytoscapeStyle.jsx @@ -63,7 +63,7 @@ const data = [ "z-index": 4999, "border-radius": "5px", "background-opacity": "0.5", - "text-wrap": "wrap", + "text-wrap": "wrap", }, }, { @@ -125,8 +125,8 @@ const data = [ "border-radius": "5px", "border-color": "orange", "background-color": "#213243", - "background-width": "100%", - "background-height": "100%", + "background-width": "100px", + "background-height": "100px", }, }, { @@ -163,15 +163,16 @@ const data = [ { selector: "node[?isSuggestion]", css: { - shape: "ellipse", - width: "50px", - height: "50px", + shape: "roundrectangle", + width: "30px", + height: "30px", "z-index": "5002", - "font-size": "0px", + filter: "grayscale(100%)", border: "1px solid rgba(255,255,255,0.9)", "background-image": "data(large_image)", - "background-color": "data(iconBackground)", - label: "data(label)", + "background-fit": "cover", + "font-size": "20px", + label: "data(label_replaced)", }, }, { @@ -199,7 +200,7 @@ const data = [ "text-margin-x": "0px", "background-color": "data(imageColor)", "background-image": "data(image)", - label: "data(label)", + label: "data(label)", }, }, { @@ -337,9 +338,9 @@ const data = [ width: "1px", "line-style": "dashed", "line-fill": "linear-gradient", - "target-arrow-color": "#f34079", + "target-arrow-color": "#555555", "line-gradient-stop-positions": ["0.0", "100"], - "line-gradient-stop-colors": ["#f86a3e", "#f34079"], + "line-gradient-stop-colors": ["#555555", "#555555"], }, }, { diff --git a/frontend/src/index.js b/frontend/src/index.js index 781cb802..d005b55a 100755 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,12 +1,16 @@ import React from "react"; -import ReactDOM from "react-dom"; -import "./index.css"; +import { createRoot } from "react-dom/client"; import App from "./App"; -import * as serviceWorker from "./serviceWorker"; -ReactDOM.render(, document.getElementById("root")); +//import "./index.css"; +//import reportWebVitals from "./reportWebVitals"; -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: http://bit.ly/CRA-PWA -serviceWorker.unregister(); +const rootElement = document.getElementById("root"); +const root = createRoot(rootElement); +root.render( + + + +); + +//reportWebVitals(); diff --git a/frontend/src/theme.js b/frontend/src/theme.js deleted file mode 100755 index ad6ca44e..00000000 --- a/frontend/src/theme.js +++ /dev/null @@ -1,102 +0,0 @@ -import React from "react"; -import { createMuiTheme } from "@material-ui/core/styles"; - -const theme = createMuiTheme({ - palette: { - primary: { - main: "#f85a3e", - }, - secondary: { - main: "#e8eaf6", - }, - text: { - secondary: "rgba(255,255,255,0.7)", - }, - type: "dark", - surfaceColor: "#27292d", - inputColor: "#383B40", - platformColor: "#1F2023", - borderRadius: 5, - defaultBorder: "1px solid rgba(255,255,255,0.3)", - jsonTheme: "brewer", - reactJsonStyle: { - borderRadius: 5, - border: "1px solid rgba(255,255,255,0.7)", - padding: 5, - }, - textFieldStyle: { - backgroundColor: "#383B40", - borderRadius: 5, - }, - innerTextfieldStyle: { - color: "white", - minHeight: 50, - marginLeft: "5px", - maxWidth: "95%", - fontSize: "1em", - borderRadius: 5, - }, - tooltip: { - backgroundColor: "white", - color: "rgba(0, 0, 0, 0.87)", - boxShadow: 1, - fontSize: 11, - }, - defaultImage: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAgAElEQVR4Xu19e9CvV1Xe3r/vnJOcBEhBSgMEBaoUK9POCOVmAuP0HwcUCNYZSUsh9xv3hGl1qNippQRE20IFEhQoBJiaKVpEgQRnhD+0QHSmRkAsxE4doBZQTs71u/zezruv6/Ksvffvkn/qd8bBfN/3XvZe+1nPevbaa+/XuxX/Tbe6C/ece8qOd5dNk3um9+7Jk/OPds49zE3uyPy4ST5zCr/y4f+sfxO4j16vHsqfmV7gpgm8Yzm/lP9+km1B9+W2zn/zzk2sDeR55ffy3enn1Lf5J/1e3bb42kX43/Do1nt9fUdpLrSbbFt8vvls+idlm/qsaJPWuNK/TfvOLU44577pnPuSc9PvT95/9szu3p9c/IH/c2oVKDbeyB8z/Yz7noNd9zzn3U+5hX+6m9wjnXM7GqXCHpbR0+PnjudGxEtBkzRo02vFtRB83rkAXPqPGD4MnmWGGa3CDqp9+pp4Bwd2dCwPzIXur6D1xaGRXaQz8nf4Kfix1/3joDXtDm0jHVbYspirbZdkj4Npcv/XO/8F59xdfv/o7zz0A1/9yxEAd4E7/by74OCke5Fb+Bvd5J7unDvGHmyCKiMzX40ByW+XQCxvyoSafgGMBT0fgTayXmA/kykr8PI1kS0JIOMPRiSJ7ctOWfuI+mcw4Uj7lO25Y0TQoyHGbKsjJbqXR5HoKijK6nuz3bPhokEX+f4956Z7nXP/8cz+w//bxR/4H00GbgJ3ep170sHC/WxgWecvUJ5AemrLA4NF2cCAa4BDSCbjYVR2RYN2ZXlAOgxZqcXUDLh9toyvSkybopAJ+KBbOpHADOEjoO1EkVYU6LB0HbPUDtaN8N7TzrmPOnfwry9631/8mcW+JnD3Xu+eu3DubZNzT1Xhu8eyxaXw47lWBIzcAW2fGSpoM1WPgzYNWqSRwqjsnSYokgMyvW5JnzWZNrQpC9tC+ZVZW1Ek9GbBQK8ZuS99mrrW1sPh7XXsTeDmqHrvYvKve+j77/8MAi9E1v6t7vnOu7c7555gaM4SuiHGOsajcV8xmeEUMSDFf4yJQLiUmpYNTg906iVCl7b6ltu4gi5lTMs6Z4RpwLQsVMP+IZaNTtYngRoNI1uOzwdYhJxtEhqKAFsdvoI0XHy/m3ZeedH7v/ZxCV7Vir1b3HP8wr3PBC1HDsog5IkJxSdDHAyBlRqBgyHwAANuI3sg3j4G+shSHASQaRlbZtDW8AmiT7b3TLJD8qACrWrwkclYg2l70qACWow5IZvQ9lHQUmeZ7p+8v/Lh7/3z36NDw6w7a9rljvvw5NwPWykOOuEono5YT8IvXQNBK5yhGnwFVoCgZYPYZgvSBtnGMMkJnR2ZrIh3Fga3mGoR9YgpL2xAjbGlBC2YTEGWJu01J3mkrwYGuFP25EF1XMFj9y6Wiyse9p+/9hXl2il78G7n3T9radomMVphpMdGUG9wAJRLYJ4Wz2CZ73QmUpkxa9jlA2fmKvNz0414Fm+Abw6b+U8D4Km+xfsb0l5E99Z+S4mApIHhaEz2bCAPSmMkaNMzW4Av94ac3vtPHdu96TG3f32evFXhuH+ru8J5f4dzrmYPVGiKN7Bfsx/syVj+C07AA3WQmlYBa4TR+bpWnhYCgjyLtN+OBrbezPdAwNcQCjq4EAsaCEAS8BF4PNIZoBKhWdo9PMO0DX5vbmGTRMi41SgyxrQlTcmsVQB+2jt/3UXv/9qdxVLTre5RBwv/m25yz+Qspe2NQWt5ZEQ5x79kMgzaoTC4DM9SuoqDvcEWSqKsNlnJHYvvQ5oWvRutiInrijdwALGBLZ003sEcMl7DSEdHoGTHyoQ4eqTxaqymzdmYuq40wrS1D5A80i+985/1u0de/LAPf+Vb4Y7917uXO+dud84fRcClIOKdVwPGQZRuNBuj6DujMHakCUCgadUsubeMS1A/vhRLBxZEgR6g8juLbUzAE1tWe8wslv9hh9HyQPknZNpEAsUQaJWPDgpttyCjsDxuTMQMwAOiUoTknNv1frrmovfd/wE/1x4cTP4jbuF+3AItwFe81ACGXm0SAC/5UTYGoSBAdwCwmZE9GAqhInmvBr8JPMmAo0zLB7G5YmdMALkTy/fOz5+NonO0VaJl0Bmyh3jDakzL2xL6JnLF8dGjk8xKDBWPtc3euY+dWT7kJX73VveMhfe/4Zy7ONOcgDpnP+a+iC1WLpZJ7RPhArLCPD7snZSVpnmGkgMZTdIrA5BIwOoHBkDLHWs1eRBMZ/Wr2FWGTcFmGQSMZYhjAInAB1C2WQIlrBGUi1JtUaJ8DHrtVIJtwyjZ6bxKjA3Q1n590y8Wl/uDW9wt08LfFgpmCCgx81FroU40KqhUvNLPYu+0NBSYiBUckjDHxtX0eCJJeqBN7cfh2WazKa/FK4MK+wGmHV/t40wbmyoBj8erYDQ5VGYC1VzFmBZpjUzEjGIjhREO5NS2A+f9v/T7t7q7nPc/qZPbYuhZeJdGL6lIMgFQ12gsJUBxwObLxP1KHujO+8nQZT15ANiOsrRcooZVXjDEyyovAzypbDKNQRifdSZiFXTI9m2JQCNBZcAcv+x7w9iF/zEACwlLZEd6ZZiFmWI7vJvu8gevd/dNk/+hPFDa02rwtXOZdZij8axlUoldwnilAYaRDKa1c5ixizxMVqfAEcUIo122RW2OCwv8/aPSotolLn5Y+nBkRWxk4aQuqGu7GOMxE5nPtQcN0DLggvGGYySjRR3L1L77/P6t/tvOuUc0gdvSZb0qL+hxtbSw/tkGma49SB3J2sl4R0SOBqNm+AYbFW9Hz0KgqIPIgWsCgHhzvaZ9rwYsjgKyzZiJMVmNVoiNsi3oWxi3BlkUwshMW7JO35mBu+dc3LmgwNvUfZVNasfH5AHVX/kOvHNBDzbXfX3jZkxnThliwSwtSswcLQDng7haFVUEWb99umAm6+5iyzA0yDbcntDmhQSwo2U5EvPzI3pWOzzDC/OaLtMm0Pm9GbhVpkD0hheX9hZwpxdWPYQ6iiqMweCgUMizB7nBYtXOeKeo0optXC1Mg8WFagMzAtV62mpKwCiC4miBdcCNSRg2aOnQNSu4Amn5MAi2LGyzoF2aaDtLflfCS2kDtBPRtBVf+cr4Dr+XgMs7Tn9CA86uNkDBr4k/AY0jQ0XRsoIdUqV9NXjLUXrywAJ8Yj2LBYrXovsrqMwI1Mhlhn6xUWq/I9tzTKsT1hNO15dNkqV5VFHAE7bTzpGep/6Afi8jXXWMBnBB2CeGXVUeaNAaYV5kD3IYLP5mFpTwlFSkx9VraWv1m+UYNqB46AL2Q6yf2lgr0HRoDX1PBTk1EliEgkO8dPhcLs+Ba9ybTRsuXmVFLI6Cwgoihh7gBcFx4JohKoli8nAJqGhczbIZQIWsynXCSA2mHQ6Don0YuHYY5HvEWsArMKs6by2Wthml9pkzeWwVilwI8KQPrdJEU/rI+xtsa/SfQwKQ1RRlKL1OLwpph6rAtUEbH5z+Xkm3LyFoOMtXwwkLBO1g8XgeSADa7COlzWqASHhKS0RU0rAwqJwyLrOGCUrD4XFKrtqusDRMefEVsfyaVR0ya17AK7GLrWXmsoxGioOKYXB4p0SjCA7ZyqpFNh3KTRG4LbZIf4vMKXa66hYKyh2ZJfNl3Poe6hhW9iCxDGkjBF6nAJwHi5EQT1hwrrBYYatOjkDM5I3FCxrJxmt9OaDs2oP2ZCqMd2jo6osL7SiJbcww1okCfu+WzNK5mXUjXjZafmAc4Lb2ZSzF1IOlafnzqIcaOw9qc0jnKhD6LMByu8Tbx3KhYiAt0ALDZ9uRqUJiAhm9UPagioTS15ZDkrqIEu0U0WBNW5+fbwDABflXjhMiXSxihDZK76x/I4+t7U3ARR1AtbRCR5mxR4TC1myarYhx7RaWcNkoU8t3KrXsMGOsqInoEJqCnA2F7x5Lz88m9m9FOMpu5LqxsksepfBuaquuAsiXYG5QT6vG3SCf1nVCMpSgGTqKHYqN/t4tCxN+sAjcvjopDvnSRiJcLOOW9fkm6LAnjw0sdryxe9Hyqnhetmyr5hSDNqGarLqxsxWkYxhEkydtyX6mz7P2aaerTVwNtENRT/Rfw6mzcJJu8BC4Bf55JEjnGsDV9bRGI1TdQZUgVVdZUUCyeSsKGJ7bOvfALD6viwsVEONOysOvBTxZTxtTekWDC5aSsixcR5weArcxEVt1qw1lxnVAW/ycSRhgU3Xhwmng1haE7BCuV2XSO/0AKn4QyMWKWKQaOYlrhArmVEL3mQOrwR0v7YX4PBlNxwT1nm+ExiwS2gfEVdCOti0afgTYluyR0my9ZVwbtNl17D1szPk6EsSF1FmYg00KuP0TCTVo1SY3GOo7RyL1QFFop2afMmzDrTgEF6eqbGSE93CBdphcT8tlUx/wehuR9V5UBL7i3rfEtPhQPQu08ffBqQoZrFZ7wHGWbGKNAxnfdeUB1dsMuCZoFTC0hGiHQnz4XGaMuN1jLOVVo8YIeMigRT/lk6Tig3bZIHcKW3pwdxZyxnTK0doDBHoJFM6erD1W9kFJwnFNC8HHcMLbB6NIa9LO5EP+obavAheE4HIvCvkpTKk/WUaqupY4ee6cAkSKrqjzOuzTkIkA1HRIw3iFaUHRi9K4lkRoRhEN2jjuKzpkM7cenpWVChnOFKuK1h/L08J0ngU+I11GMWUeu8pAi5eY/d7rYlahT/scEklbsf1JJNSmx3lv1dLyk1usMBqZsFpesEoFhhqcov1aEgJKmrktqQi8KT9Im0UYDMAeBC1tuLFVR/SNs/mcMsQBsV/WCEoTK2GAvvdDPHI6MfGuIVMcR4XvtRY/AHDJAyDTijBYrCZZU8oDYGATOCTE03BOHawJjDpoOERZuUyUORgJ0/Wa0qyGU2ikVU1rH/ckB9bapmT1TUeq8Xpa0L9evW/zFHZDcil5YGtuv5sYV64mSX6tPyPWMxqS5AFlzAIkU37USZJkWsYsEBi54ECwUiy1EA2XbRY7F0zH6NScNlkayYPVJ2J27QGaIOWRk7JrTB5Qs9UdvzpLwPCSbIBroBtEwAa4OVGc/O7rdjiENKAKfnRDRhcXJNs2JmJQG5FoaTKZZFlgIBOMaHFhtYlYn2lTaWIa4dijUdDSvnhwdBMBJyCEUqheAIWWcG25xh85tkAQMSijd4dpw4uMsknO8EPAZTWVZWJiMd78brAiVjyydboMaZxmaTLi1pLgWgcqI00LDGy2O4GvKV309nG9WGNXaWVp0ZY9DeCx72yMgraSTZ2M2hKOTpJUerSXPSi2a0QBIeQr47ZCt4yy/cxBsnUFQJywII/LwxK3k0BJkn8PU2ZEFrTAAx2NL7HGd1ttxEzDmRbd3znzoHS4x2RygYeYKnh5I3wXpkWM1ulXfk2LqBioQI2xAJ2yMwRudhfcvgHgjoR5fGJiZs32ipHWZYpZWvKAGAUyknnvINOG53Mwjy24EJAQh1w53RWEZXw/Zz7iJIp0eIiOfwY5WgbK/AOYRCvGtLT06KZSKSEa7TP6FoELdVE1TDUYQH/v8LkVWXAcfJxpYxtRiDdYkBJWKWiR90MtHs6ibUsmDdqsaRX5dEoTY8pq6Ms2iSdQBDJAKyJclWc0PYpAZrM0cy7xvQoWTcPybe78yMJHHcfQzt3XsslZ+F02Mg+DABQDp4CbeUbx0T71ThF+CM7Sfw5MxkxtJSdj4xOxvjQIoC0YaO5h6xS4w2382C4MtDzSjTOt5i+bWa0l9vAMk6ykEwwtMUOHlMCtoMjvR40IBwNyeTAM9hz00nOLkUswTE1oMnWs07WPn7dzmSx3GZkWnOaNBmx04+Wgpm2A1pJWbZbX8sDnhRTm9YwtCyj4OCAZojUnjY7ZYtGksI65MmwZ2xHg4igqgIvSMyDU9j4SEhpmTsRYgTaUBsVrbSas7IA6hu5DedoRphWar+VQWUsSpxypQNM7MizbNRiQrO02t9soWl1B0zJNrO8L6b1YeCIC5IimTZgR8oU+iLI5AS7RRqWBRgPsExPjBKKlbZTIG89lhtCpPl7XCk3cGNUmwtM7oa1ffGQ7yur3jjK7ZjU88dMTn0wWsdWD28eFBFD4r2RFkCvsYjJt2yELHEmUSsAFxmqlnhrbbTqnqLCiCNvQmAnVqYlHLnDuyBG1LV6xeJMlyeF0s4XgZNPKsZKBN5gsGt1c0RMn8zSS7wk4/DWAWFQ7ePRjNRTLAzftnSWk1l8RG4+QCLQj0kBEa8HA+ccM3OAl5Rqkv4ayBw15AItBUOcMlk+TuYrBhTvyYz/jdn7wR51bHojQxH+EYyl1n4oEzUcO/tGwR79BHEyDb5NBWr+mtsf7hdv/8/vcubtuc9PuWTsPLB6igTtQzBNsO764kAu45nSK5Sj+3GuP6P2IaM/U2l+24WyFG2ItAWttVO73O+7Yle91O0990eCwHl4mLbD/p59zp3/5ajedPSl0KdKk4XeC4AQ7lhdIQhqti0DRATu/333tkbkxMWuzxopYjHV9pi29hsciGZMkaml5EsshcDf2xP0vf86d/vfjwGXkC8d9BXkAIxxzmFD2iIOTd4Fxqw6TtkgPEpqWd6ABWlF7AAGuDCAmibV1fIHhELgPAnAx02YJUse9tzwdyAx8/jU1OeQy29utcEqQSJ1zryHAlbqPAHZM2+QHoMUBJLo7TNsqmjkE7vaAe+ZU4hQJ3Phzn2nR2M6/G1uxY4G1FATJd/PMg4/ANSZEDLhi2bFRLRU6S9JW0bmwUbT1a9ledUpw72LWuL92qHE3gG+QCkHjEuDW2S+oBBiYiJX74TIumT8C0jKPstLj78+95iiXERGsheYz0+Zbw8+t0kSRPVCgLS6MnUWfvmIAfmbcqw6BuwFuHQcuZzRddjk+gR5jWj03wsVE5b0J9LGdArh6GZezcaMAPDNqcgOmh1i8seVBdAoiN+SoUIc5BO4mmA33RuBeo7IKxt438T40EWvIA2Ns65gT/crkCcUc07iUcSVwx8O7jiujx4RWT1dgz6ZCM9igcX/N7TztMB22LoI1cKuuZHMn40gpLoDXWFxIDcd12gmwoSGa7Py51xyb9NfHI413GTCSdnw9ERyR0/un05Ql3LywRJ9XRkM/P152JAH3heuO29/4+2bgnvrla5wLeVxRy5HHQuWjENOOTMK0NIi4MWp4W9F3GaQCB67MHsQKLBTes7vUBunMQ3vHadMxSg4GvXsRvhcbGfcQuOt6YAZumJyhCfpaoMVkFtuoQM9rm+E19Z7cnPk3ALiV4eCsnjKh6NhK1f1EBkDAmx6fvPsQuOvitdxXgBvSYRJUePLMJeGaK2I5IjP8kMhagAdIa5mW78696li5fZWUVepA4MVVmTZLi+pBoxVi5NyDxSHjborcANxfmidnBLhoPqGZMr0abbw0AE8Ij0fa/AcqCfkzGL5TzYzPwB2bSeIwMF7lxfXqaoCvVVxx08cipMOOHEqFtfEbgXttzCpkxh2aiM2vHNluwyf3OLKiOYwBXHLSZwKuqMlU2obYhnQMZgGgxybAs9NNbLHOR0IXgIduJeAeaty1cev2v/x5zrhxQPPsgmjStD8s/H297IG9YVYCV8uDQoxkQcyffdV5BKbjB3WsxpYo84Bmp8ZELK3E1SGa17oX7ryrfnWzydlcDjmBU6bXx8L6d9KZB30KgVGB1chbWD4L3LCz4/a/+N/dqf9wQ5QKa+9a6JwHkV4NU16S3Y0a8GAaMUwVuCZT8swAZNnsp3lCxewUwdj8hhjeo8THqbw4gTsw7q9uJBX2P/NBt//Hdzvnd8TIGlkUGG2MyJGf2FhlzMerBozBKDeWzSmvYvATs3H5fL9w04nvuP2v/qFzB7KemYd4u2BmbAkYnoBEGw1wU5JK8NO4ziXgdlbESKfXYloCbLyF3GbajF713i0A99ydP+v2Pv0O5xdHe6dVJjvSyJEmisBJNTMm52WHKKM+06S7dggyixaF3yR8s6+kCPBmgBR2nyOXdNogB8TGxpWLwIk9ObbY5lRAlgWwgWXtiZ4/+6rz4bkKVe3wF+vMA2ScWtKWjISFuRVmyHYaybQZKNsA7kfe4Pbv+RXnFkcLVzFi6hQSxUhisaI16UhDM3Y2rdKbdGDtzxw0xqwbCSTbjh7ZBByts/0+NgVMxKztU+Raf/aV5xtTMdn5uBrGVEFrIrYFeWB++G5+71wddtV7NpIK5wJw35mAK8v3DEZMA58nG2nRT1yMQBsHqSCxOPTIZKfBnErLcuCpw5hDA0bqaRv73xRiBPhauGD3oolYm2kLb2Hgoh2kwjtaTKM+Kd+fiMUBHWDa7DlzrcLVmwN3LwGXj//IwBrn05rhr2r9qi7QbmM0mPF3+S8l6iEQgN/1o4hk2SAXmAhCS/v2xgC52qo0s3g2IQ2oaTURAMaNg1ZDEjr+xy5xK/dZIR7+nuhF677S1awDF9sB7t3vdG5nlgrFl9WuYXnuQf5ZhypsF8p69SMo48Dg77EkSA4F8f9nabaSQ5Ybx9tGEbhZygsxLRkTFuoXTgBXG0WHGkvTJb3aK5hRo03ytOYZXgBUs8a9es4qvEB57+gvzn3oDW7v0xS4IzWnKGwnPrRCaJa1pX8UGIjtKr9C0LJBRPcLSYePouJba8qLRqSLiL6pPbimRWIK4Ccv46LzglURV2wfAS7WtKU/JhOSgbS+hlhoWDaa7Etqnm0LALU14L7LuZ35bAbLIanhJSAIyw0fgTrCZhq4kTmlHTDo9WmSqG9Ivo20zXiWkofSmQ09mw/0s3aRM8+tkTkBV4NWV/J0BrZ4HfZGrYdEOqkl2hGo5rLGq+/YjHE/8ga3d/e7yuRMM7UuYuZ6UYQy9gCuac1zvBh7WkzbYPSq6dipjkUi9E6DDG1GB093okjxWS/OcEPhHWMny5m0uFDmrVpPE8Dmpb2zrzwe7s8J8OLZTLwAMOa/q3pK5Mn8d7HB/GuN8L2g1rd0KkzONgRukAoIuDKKWBMxzD5kBFJN81geFIXakvJCMgR8kagMSwyoA3o9AVd5bT/Er6tpS1daEzHm0DJn7mepcLzkcfGBxRZoeceK90jW0TMY8DmmTigjMqX+544776o73JF/tInG/VdR44Y8bp6Pyn41Io2haQNwm3laBCokQyR4CJuRd8txi5UFDVnB0D1amogJSZlgRtN8fHD4Q4NpG4sLBULhGXihpwAXAg/qWq6rasPHmTY0rCUNwt+VHha3bB243NDJ8NDvOkxWl2/HQaHfg0Ar5Yd1mLU8twAAiICCk+34Mm6RI+UBtM0GaAcWFyo+xLluJAL7s69IUmHVCvho6UQufdCqrSGmR9qGK4M7/8diS8Cd87ghHYZDIyO2sp9JOxVTVi1Q2E6biRpuhwK7B4ydKWPAG8vTctaMmjL+DoIWEh1dc7E+jRvsGRWWsJ0ixvQLf+YVxye9ImaHbl4sI/aWQaaMQ1oOVG4yrWaZ2Bu5fy3uOTvv6tu3IBVmjXuEgSWHXrL4l3CJQZG7FM0vWUKEdzXiI0X0KMqNkIUI18X266W8eqfLNORBdMrBxQW9/y05CsGOP/OKC+qP2GPqoKHywrU0LdI/NijCWEvA+53tAPeeOR12lEoXcEJ5Z+8ca9s4KKJTgu8tIOcmYwPly0j2YF15kDSz/owtGUcdQUsE8Uke6HYjohqzHwCuwbbprYoBqb6BFpWrYsZkpxo+BwMSP9LginMVtsG4u/e8y/mdo8kxrLZhRwunby9RPW/HCQsJH6nzGHXSjwQFYe4MdvV+zszxMrmZjLRt/tMcbZpRsO7EXUEe1JVXrmmzr4L5hJ6ItTIqFbjaY9TDY/+6wCtQDl8gJ4CPf2D3hzkorBBS+5zEe7fCuD/ndu95p/M7x0S9MAkjFpNNS7d4zJPdzt9/bu3T7KOwLh2Twf6f/r47+F/3haL4pKdY/MrprCpFqGSa3M6jv98decpzNDbRGJEFgtAav3DLb3/d7f3R7zq3v8+iKm3EWPUb719AZ1oNaxaoZ2UbXggmYooI63v8mZtnqWCAkcyelUhuMi0qlgGgbzgLazNcgNiGVJiB++6kcQVmgvUbNRkH++7YpVe48696q3MLq5JKPFP8eOZDv+B2P/HuWiuBpJp1ntbBgTv67Be7C69/i3M7qKa2/e75r/tf+oI7+bYbnIM7IKwNBCgSVPwwnGgnVuWusZVGbXMjEvgzN18IAjzXHqsxLZcG+Ulzco+ZEoKWh7bmh/22xrgWcO0JaiCKg3139NIr3PFNgHvnL7jdT94RMiT1H9J98XcsVG8FuJ93J992o3Nye3oam6YmDY0BTDs3NLCttB+aTFbQVvLV/VdFTnOMJsCt+kM1qK17uW8v2qtweQigHqahEL0zh9RlYMnzrn73hlkFi3E7Kbm57csDd/TSl2wZuEqjJq0IjrNaLt2xZ7/YXXD9bRsw7ufdyV+8MTJu2eWbVYsBPBQVEgCqRGg7fdXUekVMbEXRGj3PgCDjqu81IG+RjYvyIDhiU+y3S//aEwWiBefNktfM6bCf6MdE44qzd/6c25ulwlxkk/91qvZL17YOXA3aktaksixT0/JgY+Dufenz7tTMuEQqzJo2t4RzC2VCDcxqlzHQsu/NjdieXBNwVoFbG1YAaIh8rImTrl0RtPFd8pwyoIdpYXM6V+H8DYF77s7EuAW4/bLG8g2xgz139LJZKrxlfY175791u5+4vTqOsJ0KStSpZscJGndzxp13+YZV2qGUGp4PBWnQ+E6IzbJ56UFKBPAeUhdTgFuS7ubhutb+MKJTVi1NHP4ehJvcNH/KkhLjwm0HuDNwZo3ZTmGpAvCDvenopVf441dvCbggBFcCSY5MkbwVxv2CO/WLN6ZjRkcKgYxJPNS0gnxK20HBzFSSOlrrV/FbF9YK47ZOzGvSONq5gNhyYP9aaKBhGPSpoa1JhdtxVoHurxNGDyYJk2tz7OsAAB/vSURBVLOXuO0CN/afARZOYn18/49sxrh7X4rAnaXCqlvIWfYgTci4IpMMamQOGDABdkQFXH6vP3PTQ8J/K+8ugM3/oTUte6dmjPhIAMbxw/FQnWjacBiAO0/ONtW4beDmEKc02ZaAe+4TdzifsgqqOi9EgTpnrsCYgXvgjv7I5RtJhQxcfSCI1NvAoUJjVpMHHKMD2YPUYVjuGYBL8ixjZ4jFnQsFq+ZMEzeueE1TWiQJIq4pTfWzVNgUuG+Mk7NQq1AdlEwyZ7fzcCKxJeDufuIONy129A5qsxY5te5BAy4GLZNpiWFZDUkGsrqwSJDQ8Dj2CBeSbWuUVlp/ttbpxLhwwhXuIIdDhJeinQtISAPaR3uKoDzQK0nqPIfFgwFcvn08GszQfvPkbAtSYWZc7jjJbnq0+KpjAO4sFd68QTrsCyEdNp09neBmT5BUc3A9bVwJJUTIC2YGACtIkL+3gjkBdyTdRQYxOT37Liz1tMZEJ1xmMnRhWRIfa5gqr5j/ujXgzlIhTc5KDUB+U6PgYzlr3J/eWOMOAldvbJyzChsCN0iFtybgijwuIrIwbO0KrwhbMidQErTnkOnvWjZxh/ZnbnooOLbK8gyZ8mrk7EgDV9K0XAixmWSEU92efv6179pQ477R7d1N0lEJryXlxZxRhLitAfc9fOVMTcZQfncG0NIdffbl7sIb1mfcCNybuufjlqE0vufMCEVEqQqD7jaihHFAVEBe+NM3PZT7AAvd6XjJ1Jj2woIV4kAtLayN0BOx5jljs8bdMnCzCIOaVkaRWeNeNmcVbtsgj/smFxk3Lfm2zqbN6IjyLWUVtgxc5TTVc+OhlpKokFPpjY2McMojUZSv72CA52QWoo8ALvKKGi5tabBd0DIBb0mLLQF3lzJuBgViWhHi5lqFY5sC94MUuEb0YrGWSJeQDtsicK1PmI7kaMsYEaxIgDL7YdByojIiTWLfClzzXIFYexAKpUyPLNICa9NWo8XWaAXaYhTR2ZBVeKc78vQXGDWxCn3qF2c/9PNu9573kHQU0LQgdIdfHextEbg0q5GbifOgpRNbA+7N9HxcMn5pKmIc86kKX6BE6KS8yNhq6du+NwIXApJQPllNYxNGqjkJLAZPdIT7+bEexmFq5wee5fwjH08OZ7bCTz2SiKJ3ef8fuYNvfCWFwLHK+zxgoTrs2T/ljr/830SpACcdDedZLNzZD93mzt39PlEdJvpqRYEHB7iswaY8KExGL+8WzOBa7snn3WDkYf19c/70jQ9TJs8aL8CgtQQsdJ+tS1AY5Cmv6Opy/1WjdmC+YVq6sAuBnQkb+m9W/nPy3zHOh0XSR0wapsktHn6xWzz2B5PBUR/xlp/c14Nv/E+3/NZfiAHtMG0miwLcf7d2OmyenJ18682yHjcCqdQd0H4hYjDkgULViDwgTtuM0s5h4M5LrIVarQHBQr28r/wHuF+xSAVFeS0I0ZwOeEhlK4MwgtR2FNk4pUNJupqW31suD1tn5AilvliGp3aZdz6UMuU2KGoT03VbAu6pt97sptOnnMulIDMXhNEfmYjNrZJVgQZexNYkKAnNRRcWzmOqmDJu2GqjcpmlIVX/GFVEbKwa8kOsL/OcdQvwbEeGNlBse1sbccdCwG3v5I0mJO+WABU/g3DWnp3nMYISgbRtTseFydlmjHvqLTe7aS4kX+Ro58O2myI/ZX/LH7g0iBaZK8wkCyBbrfE1SYZLAdyyKmbStAWKKHrCbU3gSS1Iwm+hQQEMaDgRtoutbNCGx4+cLsNHrOSRi0Pn9nQZNQ0kG8d+2aSbyEGA9F5JBFsE7vLM6Tj0wymvyLQQ3K0QX8IikITMYREG+OnvgXEZ05bWIMq3j/WpTGaFCg7arPO4g7ZFeQQfB20FFAItZkedp+2UNBZ/bLN5LcgxnE+xEWpfo7yQ3r8/55G3wbivcMszJ/mh2tLZGEJr7UHdrNifTEWUA8IJg9o/LkrWCvvTN/4tHuFb+tDwJh5+EXA7tQeQxfSg4vNX03UtUORPcaDySKPgw1xybEUVYTtuWIMIGNMMgnYOyftzHvlFG0uFk295RZQKQ5qWfCTRlGW5Q9LJUZTsEEF6lC5wD1IhAreGUmDgTUsTxWDzAe3sps0mhTXDuWf2RGLdc7zyE2Nbx5jW3Gbf1X2tXcIGAAJwZ8Z902ZZhRWBq+RBq28getfLB2RTmtNoTkrAfXBWxDTLRpiBjX9wCZgP2Nhp19rbY6cNYMBdqsbEwRogoemVUw4yGYvO5YeGNJuBe+nl7sIbtw1c5KTcfpHpLEnIJ2M4cqH79fNaW4n8qRuyVLBZi0646MCYX8UJHQOdRYPY1X0MSPnqmK5p3FvCCARu47yEpr4jjhFFespzG4PYqz0IjRxZrROyaX7trHEvvdw9ZKvARRmAdfO087hlIxGjhgpndu4UlinWCefpUf7UDQ8Hw6+32kTT1Y41WZrkgWvTpcbphIrQcMurka6Vnj7OtDGixfvbkz09sLgQSAMtRxvtbBS4Y5ovtHcrwL3XBY0753HpsRcFEegAv4GJWJJ1Cp+43JWhOyaR+1EPALc/O69s1piIJX1DNWLpCA4zbLUrhwndeStMcaeK9wk2E5OnAiZJAErTin6mgWX61wzvQr4U3bfaEnO5LdZGuWl/PtfhhZsx7hcTcOmBIAC0tfed3djFLiPRmzt3G1MKk9MgcEd1aUp5FbShIzQbbFRGx2LbwRAPswdtXcXDToNVBPhVuGrJAwIKrWn7bJuLnIJ5Z+BetiFwv3SvO3kbyirIxQUEWDGOsW98S1fTmTm4cQTHhBHezKTCUPZglGUB5ff27TfrIpA8qMbralriFONMa7ElYfciLwZmySZwkbYUAyvD7IMGXJny6kc4Kn/gbmHm3Qg/0XZas9p2qcCNoCJ6A7GsxZYogzBSMENYJtCI8ZGQrD5Vz7g8MPeHgfsMI8X+lz9iTZt/S+ZncpZtHB00lt1QOVWk97cB3CIV8p4zuRo2NvunCwt1REc20/Lns3qTKteSmTngE3DlZAwclQ71YZoACRT0vU47gK1pG2F7marDMjHmdhjfHGPNbNQf11qKaCwO0NGJF2CWhsbO1VjxXZbEEoXkz7ncPeTmDbbuBOC+Mi1AiOyBOd5AvxLD1v8cm8TJoaPRsHUclz91wyPA19NX1LTp7XqyMhY+zexB03gLt/P3nuUWj3qiqsfVbNrKO1K1icJY7yw0cr/Z3ngNGlTK3mzQtAhOD8mctnRHvv8fuvN+9PK1tw7tAeAW3+8dElO8mcum0uyhVOBIsQ0niowxf+r6R5Bxrg9KGx7SL+SA6lpauL0dpz/YKALQVrliHqocP15y/rXvcEef8eMEuNZo///6+zknip1tpMczcB8gjFvm1D3QBS9EgEUTTD4PiVegKjIRyTAJhM8czM8gwK1hkdODMRlT4UHOAC2D5t9bjcfF19Qxwgx0seOOX/ef3NGnP39kjA6vARYowD09H3qX9G1vAl1ChwRuf4IZNVej9LExh4mvjRGcABeB1gKenIiN3is7ZkzEWjpwbnzu3CFwN3bGCtwzjT2FGJDG5Nb8nkQsfB2v16WdK6qEOJU/df338DlL+cmQB0ysjeZpqdbtZg6KzMqarzY8d2feObBwx69/xyHjbgDfCNxX15WzFuMJQuGXdspRy8fJwcQut589kF+HahYKcMt9kPHQ/rCkVZjhOnvEkr7hE5X0gEaICsAtuZKUZzwE7gaQjbcG4N726phVYJkYnBExT3TshPiYo1XPjCnDwkpC45beYUkZgNsG7fyEWgSeZ3XQak1RbyWZBzRtQTrZanMI3O0CNwIrwUjOVxBJjeZ4O9+DMCN8axK3cP5klgoDTFsEsjRZR5fm7e9aFyUvszw2r6aEIxNFwcchcB8M4Or9dDwnzjdSDkymmh/1E7KTqoaQ4VBEWHPN/uR1WeOi8BAuzJE6PXcke0BmnKnaB8sDOzyE64s2AitOM3Cvm9Nhh1mFdRFcpEKpDuMTsUi/YvKd5V5Dk4ZLzOq+fvbBXowqkX/yJ697JCBClKcFIOukTnKJGvWkYuSBe+MbQWndfO9iBu7bD4G7Lmqpxj192ihr7JcX2vn7+cSLxlctgSwpMlQhkhBX+hsGLqmuMjVtL9/XqqfV0qJoK5pBiAlxWZqYPPYQuBtANt4aGfc1sB63eeAgmTixRoTBG015oWiL7tWgDXdqxm1sIS/UiXK86+ZpReojhRnItLRO9hC4WwDuH8asQpYKhFBUuktpPYyBsXMVNGj1uXQzYOf98hK48b0EuPZO3Mh86V+Lac2Om/fy4vFinIHdrmFydigVNkHv3hclcOVqGAFYayKW/ja+L1Bo6QCwsXWD3F8O3NIAROP9ukxW6Kws2ikCL4YZAG1oXlqAeMbzNhm7v9H3BuC+ec7jzmWNqxe8yPmKwnY4WD/pXJH2yj+GSrgBTcuW/CvjysM6UFmjLQ/Ce9MXCWHKC6bLonOklEVKIIxuZ5lv3UmMewjcdb1vBu6JN7/aOQjcNN4dps27eHNMZYeENOTFKhMxtO3fn7z2UbF2wUpvNEBX1IN5jb24QCdh8TnDoI2zVb9wFwSpcAjczYD7mnIgSJWE/ZRVwGRvi7qBKRu0AgcN6RmBW3K1eMWEG0auIxuyIkposB+j5gXjQf3W2bJop3ES5uHR3h37x1e6nSc9DZyYWFsMI4BigtbQo0hDrjdf0LlPvLI4cvl9inrm81GbV3inX7j9//1Vd/Y33++m3V3yMMS0+rm5UgtiwyLBzGOQ6FT2IJlE6u48OYvAxVvBe3WZ4ECN0JEeS5cjkcaZFgt/ckynFdIMbVXb2dDUJaQYgOjaZ5UyPvAOfUhfncwa381lDjCSsmShdhC0LaYtDcA2w6fao5SX4SxpTBLjVhasHlQYLzelLAdGXYruyUrHSJUkULMshZWnVYRinPAnAbv2hk8UbdBhHbbWz85Qm8RCrmIQpd0YGOV7UPQiX0EKl4/pUnMi1tKzaexKmGfjI9+7yvjb8oAHRm5L/8C1fycMNWvzgKfKWR4AvIBeAnprGRcydWlw9JcsQWqvOCAAw/L+dfZClft72Q3ECLmKDYCuGYmSqXofCmGDJPaIhed3nCq2obC2crBGiI9OKckKOTtxIIIALC2a516QI/bpe1IxeQFuk+IZ2o3UyUCVVxMU7WqjYSMn8Ofrc8ubmqwYuKG3ufuDY4NybYX8poE2ugwmwfTLRjpKvXtkN25+S2FDnTPXJJADcRFJNfUzuoVcO23SoqLbTdCGHsOT0ZPzBMYtT+zWD4A0Wbm5X0wcL0XhV3upmcxuhbOWntUTxTqQPZbtMVFiI9W0DIyATJzHLhFEaVZiE/Jg9n0OOjfp2qUCipFATx6UXSfCASm0KQYEPNdg2kju1KnyL4gNI3DbIZoc1oAFdy97UHWIBC3SZMjAeBCZRiRM25cGIJxB8LbbV8ZuKC3Ebcfw0vrMKGHbsQOpcd/GisDB+LZ2+7YcepoZE+3kbDOtiqyl/7xt/oFrMuMaHpVutLeQG3oujWr3fFrGJo0Dz1i4lNKlhkXNeiOatqdnRdilrNLdxo21cHhEAqxyNPb8GqWKmjOjnLBLKnipcon0A4YHfn/zIG1jPMpjIRl25YFm2hTNBJE7/8A1F4Mu0A5IQU4eMTMNt3rNPLCnji4uoHc1WI9IgzHACjYKN422TTNZ6/zWGu/sKBW/I0b/YYek8qACo00YuHjfdnjZEnQKOLtGyLISfQygWcUy7D50OqchXwFwA0OFlTyac1Ne29XDuUkjmtZyDgO0gnrW0sPBwAi0CaCNMBj+1DrzoTJibmlRS6FH6SMh0NnYSNZJWOWHAflCSkohm7dCvLkiJhwFMKpdq4JLE3larvv8aMv0XgFcoi9N3TaWdomdGGWzkYJlwnjJ8Guddg21rGA9hShp1JEMALGlpDOlaS0mlCmvEdnTOenSiJDFX/KsnaVIUfvqPsfqazjXrKKKkIfx7zrKS7MFvihZBSoVClgNBoThgQ9q1rRmPa0AhQYf8jwNWjOP3FvNCr1HTsWYLDElCPOwQF4OrL4vPBDmaVV/p/q1PLptCjGtHPD4s8nkHabNk2x+f5vh87W4ymtuSv7yH0+X1qBpSB4gYPOhiPMd/oFrHl0vWW3XQnKI2jH+Lsq2yhNDu8eq7PHgFPzVsGzWRZRLWlGAi63ctxrq6945khi326bat+I3xCog4oJBs0CbpKwUoYKQrrfbrH0kUsEAbx+ahAlbpTsZBqwon3CZ+8aBS8IvoujxlNlIyqt2ooLdCpc6TGvQosUP9LyR7MEoy7ZBy9o4mO6i0qrYBQJvhGkHGDqFaO0U6Pn8d7V9KLdv1x4Qh4yRv6TMjHrt1H9JwJVxm2wLBpPuww/3rjbRWSmcEYkC70NhhWkmJA2yyxuhqjCCpemlfBGSKXs/BG26l7e7hFXmzEN9Q9KuHeJrFBqRFlj64AnqGhOxVo2FOPC5sO4sFXDFTnsJN8X69P8MYIwUvMBGE6OX2DdUZKO1aRl4EAkUKAD4WqG2OlSRFJxpUXi0IovczTwAPMFG8Q5iJ9Y/g3zUSeDyvei+egAdj84StMiZAVEwsZoB0d506R+4+jHhNjWGAyFqlcWF+I5sWrAOTVi1yM1STs9BW7A8NBFbLRLQd0cYGA5TjKYHJ7bPp7QXHdoUDpkQBSmv3kocfzfW3C2nJJqRj33oS0yGln/1PzNLmSWmtKsEP9mrY5OovaztXDHKtaJrAO5KoE0fr4olZauBYizMa4/nHYdhVqRTwjX6OPtsuB7T9pwWOFkBfH6HtbgA5AH/1YA2Je/v34s0ewMUzKnKvRGz4X87KS9hG9i+YizM5vYnFeq7/YnEuIzyjeR69bh89Xi9KvQ6jWRwYLAd4hArVO2G5Mto+G2cd9UDbSt7oPoL5AGyidLrnAVjnwf6BhcXkHTRNsfFMgAHUB6OMm0FZjUDllYAuI0kN9NDI9mD+NLciLi1jRReIBC0PHY4hIa2ZV+p+B5e7QM6jMVUNLC19oCRgAJd/Kt50Am7WbeD6gI1uAz0oI2t/rfunQtmwuG2vGetZVyTafPOX4O02gxdHaACN4dHFiq0J8cHj6SV8ApOWEk2NFQ1DDvSPw50oy6Cecfgd3vRztFWlRt/B2A8xbTZkJIJQS2tKrkUoAPSpUaw/kdCYqTkJMJY2nDKONaWdLFTXuxxQ5q2UevLQnW1iwYu8vgm06bOKQ8ywhnybK4j9P63XmV/eaZRFyFOHIyjIcGxWlqIRBE3rZCnpWGgAKNhu+gwDWnAUaIK3MOjW5NYk2mj45knJrIP9RrSQmLJkEH1cEN6Q9t5/YmrHwu+uiPYsgkMrEEboSK1jmuXOKDohHNDwCsmGK+LoObJA6sCjXo+b0cBbm8ZV9iO2iW+UzKm1nTr1tKaac6OHi5jAYGW7Fz+xp2q2pHay1pcWD0K5LEDwK0PC40wQWtV9MuVFOQ5PARV+wCQ9lJe4eYx6aJZdmQSRtpKhXMALP+bFX5nPZt7phzaYrz0ewpaBQqTLfO4rTYWNYokDLDnr1lLC48oWC+K0MDsT1z1WOFXtdHRUKOgiIPIoz7SR5hRIOP1QmgIg6PtE4zZ+kq3ofmyt+McrdCDpEMZuNU2I1VeiI0szTkie9S9Kl1YzK1OJWqDlvWLhbNGtBRjq0jAZPT4TAHcCrT6XCN7QAaXhnkOQAlcBdry0eJ8XzwkZORbEtlCxooYawgIZ0O6DxveXFxQepO3DYfudkQaZOiMnWiU1m7cFkuPTMSgZpbRBxEWQfSwZscROHQ2Mi6XB/E06YZm7IGicYJNNGxRKoMrdmhw12daXR2FmMwA7cBELKJI1tKO1THTnQv9KKSjSO5bm0C0/MEZlb48yPKI+cNgLTeTiL3oKivEMnDDfeXMA6QbLS8aKQLnTJvDAjVu/O9+agfX0iaF0tPDvS8agvBU5QHcQp6YTh4XhTTtWN9wcXznKKs0dmZtc4NlC49AsJGJWBgg4SiWxlcgrHUvhpSs8tp4pjzbzp+46pKI2fIyYxm3AQoVzpBGJBetVYeL9SwEDpqE5ZOyNYvJAeHRh8qg/KHoGvSkM4O6gzwQLUZpRCBcoM2lT8jHkE+PMyZDY8FqU8K9ot5hlGll9snQtC1p0LBLQbOqW/BBKuy5yR+J/VsNtNqx2qmdzLRUv+dQAyoqRE7SOqwjhb2WdjM/oEKBZ0mDOWkhC2aUVmeZjbYu5WE6a1JoS0r3xWjCscKNqO2N+UUB7np1B2U+wirR1gCt6p98hlkhtu9PXPW4b7tpekRT0zKkNcIF8h7GtMjIIzNsJF0QYPXzYzgxjFqkifH3/pFIOfIV4Cq2Y4wn2iekCTNfb3m6PHcctKSxkSda8kA8H0bVAjwLtLy/MdLSVrD5jviDPMaUveM7/q+vvOSPvVs8BbKgYXTaibJo0GC8ONlDE6yG0Zk3GsU8LO6DZ/U+WWSyVXw5XhEjg0HkFQcFmg+g/sdnKUD1loDTuJgLEx0mGzpdxgSu7EefaSuMUKQyMJBsC4nATX/iT1x5ya9PbvFPOHA1C+Iw3xggAWRFxpss4w5M4rqrRg0mDm1dhuR9Z39ZjAR9aQAijZglF/t3J5irTGKN6KgGo69p4fg39ojRgvaIJp8OLGQCGy9RJ80OAR/Gxt3lT1z5fbdObnqzc24nGs8GYx98yKMQ23aYdorZ3FW2tzOSaR7rn9+NBrWeeaAjEGZaFZTW3tHRtn3tHxofdG/pXyX0FGH4OKI8eLxFRgK2JG9Fq1aUUwDSUWggihwsJ/cv/Hdf/vhnOr/8qHPu4hgf5ek0cjWsYaTc01KUk79LSJiLe2m6gwLeAm1nwNLIdpmWHLNJwVls2tuNG2yEKrySXTqSiebM6/uxw4PsCGcuTjRCI0rHRPZr11PX4VxNHkA93LJLrv6DbMGwMV/5zQO38yL/zZf+gwuPH/nrDzvnf0Ks1zIaV1oDNYTpkrZ2yTgrnzRkz1uvYGbsXIDarvzKIJUhYC0WHFlcwJqe+DYZprYmDu3s1SIb1VosxFsSofzekBalpfZWm6ipetucgGRKztfbfl9OsHHut/z+4qdDS7/78u99qXP+Pc65Yxz0sSNDoE254AACMRELv1tb044zLawuY6Gbgza2Nf8z9ogpLSCWcY1CccaWBBiQjdQ7BIBSWM6/tccDkYVle1lPq+/lZx5YoENLzOJawzGi5VEtLopA4Xf7y+Xy2ks+8bvvCz+duO5Jj5x2z/5X5/xl5WEEwbbXElAVFEiwDyx1lo6tybRVV/FwaUx0FPHMxhs6gC5OxBjghx3SIAEGWgC8BFrYZnBvNUBiP4tljfcqp8g4MBh/nJCAY8C5iARt/dk7/wd+37/w4k996i/Lb7/7su/7p5N3t3vnLmBbnLmO0uHNMF69cN08rel1LCjEMArYoLPSZ8sDFOLn5wNduwJo+8BDfYjRqzoKsom+rxBNC7Qj42bZlkYpJg9J9Oo45CDgaQXb6YMDd+3jPnnPh1iPv37dYy64cPfYrzjnXtY3MtZk6r6B1I5Re4DBSIuByASQIVmFbhQCE2u2CmaE5t460ypjaYdpzrAb4Xc8T8ulU462SkJpQJQQr1ROpwA/v6OpaUHfls5/cLFz/vWP+djH5s9gcjF64p8/8UnLxXJG9FOZZGANbzMhCzWtwWnJgx6TldoKCUo7zJSIlw6fy8u4ZmgMI1KlQR2gxrI29SCyvq4Y07Jnjm6TONpARj0hy1jfeCERkU60YCZlqFuTqdBhrJlRJVlvgSk0hETGYXKMfb/3YOFe8rjfuvvPBKdXi//VlY9/rp+m9zq3eEL4bRO03GOLPQeAZzItrgnlH97oz7Dh+n3oymD2II64XFxQg5mAgR1GsfTAwgkFhXKqBuCH5IHB0uVe5gHtmmgOPBSBgYQhwBVREnhqtqn/2r7zV37vb3/qM/Qe6FLffdkTnz/56e1uchG81OMZmEX6I4xU6TDxdjmwjVpaajz53sJIwp8kI4msBgPQQD1tbAKo9OpJnzSaYwdN48kKH1Bh+x7wFI3JzIEGUxxO2pb26TJqeMzic/7MzLgQ8AJT5B33T37nlZd8/JMfl3axYoH7q5c94TneTb/knH8qWk0bK020JhNrFIH3wowxqNUIMXOg9JsaCSYPkr2xpmfGFO9XA9Rhy1iW2JM+OQbWsynCYyGTCdACeaGYlhCDciBgf72ShhyDE5mKIsz+DOz3Huy4Wx73sbt/DzmzCdz54u++/Ik/sJzcG72bLnfOX0B2Log1/E7Kiw1ae7WmNrKChXYWAg+AgodplO4iRi73V2nQ1qVigIDTMFA0QFsYT7Fley5RSQp981fWHuhJWPwNjZhGuE+AVc0bmoQlO1kH62HQnnaT//WlX77pkt/+9FdwBMKFnOzasLK2OPmCaXKvnpz/Ye/cUcbspm6JhRWZIzbZiavDGQqhIPTmy1bMHnBjDUzGoESAQNDnma37OabmMmmZiCX/1cDlQ29p1M7ignbIzBfRhKqQSIwRd/hdN/nPueX0Tnfs+G/k7MHawM03PvDSv/uofeee57z/Se+mp03O/W3n/E63AJywmQIE8wAGxmKAsXwfN0h5ZW8ixt4PFhd65YUEsCoSaIrSS+jDs3YOvPCuXi0t6xvhD1R0bk2m4a6KyqIqOrIBbh8TmoB94Jz/lp/856Zp+V+Wx93vXPLRT3/bAiv9fVMqoAcEBvYP/NByWlzm3OJZzk1Pds5f7Nx0kXPuCF7qNHbiio6q9yWDKr0IBkXei2sPLI9HW25GmXbFLfmFiRIALBsA+VG6rYEGvrWAn6/8ySxNRCwtnmmtpqVFk8i4lZC8c3uTcw+45fQN5xdfnvzyD5bTkc8e3Xf3XfypT50aAWy+5v8BUrIHNHvQF7oAAAAASUVORK5CYII=", - }, - typography: { - fontFamily: `"Roboto", "Helvetica", "Arial", sans-serif`, - useNextVariants: true, - h1: { - fontSize: 40, - }, - h4: { - fontSize: 30, - fontWeight: 500, - }, - h6: { - fontSize: 22, - }, - body1: { - fontSize: 18, - }, - }, - overrides: { - MuiMenu: { - list: { - backgroundColor: "#383B40", - }, - }, - MuiCssBaseline: { - '@global': { - /* - scrollbarColor: "#6b6b6b #2b2b2b", - "&::-webkit-scrollbar, & *::-webkit-scrollbar": { - backgroundColor: "#2b2b2b", - }, - "&::-webkit-scrollbar-thumb, & *::-webkit-scrollbar-thumb": { - borderRadius: 8, - backgroundColor: "#6b6b6b", - minHeight: 24, - border: "3px solid #2b2b2b", - }, - "&::-webkit-scrollbar-thumb:focus, & *::-webkit-scrollbar-thumb:focus": { - backgroundColor: "#959595", - }, - "&::-webkit-scrollbar-thumb:active, & *::-webkit-scrollbar-thumb:active": { - backgroundColor: "#959595", - }, - "&::-webkit-scrollbar-thumb:hover, & *::-webkit-scrollbar-thumb:hover": { - backgroundColor: "#959595", - }, - "&::-webkit-scrollbar-corner, & *::-webkit-scrollbar-corner": { - backgroundColor: "#2b2b2b", - }, - */ - } - }, - }, -}); - -export default theme; diff --git a/frontend/src/theme.jsx b/frontend/src/theme.jsx index de75558c..8d13070c 100644 --- a/frontend/src/theme.jsx +++ b/frontend/src/theme.jsx @@ -1,42 +1,49 @@ import React from "react"; -import { createMuiTheme } from "@material-ui/core/styles"; +import { createTheme, adaptV4Theme } from "@mui/material/styles"; -const theme = createMuiTheme({ +//const theme = createTheme({ +const theme = createTheme(adaptV4Theme({ palette: { + theme: "dark", + main: "#F86743", primary: { main: "#F86743", - contrastText: "#ffffff", + contrastText: "#ffffff", }, secondary: { main: "#e8eaf6", + contrastText: "#000000", }, text: { secondary: "rgba(255,255,255,0.7)", }, type: "dark", + inputColor: "rgba(39,41,45,1)", + //inputColor: "#383B40", surfaceColor: "#27292d", - inputColor: "#383B40", platformColor: "#1c1c1d", backgroundColor: "#1a1a1a", + green: "#5cc879", borderRadius: 5, defaultBorder: "1px solid rgba(255,255,255,0.3)", - jsonTheme: "brewer", - reactJsonStyle: { - borderRadius: 5, - border: "1px solid rgba(255,255,255,0.7)", - padding: 5, - }, + jsonTheme: "brewer", + reactJsonStyle: { + borderRadius: 5, + border: "1px solid rgba(255,255,255,0.7)", + padding: 5, + }, textFieldStyle: { backgroundColor: "#383B40", borderRadius: 5, }, innerTextfieldStyle: { - color: "white", - minHeight: 50, - marginLeft: "5px", - maxWidth: "95%", - fontSize: "1em", - borderRadius: 5, + // Removed since upgrading to mui 18 + //color: "white", + //minHeight: 50, + //marginLeft: "5px", + //maxWidth: "95%", + //fontSize: "1em", + //borderRadius: 5, }, tooltip: { backgroundColor: "white", @@ -71,34 +78,20 @@ const theme = createMuiTheme({ }, }, MuiCssBaseline: { - '@global': { - /* - scrollbarColor: "#6b6b6b #2b2b2b", - "&::-webkit-scrollbar, & *::-webkit-scrollbar": { - backgroundColor: "#2b2b2b", - }, - "&::-webkit-scrollbar-thumb, & *::-webkit-scrollbar-thumb": { - borderRadius: 8, - backgroundColor: "#6b6b6b", - minHeight: 24, - border: "3px solid #2b2b2b", - }, - "&::-webkit-scrollbar-thumb:focus, & *::-webkit-scrollbar-thumb:focus": { - backgroundColor: "#959595", - }, - "&::-webkit-scrollbar-thumb:active, & *::-webkit-scrollbar-thumb:active": { - backgroundColor: "#959595", - }, - "&::-webkit-scrollbar-thumb:hover, & *::-webkit-scrollbar-thumb:hover": { - backgroundColor: "#959595", - }, - "&::-webkit-scrollbar-corner, & *::-webkit-scrollbar-corner": { - backgroundColor: "#2b2b2b", - }, - */ - } + MuiCssBaseline: { + styleOverrides: ` + @font-face { + font-family: 'roboto'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('roboto'), local('roboto'), format('truetype'); + unicodeRange: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF; + } + `, }, + }, }, -}); +})); export default theme; diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 2082cf2a..346609f4 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -1,16 +1,14 @@ import React, { useState, useEffect } from "react"; -import { makeStyles } from "@material-ui/styles"; +import theme from "../theme.jsx"; +import { makeStyles } from "@mui/styles"; + import { useNavigate, Link } from "react-router-dom"; import countries from "../components/Countries.jsx"; import CodeEditor from "../components/ShuffleCodeEditor.jsx"; import getLocalCodeData from "../components/ShuffleCodeEditor.jsx"; import CacheView from "../components/CacheView.jsx"; -import theme from "../theme.jsx"; -import AddIcon from "@mui/icons-material/Add"; -import ClearIcon from '@mui/icons-material/Clear'; -import StorageIcon from '@mui/icons-material/Storage'; -//import ToggleButton from '@mui/material/ToggleButton'; + import { FormControl, InputLabel, @@ -45,19 +43,21 @@ import { DialogContent, CircularProgress, Box, - InputAdornment, -} from "@material-ui/core"; - -import { Autocomplete } from "@mui/material"; + InputAdornment, + Autocomplete +} from "@mui/material"; import { + Add as AddIcon, + Clear as ClearIcon, + Storage as StorageIcon, Edit as EditIcon, FileCopy as FileCopyIcon, SelectAll as SelectAllIcon, OpenInNew as OpenInNewIcon, CloudDownload as CloudDownloadIcon, Description as DescriptionIcon, - Polymer as PolymerIcon, + Polyline as PolylineIcon, CheckCircle as CheckCircleIcon, Close as CloseIcon, Apps as AppsIcon, @@ -66,15 +66,17 @@ import { Cached as CachedIcon, AccessibilityNew as AccessibilityNewIcon, Lock as LockIcon, - Eco as EcoIcon, Schedule as ScheduleIcon, Cloud as CloudIcon, Business as BusinessIcon, - Visibility as VisibilityIcon, - VisibilityOff as VisibilityOffIcon, -} from "@material-ui/icons"; + Visibility as VisibilityIcon, + VisibilityOff as VisibilityOffIcon, -import { useAlert } from "react-alert"; + FmdGood as FmdGoodIcon, +} from "@mui/icons-material"; + +//import { useAlert +import { ToastContainer, toast } from "react-toastify" import Dropzone from "../components/Dropzone.jsx"; import HandlePaymentNew from "../views/HandlePaymentNew.jsx"; import OrgHeader from "../components/OrgHeader.jsx"; @@ -182,6 +184,11 @@ const Admin = (props) => { const [billingInfo, setBillingInfo] = React.useState({}); const [selectedStatus, setSelectedStatus] = React.useState([]); + useEffect(() => { + getUsers() + }, []); + + useEffect(() => { if (isDropzone) { //redirectOpenApi(); @@ -210,13 +217,13 @@ const Admin = (props) => { .then((responseJson) => { //console.log("RESPONSE: ", responseJson) if (responseJson.success === true) { - //alert.info(responseJson.reason) + //toast(responseJson.reason) setImage2FA(responseJson.reason); setSecret2FA(responseJson.extra); } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -250,7 +257,7 @@ const Admin = (props) => { //} }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -281,19 +288,22 @@ const Admin = (props) => { ] */ - const alert = useAlert(); + //const alert = useAlert(); const handleStatusChange = (event) => { const { value } = event.target; + console.log("value: ", value) setSelectedStatus(value); - handleEditOrg( + + + handleEditOrg( "", "", selectedOrganization.id, "", {}, {}, - value, + value.length === 0 ? ["none"] : value, ) } @@ -325,67 +335,66 @@ const Admin = (props) => { if (org.security_framework !== undefined && org.security_framework !== null) { if (org.security_framework.cases.name !== undefined && org.security_framework.cases.name !== null && org.security_framework.cases.name !== "") { - your_apps += org.security_framework.cases.name.replace("_", " ", -1) + ", " + your_apps += org.security_framework.cases.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { - subject += " & " + subject += " and " } subject_add += 1 - subject += org.security_framework.cases.name.replace("_", " ", -1) + subject += org.security_framework.cases.name.replace("_", " ", -1).replace(" API", "", -1) } } if (org.security_framework.siem.name !== undefined && org.security_framework.siem.name !== null && org.security_framework.siem.name !== "") { - your_apps += org.security_framework.siem.name.replace("_", " ", -1) + ", " - + your_apps += org.security_framework.siem.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { - subject += " & " + subject += " and " } subject_add += 1 - subject += org.security_framework.siem.name.replace("_", " ", -1) + subject += org.security_framework.siem.name.replace("_", " ", -1).replace(" API", "", -1) } } if (org.security_framework.communication.name !== undefined && org.security_framework.communication.name !== null && org.security_framework.communication.name !== "") { - your_apps += org.security_framework.communication.name.replace("_", " ", -1) + ", " + your_apps += org.security_framework.communication.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { - subject += " & " + subject += " and " } subject_add += 1 - subject += org.security_framework.communication.name.replace("_", " ", -1) + subject += org.security_framework.communication.name.replace("_", " ", -1).replace(" API", "", -1) } } if (org.security_framework.edr.name !== undefined && org.security_framework.edr.name !== null && org.security_framework.edr.name !== "") { - your_apps += org.security_framework.edr.name.replace("_", " ", -1) + ", " + your_apps += org.security_framework.edr.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { - subject += " & " + subject += " and " } subject_add += 1 - subject += org.security_framework.edr.name.replace("_", " ", -1) + subject += org.security_framework.edr.name.replace("_", " ", -1).replace(" API", "", -1) } } if (org.security_framework.intel.name !== undefined && org.security_framework.intel.name !== null && org.security_framework.intel.name !== "") { - your_apps += org.security_framework.intel.name.replace("_", " ", -1) + ", " + your_apps += org.security_framework.intel.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { - subject += " & " + subject += " and " } subject_add += 1 - subject += org.security_framework.intel.name.replace("_", " ", -1) + subject += org.security_framework.intel.name.replace("_", " ", -1).replace(" API", "", -1) } } @@ -453,11 +462,11 @@ ${usecases} Let me know if you're interested, or set up a call here: https://drift.me/${username}` - return `mailto:${admins}?bcc=frikky@shuffler.io&subject=${subject}&body=${body}` + return `mailto:${admins}?bcc=frikky@shuffler.io,binu@shuffler.io&subject=${subject}&body=${body}` } const deleteAuthentication = (data) => { - alert.info("Deleting auth " + data.label); + toast("Deleting auth " + data.label); // Just use this one? const url = globalUrl + "/api/v1/apps/authentication/" + data.id; @@ -471,14 +480,14 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }) .then((response) => response.json().then((responseJson) => { - if (responseJson["success"] === false) { - alert.error("Failed deleting auth"); + if (responseJson.success === false) { + toast("Failed deleting auth"); } else { // Need to wait because query in ES is too fast setTimeout(() => { getAppAuthentication(); }, 1000); - //alert.success("Successfully deleted authentication!") + //toast("Successfully deleted authentication!") } }) ) @@ -510,12 +519,12 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user response.json().then((responseJson) => { console.log("RESP: ", responseJson); if (responseJson["success"] === false) { - alert.error("Failed stopping schedule"); + toast("Failed stopping schedule"); } else { setTimeout(() => { getSchedules(); }, 1500); - //alert.success("Successfully stopped schedule!") + //toast("Successfully stopped schedule!") } }) ) @@ -526,7 +535,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user if (userdata.support === true && selectedOrganization.id !== "" && selectedOrganization.id !== undefined && selectedOrganization.id !== null && selectedOrganization.id !== userdata.active_org.id) { - alert.info("Refreshing window to fix org support access") + toast("Refreshing window to fix org support access") window.location.reload() return null } @@ -551,15 +560,15 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => { if (response.status === 200) { } else { - //alert.info("Wrong code sent.") - //alert.info("Wrong code sent. Please try again.") + //toast("Wrong code sent.") + //toast("Wrong code sent. Please try again.") } return response.json(); }) .then((responseJson) => { if (responseJson.success === true) { - alert.info("Successfully enabled 2fa"); + toast("Successfully enabled 2fa"); setTimeout(() => { getUsers(); @@ -571,19 +580,19 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user setSelectedUserModalOpen(false); }, 1000); } else { - alert.info("Wrong code sent. Please try again."); - //alert.error("Failed setting 2fa: ", responseJson.reason) + toast("Wrong code sent. Please try again."); + //toast("Failed setting 2fa: ", responseJson.reason) } }) .catch((error) => { - alert.info("Wrong code sent. Please try again."); - //alert.error("Err: " + error.toString()) + toast("Wrong code sent. Please try again."); + //toast("Err: " + error.toString()) }); }; const handleStopOrgSync = (org_id) => { if (org_id === undefined || org_id === null) { - alert.error("Couldn't get org " + org_id); + toast("Couldn't get org " + org_id); return; } @@ -604,10 +613,10 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => { if (response.status === 200) { console.log("Cloud sync success?"); - alert.success("Successfully stopped cloud sync"); + toast("Successfully stopped cloud sync"); } else { console.log("Cloud sync fail?"); - alert.error( + toast( "Failed stopping sync. Try again, and contact support if this persists." ); } @@ -620,7 +629,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }, 1000); }) .catch((error) => { - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; @@ -664,16 +673,16 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user responseJson.reason !== undefined ) { setOrgSyncResponse(responseJson.reason); - alert.error("Failed to handle sync: " + responseJson.reason); + toast("Failed to handle sync: " + responseJson.reason); } else if (!responseJson.success) { - alert.error("Failed to handle sync."); + toast("Failed to handle sync."); } else { getOrgs(); if (disableSync) { - alert.success("Successfully disabled sync!"); + toast("Successfully disabled sync!"); setOrgSyncResponse("Successfully disabled syncronization"); } else { - alert.success("Cloud Syncronization successfully set up!"); + toast("Cloud Syncronization successfully set up!"); setOrgSyncResponse( "Successfully started syncronization. Cloud features you now have access to can be seen below." ); @@ -688,7 +697,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }) .catch((error) => { setLoading(false); - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; @@ -710,16 +719,16 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - alert.error("Failed changing authentication"); + toast("Failed changing authentication"); } else { - //alert.success("Successfully password!") + //toast("Successfully password!") setSelectedUserModalOpen(false); getAppAuthentication(); } }) ) .catch((error) => { - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; @@ -740,7 +749,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user image: image, defaults: defaults, sso_config: sso_config, - lead_info: lead_info, + lead_info: lead_info, }; const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; @@ -758,16 +767,16 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - alert.error("Failed updating org: ", responseJson.reason); + toast("Failed updating org: ", responseJson.reason); } else { if (lead_info === undefined || lead_info === null || lead_info === []) { - alert.success("Successfully edited org!"); + toast("Successfully edited org!"); } } }) ) .catch((error) => { - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; @@ -792,9 +801,9 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - alert.error("Failed overwriting appauth in workflows"); + toast("Failed overwriting appauth in workflows"); } else { - alert.success("Successfully updated auth everywhere!"); + toast("Successfully updated auth everywhere!"); setSelectedUserModalOpen(false); setTimeout(() => { getAppAuthentication(); @@ -803,7 +812,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }) ) .catch((error) => { - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; @@ -827,12 +836,12 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user response.json().then((responseJson) => { if (responseJson["success"] === false) { if (responseJson.reason !== undefined) { - alert.error(responseJson.reason); + toast(responseJson.reason); } else { - alert.error("Failed creating suborg. Please try again"); + toast("Failed creating suborg. Please try again"); } } else { - alert.success( + toast( "Successfully created suborg. Reloading in 3 seconds!" ); setSelectedUserModalOpen(false); @@ -847,7 +856,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }) ) .catch((error) => { - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; @@ -870,18 +879,18 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user response.json().then((responseJson) => { if (responseJson["success"] === false) { if (responseJson.reason !== undefined) { - alert.error(responseJson.reason); + toast(responseJson.reason); } else { - alert.error("Failed setting new password"); + toast("Failed setting new password"); } } else { - alert.success("Successfully updated password!"); + toast("Successfully updated password!"); setSelectedUserModalOpen(false); } }) ) .catch((error) => { - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; @@ -906,9 +915,9 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }) .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed to deactivate user: " + responseJson.reason); + toast("Failed to deactivate user: " + responseJson.reason); } else { - alert.success("Changed activation for user " + data.id); + toast("Changed activation for user " + data.id); } }) @@ -931,7 +940,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user } if (orgId.length === 0) { - alert.error("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout."); + toast("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout."); return; } @@ -952,7 +961,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }) .then((responseJson) => { if (responseJson["success"] === false) { - alert.error("Failed getting your org. If this persists, please contact support."); + toast("Failed getting your org. If this persists, please contact support."); } else { if ( responseJson.sync_features === undefined || @@ -987,6 +996,14 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user leads.push("student") } + if (responseJson.lead_info.internal) { + leads.push("internal") + } + + if (responseJson.lead_info.sub_org) { + leads.push("sub_org") + } + setSelectedStatus(leads) } @@ -1016,7 +1033,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }) .catch((error) => { console.log("Error getting org: ", error); - alert.error("Error getting current organization"); + toast("Error getting current organization"); }); }; @@ -1045,7 +1062,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user response.json().then((responseJson) => { if (responseJson["success"] === false) { setLoginInfo("Error: " + responseJson.reason); - alert.error("Failed to send email (2). Please try again and contact support if this persists.") + toast("Failed to send email (2). Please try again and contact support if this persists.") } else { setLoginInfo(""); setModalOpen(false); @@ -1053,13 +1070,13 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user getUsers(); }, 1000); - alert.info("Invite sent! They will show up in the list when they have accepted the invite.") + toast("Invite sent! They will show up in the list when they have accepted the invite.") } }) ) .catch((error) => { console.log("Error in userdata: ", error); - alert.error("Failed to send email. Please try again and contact support if this persists.") + toast("Failed to send email. Please try again and contact support if this persists.") }); }; @@ -1101,12 +1118,12 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user // Horrible frontend fix for environments const setDefaultEnvironment = (environment) => { // FIXME - add more checks to this - alert.info("Setting default env to " + environment.name); + toast("Setting default env to " + environment.name); var newEnv = []; for (var key in environments) { if (environments[key].id == environment.id) { if (environments[key].archived) { - alert.error("Can't set archived to default"); + toast("Can't set archived to default"); return; } @@ -1134,7 +1151,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - alert.error(responseJson.reason); + toast(responseJson.reason); setTimeout(() => { getEnvironments(); }, 1500); @@ -1165,7 +1182,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - alert.error(responseJson.reason); + toast(responseJson.reason); getEnvironments(); } else { setLoginInfo(""); @@ -1180,7 +1197,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }; const rerunCloudWorkflows = (environment) => { - alert.info("Starting execution reruns. This can run in the background.") + toast("Starting execution reruns. This can run in the background.") fetch( `${globalUrl}/api/v1/environments/${environment.id}/rerun`, { @@ -1193,8 +1210,8 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user console.log("Status not 200 for apps :O!"); return; } else { - alert.error(response.reason); - //alert.info("Aborted all dangling workflows"); + toast(response.reason); + //toast("Aborted all dangling workflows"); } return response.json(); @@ -1205,7 +1222,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user //setFiles(responseJson) }) .catch((error) => { - //alert.error(error.toString()) + //toast(error.toString()) }); }; @@ -1222,10 +1239,10 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); - alert.error("Failed aborting dangling workflows"); + toast("Failed aborting dangling workflows"); return; } else { - alert.info("Aborted all dangling workflows"); + toast("Aborted all dangling workflows"); } return response.json(); @@ -1236,7 +1253,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user //setFiles(responseJson) }) .catch((error) => { - //alert.error(error.toString()) + //toast(error.toString()) }); }; @@ -1244,17 +1261,17 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user // FIXME - add some check here ROFL //const name = environment.name - //alert.info("Modifying environment " + name) + //toast("Modifying environment " + name) //var newEnv = [] //for (var key in environments) { // if (environments[key].Name == name) { // if (environments[key].default) { - // alert.error("Can't modify the default environment") + // toast("Can't modify the default environment") // return // } // if (environments[key].type === "cloud" && !environments[key].archived) { - // alert.error("Can't modify cloud environments") + // toast("Can't modify cloud environments") // return // } @@ -1265,17 +1282,17 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user //} const id = environment.id; - //alert.info("Modifying environment " + environment.Name) + //toast("Modifying environment " + environment.Name) var newEnv = []; for (var key in environments) { if (environments[key].id == id) { if (environments[key].default) { - alert.error("Can't modify the default environment"); + toast("Can't modify the default environment"); return; } if (environments[key].type === "cloud" && !environments[key].archived) { - alert.error("Can't modify cloud environments"); + toast("Can't modify cloud environments"); return; } @@ -1298,7 +1315,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - alert.error(responseJson.reason); + toast(responseJson.reason); getEnvironments(); } else { setLoginInfo(""); @@ -1371,7 +1388,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user setSchedules(responseJson); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -1393,16 +1410,16 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user return response.json(); }) .then((responseJson) => { - if (responseJson.success) { + if (responseJson.success === true) { //console.log(responseJson.data) //console.log(responseJson) setAuthentication(responseJson.data); } else { - alert.error("Failed getting authentications"); + toast("Failed getting authentications"); } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -1427,7 +1444,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user setEnvironments(responseJson); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -1455,7 +1472,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user setOrganizations(responseJson); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -1481,14 +1498,10 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user setUsers(responseJson); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; - useEffect(() => { - getUsers() - }, []); - const getSettings = () => { fetch(globalUrl + "/api/v1/getsettings", { method: "GET", @@ -1572,6 +1585,8 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user if (firstRequest) { setFirstRequest(false); document.title = "Shuffle - admin"; + + getEnvironments(); if (!isCloud) { getUsers(); } else { @@ -1658,10 +1673,10 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user }) .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed setting user: " + responseJson.reason); + toast("Failed setting user: " + responseJson.reason); } else { - //alert.success("Set the user field " + field + " to " + value); - alert.success("Successfully updated user field " + field) + //toast("Set the user field " + field + " to " + value); + toast("Successfully updated user field " + field) if (field !== "suborgs") { setSelectedUserModalOpen(false); @@ -1677,15 +1692,24 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user const userId = user.id; const data = { user_id: userId }; - fetch(globalUrl + "/api/v1/generateapikey", { + console.log(user, userdata) + + var fetchdata = { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, - body: JSON.stringify(data), credentials: "include", - }) + } + + if (userId === userdata.id) { + fetchdata.method = "GET" + } else { + fetchdata.body = JSON.stringify(data) + } + + fetch(globalUrl + "/api/v1/generateapikey", fetchdata) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for WORKFLOW EXECUTION :O!"); @@ -1698,9 +1722,9 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user .then((responseJson) => { console.log("RESP: ", responseJson); if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed getting new: " + responseJson.reason); + toast("Failed getting new: " + responseJson.reason); } else { - alert.success("Got new API key"); + toast("Got new API key"); } }) .catch((error) => { @@ -1794,9 +1818,9 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user } if (error) { - alert.error("All fields must have a new value"); + toast("All fields must have a new value"); } else { - alert.success("Saving new version of this authentication"); + toast("Saving new version of this authentication"); selectedAuthentication.fields = authenticationFields; saveAuthentication(selectedAuthentication); setSelectedAuthentication({}); @@ -1813,7 +1837,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user const handleOrgEditChange = (event) => { if (userdata.id === selectedUser.id) { - alert.info("Can't remove orgs from yourself"); + toast("Can't remove orgs from yourself"); return; } @@ -1874,8 +1898,11 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user open={selectedUserModalOpen} onClose={() => { setSelectedUserModalOpen(false); - setImage2FA(""); - setSecret2FA(""); + + setImage2FA(""); + setValue2FA(""); + setSecret2FA(""); + setShow2faSetup(false); }} PaperProps={{ style: { @@ -2141,7 +2168,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user , + icon: , }, { primary: "Apps", @@ -2356,23 +2383,22 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user */} {userdata.support === true ? - - - - + + {/**/} + Status { disabled={selectedTrigger.status === "running"} SelectDisplayProps={{ style: { - marginLeft: 10, }, }} onChange={(e) => { @@ -9076,7 +10033,7 @@ const AngularWorkflow = (defaultprops) => {
    - +

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

    + + What are email triggers? + {
    Name
    { placeholder={selectedTrigger.label} onChange={selectedTriggerChange} /> - + {/*
    Environment: { value={selectedTrigger.environment} />
    + */} { setUpdate(Math.random()); if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) { - console.log("Returning as there's no id") + console.log("Returning as there's no id. Value: ", e.target.value); return null } @@ -9906,23 +10850,17 @@ const AngularWorkflow = (defaultprops) => { return (
    -
    -
    -

    - {selectedTrigger.app_name} -

    - - What are subflows? - -
    -
    +

    + {selectedTrigger.app_name} +

    + + What are subflows? + { Name { { .value === "true" } onChange={() => { - const newvalue = - workflow.triggers[selectedTriggerIndex].parameters[4] === - undefined || - workflow.triggers[selectedTriggerIndex].parameters[4] - .value === "false" - ? "true" - : "false"; + 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, @@ -10080,8 +11003,7 @@ const AngularWorkflow = (defaultprops) => { { borderRadius: theme.palette.borderRadius, }} onChange={(event, newValue) => { - console.log("Found value: ", newValue) + console.log("Found value: ", newValue) - var parsedinput = { target: { value: newValue } } + var parsedinput = { target: { value: newValue } } - // For variables - if (typeof newValue === 'string' && newValue.startsWith("$")) { - parsedinput = { - target: { - value: { - "name": newValue, - "id": newValue, - "actions": [], - "triggers": [], - } - } - } - } + // For variables + if (typeof newValue === 'string' && newValue.startsWith("$")) { + parsedinput = { + target: { + value: { + "name": newValue, + "id": newValue, + "actions": [], + "triggers": [], + } + } + } + } handleWorkflowSelectionUpdate(parsedinput) }} - renderOption={(data, index) => { + renderOption={(props, data, state) => { if (data.id === workflow.id) { data = workflow; } @@ -10156,9 +11078,16 @@ const AngularWorkflow = (defaultprops) => { backgroundColor: theme.palette.inputColor, color: data.id === workflow.id ? "red" : "white", }} - key={index} value={data} + onClick={() => { + handleWorkflowSelectionUpdate({ + target: { + value: data + } + }) + }} > + {data.name} @@ -10234,7 +11163,7 @@ const AngularWorkflow = (defaultprops) => { onChange={(event, newValue) => { handleSubflowStartnodeSelection({ target: { value: newValue } }) }} - renderOption={(action) => { + renderOption={(props, action, state) => { const isParent = getParents(selectedTrigger).find( (parent) => parent.id === action.id ) @@ -10252,6 +11181,13 @@ const AngularWorkflow = (defaultprops) => { } }} disabled={isCloud && isParent} + onClick={() => { + handleSubflowStartnodeSelection({ + target: { + value: action + } + }) + }} style={{ backgroundColor: theme.palette.inputColor, color: isParent ? "red" : "white", @@ -10270,7 +11206,7 @@ const AngularWorkflow = (defaultprops) => { borderRadius: theme.palette.borderRadius, }} {...params} - label="Find your start-node" + label="Select a start-node (optional)" variant="outlined" /> ); @@ -10296,10 +11232,6 @@ const AngularWorkflow = (defaultprops) => { }} InputProps={{ style: { - color: "white", - marginLeft: "5px", - maxWidth: "95%", - fontSize: "1em", }, endAdornment: ( @@ -10358,11 +11290,6 @@ const AngularWorkflow = (defaultprops) => { }} InputProps={{ style: { - color: "white", - marginLeft: "5px", - maxWidth: "95%", - fontSize: "1em", - height: 50, }, }} fullWidth @@ -10416,21 +11343,15 @@ const AngularWorkflow = (defaultprops) => { return (
    -
    - -
    +

    Comment

    + + What are comments? + {
    Name
    {
    Height
    {
    Width
    {
    Background
    {
    Text Color
    {
    Background-Image
    { return null; }; - const WebhookSidebar = () => { - if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (workflow.triggers[selectedTriggerIndex] === undefined) { - return null; - } + // Special SCHEDULE handler + var trigger_header_auth = "" + if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined ) { + if (selectedTrigger.trigger_type === "SCHEDULE" && workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null) { + console.log("Autofixing schedule") - if ( - workflow.triggers[selectedTriggerIndex].parameters === undefined || - workflow.triggers[selectedTriggerIndex].parameters === null || - workflow.triggers[selectedTriggerIndex].parameters.length === 0 - ) { workflow.triggers[selectedTriggerIndex].parameters = []; workflow.triggers[selectedTriggerIndex].parameters[0] = { - name: "url", - value: referenceUrl + "webhook_" + selectedTrigger.id, + name: "cron", + value: isCloud ? "*/25 * * * *" : "60", }; workflow.triggers[selectedTriggerIndex].parameters[1] = { - name: "tmp", - value: "webhook_" + selectedTrigger.id, - }; - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "auth_headers", - value: "", - }; - workflow.triggers[selectedTriggerIndex].parameters[3] = { - name: "custom_response_body", - value: "", - }; - workflow.triggers[selectedTriggerIndex].parameters[4] = { - name: "await_response", - value: "v1", + name: "execution_argument", + value: '{"example": {"json": "is cool"}}', }; setWorkflow(workflow); - } else { - // Always update - const newUrl = referenceUrl + "webhook_" + selectedTrigger.id; - //console.log("Validating webhook url: ", newUrl); - if (selectedTrigger.environment !== "cloud") { - if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) { - console.log("Url is wrong. NOT updating because of hybrid."); - //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl; - //setWorkflow(workflow); - } - } - } + } else if (selectedTrigger.trigger_type === "WEBHOOK") { + if (workflow.triggers[selectedTriggerIndex] === undefined) { + return null; + } - const trigger_header_auth = - workflow.triggers[selectedTriggerIndex].parameters.length > 2 - ? workflow.triggers[selectedTriggerIndex].parameters[2].value - : ""; + if ( + workflow.triggers[selectedTriggerIndex].parameters === undefined || + workflow.triggers[selectedTriggerIndex].parameters === null || + workflow.triggers[selectedTriggerIndex].parameters.length === 0 + ) { + workflow.triggers[selectedTriggerIndex].parameters = []; + workflow.triggers[selectedTriggerIndex].parameters[0] = { + name: "url", + value: referenceUrl + "webhook_" + selectedTrigger.id, + }; + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "tmp", + value: "webhook_" + selectedTrigger.id, + }; + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "auth_headers", + value: "", + }; + workflow.triggers[selectedTriggerIndex].parameters[3] = { + name: "custom_response_body", + value: "", + }; + workflow.triggers[selectedTriggerIndex].parameters[4] = { + name: "await_response", + value: "v1", + }; + setWorkflow(workflow); + } else { + // Always update + const newUrl = referenceUrl + "webhook_" + selectedTrigger.id; + //console.log("Validating webhook url: ", newUrl); + if (selectedTrigger.environment !== "cloud") { + if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) { + console.log("Url is wrong. NOT updating because of hybrid."); + //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl; + //setWorkflow(workflow); + } + } + } - return ( + trigger_header_auth = + workflow.triggers[selectedTriggerIndex].parameters.length > 2 + ? workflow.triggers[selectedTriggerIndex].parameters[2].value + : ""; + } + } + + const WebhookSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "WEBHOOK" ? null :
    -
    -
    -

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

    - - What are webhooks? - -
    -
    +

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

    + + What are webhooks? + {
    Name
    { }, }} filterOptions={(options, { inputValue }) => { - //console.log("Option contains?: ", inputValue, options) + 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)) @@ -10764,7 +11662,7 @@ const AngularWorkflow = (defaultprops) => { // }); //} }} - renderOption={(app) => { + renderOption={(props, app, state) => { var appname = app.name.replaceAll("_", " ") appname = appname.charAt(0).toUpperCase() + appname.substring(1) @@ -10774,7 +11672,19 @@ const AngularWorkflow = (defaultprops) => { title={appname} placement="left" > -
    + { + const newValue = app + + if (newValue !== undefined && newValue !== null) { + var parsedvalue = JSON.parse(JSON.stringify(newValue)) + parsedvalue.actions = [] + parsedvalue.authentication = {} + selectedTrigger.app_association = parsedvalue + setUpdate(Math.random()); + } + }} + >
    { {appname}
    -
    + ) }} @@ -10811,7 +11721,7 @@ const AngularWorkflow = (defaultprops) => {
    : null} {selectedTrigger.status === "running" ? null : -
    +
    Environment

    Tags

    - { color="primary" fullWidth value={newWorkflowTags} - onAdd={(chip) => { - newWorkflowTags.push(chip); - setNewWorkflowTags(newWorkflowTags); - setUpdate("added" + chip); - }} - onDelete={(chip, index) => { - newWorkflowTags.splice(index, 1); - setNewWorkflowTags(newWorkflowTags); - setUpdate("delete " + chip); - }} + onChange={(chips) => { + setNewWorkflowTags(chips) + setUpdate("added "+chips) + }} + />
    ); @@ -4622,11 +4707,11 @@ const AppCreator = (defaultprops) => { setSelectedAction(selectedAction); } - //alert.error("Failed getting authentications") + //toast("Failed getting authentications") } }) .catch((error) => { - alert.error("Auth loading error: " + error.toString()); + toast("Auth loading error: " + error.toString()); }); }; @@ -4682,17 +4767,17 @@ const AppCreator = (defaultprops) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to set app auth: " + responseJson.reason); + toast("Failed to set app auth: " + responseJson.reason); } else { getAppAuthentication(true); setAuthenticationModalOpen(false); // Needs a refresh with the new authentication.. - //alert.success("Successfully saved new app auth") + //toast("Successfully saved new app auth") } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -4740,10 +4825,10 @@ const AppCreator = (defaultprops) => { } const handleSubmitCheck = () => { - console.log("NEW AUTH: ", authenticationOption); + //console.log("NEW AUTH: ", authenticationOption); if (authenticationOption.label.length === 0) { authenticationOption.label = `Auth for ${selectedApp.name}`; - //alert.info("Label can't be empty") + //toast("Label can't be empty") //return } @@ -4753,7 +4838,7 @@ const AppCreator = (defaultprops) => { selectedApp.authentication.parameters[key].name ].length === 0 ) { - alert.info( + toast( "Field " + selectedApp.authentication.parameters[key].name + " can't be empty" @@ -4963,10 +5048,10 @@ const AppCreator = (defaultprops) => { {projectCategories.map((tag, index) => { const newname = tag.charAt(0).toUpperCase() + tag.slice(1); - //var regex = /_shuffle_replace_\d/i; - ////console.log("NEW: ", - //newname = newname.replaceAll(regex, "") - //console.log("Replaced: ", newname) + //var regex = /_shuffle_replace_\d/i; + ////console.log("NEW: ", + //newname = newname.replaceAll(regex, "") + //console.log("Replaced: ", newname) return ( { setFileBase64(canvasUrl); } } catch (e) { - alert.error("Failed to parse canvasurl!"); + toast("Failed to parse canvasurl!"); } }; @@ -5249,7 +5334,7 @@ const AppCreator = (defaultprops) => { setOpenImageModal(false); setDisableImageUpload(true); } catch (e) { - alert.error("Failed to set image. Replace it if this persists."); + toast("Failed to set image. Replace it if this persists."); } } }; @@ -5363,6 +5448,167 @@ const AppCreator = (defaultprops) => { ) : null; + const validateRemote = () => { + setValidation(true); + + fetch(globalUrl + "/api/v1/get_openapi_uri", { + method: "POST", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(openApi), + credentials: "include", + }) + .then((response) => { + setValidation(false); + if (response.status !== 200) { + return response.json(); + } + + return response.text(); + }) + .then((responseJson) => { + if (typeof responseJson !== "string" && !responseJson.success) { + console.log(responseJson.reason); + if (responseJson.reason !== undefined) { + setOpenApiError(responseJson.reason); + } else { + setOpenApiError("Undefined issue with OpenAPI validation"); + } + return; + } + + console.log("Validating response!"); + validateOpenApi(responseJson); + }) + .catch((error) => { + toast(error.toString()); + setOpenApiError(error.toString()); + }); + } + + const circularLoader = validation ? ( + + ) : null; + + const newApimodalView = openApiModal ? + { + setOpenApiModal(false) + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + + +
    + Merge with another OpenAPI document. You will get to choose Actions before they are merged. +
    +
    + + Paste in the URI for the OpenAPI + 0} + color="primary" + onClick={() => { + setOpenApiError(""); + validateRemote(); + }} + > + Validate + + ), + }} + onChange={(e) => { + setOpenApi(e.target.value); + }} + helperText={ + + Must point to a version 2 or 3 OpenAPI specification. + + } + placeholder="OpenAPI URI" + fullWidth + /> + {/* +
    + Example: +
    + https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json + */} +

    Or upload a YAML/JSON specification

    + + + {errorText} + + + {circularLoader} + + + + +
    + : null + // Random names for type & autoComplete. Didn't research :^) const landingpageDataBrowser = (
    @@ -5394,9 +5640,30 @@ const AppCreator = (defaultprops) => { onChange={editHeaderImage} /> -

    - General information -

    +
    +
    +

    + General information +

    +
    +
    + + { + setOpenApiModal(true) + }} + > + { + setOpenApiModal(true) + }} + /> + + +
    +
    { const invalid = ["#", ":", "."]; for (var key in invalid) { if (e.target.value.includes(invalid[key])) { - alert.error("Can't use " + invalid[key] + " in name"); + toast("Can't use " + invalid[key] + " in name"); setName(e.target.value.replaceAll(".", "").replaceAll("#", "").replaceAll(":", "").replaceAll(",", "")) return; @@ -5472,7 +5739,7 @@ const AppCreator = (defaultprops) => { } if (e.target.value.length > 29) { - alert.error("Choose a shorter name (max 29)."); + toast("Choose a shorter name (max 29)."); setName(e.target.value.slice(0,28)) return; } @@ -5584,7 +5851,7 @@ const AppCreator = (defaultprops) => { !tmpstring.startsWith("http") && !tmpstring.startsWith("ftp") ) { - alert.error("URL must start with http(s)://"); + toast("URL must start with http(s)://"); } if (tmpstring.includes("?")) { @@ -5709,6 +5976,7 @@ const AppCreator = (defaultprops) => { isLoaded && isAppLoaded ? (
    {landingpageDataBrowser}
    + {newApimodalView}
    ) : (
    diff --git a/frontend/src/views/AppExplorer.jsx b/frontend/src/views/AppExplorer.jsx deleted file mode 100644 index 01fc4546..00000000 --- a/frontend/src/views/AppExplorer.jsx +++ /dev/null @@ -1,468 +0,0 @@ - -import { Grid, Divider, List, ListItem, ListItemText } from "@mui/material"; -import { experimentalStyled as styled } from '@mui/material/styles'; -import Typography from "@material-ui/core/Typography"; -import Paper from "@material-ui/core/Paper"; -import Button from '@mui/material/Button'; -import Box from '@material-ui/core/Box'; -import React, { useState, useEffect } from "react"; - - -import algoliasearch from "algoliasearch"; -//import algoliarecommend from "algoliarecommend"; - -const searchClient = algoliasearch( - "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" -); - -// https://www.algolia.com/doc/api-client/getting-started/install/ -/*const algoliarecommend = require('@algolia/recommend'); - -const client = algoliarecommend( - "NSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" -);*/ - - -const Item = styled(Paper)(({ theme }) => ({ - padding: theme.spacing(2), - border: "0.0625rem solid #b2b2b2", - borderRadius: "1.5625rem", - boxSizing: "content-box", - backgroundColor: "transparent", - width: "200px", - textAlign: 'center', - color: theme.palette.text.secondary, - marginTop: "20px", - marginBottom: "20px", - color: "textPrimary" -})); -const AppExplorer = (props) => { - const [algoliaResult, setAlgoliaResult] = useState(""); - - const runAlgoliaAppSearch = (query) => { - const index = searchClient.initIndex("appsearch"); - - index - .search(`${query}`) - .then(({ hits }) => { - setAlgoliaResult(hits); - }) - .catch((err) => { - console.log(err); - }); - }; - - useEffect(() => { - - runAlgoliaAppSearch("wazuh") - - }, []) - - - const brandApp = () => { - const index = searchClient.initIndex("appsearch"); - - const replicaIndex = searchClient.initIndex('appsearch'); - replicaIndex.setSettings({ - customRanking: [ - "asc(time_edited)" - ] - }) - - .then(({ hits }) => { - console.log(hits); - }) - .catch((err) => { - console.log(err); - }); - }; - - useEffect(() => { - - brandApp() - - }, []) - - /*const trandingApp = () => { - - const index = client.getTrendingGlobalItems([ - { - indexName: "appsearch", - threshold: 60 - }, - ]) - .then(({ results }) => { - console.log(results); - }) - .catch(err => { - console.log(err); - }); - }; - - useEffect(() => { - - trandingApp(); - - }, []) - */ - - const SideBar = { - minWidth: 250, - - borderRight: "1px solid rgba(255,255,255,0.3)", - left: 0, - position: "sticky", - minHeight: "90vh", - maxHeight: "90vh", - overflowX: "hidden", - overflowY: "auto", - zIndex: 1000, - color: "white" - }; - const contentbar = { - padding: "40px", - - }; - const boxdata = { - paddingLeft: "30px" - } - const link = { - textDecoration: "none" - } - const catItems = ( -
    - - - - Categories - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - ) - return ( -
    -
    - - {catItems} -
    - - - - - Getting Started - -
    -
    - -
    -
    - - - {Array.from(Array(algoliaResult.length)).map((_, index) => ( - - -
    - - - -
    - -
    - shuffle -
    -
    -
    - - {algoliaResult[0]["name"]} - -
    -
    - - {algoliaResult[0]["description"].substring(0, 20)} - -
    -
    - -
    -
    -
    - - ))} - - - - - - Most Popular - -
    -
    - -
    - - - {Array.from(Array(3)).map((_, index) => ( - - - -
    -
    - shuffle -
    -
    -
    - - App Name - -
    -
    - - Description - -
    -
    - -
    -
    -
    -
    - ))} -
    -
    - - - - - Brand New - -
    -
    - -
    - - - {Array.from(Array(3)).map((_, index) => ( - - - -
    -
    - shuffle -
    -
    -
    - - App Name - -
    -
    - - Description - -
    -
    - -
    -
    -
    -
    - ))} -
    -
    - - - - - {Array.from(Array(3)).map((_, index) => ( - - -
    -
    - shuffle -
    -
    - -
    - ))} -
    -
    - - - - - Hybrid work - -
    -
    - -
    - - - - {Array.from(Array(3)).map((_, index) => ( - - - -
    -
    - shuffle -
    -
    -
    - - App Name - -
    -
    - - Description - -
    -
    - -
    -
    -
    -
    - ))} -
    -
    - -
    -
    - Don't see it? Build it! - Use our APIs to create an app that makes your working life better.And maybe even share it with the world. - visit developer portal -
    -
    -
    -
    - shuffle -
    -
    - shuffle -
    -
    - shuffle -
    -
    -
    -
    - -
    - -
    -
    - - ); -} -export default AppExplorer; diff --git a/frontend/src/views/AppHub.jsx b/frontend/src/views/AppHub.jsx deleted file mode 100644 index 5a4db83a..00000000 --- a/frontend/src/views/AppHub.jsx +++ /dev/null @@ -1,789 +0,0 @@ -import React from "react"; -import { Grid, Container, Divider, CardMedia, List, ListItem, ListItemText } from "@mui/material"; - -import { makeStyles } from "@material-ui/core/styles"; -import Card from "@material-ui/core/Card"; -import CardContent from "@material-ui/core/CardContent"; - -import Table from "@material-ui/core/Table"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableContainer from "@material-ui/core/TableContainer"; -import TableHead from "@material-ui/core/TableHead"; -import TableRow from "@material-ui/core/TableRow"; -import Paper from "@material-ui/core/Paper"; - -import { LineChart, LineSeries, BarChart } from "reaviz"; -import { Gridline, GridStripe } from "reaviz"; -import { GridlineSeries } from "reaviz"; - -import InputLabel from '@material-ui/core/InputLabel'; -import FormControl from '@material-ui/core/FormControl'; -import Select from '@material-ui/core/Select'; - -import { styled, alpha } from '@mui/material/styles'; -import AppBar from '@mui/material/AppBar'; -import Box from '@mui/material/Box'; -import Toolbar from '@mui/material/Toolbar'; -import IconButton from '@mui/material/IconButton'; -import Typography from '@mui/material/Typography'; -import InputBase from '@mui/material/InputBase'; -import Badge from '@mui/material/Badge'; -import MenuItem from '@mui/material/MenuItem'; -import Menu from '@mui/material/Menu'; -import MenuIcon from '@mui/icons-material/Menu'; -import SearchIcon from '@mui/icons-material/Search'; -import AccountCircle from '@mui/icons-material/AccountCircle'; -import MailIcon from '@mui/icons-material/Mail'; -import NotificationsIcon from '@mui/icons-material/Notifications'; -import MoreIcon from '@mui/icons-material/MoreVert'; -import SearchField from "../components/Searchfield"; -import { SpaRounded } from "@material-ui/icons"; -import { isMobile } from "react-device-detect" - -const data = [ - { - key: new Date("11/29/2019"), - data: 10, - }, - { - key: new Date("11/30/2019"), - data: 14, - }, - { - key: new Date("12/01/2019"), - data: 5, - }, - { - key: new Date("12/02/2019"), - data: 18, - }, -]; - -const useStyles1 = makeStyles((theme) => ({ - formControl: { - margin: theme.spacing(1), - minWidth: 120, - }, - selectEmpty: { - marginTop: theme.spacing(2), - }, -})); - - -const useStyles = makeStyles({ - table: { - minWidth: 650, - }, - root: { - minWidth: 275, - }, - bullet: { - display: "inline-block", - margin: "0 2px", - transform: "scale(0.8)", - }, - title: { - fontSize: 14, - }, - pos: { - marginBottom: 12, - }, -}); - -function createData(name, calories, fat, carbs, protein) { - return { name, calories, fat, carbs, protein }; -} - -const rows = [ - createData("Frozen yoghurt", 159, 6.0, 24, 4.0), - createData("Ice cream sandwich", 237, 9.0, 37, 4.3), - createData("Eclair", 262, 16.0, 24, 6.0), - createData("Cupcake", 305, 3.7, 67, 4.3), - createData("Gingerbread", 356, 16.0, 49, 3.9), -]; - -const Search = styled('div')(({ theme }) => ({ - position: 'relative', - borderRadius: theme.shape.borderRadius, - backgroundColor: alpha(theme.palette.common.white, 0.15), - '&:hover': { - backgroundColor: alpha(theme.palette.common.white, 0.25), - }, - marginRight: theme.spacing(2), - marginLeft: 0, - width: '100%', - [theme.breakpoints.up('sm')]: { - marginLeft: theme.spacing(3), - width: 'auto', - }, -})); - -const SearchIconWrapper = styled('div')(({ theme }) => ({ - padding: theme.spacing(0, 2), - height: '100%', - position: 'absolute', - pointerEvents: 'none', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', -})); - -const StyledInputBase = styled(InputBase)(({ theme }) => ({ - color: 'inherit', - '& .MuiInputBase-input': { - padding: theme.spacing(1, 1, 1, 0), - // vertical padding + font size from searchIcon - paddingLeft: `calc(1em + ${theme.spacing(4)})`, - transition: theme.transitions.create('width'), - width: '100%', - [theme.breakpoints.up('md')]: { - width: '20ch', - }, - }, -})); - -function PrimarySearchAppBar() { - const [anchorEl, setAnchorEl] = React.useState(null); - const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null); - - const isMenuOpen = Boolean(anchorEl); - const isMobileMenuOpen = Boolean(mobileMoreAnchorEl); - - const handleProfileMenuOpen = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handleMobileMenuClose = () => { - setMobileMoreAnchorEl(null); - }; - - const handleMenuClose = () => { - setAnchorEl(null); - handleMobileMenuClose(); - }; - - const handleMobileMenuOpen = (event) => { - setMobileMoreAnchorEl(event.currentTarget); - }; - - const menuId = 'primary-search-account-menu'; - const renderMenu = ( - - Profile - My account - - ); - - const mobileMenuId = 'primary-search-account-menu-mobile'; - const renderMobileMenu = ( - - - - - - - -

    Messages

    -
    - - - - - - -

    Notifications

    -
    - - - - -

    Profile

    -
    -
    - ); - - return ( - - - - shuffle img - - {/* */} - - - - {renderMobileMenu} - {renderMenu} - - ); -} - -const AppHub = () => { - const classes = useStyles(); - const classes1 = useStyles1(); - - const [usecases, setUsecases] = React.useState([ - { - "name": "1. Collect", - "color": "#c51152", - "list": [ - { - "name": "Email management", - "priority": 100, - "type": "communication", - "items": { - "name": "Release a quarantined message", - "items": {} - }, - "matches": [] - }, - { - "name": "EDR to ticket", - "priority": 100, - "type": "edr", - "items": { - "name": "Get host information", - "items": {} - }, - "matches": [] - }, - { - "name": "SIEM to ticket", - "priority": 100, - "type": "siem", - "description": "Ensure tickets are forwarded to the correct destination. Alternatively add enrichment on it's way there.", - "video": "https://www.youtube.com/watch?v=FBISHA7V15c&t=197s&ab_channel=OpenSecure", - "blogpost": "https://medium.com/shuffle-automation/introducing-shuffle-an-open-source-soar-platform-part-1-58a529de7d12", - "reference_image": "/images/detectionframework.png", - "items": {}, - "matches": [] - }, - { - "name": "2-way Ticket synchronization", - "priority": 90, - "items": {}, - "matches": [] - }, - { - "name": "ChatOps", - "priority": 70, - "items": {}, - "matches": [] - }, - { - "name": "Threat Intel received", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Assign tickets", - "priority": 30, - "items": {}, - "matches": [] - }, - { - "name": "Firewall alerts", - "priority": 90, - "items": { - "name": "URL filtering", - "items": {} - }, - "matches": [] - }, - { - "name": "IDS/IPS alerts", - "priority": 90, - "items": { - "name": "Manage policies", - "items": {} - }, - "matches": [] - }, - { - "name": "Deduplicate information", - "priority": 70, - "items": {}, - "matches": [] - } - ], - "matches": [] - }, - { - "name": "2. Enrich", - "color": "#f4c20d", - "list": [ - { - "name": "Internal Enrichment", - "priority": 100, - "items": { - "name": "...", - "items": {} - }, - "matches": [] - }, - { - "name": "External historical Enrichment", - "priority": 90, - "items": { - "name": "...", - "items": {} - }, - "matches": [] - }, - { - "name": "Realtime", - "priority": 50, - "items": { - "name": "Analyze screenshots", - "items": {} - }, - "matches": [] - } - ], - "matches": [] - }, - { - "name": "3. Detect", - "color": "#3cba54", - "list": [ - { - "name": "Search SIEM (Sigma)", - "priority": 90, - "items": { - "name": "Endpoint", - "items": {} - }, - "matches": [] - }, - { - "name": "Search EDR (OSQuery)", - "priority": 90, - "items": {}, - "matches": [] - }, - { - "name": "Search emails (Sublime)", - "priority": 90, - "items": { - "name": "Check headers and IOCs", - "items": {} - }, - "matches": [] - }, - { - "name": "Search IOCs (ioc-finder)", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Search files (Yara)", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Memory Analysis (Volatility)", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "IDS & IPS (Snort/Surricata)", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Validate old tickets", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Honeypot access", - "priority": 50, - "items": { - "name": "...", - "items": {} - }, - "matches": [] - } - ], - "matches": [] - }, - { - "name": "4. Respond", - "color": "#4885ed", - "list": [ - { - "name": "Eradicate malware", - "priority": 90, - "items": {}, - "matches": [] - }, - { - "name": "Quarantine host(s)", - "priority": 90, - "items": {}, - "matches": [] - }, - { - "name": "Block IPs, URLs, Domains and Hashes", - "priority": 90, - "items": {}, - "matches": [] - }, - { - "name": "Trigger scans", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Update indicators (FW, EDR, SIEM...)", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Autoblock activity when threat intel is received", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Lock/Delete/Reset account", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Lock vault", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Increase authentication", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Get policies from assets", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Run ansible scripts", - "priority": 50, - "items": {}, - "matches": [] - } - ], - "matches": [] - }, - { - "name": "5. Verify", - "color": "#7f00ff", - "list": [ - { - "name": "Discover vulnerabilities", - "priority": 80, - "items": {}, - "matches": [] - }, - { - "name": "Discover assets", - "priority": 80, - "items": {}, - "matches": [] - }, - { - "name": "Ensure policies are followed", - "priority": 80, - "items": {}, - "matches": [] - }, - { - "name": "Find Inactive users", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Botnet tracker", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Ensure access rights match HR systems", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Ensure onboarding is followed", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Third party apps in SaaS", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Devices used for your cloud account", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Too much access in GCP/Azure/AWS/ other clouds", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Certificate validation", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Domain investigation with LetsEncrypt", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Monitor new DNS entries for domain with passive DNS", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Monitor and track password dumps", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Monitor for mentions of domain on darknet sites", - "priority": 50, - "items": {}, - "matches": [] - }, - { - "name": "Reporting", - "priority": 50, - "items": { - "name": "Monthly reports", - "items": { - "name": "...", - "items": {} - } - }, - "matches": [] - } - ], - "matches": [] - } - ]); - - const SideBar = { - minWidth: 250, - maxWidth: 300, - borderRight: "1px solid rgba(255,255,255,0.3)", - left: 0, - position: "sticky", - minHeight: "90vh", - maxHeight: "90vh", - overflowX: "hidden", - overflowY: "auto", - zIndex: 1000, - color: "black" - }; - - const [age, setAge] = React.useState(0); - - const handleChange = (event) => { - setAge(event.target.value); - - }; - - return ( -
    - - -
    -
    -
    - -
    -
    -
    - shuffle img - - SHUFFLE - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    - - - - Categories - - - - - - - Workflows - - - - - Apps - - - - - Docs - - - -
    -
    - - Workflow - -
    - {!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ? -
    - - {usecases.map((usecase, index) => { - //console.log(usecase) - return ( - - { - console.log("clicked...") - }} - > - - - {usecase.name} - - - In use: {usecase.matches.length}/{usecase.list.length} - - - - - ) - })} - -
    - : null} - -
    -
    -
    - - - - footer - - - -
    - ); -}; - -export default AppHub; \ No newline at end of file diff --git a/frontend/src/views/Appdemo.jsx b/frontend/src/views/Appdemo.jsx deleted file mode 100644 index 3fe5aae1..00000000 --- a/frontend/src/views/Appdemo.jsx +++ /dev/null @@ -1,268 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { Grid, Container, Divider, CardMedia, List, ListItem, ListItemText } from "@mui/material"; -import Typography from "@material-ui/core/Typography"; - -import theme from '../theme'; -import {isMobile} from "react-device-detect"; -import AppGrid1 from "../components/AppGrid1.jsx" -import WorkflowGrid from "../components/WorkflowGrid.jsx" -import CreatorGrid from "../components/CreatorGrid.jsx" -import DocsGrid from "../components/DocsGrid.jsx" -import Button from '@mui/material/Button'; -import { useNavigate, Link } from "react-router-dom"; - -import { - Tabs, - Paper, - Tab, -} from "@material-ui/core"; - -import { - Business as BusinessIcon, - Apps as AppsIcon, - Polymer as PolymerIcon, - EmojiObjects as EmojiObjectsIcon, - Description as DescriptionIcon, -} from "@material-ui/icons"; - - -const bodyDivStyle = { - margin: "auto", - maxWidth: 1024, - scrollX: "hidden", - overflowX: "hidden", -} - -// Should be different if logged in :| -const Appdemo = (props) => { - const { globalUrl, isLoaded, serverside, userdata, hidemargins, } = props; - const [appCategory,setAppCategory] = useState(); - let navigate = useNavigate(); - - const [curTab, setCurTab] = useState(0); - const iconStyle = { marginRight: 10 }; - - useEffect(() => { - if (serverside !== true && window.location.search !== undefined && window.location.search !== null) { - const urlSearchParams = new URLSearchParams(window.location.search) - const params = Object.fromEntries(urlSearchParams.entries()) - const foundTab = params["tab"] - if (foundTab !== null && foundTab !== undefined) { - for (var key in Object.keys(views)) { - const value = views[key] - console.log(key, value) - if (value === foundTab) { - setConfig("", key) - break - } - } - } - } - }, []) - - if (serverside === true) { - return null - } - - const boxStyle = { - color: "white", - flex: "1", - marginLeft: 10, - marginRight: 10, - paddingLeft: 30, - paddingRight: 30, - paddingBottom: 30, - paddingTop: hidemargins === true ? 0 : 30, - display: "flex", - flexDirection: "column", - overflowX: "hidden", - minHeight: 400, - } - const NoArguments_NoReturn = () => { - - alert('Function Called...'); - - } - const SideBar = { - minWidth: 250, - maxWidth: 300, - borderRight: "1px solid rgba(255,255,255,0.3)", - left: 0, - position: "sticky", - minHeight: "90vh", - maxHeight: "90vh", - overflowX: "hidden", - overflowY: "auto", - zIndex: 1000, - color: "white" - }; - const catItems = ( -
    - - - - Categories - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -) - - const views = { - 0: "apps", - 1: "workflows", - 2: "docs", - 3: "creators", - } - - const setConfig = (event, inputValue) => { - const newValue = parseInt(inputValue) - - setCurTab(newValue) - if (newValue === 0) { - document.title = "Shuffle - search - apps"; - } else if (newValue === 1) { - document.title = "Shuffle - search - workflows"; - } else if (newValue === 2) { - document.title = "Shuffle - search - documentation"; - } else if (newValue === 3) { - document.title = "Shuffle - search - creators"; - } else { - document.title = "Shuffle - search"; - } - - - const urlSearchParams = new URLSearchParams(window.location.search) - const params = Object.fromEntries(urlSearchParams.entries()) - const foundQuery = params["q"] - var extraQ = "" - if (foundQuery !== null && foundQuery !== undefined) { - extraQ = "&q="+foundQuery - } - - - if ((serverside === false || serverside === undefined) && window.location.pathname.includes("/search")) { - navigate(`/search?tab=${views[newValue]}`+extraQ) - } - } - - if (isLoaded === false) { - return null - } - - - // Random names for type & autoComplete. Didn't research :^) - const landingpageDataBrowser = -
    -
    - - - Apps - - /> - - - {curTab === 0 ? - - : - curTab === 1 ? - window.location.pathname === "/search" ? - - : - - : - curTab === 2 ? - - : - curTab === 3 ? - - : - null} -
    -
    - //{/*alternativeView={true} />*/} - - const loadedCheck = isLoaded ? -
    -
    {landingpageDataBrowser}
    -
    - : -
    -
    - - // #1f2023? - return( -
    - {catItems} - {loadedCheck} -
    - ) -} - -export default Appdemo; diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 44c3e594..d5e88e8c 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import { useInterval } from "react-powerhooks"; +import theme from '../theme.jsx'; import { IconButton, @@ -32,9 +33,10 @@ import { ListItemAvatar, ListItemText, Avatar, -} from "@material-ui/core"; +} from "@mui/material"; import { + AutoFixHigh as AutoFixHighIcon, LockOpen as LockOpenIcon, OpenInNew as OpenInNewIcon, Apps as AppsIcon, @@ -46,20 +48,20 @@ import { Search as SearchIcon, Folder as FolderIcon, LibraryBooks as LibraryBooksIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; import { ForkRight as ForkRightIcon, } from '@mui/icons-material'; import aa from 'search-insights' -import { useTheme } from "@material-ui/core/styles"; import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; import algoliasearch from 'algoliasearch/lite'; import YAML from "yaml"; import { useNavigate, Link, useParams } from "react-router-dom"; -import { useAlert } from "react-alert"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" import Dropzone from "../components/Dropzone.jsx"; const surfaceColor = "#27292D"; @@ -271,9 +273,8 @@ const Apps = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata } = props; //const [workflows, setWorkflows] = React.useState([]); - const theme = useTheme(); const baseRepository = "https://github.com/frikky/shuffle-apps"; - const alert = useAlert(); + //const alert = useAlert(); let navigate = useNavigate(); const [selectedApp, setSelectedApp] = React.useState({}); @@ -286,6 +287,7 @@ const Apps = (props) => { const [selectedAction, setSelectedAction] = React.useState({}); const [searchBackend, setSearchBackend] = React.useState(false); const [searchableApps, setSearchableApps] = React.useState([]); + const [publishModalOpen, setPublishModalOpen] = React.useState(false); const [openApi, setOpenApi] = React.useState(""); const [openApiData, setOpenApiData] = React.useState(""); @@ -293,6 +295,8 @@ const Apps = (props) => { const [loadAppsModalOpen, setLoadAppsModalOpen] = React.useState(false); const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); const [openApiModal, setOpenApiModal] = React.useState(false); + const [generateAppModal, setGenerateAppModal] = React.useState(false); + const [openApiModalType, setOpenApiModalType] = React.useState(""); const [openApiError, setOpenApiError] = React.useState(""); const [field1, setField1] = React.useState(""); @@ -438,6 +442,7 @@ const Apps = (props) => { if (privateapps.length > 0) { if (selectedApp.id === undefined || selectedApp.id === null) { setSelectedApp(privateapps[0]); + setSharingConfiguration(privateapps[0].sharing === true ? "public" : "you") } if ( @@ -455,7 +460,7 @@ const Apps = (props) => { //}, 5000) }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); setIsLoading(false); }); }; @@ -463,7 +468,7 @@ const Apps = (props) => { const downloadApp = (inputdata) => { const id = inputdata.id; - alert.info("Downloading.."); + toast("Downloading.."); fetch(globalUrl + "/api/v1/apps/" + id + "/config", { method: "GET", headers: { @@ -481,7 +486,7 @@ const Apps = (props) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to download file"); + toast("Failed to download file"); } else { console.log(responseJson); const basedata = atob(responseJson.openapi); @@ -539,7 +544,7 @@ const Apps = (props) => { }) .catch((error) => { console.log(error); - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -643,6 +648,7 @@ const Apps = (props) => { if (selectedApp.id !== data.id) { data.name = newAppname; setSelectedApp(data); + setSharingConfiguration(data.sharing === true ? "public" : "you") if ( data.actions !== undefined && @@ -655,7 +661,7 @@ const Apps = (props) => { } if (data.sharing) { - setSharingConfiguration(isCloud ? "public" : "everyone"); + setSharingConfiguration("public"); } } }} @@ -996,11 +1002,11 @@ const Apps = (props) => { ); }; - const userRoles = ["you", isCloud ? "public" : "everyone"]; + const userRoles = ["you", "public"]; - // Admin in org or creator of app - // FIXME: Missing check for if same creator account - const canEditApp = userdata !== undefined && (userdata.admin === "true" || userdata.id === selectedApp.owner || selectedApp.owner === "" || (userdata.admin === "true" && userdata.active_org.id === selectedApp.reference_org)) || !selectedApp.generated + // Admin in org or creator of app + // FIXME: Missing check for if same creator account + const canEditApp = userdata !== undefined && (userdata.admin === "true" || userdata.id === selectedApp.owner || selectedApp.owner === "" || (userdata.admin === "true" && userdata.active_org.id === selectedApp.reference_org)) || !selectedApp.generated //fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), var baseInfo = @@ -1051,6 +1057,7 @@ const Apps = (props) => { console.log("New version: ", newversion); selectedApp.app_version = selectedApp.app_version; setSelectedApp(selectedApp); + setSharingConfiguration(selectedApp.sharing === true ? "public" : "you") if (newversion !== undefined && newversion !== null) { getApp(newversion.id, true); @@ -1153,17 +1160,22 @@ const Apps = (props) => { + + {errorText} + + + {circularLoader} + + + + + + ) : null + + + const modalView = openApiModal ? ( + { + setOpenApiModal(false); + setGenerateAppModal(false); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + + +
    + Create a new app from OpenAPI / Swagger
    @@ -2677,6 +3003,8 @@ const Apps = (props) => {
    {appView} {modalView} + {publishModal} + {generateAppView} {appsModalLoad} {deleteModal}
    diff --git a/frontend/src/views/Contact.jsx b/frontend/src/views/Contact.jsx deleted file mode 100755 index 5a34382c..00000000 --- a/frontend/src/views/Contact.jsx +++ /dev/null @@ -1,345 +0,0 @@ -import React, { useState } from 'react'; -import { BrowserView, MobileView } from "react-device-detect"; - -import Paper from '@material-ui/core/Paper'; -import Button from '@material-ui/core/Button'; - -import TextField from '@material-ui/core/TextField'; - -import { useTheme } from '@material-ui/core/styles'; - -const bodyDivStyle = { - margin: "auto", - textAlign: "center", - width: "900px", -} - - -// Should be different if logged in :| -const Contact = (props) => { - const { globalUrl, isLoaded } = props; - - const theme = useTheme(); - - const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: theme.palette.surfaceColor, - display: "flex", - flexDirection: "column" - } - - const bodyTextStyle = { - color: "#ffffff", - } - - const [firstname, setFirstname] = useState(""); - const [lastname, setLastname] = useState(""); - const [title, setTitle] = useState(""); - const [companyname, setCompanyname] = useState(""); - const [email, setEmail] = useState(""); - const [phone, setPhone] = useState(""); - const [message, setMessage] = useState(""); - - const [formMessage, setFormMessage] = useState(""); - - const submitContact = () => { - const data = { - "firstname": firstname, - "lastname": lastname, - "title": title, - "companyname": companyname, - "email": email, - "phone": phone, - "message": message, - } - console.log(data) - - fetch(globalUrl + "/api/v1/contact", { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }) - .then(response => response.json()) - .then(response => { - if (response.success === true) { - setFormMessage(response.message) - } else { - setFormMessage("Something went wrong. Please contact frikky@shuffler.io.") - } - console.log(response) - }) - .catch(error => { - console.log(error) - }); - } - - // Random names for type & autoComplete. Didn't research :^) - const landingpageDataBrowser = -
    -
    -

    Contact us

    -

    Lets talk!

    -
    -
    - -

    Contact Details

    -
    - setFirstname(e.target.value)} - /> - setLastname(e.target.value)} - /> -
    -
    - setTitle(e.target.value)} - /> - setCompanyname(e.target.value)} - /> -
    -
    - setEmail(e.target.value)} - /> - setPhone(e.target.value)} - /> -
    -
    -

    Message

    -
    -
    - setMessage(e.target.value)} - /> -
    - -

    {formMessage}

    -
    -
    -
    - - const landingpageDataMobile = -
    -
    -

    Contact us

    -

    Lets talk!

    -
    -
    - -

    Contact Details

    -
    - setFirstname(e.target.value)} - /> -
    -
    - setEmail(e.target.value)} - /> -
    -
    -

    Message

    -
    -
    - setMessage(e.target.value)} - /> -
    - -

    {formMessage}

    -
    -
    -
    - - - const loadedCheck = isLoaded ? -
    - -
    {landingpageDataBrowser}
    -
    - - {landingpageDataMobile} - -
    - : -
    -
    - - return ( -
    - {loadedCheck} -
    - ) -} -export default Contact; diff --git a/frontend/src/views/Dashboard.jsx b/frontend/src/views/Dashboard.jsx index 15549a36..94317c55 100755 --- a/frontend/src/views/Dashboard.jsx +++ b/frontend/src/views/Dashboard.jsx @@ -1,18 +1,22 @@ import React, { useState, useEffect } from "react"; import { useInterval } from "react-powerhooks"; import AppFramework from "../components/AppFramework.jsx"; -import { makeStyles, useTheme } from "@material-ui/core/styles"; +import { makeStyles, } from "@mui/styles"; // nodejs library that concatenates classes import classNames from "classnames"; import theme from '../theme.jsx'; import { useNavigate, Link, useParams } from "react-router-dom"; +import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx"; // react plugin used to create charts //import { Line, Bar } from "react-chartjs-2"; -import { useAlert } from "react-alert"; -import Autocomplete from "@material-ui/lab/Autocomplete"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" +import { parsedDatatypeImages } from "../components/AppFramework.jsx" +import { findSpecificApp } from "../components/AppFramework.jsx" import { + Autocomplete, Tooltip, TextField, IconButton, @@ -22,7 +26,7 @@ import { Paper, Chip, Checkbox, -} from "@material-ui/core"; +} from "@mui/material"; import { Close as CloseIcon, @@ -33,7 +37,7 @@ import { CheckBox as CheckBoxIcon, CheckBoxOutlineBlank as CheckBoxOutlineBlankIcon, OpenInNew as OpenInNewIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; import WorkflowPaper from "../components/WorkflowPaper.jsx" import { removeParam } from "../views/AngularWorkflow.jsx" @@ -83,14 +87,15 @@ const useStyles = makeStyles({ }, inputRoot: { color: "white", - // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90 "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: "#f86a3e", }, }, }); -const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLoggedIn, workflows, setWorkflows}) => { + + +const UsecaseListComponent = ({userdata, keys, isCloud, globalUrl, frameworkData, isLoggedIn, workflows, setWorkflows}) => { const [expandedIndex, setExpandedIndex] = useState(-1); const [expandedItem, setExpandedItem] = useState(-1); const [inputUsecase, setInputUsecase] = useState({}); @@ -103,17 +108,92 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged const [selectedWorkflows, setSelectedWorkflows] = useState([]) const [firstLoad, setFirstLoad] = useState(true) + const [apps, setApps] = useState([]) - const classes = useStyles(); + const classes = useStyles(); let navigate = useNavigate(); const [mitreTags, setMitreTags] = useState([]); + + const loadApps = () => { + fetch(`${globalUrl}/api/v1/apps`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + return response.json(); + }) + .then((responseJson) => { + if (responseJson === null) { + console.log("null-response from server") + const pretend_apps = [{ + "name": "TBD", + "app_name": "TBD", + "app_version": "TBD", + "description": "TBD", + "version": "TBD", + "large_image": "", + }] + + setApps(pretend_apps) + return + } + + if (responseJson.success === false) { + console.log("error loading apps: ", responseJson) + return + } + + setApps(responseJson); + }) + .catch((error) => { + console.log("App loading error: " + error.toString()); + }) + } + + useEffect(() => { + loadApps() + }, []) + if (keys === undefined || keys === null || keys.length === 0) { return null } - const getUsecase = (name, index, subindex) => { - fetch(`${globalUrl}/api/v1/workflows/usecases/${escape(name.replaceAll(" ", "_"))}`, { + + const parseUsecase = (subcase) => { + console.log("parseUsecase: ", subcase) + const srcdata = findSpecificApp(frameworkData, subcase.type) + const dstdata = findSpecificApp(frameworkData, subcase.last) + + console.log("srcdata: ", srcdata) + console.log("dstdata: ", dstdata) + + if (srcdata !== undefined && srcdata !== null) { + subcase.srcimg = srcdata.large_image + subcase.srcapp = srcdata.name + } + + if (dstdata !== undefined && dstdata !== null) { + subcase.dstimg = dstdata.large_image + subcase.dstapp = dstdata.name + } + + return subcase + } + + const getUsecase = (subcase, index, subindex) => { + subcase = parseUsecase(subcase) + + // Timeout 50ms to delay it slightly + setTimeout(() => { + setInputUsecase(subcase) + }, 50) + + fetch(`${globalUrl}/api/v1/workflows/usecases/${escape(subcase.name.replaceAll(" ", "_"))}`, { method: "GET", headers: { "Content-Type": "application/json", @@ -121,50 +201,58 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged }, credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for framework!"); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success === false) { - setInputUsecase({ - "name": name, + return response.json(); + }) + .then((responseJson) => { + console.log("In responseJson for usecase: ", responseJson) + var parsedUsecase = responseJson + + if (responseJson.success === false) { + parsedUsecase = subcase + } else { + parsedUsecase = responseJson + + parsedUsecase.srcimg = subcase.srcimg + parsedUsecase.srcapp = subcase.srcapp + parsedUsecase.dstimg = subcase.dstimg + parsedUsecase.dstapp = subcase.dstapp + } + + // Look for the type of app and fill in img1, srcapp... + setInputUsecase(parsedUsecase) + setExpandedIndex(index) + setExpandedItem(subindex) + + setTimeout(() => { + //console.log("Scroll!") + const found = document.getElementById("selected_box"); + if (found !== undefined && found !== null) { + //console.log("FOUND!!") + found.scrollTo({ + top: 100, + behavior: "smooth", }) - } else { - setInputUsecase(responseJson) } - setExpandedIndex(index) - setExpandedItem(subindex) - - setTimeout(() => { - //console.log("Scroll!") - const found = document.getElementById("selected_box"); - if (found !== undefined && found !== null) { - //console.log("FOUND!!") - found.scrollTo({ - top: 100, - behavior: "smooth", - }) - } - - setFirstLoad(true) - setSelectedWorkflows([]) - }, 100); -}) - .catch((error) => { - //alert.error(error.toString()); - setInputUsecase({}) - setExpandedIndex(index) - setExpandedItem(subindex) - setFirstLoad(true) setSelectedWorkflows([]) - }) - } + }, 100); + }) + .catch((error) => { + //toast(error.toString()); + setInputUsecase({}) + setExpandedIndex(index) + setExpandedItem(subindex) + + setFirstLoad(true) + setSelectedWorkflows([]) + }) + } const setUsecaseItem = (inputUsecase) => { var parsedUsecase = inputUsecase @@ -213,19 +301,19 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - //alert.error("Failed updating: " + responseJson.reason) + //toast("Failed updating: " + responseJson.reason) } else { - //alert.error("Failed to update framework for your org.") + //toast("Failed to update framework for your org.") } } else { - //alert.info("Updated usecase.") + //toast("Updated usecase.") } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); //setFrameworkLoaded(true) }) - } + } const setWorkflow = (workflowdata) => { const new_url = `${globalUrl}/api/v1/workflows/${workflowdata.id}` @@ -250,9 +338,9 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Error updating workflow: ", responseJson.reason) + toast("Error updating workflow: ", responseJson.reason) } else { - alert.error("Error updating workflow.") + toast("Error updating workflow.") } return @@ -261,7 +349,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged return responseJson; }) .catch((error) => { - alert.error("Problem setting workflow: ", error.toString()); + toast("Problem setting workflow: ", error.toString()); }); }; @@ -289,6 +377,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged if (selectedItem && subcase.matches.length > 0 && selectedWorkflows.length === 0 && firstLoad === true) { setFirstLoad(false) setSelectedWorkflows(subcase.matches) + } } @@ -316,10 +405,8 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged } } - //const backgroundColor = selectedItem ? "inherit" : finished ? "inherit" : usecase.color const finished = subcase.matches.length > 0 const backgroundColor = theme.palette.surfaceColor - //"inherit" const itemBorder = `${selectedItem ? "3px" : expandedItem >= 0 ? "0px" : "1px"} solid ${usecase.color}` const fixedName = subcase.name.toLowerCase().replace("_", " ") @@ -330,14 +417,14 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged //setSelectedWorkflows([]) if (selectedItem) { } else { - getUsecase(subcase.name, index, subindex) + getUsecase(subcase, index, subindex) navigate(`/usecases?selected_object=${fixedName}`) //const newitem = removeParam("selected_object", cursearch); //navigate(curpath + newitem) } }}> - { + { }}> {!selectedItem ?
    @@ -637,24 +724,36 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
    : -
    - - {subcase.description} - +
    + + {subcase.description} + - {workflows !== undefined && workflows !== null && workflows.length > 0 ? - - Select relevant workflows - - : null} + {workflows !== undefined && workflows !== null && workflows.length > 0 ? + + Select relevant workflows + + : + + + Find workflows related to this usecase: + + + + + + + + + } - {workflows !== undefined && workflows !== null && workflows.length > 0 ? - 0 ? + option.id === value.id} + getOptionSelected={(option, value) => option.id === value.id} getOptionLabel={(option) => { if ( @@ -685,120 +784,125 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged borderRadius: theme.palette.borderRadius, }} onChange={(event, newValue) => { - console.log("CLICK: ", newValue) - //handleWorkflowSelectionUpdate({ target: { value: newValue} }) - //setSelectedWorkflows= - //var newvalue = [] - //for (var key in newValue) { - // if (newValue[key].id !== undefined) { - // newvalue.push(newValue[key].id) - // } - //} + console.log("CLICK: ", newValue) + //handleWorkflowSelectionUpdate({ target: { value: newValue} }) + //setSelectedWorkflows= + //var newvalue = [] + //for (var key in newValue) { + // if (newValue[key].id !== undefined) { + // newvalue.push(newValue[key].id) + // } + //} - // Doing this way as you may want to remove some too - for (var key in workflows) { - if (!newValue.find(data => data.id === workflows[key].id)) { - // Check if it has the one in it - if (workflows[key]["usecase_ids"] !== undefined && workflows[key]["usecase_ids"] !== null && workflows[key]["usecase_ids"].includes(subcase.name)) { - const filtered = workflows[key]["usecase_ids"].filter(data => data !== subcase.name) - if (filtered !== undefined && filtered !== null) { - //console.log("Removing: ", workflows[key].name, workflows[key]) - workflows[key]["usecase_ids"] = filtered - - setWorkflow(workflows[key]) - } - } + // Doing this way as you may want to remove some too + for (var key in workflows) { + if (!newValue.find(data => data.id === workflows[key].id)) { + // Check if it has the one in it + if (workflows[key]["usecase_ids"] !== undefined && workflows[key]["usecase_ids"] !== null && workflows[key]["usecase_ids"].includes(subcase.name)) { + const filtered = workflows[key]["usecase_ids"].filter(data => data !== subcase.name) + if (filtered !== undefined && filtered !== null) { + //console.log("Removing: ", workflows[key].name, workflows[key]) + workflows[key]["usecase_ids"] = filtered + + setWorkflow(workflows[key]) + } + } - continue - } + continue + } - if (workflows[key]["usecase_ids"] === undefined || workflows[key]["usecase_ids"] === null) { - workflows[key]["usecase_ids"] = [subcase.name] - console.log("Setting: ", workflows[key].name) - setWorkflow(workflows[key]) + if (workflows[key]["usecase_ids"] === undefined || workflows[key]["usecase_ids"] === null) { + workflows[key]["usecase_ids"] = [subcase.name] + console.log("Setting: ", workflows[key].name) + setWorkflow(workflows[key]) - } else if (!workflows[key]["usecase_ids"].includes(subcase.name)) { - workflows[key]["usecase_ids"].push(subcase.name) - console.log("Adding: ", workflows[key].name) - setWorkflow(workflows[key]) + } else if (!workflows[key]["usecase_ids"].includes(subcase.name)) { + workflows[key]["usecase_ids"].push(subcase.name) + console.log("Adding: ", workflows[key].name) + setWorkflow(workflows[key]) - } - } + } + } - setWorkflows(workflows) - console.log("New: ", newValue) - setSelectedWorkflows(newValue) + setWorkflows(workflows) + console.log("New: ", newValue) + setSelectedWorkflows(newValue) //setUpdate(Math.random()) }} - renderOption={(props, option) => { - //console.log("In options?: ", props, option) + renderOption={(props, data, state) => { + var newname = data.name + if (newname === undefined || newname === null) { + newname = "placeholder" + } - var newname = props.name - if (newname === undefined || newname === null) { - newname = "placeholder" - } - - if (newname.length > 2) { - newname = newname.charAt(0).toUpperCase() + newname.substring(1) - } - return ( -
  • - - {props.image !== undefined && props.image !== null && props.image.length > 0 ? - {newname} - : null} - - Choose {newname} - - - } placement="bottom"> - - } - checkedIcon={} - style={{ marginRight: 8 }} - checked={option.selected} - /> - {newname} - - -
  • - ) - }} + if (newname.length > 2) { + newname = newname.charAt(0).toUpperCase() + newname.substring(1) + } + return ( +
  • + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {newname} + : null} + + Choose {newname} + + + } placement="bottom"> + + } + checkedIcon={} + style={{ marginRight: 8 }} + checked={selectedWorkflows.find(wf => wf.id === data.id) !== undefined} + /> + {newname} + + +
  • + ) + }} renderInput={(params) => { return ( - ); }} /> - : null} + : null} + + {}} + > + Try it out: + + + + {/* - {/*subcase.matches.length > 0 ? - - {subcase.matches.map((workflow, workflowindex) => { - return ( - - - - ) - })} - - : -
    - - No workflow selected yet. - -
    - */} {subcase.extra_buttons !== undefined && subcase.extra_buttons !== null && subcase.extra_buttons.length > 0 ?
    @@ -852,15 +956,9 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged {}}> See other Public Workflows for {} - {/* -
    - - No workflows yet. - -
    - */}
    + */}
    }
    { // What data do we fill in here? Idk const Dashboard = (props) => { const { globalUrl, isLoggedIn } = props; - const alert = useAlert(); + //const alert = useAlert(); const [bigChartData, setBgChartData] = useState("data1"); const [dayAmount, setDayAmount] = useState(7); const [firstRequest, setFirstRequest] = useState(true); @@ -1118,16 +1218,16 @@ const Dashboard = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - //alert.error("Failed loading: " + responseJson.reason) + //toast("Failed loading: " + responseJson.reason) } else { - //alert.error("Failed to load framework for your org.") + //toast("Failed to load framework for your org.") } } else { setFrameworkData(responseJson) } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }) } @@ -1158,7 +1258,7 @@ const Dashboard = (props) => { }) .catch((error) => { fetchUsecases() - //alert.error(error.toString()); + //toast(error.toString()); }); } @@ -1258,7 +1358,7 @@ const Dashboard = (props) => { } }) .catch((error) => { - //alert.error("ERROR: " + error.toString()); + //toast("ERROR: " + error.toString()); console.log("ERROR: " + error.toString()); }); }; @@ -1292,7 +1392,7 @@ const Dashboard = (props) => { setChangeme(stats_id); }) .catch((error) => { - //alert.error("ERROR: " + error.toString()); + //toast("ERROR: " + error.toString()); console.log("ERROR: " + error.toString()); }); }; diff --git a/frontend/src/views/DashboardViews.jsx b/frontend/src/views/DashboardViews.jsx index 19c678fa..94b113f3 100644 --- a/frontend/src/views/DashboardViews.jsx +++ b/frontend/src/views/DashboardViews.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import { useInterval } from "react-powerhooks"; -import { makeStyles, useTheme } from "@material-ui/core/styles"; +import { makeStyles, } from "@mui/styles"; // nodejs library that concatenates classes import classNames from "classnames"; import theme from '../theme.jsx'; @@ -8,11 +8,12 @@ import { useNavigate, Link, useParams } from "react-router-dom"; // react plugin used to create charts //import { Line, Bar } from "react-chartjs-2"; -import { useAlert } from "react-alert"; -import Autocomplete from "@material-ui/lab/Autocomplete"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" import Draggable from "react-draggable"; import { + Autocomplete, Tooltip, TextField, IconButton, @@ -22,7 +23,7 @@ import { Paper, Chip, Checkbox, -} from "@material-ui/core"; +} from "@mui/material"; import { Close as CloseIcon, @@ -33,7 +34,7 @@ import { CheckBox as CheckBoxIcon, CheckBoxOutlineBlank as CheckBoxOutlineBlankIcon, OpenInNew as OpenInNewIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; import WorkflowPaper from "../components/WorkflowPaper.jsx" import { removeParam } from "../views/AngularWorkflow.jsx" @@ -93,7 +94,6 @@ const useStyles = makeStyles({ }, inputRoot: { color: "white", - // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90 "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: "#f86a3e", }, @@ -333,7 +333,7 @@ const RadialChart = ({keys, setSelectedCategory}) => { // What data do we fill in here? Idk const Dashboard = (props) => { const { globalUrl, isLoggedIn } = props; - const alert = useAlert(); + //const alert = useAlert(); const [bigChartData, setBgChartData] = useState("data1"); const [dayAmount, setDayAmount] = useState(7); const [firstRequest, setFirstRequest] = useState(true); @@ -428,9 +428,9 @@ const Dashboard = (props) => { console.log("Resp: ", responseJson) if (responseJson.success === false) { if (responseJson.reason !== undefined) { - //alert.error("Failed loading: " + responseJson.reason) + //toast("Failed loading: " + responseJson.reason) } else { - //alert.error("Failed to load framework for your org.") + //toast("Failed to load framework for your org.") } } else { var tmpdata = responseJson @@ -453,7 +453,7 @@ const Dashboard = (props) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); }) } @@ -505,7 +505,7 @@ const Dashboard = (props) => { setChangeme(stats_id); }) .catch((error) => { - //alert.error("ERROR: " + error.toString()); + //toast("ERROR: " + error.toString()); console.log("ERROR: " + error.toString()); }); }; diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index e0d974f5..da00e61d 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -1,10 +1,11 @@ import React, { useEffect, useState } from "react"; -import { useTheme } from "@material-ui/core/styles"; import ReactMarkdown from "react-markdown"; import { BrowserView, MobileView } from "react-device-detect"; import { useParams, useNavigate, Link } from "react-router-dom"; import { isMobile } from "react-device-detect"; +import theme from '../theme.jsx'; +import remarkGfm from 'remark-gfm' import { Grid, @@ -18,12 +19,12 @@ import { Typography, Paper, List, -} from "@material-ui/core"; +} from "@mui/material"; import { Link as LinkIcon, Edit as EditIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; const Body = { //maxWidth: 1000, @@ -57,7 +58,6 @@ const Docs = (defaultprops) => { const { globalUrl, selectedDoc, serverside, serverMobile } = defaultprops; let navigate = useNavigate(); - const theme = useTheme(); // Quickfix for react router 5 -> 6 const params = useParams(); @@ -365,6 +365,7 @@ const Docs = (defaultprops) => { }; function OuterLink(props) { + console.log("Link: ", props.href) if (props.href.includes("http") || props.href.includes("mailto")) { return ( { } function Img(props) { - return {props.alt}; + return {props.alt}; } function CodeHandler(props) { + console.log("PROPS: ", props) + + const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : "" + return ( -
    -        {props.value}
    -      
    + {propvalue} +
    ); } @@ -435,7 +445,7 @@ const Docs = (defaultprops) => { href={selectedMeta.link} style={{ textDecoration: "none", color: "#f85a3e" }} > - @@ -637,7 +647,7 @@ const Docs = (defaultprops) => { Why Shuffle? - Security first. We incentivize trying before buying, and give you the full set of tools you need to automate your operations. What's more is we also help you find usecases that fit your your unique needs. Accessibility is key, and we intend to help every SOC globally use and share their usecases. + Security first. We incentivize trying before buying, and give you the full set of tools you need to automate your operations. What's more is we also help you find usecases that fit your unique needs. Accessibility is key, and we intend to help every SOC globally use and share their usecases. Get help @@ -790,22 +800,31 @@ const Docs = (defaultprops) => { :
    + > + {data} +
    }
    ); + // remarkPlugins={[remarkGfm]} const mobileStyle = { color: "white", @@ -868,16 +887,25 @@ const Docs = (defaultprops) => { :
    + > + {data} +
    } { - const { globalUrl } = props; - - const [tmpSrcApp, setTmpSrcApp] = useState({}); - const [tmpDstApp, setTmpDstApp] = useState({}); - const [srcApp, setSrcApp] = useState({}); - const [srcAppConfig, setSrcAppConfig] = useState([]); - const [srcAppConfigOpen, setSrcAppConfigOpen] = useState(false); - const [srcAppAction, setSrcAppAction] = useState(""); - - const [dstApp, setDstApp] = useState({}); - const [dstAppAction, setDstAppAction] = useState(""); - const [dstAppConfig, setDstAppConfig] = useState([]); - const [dstAppConfigOpen, setDstAppConfigOpen] = useState(false); - - // Lets set the real data here - const [selectedSrc, setSelectedSrc] = React.useState(""); - const [, setSelectedSrcData] = React.useState({}); - const [selectedDst, setSelectedDst] = React.useState([]); - - // FIXME - const [suggestions, setSuggestions] = React.useState([]); - const [inputappdata, setInputAppData] = React.useState({}); - - const [scheduleConfig, setScheduleConfig] = React.useState({}); - const [selectedSrcParameters, setSelectedSrcParameters] = React.useState([]); - - const getCurrentSchedule = () => { - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; - - const loadAppSuggestions = () => { - fetch(globalUrl + "/api/v1/schedules/apps", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - }) - .then((response) => response.json()) - .then((responseJson) => { - setSuggestions(responseJson.apps); - }) - .catch((error) => { - console.log(error); - }); - }; - - // FIXME - this is generated from app selection input with required items - useEffect(() => { - if (Object.getOwnPropertyNames(scheduleConfig).length <= 0) { - getCurrentSchedule(); - } - - // Load apps if destination or source is - if (suggestions.length === 0) { - loadAppSuggestions(); - } - - // Load everything else - if ( - Object.getOwnPropertyNames(scheduleConfig).length > 0 && - Object.getOwnPropertyNames(scheduleConfig.appinfo.sourceapp).length > 0 && - Object.getOwnPropertyNames(srcApp).length <= 0 - ) { - if (scheduleConfig.appinfo.sourceapp.name.length > 0) { - setSrcApp(scheduleConfig.appinfo.sourceapp); - setSrcAppAction(scheduleConfig.appinfo.sourceapp.action); - } - } - - // Use sourceapp.name&version and sourceapp.action and look for name in inputappdata - // Basically fix everything in this one lol - if ( - suggestions.length > 0 && - srcAppAction.length > 0 && - Object.getOwnPropertyNames(scheduleConfig).length > 0 && - Object.getOwnPropertyNames(inputappdata).length <= 0 - ) { - // Loops all apps and finds current - for (var key in suggestions) { - var curapp = suggestions[key]; - if (curapp.name === srcApp.name) { - break; - } - } - - setInputAppData(curapp); - - // Loops the apps actions to find current - for (key in curapp.output) { - var curappaction = curapp.output[key]; - if (curappaction.name === srcAppAction) { - break; - } - } - - setSelectedSrcParameters(curappaction.outputparameters); - if (curappaction.config !== null && curappaction.config !== undefined) { - setSrcAppConfig(curappaction.config); - } - - // FIXME - set src of all translator nodes to curappaction.outputparameters[0] if not defined - //value={scheduleConfig.translator[count].src.name} - - //console.log(scheduleConfig) - var newtranslator = []; - for (key in scheduleConfig.translator) { - var curtranslator = scheduleConfig.translator[key]; - - // Overwrite issues - if (curtranslator.src.name === "") { - curtranslator.src = curappaction.outputparameters[0]; - } - - newtranslator.push(curtranslator); - } - - scheduleConfig.translator = newtranslator; - setScheduleConfig(scheduleConfig); - } - - if ( - suggestions.length > 0 && - dstAppAction.length <= 0 && - Object.getOwnPropertyNames(scheduleConfig).length > 0 - ) { - // Loops all apps and finds current - for (key in suggestions) { - curapp = suggestions[key]; - if (curapp.name === dstApp.name) { - break; - } - } - - const curAction = scheduleConfig.appinfo.destinationapp.action; - - // Loops the apps actions to find current - for (key in curapp.output) { - curappaction = curapp.output[key]; - if (curappaction.name === curAction) { - break; - } - } - - setDstAppAction(curappaction.name); - if (curappaction.config !== null && curappaction.config !== undefined) { - setDstAppConfig(curappaction.config); - } - } - }); - - const getSuggestions = (value, type, { showEmpty = false } = {}) => { - const inputValue = deburr(value.trim()).toLowerCase(); - const inputLength = inputValue.length; - let count = 0; - - return inputLength === 0 && !showEmpty - ? [] - : suggestions.filter((suggestion) => { - const keep = - count < 5 && - suggestion.types && - suggestion.types.includes(type) && - suggestion.name.slice(0, inputLength).toLowerCase() === inputValue; - - if (keep) { - count += 1; - } - - return keep; - }); - }; - - const setSrcAppWrapper = (currentapps) => { - setSrcApp(currentapps[0]); - if (currentapps[0].output.length > 0) { - selectSrcAction(currentapps[0].output[0].name); - } - }; - - const setDstAppWrapper = (currentapps) => { - setDstApp(currentapps[0]); - if (currentapps[0].input.length > 0) { - selectInitialDstAction(currentapps); - } - }; - - const renderInput = (type, inputProps) => { - const { InputProps, classes, ref, ...other } = inputProps; - - // Sets the srcapp if string is matching exactly for name - if ( - InputProps["aria-activedescendant"] === null && - InputProps["value"].length > 0 - ) { - const currentapps = suggestions.filter( - (data) => data.name === InputProps["value"] - ); - if (currentapps.length === 1) { - if (type === "output") { - if (currentapps[0].name !== srcApp.name) { - setSrcAppWrapper(currentapps); - } - } else if (type === "input") { - if (currentapps[0].name !== dstApp.name) { - setDstAppWrapper(currentapps); - } - } - } - } - - return ( -
    - - - - ), - }} - {...other} - /> -
    - ); - }; - - const renderSuggestion = (suggestionProps) => { - const { suggestion, index, itemProps, highlightedIndex, selectedItem } = - suggestionProps; - const isHighlighted = highlightedIndex === index; - const isSelected = (selectedItem || "").indexOf(suggestion.name) > -1; - - return ( - - {suggestion.name} - - ); - }; - - const bodyDivStyle = { - marginLeft: "20px", - marginTop: "50px", - marginRight: "20px", - margin: "auto", - width: "1350px", - display: "flex", - }; - - const appActionStyle = { - height: "50px", - marginTop: "10px", - }; - - // FIXME - set this - //const setRelationshipsFromSource = () => { - - //} - - //// FIXME - set this - //const setRelationshipsFromGenerator = () => { - - //} - - const selectDstAppAction = (event) => { - if (event.target.value === dstAppAction) { - return; - } - - setDstAppAction(event.target.value); - refactorTranslations(event.target.value); - }; - - const refactorTranslations = (action) => { - // dstapp is chosen - var found = false; - for (var key in dstApp.input) { - var curinput = dstApp.input[key]; - if (curinput.name === action) { - found = true; - break; - } - } - - // Should never happen.. - if (!found) { - return; - } - - var tmprelationships = []; - for (key in curinput.inputparameters) { - var curRelation = { dst: curinput.inputparameters[key], src: {} }; - tmprelationships.push(curRelation); - } - - scheduleConfig["translator"] = tmprelationships; - setScheduleConfig(scheduleConfig); - }; - - const selectInitialDstAction = (value) => { - var action = value[0].input[0].name; - setDstAppAction(action); - - var found = false; - for (var key in value) { - var curinput = value[0].input[key]; - if (curinput.name === action) { - found = true; - break; - } - } - - // Should never happen.. - if (!found) { - return; - } - - var tmprelationships = []; - for (key in curinput.inputparameters) { - var curRelation = { dst: curinput.inputparameters[key], src: {} }; - tmprelationships.push(curRelation); - } - - scheduleConfig["translator"] = tmprelationships; - setScheduleConfig(scheduleConfig); - }; - - const selectSrcAction = (value) => { - // FIXME - load the config for this action - var found = false; - - setSrcAppAction(value); - - var curitem = {}; - for (var key in inputappdata.output) { - curitem = inputappdata.output[key]; - if (curitem.name === value) { - found = true; - break; - } - } - - if (!found) { - setSelectedSrcParameters([]); - return; - } - - // Generate this for every relation? - setSelectedSrcParameters(curitem.outputparameters); - - //FIXME - check if source has the right attribute - var tmprelationships = []; - key = 0; - for (key in scheduleConfig.translator) { - var curRelation = scheduleConfig.translator[key]; - curRelation["src"] = curitem.outputparameters[0]; - tmprelationships.push(curRelation); - } - - scheduleConfig["translator"] = tmprelationships; - setScheduleConfig(scheduleConfig); - }; - - // Wrapper to handle event click - const selectSrcActionWrapper = (event) => { - return selectSrcAction(event.target.value); - }; - - const srcappaction = - Object.getOwnPropertyNames(srcApp).length > 0 && srcApp.output ? ( -
    - - Actions - - -
    - ) : null; - - const dstappaction = - Object.getOwnPropertyNames(dstApp).length > 0 ? ( -
    - - Actions - - -
    - ) : null; - - const downshiftStyle = { - //marginLeft: "20px", - //marginTop: "20px", - //marginRight: "20px", - display: "flex", - width: "100%", - }; - - const submitDisabled = selectedSrc.length > 0 && selectedDst.length > 0; - const submitButtonClick = () => { - var newtranslations = []; - var tobeadded = []; - var tobechanged = []; - - // Go find the information again in testdata - - //newrelationship["translator"] = {"src": {}, "dst": {}} - //newrelationship["static"] = {} - - // Reformat relationships - // clean up old relationships - // - - if (Object.getOwnPropertyNames(scheduleConfig.Translator).length <= 0) { - return; - } - - // Clean up old translations for specified elements - // FIXME - maybe just send a request with relationships and fix in backend? - // FIXME - there is an issue here for some multifield stuff - // Literally have to verify every single one anyway.. - for (var selected in selectedDst) { - var found = false; - var index = 0; - for (var key in scheduleConfig["translator"]) { - if ( - scheduleConfig["translator"][key]["dst"]["name"] === - selectedDst[selected] - ) { - index = key; - found = true; - break; - } - } - - if (!found) { - tobeadded.push(selectedDst[selected]); - } else { - // Dst can be the - tobechanged.push(index); - } - } - - var tmprelationships = scheduleConfig; - - key = 0; - for (key in scheduleConfig["translator"]) { - if (tobechanged.includes(key)) { - var tmprel = scheduleConfig["translator"][key]; - tmprel["src"] = { name: selectedSrc }; - tmprelationships["translator"].splice(key, 1); - newtranslations.push(tmprel); - } - } - - key = 0; - for (key in tobeadded) { - tmprel = { src: { name: selectedSrc }, dst: { name: tobeadded[key] } }; - newtranslations.push(tmprel); - } - - // delete deletable keys from copy (new) - // - key = 0; - for (key in newtranslations) { - tmprelationships["translator"].push(newtranslations[key]); - } - - setSelectedSrc(""); - setSelectedDst([]); - setSelectedSrcData({}); - - // FIXME - does this work? - scheduleConfig["translator"] = tmprelationships["translator"]; - setScheduleConfig(scheduleConfig); - }; - - // When it's set, need to find the destination in the row and set static - const setStaticValue = (event, row) => { - console.log("HI"); - // FIXME - modify the row first - // FIXME - set generator and source to "nothing" - - console.log(row.dst.name); - console.log(row); - console.log(event.target.value); - var newsrcrow = { - name: "static", - description: "Static value set", - type: "static", - value: event.target.value, - schema: { type: "string" }, - }; - - var relationshipclone = JSON.parse(JSON.stringify(scheduleConfig)); - for (var key in scheduleConfig["translator"]) { - var curItem = scheduleConfig["translator"][key]; - if (curItem.dst.name === row.dst.name) { - break; - } - } - - relationshipclone["translator"][key]["src"] = newsrcrow; - - scheduleConfig["translator"] = relationshipclone["translator"]; - console.log(scheduleConfig); - - // Fuck this :( - // DO DIS IN FRONTEND AND HAVE A SUBMIT BUTTON :( - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(scheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}); - }) - .catch((error) => { - console.log(error); - }); - }; - - // Rewrites relationships clientside - const setCurrentSrcPropRelation = (event, rowkey) => { - console.log(event, rowkey); - var found = false; - - for (var key in selectedSrcParameters) { - var curItem = selectedSrcParameters[key]; - if (curItem.name === event.target.value) { - found = true; - break; - } - } - - // No idea how this would ever happen, but but (: - if (!found) { - return; - } - - var row = scheduleConfig.translator[rowkey]; - var rowclone = JSON.parse(JSON.stringify(row)); - rowclone.src = curItem; - - var relationshipclone = JSON.parse(JSON.stringify(scheduleConfig)); - for (key in scheduleConfig["translator"]) { - curItem = scheduleConfig["translator"][key]; - if (curItem.dst.name === row.dst.name) { - break; - } - } - - relationshipclone["translator"][key] = rowclone; - - scheduleConfig["translator"] = relationshipclone["translator"]; - console.log(scheduleConfig); - - // Fuck this :( - // DO DIS IN FRONTEND AND HAVE A SUBMIT BUTTON :( - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(scheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}); - }) - .catch((error) => { - console.log(error); - }); - return; - }; - - const relationshipBody = - Object.getOwnPropertyNames(scheduleConfig).length > 0 && - scheduleConfig["translator"] !== undefined && - scheduleConfig.translator.length > 0 ? ( - - - - - Action - - - Source Field - - - Destination Field - - - Field Type - - - Required - - - Static - - - Generator - - - Transform - - - {scheduleConfig.translator.map((row, count) => { - var buttonIcon = ; - if (!row.dst.required) { - buttonIcon = ( - - ); - } - - const srcpropsSelector = - (Object.getOwnPropertyNames(selectedSrcParameters).length > 0 || - Object.getOwnPropertyNames(scheduleConfig.translator).length > - 0) && - Object.getOwnPropertyNames(srcApp).length > 0 ? ( - - ) : ( -
    No defined fields
    - ); - - const placeholderValue = - row.src.type === "static" - ? row.src.value - : "... Set a static value"; - - return ( - - - {buttonIcon} - - {srcpropsSelector} - {row.dst.name} - {row.dst.schema.type} - {row.dst.required} - - { - if (event.key === "Enter") { - event.preventDefault(); - console.log(`Pressed keyCode ${event.key}`); - setStaticValue(event, row); - } - }} - /> - - INSERT SCRIPT THINGY - Cortex responder? - - ); - })} -
    -
    - ) : null; - - // FIXME - use this - //const submitRelationships = () => { - // // Make an API-call to the backend for verification - // // Return with failures etc, and mark row issues? - // - // var apiUrl = "http://localhost:5000" - // fetch(apiUrl+"/api/v1/schedules", - // { - // method: "POST", - // headers: {"content-type": "application/json"}, - // body: JSON.stringify(scheduleConfig), - // } - // ) - // .then((response) => response.json()) - // .then((responseJson) => { - // console.log(responseJson) - // }) - // .catch((error) => { - // console.log(error); - // }); - //} - - const executeSchedule = () => { - fetch( - globalUrl + "/api/v1/schedules/" + props.match.params.key + "/execute", - { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - } - ) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; - - const submitButton = ( -
    - -
    - ); - - // Requires src or dst as input - // FIXME - use this shit to edit an app or something - //const editButtonFix = (app, apptype) => { - // if (apptype === "src") { - - // } else if (apptype === "dst") { - - // } - //} - - const editSrcApp = (event) => { - // if tmpsrcapp, show RESET (can be an X or something too) - // if reset is clicked, set source app back to the original - setTmpSrcApp(scheduleConfig.appinfo.sourceapp); - - var tmpscheduleConfig = scheduleConfig; - tmpscheduleConfig.appinfo.sourceapp = {}; - - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}); - setSrcApp({}); - setSrcAppAction(""); - }) - .catch((error) => { - console.log(error); - }); - }; - - const editDstApp = (event) => { - // if tmpsrcapp, show RESET (can be an X or something too) - // if reset is clicked, set source app back to the original - setTmpDstApp(scheduleConfig.appinfo.destinationapp); - - var tmpscheduleConfig = scheduleConfig; - tmpscheduleConfig.appinfo.destinationapp = {}; - - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}); - setDstApp({}); - setDstAppAction(""); - }) - .catch((error) => { - console.log(error); - }); - }; - - const scheduleApp = (app, actiondata) => { - const editButton = - actiondata === "src" ? ( - - ) : ( - - ); - - const configureButton = - actiondata === "src" ? ( - - ) : ( - - ); - - // FIXME - set src vs dst - return ( - - - - - - - {splitter} - - - -
    -

    {app.name}

    -
    -
    {app.description}
    -
    - {app.action} -
    - {splitter} - -
    {editButton}
    -
    {configureButton}
    -
    -
    -
    - ); - }; - - const splitter = ( -
    - ); - - const dstDownshift = ( - - {({ - getInputProps, - getItemProps, - getMenuProps, - highlightedIndex, - inputValue, - isOpen, - selectedItem, - }) => ( -
    - {renderInput("input", { - fullWidth: true, - InputProps: getInputProps({ - placeholder: "Search destination apps", - }), - })} - -
    -
    - {isOpen ? ( - - {getSuggestions(inputValue, "input").map( - (suggestion, index) => - renderSuggestion({ - suggestion, - index, - itemProps: getItemProps({ item: suggestion.name }), - highlightedIndex, - selectedItem, - }) - )} - - ) : null} -
    - {dstappaction} -
    -
    - )} -
    - ); - - const srcDownshift = ( - - {({ - getInputProps, - getItemProps, - getMenuProps, - highlightedIndex, - inputValue, - isOpen, - selectedItem, - }) => ( -
    - {renderInput("output", { - fullWidth: true, - InputProps: getInputProps({ - placeholder: "Search source apps", - }), - })} - -
    -
    - {isOpen ? ( - - {getSuggestions(inputValue, "output").map( - (suggestion, index) => - renderSuggestion({ - suggestion, - index, - itemProps: getItemProps({ item: suggestion.name }), - highlightedIndex, - selectedItem, - }) - )} - - ) : null} -
    - {srcappaction} -
    -
    - )} -
    - ); - - const submitDstApp = (event) => { - var packagedSrc = { - name: dstApp.name, - id: dstApp.id, - description: dstApp.description, - action: dstAppAction, - }; - - var tmpscheduleConfig = scheduleConfig; - - tmpscheduleConfig.appinfo.destinationapp = packagedSrc; - - // Hmm, do this here? Idk - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}); - }) - .catch((error) => { - console.log(error); - }); - }; - - const submitSrcApp = (event) => { - var packagedSrc = { - name: srcApp.name, - id: srcApp.id, - description: srcApp.description, - action: srcAppAction, - }; - - var tmpscheduleConfig = scheduleConfig; - - // FIXME - future fred - // - tmpscheduleConfig.appinfo.sourceapp = packagedSrc; - - // Hmm, do this here? Idk - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}); - }) - .catch((error) => { - console.log(error); - }); - }; - - const resetDstApp = (event) => { - var tmpscheduleConfig = scheduleConfig; - tmpscheduleConfig.appinfo.destinationapp = tmpDstApp; - - // Hmm, do this here? Idk - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}); - setDstApp(tmpSrcApp); - setDstAppAction(tmpDstApp.action); - setTmpDstApp({}); - }) - .catch((error) => { - console.log(error); - }); - }; - - const resetSrcApp = (event) => { - var tmpscheduleConfig = scheduleConfig; - tmpscheduleConfig.appinfo.sourceapp = tmpSrcApp; - - // Hmm, do this here? Idk - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}); - setSrcApp(tmpSrcApp); - setSrcAppAction(tmpSrcApp.action); - setTmpSrcApp({}); - }) - .catch((error) => { - console.log(error); - }); - }; - - // Based on some srcthing - const searchGridSrc = ( - - - Choose Source app (FIXME) - clickable - - {splitter} - - - -
    {srcDownshift}
    -
    -
    - {splitter} - -
    - -
    -
    - -
    -
    -
    -
    - ); - - const searchGridDst = ( - - - Choose Source app (FIXME) - clickable - - {splitter} - - - -
    {dstDownshift}
    -
    -
    - {splitter} - -
    - -
    -
    - -
    -
    -
    -
    - ); - - const srcField = - Object.getOwnPropertyNames(scheduleConfig).length > 0 && - Object.getOwnPropertyNames(scheduleConfig.appinfo.sourceapp).length > 0 && - scheduleConfig.appinfo.sourceapp.name.length > 0 ? ( -
    - - {scheduleApp(scheduleConfig.appinfo.sourceapp, "src")} - -
    - ) : ( -
    - - {searchGridSrc} - -
    - ); - - const dstField = - Object.getOwnPropertyNames(scheduleConfig).length > 0 && - Object.getOwnPropertyNames(scheduleConfig.appinfo.destinationapp).length > - 0 && - scheduleConfig.appinfo.destinationapp.name.length > 0 ? ( -
    - - {scheduleApp(scheduleConfig.appinfo.destinationapp, "dst")} - -
    - ) : ( -
    - - {searchGridDst} - -
    - ); - - // Have to use array cus of datastore lol (no map[string]string) - const srcModalData = []; - const buildSrcModal = (event, fieldname) => { - var fieldfound = false; - for (var key in srcModalData) { - if (srcModalData[key]["key"] === fieldname) { - fieldfound = true; - srcModalData[key]["value"] = event.target.value; - break; - } - } - - if (!fieldfound) { - srcModalData.push({ key: fieldname, value: event.target.value }); - } - console.log(srcModalData); - }; - - // FIXME - verify required fields? - const submitSrcConfig = () => { - scheduleConfig.appinfo.sourceapp.config = srcModalData; - setScheduleConfig(scheduleConfig); - setSrcAppConfigOpen(false); - - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(scheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; - - const dstModalData = []; - const buildDstModal = (event, fieldname) => { - var fieldfound = false; - for (var key in dstModalData) { - if (dstModalData[key]["key"] === fieldname) { - fieldfound = true; - dstModalData[key]["value"] = event.target.value; - break; - } - } - - if (!fieldfound) { - dstModalData.push({ key: fieldname, value: event.target.value }); - } - console.log(dstModalData); - }; - - // FIXME - verify required fields - const submitDstConfig = () => { - scheduleConfig.appinfo.destinationapp.config = dstModalData; - setScheduleConfig(scheduleConfig); - setDstAppConfigOpen(false); - - fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(scheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; - - const dstConfigModal = dstAppConfigOpen ? ( - { - setDstAppConfigOpen(false); - }} - > - Source configuration - - Configure {dstApp.name}'s required fields - {dstAppConfig.map((data) => ( - { - buildDstModal(event, data.name); - }} - autofocus - color="primary" - name="searchtext" - placeholder={data.name} - margin="dense" - id={data.name} - label={data.name} - fullWidth - /> - ))} - - - - - - - ) : null; - - // FIXME - load the actual fields! - const srcConfigModal = srcAppConfigOpen ? ( - { - setSrcAppConfigOpen(false); - }} - > - Source configuration - - Configure {srcApp.name}'s required fields - {srcAppConfig.map((data) => ( - { - buildSrcModal(event, data.name); - }} - autofocus - color="primary" - name="searchtext" - placeholder={data.name} - margin="dense" - id={data.name} - label={data.name} - fullWidth - /> - ))} - - - - - - - ) : null; - - return ( -
    - {srcConfigModal} - {dstConfigModal} -
    - {srcField} - {dstField} -
    - -
    {submitButton}
    -
    {relationshipBody}
    -
    - ); -}; - -export default EditSchedule; diff --git a/frontend/src/views/EditWebhook.jsx b/frontend/src/views/EditWebhook.jsx deleted file mode 100755 index e1a5d516..00000000 --- a/frontend/src/views/EditWebhook.jsx +++ /dev/null @@ -1,393 +0,0 @@ -import React, { useState, useEffect } from "react"; - -import Button from "@material-ui/core/Button"; -import Paper from "@material-ui/core/Paper"; -import Divider from "@material-ui/core/Divider"; -import Select from "@material-ui/core/Select"; -import MenuItem from "@material-ui/core/MenuItem"; - -import WebhookImage from "../assets/img/webhook.png"; -import KafkaImage from "../assets/img/kafka.png"; - -import EditWorkflow from "./EditWorkflow"; - -const EditWebhook = (props) => { - const { globalUrl, isLoaded } = props; - - // FIXME - //const [webhookData, setWebhookData] = useState(webhooktest) - const [webhookData, setWebhookData] = useState({}); - const [workflows, setWorkflows] = useState([]); - const [firstrequest, setFirstrequest] = React.useState(true); - - const [selectedWorkflows, setSelectedWorkflows] = useState([]); - - const getWorkflows = () => { - 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 response.json(); - }) - .then((responseJson) => { - setWorkflows(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; - - const setWebhook = (inputdata) => { - console.log(inputdata); - - fetch(globalUrl + "/api/v1/hooks/" + props.match.params.key, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - body: JSON.stringify(inputdata), - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; - - const getCurrentWebhook = () => { - fetch(globalUrl + "/api/v1/hooks/" + props.match.params.key, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200!"); - window.location.pathname = "webhooks"; - } - return response.json(); - }) - .then((responseJson) => { - if (responseJson.actions === null) { - responseJson.actions = []; - } - - if (responseJson.transforms === null) { - responseJson.transforms = []; - } - - setWebhookData(responseJson); - }) - .catch((error) => { - console.log(error); - //window.location.pathname = "webhooks" - }); - }; - - useEffect(() => { - if (firstrequest) { - setFirstrequest(false); - getCurrentWebhook(); - if (workflows.length <= 0) { - getWorkflows(); - } - } - - // After everything is loaded - if ( - Object.getOwnPropertyNames(webhookData).length > 0 && - webhookData.actions.length > 0 && - workflows.length > 0 && - selectedWorkflows.length === 0 - ) { - // Setting startup actions. making like this in case we want other actions - var tmpActionWorkflows = []; - for (var key in webhookData.actions) { - if (webhookData.actions[key].type === "workflow") { - tmpActionWorkflows.push(webhookData.actions[key]); - } - } - - // Fix duplicates... Meh - var foundWorkflowIds = []; - var tmpWorkflows = []; - for (key in tmpActionWorkflows) { - if (foundWorkflowIds.includes(tmpActionWorkflows[key].id)) { - continue; - } - - for (var subkey in workflows) { - if (tmpActionWorkflows[key].id === workflows[subkey]["id_"]) { - console.log(tmpActionWorkflows[key].id, workflows[subkey]["id_"]); - foundWorkflowIds.push(tmpActionWorkflows[key].id); - tmpWorkflows.push(workflows[subkey]); - break; - } - } - } - - if (tmpWorkflows.length > 0) { - setSelectedWorkflows(tmpWorkflows); - } - } - }); - - const hookPicture = - Object.getOwnPropertyNames(webhookData).length > 0 && - webhookData.type === "webhook" ? ( - webhook - ) : ( - MQ - ); - - const executeHook = (action) => { - fetch( - globalUrl + "/api/v1/hooks/" + props.match.params.key + "/" + action, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - } - ) - .then((response) => response.json()) - .then((responseJson) => { - setWebhookData({}); - }) - .catch((error) => { - console.log(error); - }); - }; - - const headerPaperStyle = { - display: "flex", - maxHeight: "800px", - minHeight: "800px", - margin: "10px 30px 10px 10px", - padding: "10px 5px 5px 5px", - flexDirection: "column", - }; - - // FIXME - add with counter to change the correct one (not just edit) - const addNewWorkflow = (event) => { - // Verify if it already exists in the array. Returns if it exists - for (var key in selectedWorkflows) { - var item = selectedWorkflows[key]; - if (item["id_"] === event.target.value["id_"]) { - return; - } - } - - // FIXME - make this possible for all accounts - if (selectedWorkflows.length === 0) { - console.log("ADD FIRST ITEM FOR SELECTEDWORKFLOWS"); - console.log(event.target.value); - - // Cleanup previous actions - var newActions = []; - if (webhookData.actions.length > 0) { - for (key in webhookData.actions) { - if ( - webhookData.actions[key].type === "" || - webhookData.actions[key].type === undefined - ) { - continue; - } - - newActions.push(webhookData.actions[key]); - } - } - - // FIXME - how to stringify this better hurr - var formattedWorkflow = { - type: "workflow", - name: event.target.value.name, - id: event.target.value.id_, - field: "", - }; - - // FIXME: patch this n - newActions.push(formattedWorkflow); - console.log(newActions); - - webhookData.actions = newActions; - setWebhook(webhookData); - } - - var tmpSelectedWorkflows = [].concat(selectedWorkflows, [ - event.target.value, - ]); - setSelectedWorkflows(tmpSelectedWorkflows); - }; - - // FIXME - // Create a list with + button - // For each, choose the new workflow I wanna add - // Current: JUST ONE - const selectedWorkflowIds = selectedWorkflows.map((data) => { - return data["id_"]; - }); - const availableWorkflows = workflows.filter( - (data) => !selectedWorkflowIds.includes(data["id_"]) - ); - - const WorkflowSelect = (counter) => { - if (selectedWorkflows[counter.counter] === undefined) { - return null; - } - - console.log(selectedWorkflows[0]); - console.log(selectedWorkflows[0]); - console.log(selectedWorkflows[0]); - console.log(selectedWorkflows[counter.counter]); - console.log(selectedWorkflows[counter.counter].name); - return ( -
    - Workflow select: - -
    - ); - }; - - const extraWorkflow = - workflows.length > 0 && availableWorkflows.length > 0 ? ( - - ) : null; - - const multiWorkflowSelect = - workflows.length > 0 && selectedWorkflows.length > 0 ? ( -
    - {selectedWorkflows.map((data, count) => ( - - ))} - {extraWorkflow} -
    - ) : ( - - ); - - const headerInfo = - Object.getOwnPropertyNames(webhookData).length > 0 ? ( -
    - -
    -
    {hookPicture}
    -
    -
    -

    Name: {webhookData.info.name}

    -
    -
    -
    -
    - Description: {webhookData.info.description} -
    Id: {webhookData.id}
    -
    Url: {webhookData.info.url}
    -
    Type: {webhookData.type}
    -
    Status: {webhookData.status}
    -
    - CHOOSE ACTIONS: - {multiWorkflowSelect} -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - ) : null; - - // FIXME - needs refresh every time you add a new workflow - const workflowdata = - Object.getOwnPropertyNames(webhookData).length > 0 && - selectedWorkflows.length > 0 ? ( - - ) : null; - - const loadedCheck = isLoaded ? ( -
    -
    {workflowdata}
    -
    {headerInfo}
    -
    - ) : ( -
    - ); - - // FIXME: Use this for testing - // : null - return
    {loadedCheck}
    ; -}; - -export default EditWebhook; diff --git a/frontend/src/views/Faq.jsx b/frontend/src/views/Faq.jsx index 42d24261..146ceb68 100644 --- a/frontend/src/views/Faq.jsx +++ b/frontend/src/views/Faq.jsx @@ -2,8 +2,8 @@ import React, {useState} from 'react'; import {isMobile} from "react-device-detect"; import {Link} from 'react-router-dom'; -import {Divider, List, ListItem, ListItemText, Card, CardContent, Grid, Typography, Button, ButtonGroup, FormControl, Dialog, DialogTitle, DialogActions, DialogContent, Tooltip} from '@material-ui/core'; -import {ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon} from '@material-ui/icons'; +import {Divider, List, ListItem, ListItemText, Card, CardContent, Grid, Typography, Button, ButtonGroup, FormControl, Dialog, DialogTitle, DialogActions, DialogContent, Tooltip} from '@mui/material'; +import {ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon} from '@mui/icons-material'; const hrefStyle = { textDecoration: "none", @@ -255,4 +255,4 @@ const Faq = (props) => { ) } -export default Faq; \ No newline at end of file +export default Faq; diff --git a/frontend/src/views/ForgotPassword.jsx b/frontend/src/views/ForgotPassword.jsx deleted file mode 100755 index e4c95013..00000000 --- a/frontend/src/views/ForgotPassword.jsx +++ /dev/null @@ -1,118 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { useState } from "react"; - -import TextField from "@material-ui/core/TextField"; -import Button from "@material-ui/core/Button"; -import Paper from "@material-ui/core/Paper"; - -const bodyDivStyle = { - margin: "auto", - marginTop: "100px", - width: "500px", -}; - -const ForgotPassword = (props) => { - const { globalUrl, isLoaded, isLoggedIn, surfaceColor, inputColor } = props; - - const boxStyle = { - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: surfaceColor, - }; - - const [username, setUsername] = useState(""); - const [resetInfo, setResetInfo] = useState( - "You will receive an email with instructions shortly." - ); - - const handleValidateForm = () => { - return username.length > 3; - }; - - if (isLoggedIn === true) { - window.location.pathname = "/"; - } - - const onSubmit = (e) => { - e.preventDefault(); - // FIXME - add some check here ROFL - - // Just use this one? - var data = { username: username }; - var baseurl = globalUrl; - var url = baseurl + "/api/v1/passwordresetmail"; - fetch(url, { - method: "POST", - body: JSON.stringify(data), - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - setResetInfo(responseJson["reason"]); - } - }) - ) - .catch((error) => { - setResetInfo("Error in userdata: " + error); - }); - }; - - const onChangeUser = (e) => { - setUsername(e.target.value); - }; - - const data = ( -
    - -
    -

    Password reset

    -
    - -
    -
    - -
    -
    {resetInfo}
    -
    -
    -
    - ); - - const loadedCheck = isLoaded ?
    {data}
    :
    ; - - return
    {loadedCheck}
    ; -}; - -export default ForgotPassword; diff --git a/frontend/src/views/ForgotPasswordLink.jsx b/frontend/src/views/ForgotPasswordLink.jsx deleted file mode 100755 index 8944deed..00000000 --- a/frontend/src/views/ForgotPasswordLink.jsx +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useState, useEffect } from "react"; - -import Paper from "@material-ui/core/Paper"; -import Button from "@material-ui/core/Button"; - -import TextField from "@material-ui/core/TextField"; - -const bodyDivStyle = { - margin: "auto", - textAlign: "center", - width: "768px", -}; - -const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: "#e8eaf6", - display: "flex", - flexDirection: "column", -}; - -//const tmpdata = { -// "username": "frikky", -// "firstname": "fred", -// "lastname": "ode", -// "title": "topkek", -// "companyname": "company here", -// "email": "your email pls", -// "phone": "PHONE!!", -//} - -// FIXME - add fetch for data fields -// FIXME - remove tmpdata -// FIXME: Use isLoggedIn :) -const Settings = (props) => { - const { globalUrl, isLoaded } = props; - - const [newPassword, setNewPassword] = useState(""); - const [newPassword2, setNewPassword2] = useState(""); - const [passwordFormMessage, setPasswordFormMessage] = useState(""); - - const onPasswordChange = () => { - const data = { - newpassword: newPassword, - newpassword2: newPassword2, - reference: props.match.params.key, - }; - const url = globalUrl + "/api/v1/passwordreset"; - 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) { - setPasswordFormMessage(responseJson["reason"]); - } - }) - ) - .catch((error) => { - setPasswordFormMessage("Something went wrong."); - }); - }; - - // This should "always" have data - useEffect(() => {}); - - // Random names for type & autoComplete. Didn't research :^) - const landingpageData = ( -
    - -

    Password Reset

    -
    - setNewPassword(e.target.value)} - /> - setNewPassword2(e.target.value)} - /> -
    - -

    {passwordFormMessage}

    -
    -
    - ); - - const loadedCheck = isLoaded ? ( -
    {landingpageData}
    - ) : ( -
    - ); - - return
    {loadedCheck}
    ; -}; -export default Settings; diff --git a/frontend/src/views/FrameworkWrapper.jsx b/frontend/src/views/FrameworkWrapper.jsx index 2d96e03c..d5e5813b 100644 --- a/frontend/src/views/FrameworkWrapper.jsx +++ b/frontend/src/views/FrameworkWrapper.jsx @@ -2,18 +2,19 @@ import React, { useEffect, useState } from 'react'; import ReactDOM from "react-dom" import AppFramework from "../components/AppFramework.jsx"; -import { useAlert } from "react-alert"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" import { Link, useParams } from "react-router-dom"; import theme from '../theme.jsx'; import { Button, -} from "@material-ui/core"; +} from "@mui/material"; const Framework = (props) => { const {globalUrl, isLoaded, isLoggedIn, showOptions, selectedOption, rolling, } = props; - const alert = useAlert() + //const alert = useAlert() const [frameworkLoaded, setFrameworkLoaded] = useState(false) const [frameworkData, setFrameworkData] = useState() @@ -36,9 +37,9 @@ const Framework = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Failed loading: " + responseJson.reason) + toast("Failed loading: " + responseJson.reason) } else { - alert.error("Failed to load framework for your org.") + toast("Failed to load framework for your org.") } setFrameworkLoaded(true) @@ -51,7 +52,7 @@ const Framework = (props) => { }) .catch((error) => { setFrameworkLoaded(true) - alert.error(error.toString()); + toast(error.toString()); }) } diff --git a/frontend/src/views/GettingStarted.jsx b/frontend/src/views/GettingStarted.jsx index 87dd1be0..39e59f2a 100644 --- a/frontend/src/views/GettingStarted.jsx +++ b/frontend/src/views/GettingStarted.jsx @@ -1,9 +1,9 @@ import React, { useEffect, useContext } from "react"; -import { makeStyles } from "@material-ui/core/styles"; -import { useTheme } from "@material-ui/core/styles"; +import { makeStyles } from "@mui/styles"; import ReactGA from 'react-ga4'; import SecurityFramework from '../components/SecurityFramework.jsx'; +import theme from '../theme.jsx'; import { Badge, @@ -28,7 +28,7 @@ import { DialogTitle, DialogActions, DialogContent, -} from "@material-ui/core"; +} from "@mui/material"; import { Check as CheckIcon, @@ -58,10 +58,7 @@ import { CloudDownload as CloudDownloadIcon, } from "@mui/icons-material"; -import NestedMenuItem from "material-ui-nested-menu-item"; -//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons'; -//https://next.material-ui.com/components/material-icons/ import { DataGrid, GridToolbar } from "@mui/x-data-grid"; //import JSONPretty from 'react-json-pretty'; @@ -69,8 +66,9 @@ import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import Dropzone from "../components/Dropzone.jsx"; import { useNavigate, Link, useParams } from "react-router-dom"; -import { useAlert } from "react-alert"; -import ChipInput from "material-ui-chip-input"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" +import { MuiChipsInput } from "mui-chips-input"; import { v4 as uuidv4 } from "uuid"; const inputColor = "#383B40"; @@ -126,8 +124,7 @@ const GettingStarted = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata } = props; document.title = "Getting Started with Shuffle"; - const theme = useTheme(); - const alert = useAlert(); + //const alert = useAlert(); const classes = useStyles(theme); let navigate = useNavigate(); const imgSize = 60; @@ -446,7 +443,7 @@ const GettingStarted = (props) => { const files = isDropzone ? e.dataTransfer.files : e.target.files; const reader = new FileReader(); - alert.info("Starting upload. Please wait while we validate the workflows"); + toast("Starting upload. Please wait while we validate the workflows"); try { reader.addEventListener("load", (e) => { @@ -455,7 +452,7 @@ const GettingStarted = (props) => { try { data = JSON.parse(reader.result); } catch (e) { - alert.error("Invalid JSON: " + e); + toast("Invalid JSON: " + e); return; } @@ -483,13 +480,13 @@ const GettingStarted = (props) => { false ).then((response) => { if (response !== undefined) { - alert.success(`Successfully imported ${data.name}`); + toast(`Successfully imported ${data.name}`); } }); } }) .catch((error) => { - alert.error("Import error: " + error.toString()); + toast("Import error: " + error.toString()); }); }); } catch (e) { @@ -522,7 +519,7 @@ const GettingStarted = (props) => { window.location.pathname = "/login"; } - alert.info("Failed getting workflows."); + toast("Failed getting workflows."); setWorkflowDone(true); return; @@ -563,7 +560,7 @@ const GettingStarted = (props) => { }, 100) } else { if (isLoggedIn) { - alert.error("An error occurred while loading workflows"); + toast("An error occurred while loading workflows"); } return; @@ -572,7 +569,7 @@ const GettingStarted = (props) => { .catch((error) => { setVideoViewOpen(true) - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -689,7 +686,7 @@ const GettingStarted = (props) => { trigger.parameters[1].value = "webhook_" + trigger.id; // FIXME: Add auth here? } else { - alert.info("Something is wrong with the webhook in the copy"); + toast("Something is wrong with the webhook in the copy"); } } @@ -807,7 +804,7 @@ const GettingStarted = (props) => { data = sanitizeWorkflow(data); if (data.subflows !== null && data.subflows !== undefined) { - alert.info( + toast( "Not exporting with subflows when sanitizing. Please manually export them." ); data.subflows = []; @@ -834,7 +831,7 @@ const GettingStarted = (props) => { const publishWorkflow = (data) => { data = JSON.parse(JSON.stringify(data)); data = sanitizeWorkflow(data); - alert.info("Sanitizing and publishing " + data.name); + toast("Sanitizing and publishing " + data.name); // This ALWAYS talks to Shuffle cloud fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish", { @@ -851,9 +848,9 @@ const GettingStarted = (props) => { console.log("Status not 200 for workflow publish :O!"); } else { if (isCloud) { - alert.success("Successfully published workflow"); + toast("Successfully published workflow"); } else { - alert.success( + toast( "Successfully published workflow to https://shuffler.io" ); } @@ -863,19 +860,19 @@ const GettingStarted = (props) => { }) .then((responseJson) => { if (responseJson.reason !== undefined) { - alert.error("Failed publishing: ", responseJson.reason); + toast("Failed publishing: ", responseJson.reason); } getAvailableWorkflows(); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; const copyWorkflow = (data) => { data = JSON.parse(JSON.stringify(data)); - alert.success("Copying workflow " + data.name); + toast("Copying workflow " + data.name); data.id = ""; data.name = data.name + "_copy"; data = deduplicateIds(data); @@ -902,7 +899,7 @@ const GettingStarted = (props) => { }, 1000); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -918,9 +915,9 @@ const GettingStarted = (props) => { .then((response) => { if (response.status !== 200) { console.log("Status not 200 for setting workflows :O!"); - alert.error("Failed deleting workflow. Do you have access?"); + toast("Failed deleting workflow. Do you have access?"); } else { - alert.success("Deleted workflow " + id); + toast("Deleted workflow " + id); } return response.json(); @@ -931,7 +928,7 @@ const GettingStarted = (props) => { }, 1000); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -1061,13 +1058,6 @@ const GettingStarted = (props) => { {"Duplicate Workflow"} - {/*= 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => { - //copyWorkflow(data) - //setOpen(false) - }} key={"duplicate"}> - - {"Copy to Child Org"} - */} { @@ -1283,7 +1273,7 @@ const GettingStarted = (props) => { }} onClick={() => { if (subflows === 0) { - alert.info("No subflows for " + data.name); + toast("No subflows for " + data.name); return; } @@ -1445,9 +1435,9 @@ const GettingStarted = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Error setting workflow: ", responseJson.reason) + toast("Error setting workflow: ", responseJson.reason) } else { - alert.error("Error setting workflow.") + toast("Error setting workflow.") } return @@ -1464,14 +1454,14 @@ const GettingStarted = (props) => { setImportLoading(false); setModalOpen(false); } else { - alert.info("Successfully changed basic info for workflow"); + toast("Successfully changed basic info for workflow"); setModalOpen(false); } return responseJson; }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); setImportLoading(false); setModalOpen(false); setSubmitLoading(false); @@ -1487,7 +1477,7 @@ const GettingStarted = (props) => { const file = event.target.files[key]; if (file.type !== "application/json") { if (file.type !== undefined) { - alert.error("File has to contain valid json"); + toast("File has to contain valid json"); setImportLoading(false); } @@ -1501,7 +1491,7 @@ const GettingStarted = (props) => { try { data = JSON.parse(reader.result); } catch (e) { - alert.error("Invalid JSON: " + e); + toast("Invalid JSON: " + e); setImportLoading(false); return; } @@ -1533,13 +1523,13 @@ const GettingStarted = (props) => { false ).then((response) => { if (response !== undefined) { - alert.success("Successfully imported " + data.name); + toast("Successfully imported " + data.name); } }); } }) .catch((error) => { - alert.error("Import error: " + error.toString()); + toast("Import error: " + error.toString()); }); }); @@ -1738,7 +1728,7 @@ const GettingStarted = (props) => { }} onClick={() => { if (subflows === 0) { - alert.info("No subflows for " + data.name); + toast("No subflows for " + data.name); return; } @@ -1939,7 +1929,7 @@ const GettingStarted = (props) => { margin="dense" fullWidth /> - { }) return } else { - alert.success("TBD: Coming in version 1.0.0"); + toast("TBD: Coming in version 1.0.0"); } const ele = document.getElementById("shuffle_search_field") @@ -2190,7 +2180,7 @@ const GettingStarted = (props) => { ele.style.borderWidth = "2px" } else { - //alert.success("TBD: Coming in version 1.0.0"); + //toast("TBD: Coming in version 1.0.0"); } }}> workflows made by other creators! @@ -2444,7 +2434,7 @@ const GettingStarted = (props) => {
    - { parsedData["field_2"] = field2; } - alert.success("Getting specific workflows from your URL."); + toast("Getting specific workflows from your URL."); fetch(globalUrl + "/api/v1/workflows/download_remote", { method: "POST", mode: "cors", @@ -2622,7 +2612,7 @@ const GettingStarted = (props) => { }) .then((response) => { if (response.status === 200) { - alert.success("Successfully loaded workflows from " + downloadUrl); + toast("Successfully loaded workflows from " + downloadUrl); setTimeout(() => { getAvailableWorkflows(); }, 1000); @@ -2633,14 +2623,14 @@ const GettingStarted = (props) => { .then((responseJson) => { if (!responseJson.success) { if (responseJson.reason !== undefined) { - alert.error("Failed loading: " + responseJson.reason); + toast("Failed loading: " + responseJson.reason); } else { - alert.error("Failed loading"); + toast("Failed loading"); } } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; diff --git a/frontend/src/views/HandlePaymentNew.jsx b/frontend/src/views/HandlePaymentNew.jsx index 1aa24cc6..89546b9b 100644 --- a/frontend/src/views/HandlePaymentNew.jsx +++ b/frontend/src/views/HandlePaymentNew.jsx @@ -28,7 +28,7 @@ import { DialogActions, DialogContent, Tooltip -} from '@material-ui/core'; +} from '@mui/material'; import FAQ from "./Faq.jsx"; import Newsletter from "../components/Newsletter.jsx"; import Services from "./Services.jsx"; diff --git a/frontend/src/views/Introduction.jsx b/frontend/src/views/Introduction.jsx deleted file mode 100755 index bc3e1734..00000000 --- a/frontend/src/views/Introduction.jsx +++ /dev/null @@ -1,222 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useTheme } from "@material-ui/core/styles"; -import { Link, useParams } from "react-router-dom"; - -import Grid from "@material-ui/core/Grid"; -import Card from "@material-ui/core/Card"; -import CardActionArea from "@material-ui/core/CardActionArea"; -import CardContent from "@material-ui/core/CardContent"; -import CardHeader from "@material-ui/core/CardHeader"; -import Typography from "@material-ui/core/Typography"; -import Button from "@material-ui/core/Button"; - -const Workflows = (defaultprops) => { - const { globalUrl, isLoggedIn, isLoaded } = defaultprops; - - const theme = useTheme(); - const params = useParams(); - var props = JSON.parse(JSON.stringify(defaultprops)) - props.match = {} - props.match.params = params - - const [curView, setCurView] = useState(0); - const [firstrequest, setFirstrequest] = useState(true); - const [selectedItems, setSelectedItems] = useState([]); - - const viewdata1 = [ - { - title: "General", - content: "Learn about our ticketing solutions", - subitems: [ - { - name: "Search", - subtitle: "Search for anything, anywhere", - }, - { - name: "Message", - subtitle: "Read and send messages", - }, - { - name: "Parse emails", - subtitle: "what", - }, - ], - }, - { - title: "Ticketing", - subitems: [ - { - name: "Search", - subtitle: "Search for anything, anywhere", - }, - { - name: "Message", - subtitle: "Read and send messages", - }, - { - name: "Parse emails", - subtitle: "what", - }, - ], - }, - { - title: "Threat intel", - subitems: [ - { - name: "Search", - subtitle: "Search for anything, anywhere", - }, - { - name: "Message", - subtitle: "Read and send messages", - }, - { - name: "Parse emails", - subtitle: "what", - }, - ], - }, - ]; - - if (firstrequest) { - setFirstrequest(false); - if (props.match.params.key) { - console.log("PROPS: ", props.match.params.key); - const viewitem = viewdata1.find( - (item) => - item.title.toLowerCase() === props.match.params.key.toLowerCase() - ); - if (viewitem !== undefined && viewitem !== null) { - setCurView(1); - //setSelectedItem(viewitem) - } - } - } - - const cardContentStyle = { - height: "100%", - width: "100%", - padding: 40, - }; - - const outerGridView = { - width: "100%", - marginTop: 15, - }; - - const paperStyle = { - height: 300, - color: "white", - backgroundColor: theme.palette.surfaceColor, - color: "white", - cursor: "pointer", - display: "flex", - textAlign: "center", - }; - - const HandleSelection = (data) => { - const [selected, setSelected] = useState(false); - - var baseStyle = JSON.parse(JSON.stringify(paperStyle)); - if (selected) { - baseStyle.backgroundColor = "white"; - baseStyle.color = "black"; - } - - return ( - { - console.log(selectedItems); - if (selected) { - const index = selectedItems.findIndex( - (item) => item.title === data.title - ); - if (index >= 0) { - selectedItems.splice(index, 1); - setSelectedItems(selectedItems); - } - } else { - selectedItems.push(data); - setSelectedItems(selectedItems); - } - - setSelected(!selected); - - //setCurView(1) - //setSelectedItem(data) - //window.location.pathname += "/"+data.title.toLowerCase() - }} - > - - - - {data.title} - - - - - ); - }; - - const view1 = - curView === 0 ? ( -
    - What are you interested in? - - {viewdata1.map((data) => { - return HandleSelection(data); - })} - - {/* - - */} -
    - ) : null; - - const view2 = - curView === 1 ? ( -
    - Step 2. - {/* - - {selectedItem.subitems === undefined ? null : - selectedItem.subitems.map(data => { - return ( - - - - - - {data.name} - - - {data.subtitle} - - - - - - ) - })} - - */} -
    - ) : null; - - const baseView = ( -
    - {view1} - {view2} -
    - ); - - return
    {baseView}
    ; -}; - -export default Workflows; diff --git a/frontend/src/views/Landingpage.jsx b/frontend/src/views/Landingpage.jsx deleted file mode 100755 index ed02060f..00000000 --- a/frontend/src/views/Landingpage.jsx +++ /dev/null @@ -1,205 +0,0 @@ -import React from "react"; - -import Paper from "@material-ui/core/Paper"; -import Button from "@material-ui/core/Button"; -import Divider from "@material-ui/core/Divider"; -import { BrowserView, MobileView } from "react-device-detect"; -import ScheduleIcon from "@material-ui/icons/Schedule"; -import Web from "@material-ui/icons/Web"; -import AccountTree from "@material-ui/icons/AccountTree"; - -const bodyDivStyle = { - margin: "auto", - marginTop: "75px", - textAlign: "center", - width: "1100px", -}; - -const surfaceColor = "#27292D"; -const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - height: "400px", - //backgroundColor: "#e8eaf6", - backgroundColor: surfaceColor, - textAlign: "center", - display: "flex", - flexDirection: "column", -}; - -const bodyTextStyle = { - color: "#ffffff", -}; - -const hrefStyle = { - color: "black", - textDecoration: "none", -}; - -// Should be different if logged in :| -const LandingPage = (props) => { - const { isLoaded } = props; - - const textColor = "#8899A6"; - const iconColor = "#1DA1F2"; - const iconSize = "8em"; - const GridLayout = (header, description, link, icon) => { - return ( - - -
    -

    {header}

    -
    - -
    - {description} -
    -
    {icon}
    - -
    -
    Learn more
    -
    -
    -
    - ); - }; - - const listitems = [ - GridLayout( - "Simple integrations", - "Easily use others' or create your own integration", - "/docs/apps", - - ), - GridLayout( - "Workflows", - "Access the power of automation within minutes, whether its on premise or in the cloud", - "/docs/workflows", - - ), - GridLayout( - "Realtime actions", - "Beat the clock by leveraging our realtime triggers", - "/docs/triggers", - - ), - ]; - - // The actual landing page - // {"logo"} - const landingpageDataBrowser = ( -
    -
    -

    Shuffle

    -

    - A general automation solution for Infosec and IT Professionals -

    -
    - - - - - - -
    - {listitems.map((item) => { - return
    {item}
    ; - })} -
    -
    - ); - - const landingpageDataMobile = ( -
    -
    -

    Shuffle

    -

    A general automation solution for Infosec and IT Professionals

    - - - -
    -
    -
    {listitems[0]}
    -
    {listitems[1]}
    -
    - {listitems[2]} -
    - -
    -
    - ); - - // Reroute if the user is logged in - // const landingSite = isLoggedIn ? :
    {landingpageData}
    - const landingSite =
    {landingpageDataBrowser}
    ; - - const loadedCheck = isLoaded ? ( -
    - {landingSite} - {landingpageDataMobile} -
    - ) : ( -
    - ); - - return
    {loadedCheck}
    ; -}; -export default LandingPage; diff --git a/frontend/src/views/LandingpageNew.jsx b/frontend/src/views/LandingpageNew.jsx deleted file mode 100755 index 252d389c..00000000 --- a/frontend/src/views/LandingpageNew.jsx +++ /dev/null @@ -1,529 +0,0 @@ -import React, { useState } from "react"; - -import Paper from "@material-ui/core/Paper"; -import Card from "@material-ui/core/Card"; -import CardActionArea from "@material-ui/core/CardActionArea"; -import CardMedia from "@material-ui/core/CardMedia"; -import CardContent from "@material-ui/core/CardContent"; -import CardActions from "@material-ui/core/CardActions"; -import Button from "@material-ui/core/Button"; -import Divider from "@material-ui/core/Divider"; -import Grid from "@material-ui/core/Grid"; -import { BrowserView, MobileView } from "react-device-detect"; - -import ScheduleIcon from "@material-ui/icons/Schedule"; -import Web from "@material-ui/icons/Web"; -import AccountTree from "@material-ui/icons/AccountTree"; -import InfoIcon from "@material-ui/icons/Info"; -import ArrowForwardIcon from "@material-ui/icons/ArrowForward"; -import CreateIcon from "@material-ui/icons/Create"; - -const bodyDivStyle = { - margin: "auto", -}; - -const surfaceColor = "#27292D"; -const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - height: "400px", - //backgroundColor: "#e8eaf6", - backgroundColor: surfaceColor, - textAlign: "center", - display: "flex", - flexDirection: "column", -}; - -const bodyTextStyle = { - color: "#ffffff", -}; - -const hrefStyle = { - color: "inherit", - textDecoration: "none", -}; - -// Should be different if logged in :| -const LandingPage = (props) => { - const { isLoaded } = props; - - const textColor = "#8899A6"; - const iconColor = "#1DA1F2"; - const iconSize = "8em"; - - const GridLayout = (header, description, link, icon) => { - return ( - - -
    -

    {header}

    -
    - -
    - {description} -
    -
    {icon}
    - -
    -
    Learn more
    -
    -
    -
    - ); - }; - - const listitems = [ - GridLayout( - "Simple integrations", - "Easily use others' or create your own integration", - "/docs/features", - - ), - GridLayout( - "Workflows", - "Access the power of automation within minutes, whether its on premise or in the cloud", - "/docs/features", - - ), - GridLayout( - "Realtime actions", - "Beat the clock by leveraging our realtime triggers", - "/docs/features", - - ), - ]; - - // The actual landing page - // {"logo"} - //We start by understanding your unique environment to help identify the right thing to automate. - const secondaryColor = "rgba(167,46,87,1)"; - const primaryColor = "rgba(25, 35, 94, 1)"; - - const paperStyle = { - flex: 1, - backgroundColor: "inherit", - cursor: "pointer", - }; - - const secondaryItemList = [ - { - primaryText: "No time to waste", - secondaryText: - "Bring all your applications into a single view, and make them all work together flawlessly!", - image: "/images/time.jpg", - }, - { - primaryText: "Get a better overview", - secondaryText: - "Don't know what's happening? We'll help you track and act on your most valuable KPI's!", - image: "/images/overview.jpg", - }, - { - primaryText: "Conquer your tasks", - secondaryText: - "Get access to powerful tools and pre-made workflows to help you crush your teams daily tasks!", - image: "/images/burnout.jpg", - }, - ]; - const [image, setImage] = useState(secondaryItemList[0].image); - - const landingpageDataBrowser = ( -
    -
    - -
    -
    Shuffle
    -
    - INFORMATION
    OVERLOAD
    -
    -
    - Everyone run into the same fundamental operational problems. Mailbox - chaos, tickets getting out of hand and a constant feeling of being - overwhelmed. The good news?{" "} -
    - Shuffle solves them. -
    -
    - - - -
    -
    -
    -
    -
    - Automation is just the beginning -
    -
    -
    - {secondaryItemList.map((data, index) => { - const color = - image === data.image - ? "rgba(255,255,255,1)" - : "rgba(255,255,255,0.4)"; - return ( -
    setImage(data.image)} - > - {data.primaryText} -
    - {data.secondaryText} -
    -
    - ); - })} -
    -
    -
    - -
    -
    -
    -
    - -
    -
    - Learn more about the benefits of Shuffle -
    - -
    -
    -
    -
    -
    - Focus on the work that matters to you -
    -
    - Menial tasks, scattered content, constant copy pasting, waste of - talent - there's a smarter way to work. -
    -
    - { - window.location.pathname = "/docs/features"; - }} - style={{ flex: 1, margin: 10, textAlign: "center" }} - > - - - -

    Premade playbooks

    -

    Get your automation done with minimal effort

    -
    -
    - -
    - { - window.location.pathname = "/docs/features"; - }} - style={{ flex: 1, margin: 10, textAlign: "center" }} - > - - - -

    Open frameworks

    -

    Mitre Att&ck, OpenAPI and more!

    -
    -
    - -
    - { - window.location.pathname = "/docs/features"; - }} - style={{ flex: 1, margin: 10, textAlign: "center" }} - > - - - -

    Hundreds of integrations

    -

    Quickly integrate your software applications

    -
    -
    - -
    - { - window.location.pathname = "/docs/features"; - }} - > - - - -

    Automated compliance

    -

    Stuck with compliance needs you can't meet?

    -
    -
    - -
    -
    -
    -
    - ); - - const landingpageDataMobile = ( -
    -
    -

    Shuffle

    -

    A general automation solution for Infosec and IT Professionals

    - - - -
    -
    -
    {listitems[0]}
    -
    {listitems[1]}
    -
    - {listitems[2]} -
    - -
    -
    - ); - - // Reroute if the user is logged in - // const landingSite = isLoggedIn ? :
    {landingpageData}
    - const landingSite =
    {landingpageDataBrowser}
    ; - - const loadedCheck = isLoaded ? ( -
    - {landingSite} - {landingpageDataMobile} -
    - ) : ( -
    - ); - - return
    {loadedCheck}
    ; -}; -export default LandingPage; diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index 4cb4b27c..5958b2fc 100755 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -1,7 +1,8 @@ /* eslint-disable react/no-multi-comp */ import React, { useState, useEffect } from "react"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@mui/styles"; import { useInterval } from "react-powerhooks"; +import theme from '../theme.jsx'; import { CircularProgress, @@ -9,9 +10,8 @@ import { Button, Paper, Typography, -} from "@material-ui/core"; +} from "@mui/material"; -import { useTheme } from "@material-ui/core/styles"; import { useNavigate } from "react-router-dom"; const hrefStyle = { @@ -42,7 +42,6 @@ const LoginDialog = (props) => { checkLogin, } = props; - const theme = useTheme(); let navigate = useNavigate(); const classes = useStyles(); @@ -306,7 +305,6 @@ const LoginDialog = (props) => { paddingBottom: "30px", paddingTop: "30px", position: "relative", - backgroundColor: theme.palette.inputColor, textAlign: "left", marginTop: 15, }} @@ -332,20 +330,28 @@ const LoginDialog = (props) => { variant="body2" style={{ marginBottom: 20, color: "white" }} > - 1. Make sure shuffle-database folder has correct access:{" "} + 1. Make sure shuffle-database folder has correct access, and that you have a minimum of 2Gb of RAM available:{" "}

    sudo chown 1000:1000 -R shuffle-database - - 2. Restart docker-compose: + 2. Disable memory swap on the host:

    - sudo docker-compose restart + sudo swapoff -a +
    + + 3. Restart the database: +
    +
    + sudo docker restart shuffle-opensearch
    { //id="sso_button" const ssoBtn = document.getElementById("sso_button"); if (ssoBtn !== undefined && ssoBtn !== null) { - console.log("SSO BTN: ", ssoBtn) + //console.log("SSO BTN: ", ssoBtn) const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; var tmpView = new URLSearchParams(cursearch).get("autologin"); if (tmpView !== undefined && tmpView !== null) { @@ -528,7 +534,7 @@ const LoginDialog = (props) => { }, 200); }, [ssoUrl]) - return
    {loadedCheck}
    ; + return
    {loadedCheck}
    ; }; export default LoginDialog; diff --git a/frontend/src/views/MyView.jsx b/frontend/src/views/MyView.jsx deleted file mode 100755 index 42c72bc3..00000000 --- a/frontend/src/views/MyView.jsx +++ /dev/null @@ -1,2147 +0,0 @@ -import React, { useEffect } from "react"; -import { useInterval } from "react-powerhooks"; - -import Grid from "@material-ui/core/Grid"; -import Paper from "@material-ui/core/Paper"; -import Tooltip from "@material-ui/core/Tooltip"; -import Divider from "@material-ui/core/Divider"; -import Button from "@material-ui/core/Button"; -import TextField from "@material-ui/core/TextField"; -import FormControl from "@material-ui/core/FormControl"; -import IconButton from "@material-ui/core/IconButton"; -import Menu from "@material-ui/core/Menu"; -import MenuItem from "@material-ui/core/MenuItem"; -import FormControlLabel from "@material-ui/core/FormControlLabel"; -import Chip from "@material-ui/core/Chip"; -import Switch from "@material-ui/core/Switch"; -import Typography from "@material-ui/core/Typography"; -import Zoom from "@material-ui/core/Zoom"; - -import CircularProgress from "@material-ui/core/CircularProgress"; -import CachedIcon from "@material-ui/icons/Cached"; -import GetAppIcon from "@material-ui/icons/GetApp"; -import AppsIcon from "@material-ui/icons/Apps"; -import EditIcon from "@material-ui/icons/Edit"; -import MoreVertIcon from "@material-ui/icons/MoreVert"; -import PlayArrowIcon from "@material-ui/icons/PlayArrow"; -import AddIcon from "@material-ui/icons/Add"; -import PublishIcon from "@material-ui/icons/Publish"; -//import JSONPretty from 'react-json-pretty'; -//import JSONPrettyMon from 'react-json-pretty/dist/monikai' -import ReactJson from "react-json-view"; - -import { Link } from "react-router-dom"; -import { useAlert } from "react-alert"; -import ChipInput from "material-ui-chip-input"; - -import Dialog from "@material-ui/core/Dialog"; -import DialogTitle from "@material-ui/core/DialogTitle"; -import DialogActions from "@material-ui/core/DialogActions"; -import DialogContent from "@material-ui/core/DialogContent"; -import CloudDownloadIcon from "@material-ui/icons/CloudDownload"; -import BubbleChartIcon from "@material-ui/icons/BubbleChart"; -import RestoreIcon from "@material-ui/icons/Restore"; - -import mobileImage from "../assets/img/mobile.svg"; -import bagImage from "../assets/img/bag.svg"; -import bookImage from "../assets/img/book.svg"; - -import { - DataGrid, - GridToolbarContainer, - GridDensitySelector, - GridToolbar, -} from "@mui/x-data-grid"; - -import { makeStyles } from "@material-ui/core/styles"; - -import ListIcon from "@material-ui/icons/List"; -import GridOnIcon from "@material-ui/icons/GridOn"; - -const inputColor = "#383B40"; -const surfaceColor = "#27292D"; - -const flexContainerStyle = { - display: "flex", - flexDirection: "row", - justifyContent: "left", - alignContent: "space-between", -}; - -const flexBoxStyle = { - height: 125, - borderRadius: 4, - boxSizing: "border-box", - letterSpacing: "0.4px", - color: "#D6791E", - margin: 10, - flex: 1, -}; - -//const activeWorkflowStyle = {backgroundColor: "#FFF5EE"} -//const notificationStyle = {backgroundColor: "#E5F9FF"} -//const activeWorkflowStyle = {backgroundColor: "#3d3f43"} -const availableWorkflowStyle = { backgroundColor: "#3d3f43" }; -const notificationStyle = { backgroundColor: "#3d3f43" }; -const activeWorkflowStyle = { backgroundColor: "#3d3f43" }; - -const flexContentStyle = { - display: "flex", - flexDirection: "row", -}; - -const iconStyle = { - width: "75px", - height: "75px", - padding: "20px", -}; - -const fontSize_16 = { fontSize: "16px" }; -const counterStyle = { fontSize: "36px", fontWeight: "bold" }; -const blockRightStyle = { - textAlign: "right", - padding: "20px 20px 0px 0px", - width: "100%", -}; - -export const validateJson = (showResult) => { - //showResult = showResult.split(" None").join(" \"None\"") - showResult = showResult.split(" False").join(" false"); - showResult = showResult.split(" True").join(" true"); - - var jsonvalid = true; - try { - const tmp = String(JSON.parse(showResult)); - if (!showResult.includes("{") && !showResult.includes("[")) { - jsonvalid = false; - } - } catch (e) { - showResult = showResult.split("'").join('"'); - - try { - const tmp = String(JSON.parse(showResult)); - if (!showResult.includes("{") && !showResult.includes("[")) { - jsonvalid = false; - } - } catch (e) { - jsonvalid = false; - } - } - - const result = jsonvalid ? JSON.parse(showResult) : showResult; - //console.log("VALID: ", jsonvalid, result) - return { - valid: jsonvalid, - result: result, - }; -}; - -const MyView = (props) => { - const { globalUrl, isLoggedIn, isLoaded, userdata } = props; - document.title = "Shuffle - Workflows"; - - const alert = useAlert(); - - var upload = ""; - const [file, setFile] = React.useState(""); - - const [workflows, setWorkflows] = React.useState([]); - const [selectedWorkflow, setSelectedWorkflow] = React.useState({}); - const [selectedExecution, setSelectedExecution] = React.useState({}); - const [workflowExecutions, setWorkflowExecutions] = React.useState([]); - const [firstrequest, setFirstrequest] = React.useState(true); - const [workflowDone, setWorkflowDone] = React.useState(false); - const [, setTrackingId] = React.useState(""); - const [selectedWorkflowId, setSelectedWorkflowId] = React.useState(""); - - const [collapseJson, setCollapseJson] = React.useState(false); - const [field1, setField1] = React.useState(""); - const [field2, setField2] = React.useState(""); - const [downloadUrl, setDownloadUrl] = React.useState( - "https://github.com/frikky/shuffle-workflows" - ); - const [downloadBranch, setDownloadBranch] = React.useState("master"); - const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = - React.useState(false); - - const [modalOpen, setModalOpen] = React.useState(false); - const [newWorkflowName, setNewWorkflowName] = React.useState(""); - const [newWorkflowDescription, setNewWorkflowDescription] = - React.useState(""); - const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); - const [update, setUpdate] = React.useState("test"); - const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); - const [editingWorkflow, setEditingWorkflow] = React.useState({}); - const [executionLoading, setExecutionLoading] = React.useState(false); - const [view, setView] = React.useState("grid"); - const { start, stop } = useInterval({ - duration: 5000, - startImmediate: false, - callback: () => { - //getWorkflowExecution(selectedWorkflow.id) - }, - }); - - // DEBUG HERE - const handleClickLogout = () => {}; - - const deleteModal = deleteModalOpen ? ( - { - setDeleteModalOpen(false); - setSelectedWorkflowId(""); - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: 500, - }, - }} - > - -
    - Are you sure?
    - Other workflows relying on this one may stop working -
    - - - - - -
    - ) : null; - - const getAvailableWorkflows = () => { - 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) => { - setSelectedExecution({}); - setWorkflowExecutions([]); - - if (responseJson !== undefined) { - setWorkflows(responseJson); - setWorkflowDone(true); - } else { - if (isLoggedIn) { - alert.error("An error occurred while loading workflows"); - } else { - handleClickLogout(); - } - - return; - } - - if (responseJson.length > 0) { - setSelectedWorkflow(responseJson[0]); - //getWorkflowExecution(responseJson[0].id) - } - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; - - useEffect(() => { - if (workflows.length <= 0 && firstrequest) { - setFirstrequest(false); - getAvailableWorkflows(); - } - }); - - const viewStyle = { - color: "#ffffff", - width: "100%", - display: "flex", - minWidth: 1024, - maxWidth: 1024, - margin: "auto", - /*maxHeight: "90vh",*/ - }; - - const emptyWorkflowStyle = { - paddingTop: "200px", - width: 1024, - margin: "auto", - }; - - const boxStyle = { - padding: "20px 20px 20px 20px", - width: "100%", - height: "250px", - color: "white", - backgroundColor: surfaceColor, - display: "flex", - flexDirection: "column", - }; - - const scrollStyle = { - marginTop: "10px", - overflow: "scroll", - height: "90%", - overflowX: "hidden", - overflowY: "auto", - }; - - const paperAppContainer = { - display: "flex", - flexWrap: "wrap", - alignContent: "space-between", - }; - - const paperAppStyle = { - minHeight: 130, - width: "100%", - color: "white", - backgroundColor: surfaceColor, - padding: "12px 12px 0px 15px", - borderRadius: 5, - display: "flex", - boxSizing: "border-box", - position: "relative", - }; - - const gridContainer = { - height: "auto", - color: "white", - margin: "10px", - backgroundColor: surfaceColor, - }; - - const workflowActionStyle = { - flex: "1", - display: "flex", - width: 150, - height: 44, - justifyContent: "space-between", - overflow: "hidden", - }; - - const getWorkflowExecution = (id) => { - setExecutionLoading(true); - fetch(globalUrl + "/api/v1/workflows/" + id + "/executions", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - setExecutionLoading(false); - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success === false) { - alert.error("Failed getting executions"); - } else { - if (responseJson.length > 0) { - setSelectedExecution(responseJson[0]); - setWorkflowExecutions(responseJson); - } else { - //alert.info("Couldn't find executions for the workflow") - setSelectedExecution({}); - setWorkflowExecutions([]); - } - } - }) - .catch((error) => { - setExecutionLoading(false); - alert.error(error.toString()); - }); - }; - - const abortExecution = (workflowid, executionid) => { - alert.success("Aborting execution"); - fetch( - globalUrl + - "/api/v1/workflows/" + - workflowid + - "/executions/" + - executionid + - "/abort", - { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - } - ) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } - //getWorkflowExecution(workflowid) - - return response.json(); - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; - - const executeWorkflow = (id) => { - alert.show("Executing workflow " + id); - setTrackingId(id); - fetch(globalUrl + "/api/v1/workflows/" + id + "/execute", { - 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) { - alert.error(responseJson.reason); - } - }) - .catch((error) => { - alert.error(error.toString()); - }); - - if (id === selectedWorkflow.id) { - sleep(2000).then(() => { - stop(); - start(); - }); - } - }; - - function sleep(time) { - return new Promise((resolve) => setTimeout(resolve, time)); - } - - const exportAllWorkflows = () => { - for (var key in workflows) { - exportWorkflow(workflows[key]); - } - }; - - const exportWorkflow = (data) => { - console.log("export"); - let dataStr = JSON.stringify(data); - - let dataUri = - "data:application/json;charset=utf-8," + encodeURIComponent(dataStr); - let exportFileDefaultName = data.name + ".json"; - - data["owner"] = ""; - for (var key in data.triggers) { - const trigger = data.triggers[key]; - if (trigger.app_name === "Shuffle Workflow") { - if (trigger.parameters.length > 2) { - trigger.parameters[2].value = ""; - } - } - - if (trigger.status == "running") { - trigger.status = "stopped"; - } - } - - for (var key in data.actions) { - data.actions[key].authentication_id = ""; - } - - //return - - data["org"] = []; - data["org_id"] = ""; - data.execution_org = { id: "" }; - console.log(data); - - let linkElement = document.createElement("a"); - linkElement.setAttribute("href", dataUri); - linkElement.setAttribute("download", exportFileDefaultName); - linkElement.click(); - }; - - const copyWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)); - alert.success("Copying workflow " + data.name); - console.log("data: ", data); - data.id = ""; - data.name = data.name + "_copy"; - //return - - fetch(globalUrl + "/api/v1/workflows", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(data), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for workflows :O!"); - return; - } - return response.json(); - }) - .then((responseJson) => { - getAvailableWorkflows(); - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; - - const deleteWorkflow = (id) => { - alert.success("Deleted workflow " + id); - fetch(globalUrl + "/api/v1/workflows/" + id, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for setting workflows :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - getAvailableWorkflows(); - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; - - const getWorkflowMeta = (data) => { - let triggers = 0; - let schedules = 0; - let webhooks = 0; - let subflows = 0; - if ( - data.triggers !== undefined && - data.triggers !== null && - data.triggers.length > 0 - ) { - triggers = data.triggers.length; - for (let key in data.triggers) { - if (data.triggers[key].app_name === "Webhook") { - webhooks += 1; - //webhookImg = data.triggers[key].large_image - } else if (data.triggers[key].app_name === "Schedule") { - schedules += 1; - //scheduleImg = data.triggers[key].large_image - } else if (data.triggers[key].app_name === "Subflow") { - subflows += 1; - } - } - } - - return [triggers, schedules, webhooks, subflows]; - }; - - // dropdown with copy etc I guess - const WorkflowPaper = (props) => { - const { data } = props; - const [open, setOpen] = React.useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); - - var boxWidth = "2px"; - if (selectedWorkflow.id === data.id) { - boxWidth = "4px"; - } - - var boxColor = "#FECC00"; - if (data.is_valid) { - boxColor = "#86c142"; - } - - if (!data.previously_saved) { - boxColor = "#f85a3e"; - } - - const menuClick = (event) => { - setOpen(!open); - setAnchorEl(event.currentTarget); - }; - - var parsedName = data.name; - if ( - parsedName !== undefined && - parsedName !== null && - parsedName.length > 25 - ) { - parsedName = parsedName.slice(0, 25) + ".."; - } - - const actions = data.actions !== null ? data.actions.length : 0; - const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data); - - return ( - - -
    - - - - {parsedName} - - - - - - - - {actions} - - - - - - - - {triggers} - - - - - - - - - - {subflows} - - - - {/* - - - - - - - : null} - {schedules > 0 ? - - - - : null} - */} - - - {data.tags !== undefined - ? data.tags.map((tag, index) => { - if (index >= 3) { - return null; - } - - return ( - - ); - }) - : null} - - - {data.actions !== undefined && data.actions !== null ? ( - - - - - - { - setOpen(false); - setAnchorEl(null); - }} - > - { - setModalOpen(true); - setEditingWorkflow(data); - setNewWorkflowName(data.name); - setNewWorkflowDescription(data.description); - if (data.tags !== undefined && data.tags !== null) { - setNewWorkflowTags( - JSON.parse(JSON.stringify(data.tags)) - ); - } - }} - key={"change"} - > - {"Change details"} - - { - copyWorkflow(data); - setOpen(false); - }} - key={"copy"} - > - {"Copy"} - - { - exportWorkflow(data); - setOpen(false); - }} - key={"export"} - > - {"Export"} - - { - setDeleteModalOpen(true); - setSelectedWorkflowId(data.id); - setOpen(false); - }} - key={"delete"} - > - {"Delete"} - - - - - - - - - - - - ) : null} - - - ); - }; - - const executionPaper = (data) => { - var boxWidth = "2px"; - if (selectedExecution.execution_id === data.execution_id) { - boxWidth = "4px"; - } - - var boxColor = "orange"; - if ( - data.status === "ABORTED" || - data.status === "UNFINISHED" || - data.status === "FAILURE" - ) { - boxColor = "red"; - } else if (data.status === "FINISHED") { - boxColor = "green"; - } - - var t = new Date(data.started_at * 1000); - if (data.workflow.actions === null || data.workflow.actions === undefined) { - return null; - } - - if (data.workflow.actions === null || data.workflow.actions === undefined) { - return null; - } - - var actions = data.workflow.actions.length; - if (data.results !== null) { - var results = data.results.length; - } - - return ( - { - setSelectedExecution(data); - }} - > -
    - - - -
    -

    - Status: {data.status} -

    - Actions: {results}/{actions} -
    -
    - -
    -
    -
    - - Started: {t.toISOString()} - -
    -
    -
    - - ); - }; - - const dividerColor = "rgb(225, 228, 232)"; - - const resultPaperAppStyle = { - minHeight: "100px", - minWidth: "100%", - overflow: "hidden", - maxWidth: "100%", - marginTop: "5px", - color: "white", - backgroundColor: surfaceColor, - display: "flex", - }; - - function replaceAll(string, search, replace) { - return string.split(search).join(replace); - } - - const resultsPaper = (data) => { - var boxWidth = "2px"; - var boxColor = "orange"; - if ( - data.status === "ABORTED" || - data.status === "UNFINISHED" || - data.status === "FAILURE" - ) { - boxColor = "red"; - } else if (data.status === "FINISHED" || data.status === "SUCCESS") { - boxColor = "green"; - } else if (data.status === "SKIPPED" || data.status === "EXECUTING") { - boxColor = "yellow"; - } else { - boxColor = "green"; - } - - var t = new Date(data.started_at * 1000); - var showResult = data.result.trim(); - const validate = validateJson(showResult); - - if (validate.valid) { - showResult = ( - - ); - } else { - // FIXME - have everything parsed as json, either just for frontend - // or in the backend? - /* - const newdata = {"result": data.result} - showResult = - */ - } - - return ( - {}} - > -
    - - - -

    - Name: {data.action.label} -

    -
    - - App: {data.action.app_name}, Version: {data.action.app_version} - - - Action: {data.action.name}, Environment: {data.action.environment} - , Status: {data.status} - -
    - - Started: {t.toISOString()} - -
    - -
    - - {showResult} - -
    -
    -
    -
    - ); - }; - - const resultsHandler = - Object.getOwnPropertyNames(selectedExecution).length > 0 && - selectedExecution.results !== null ? ( -
    - {selectedExecution.results - .sort((a, b) => a.started_at - b.started_at) - .map((data, index) => { - return
    {resultsPaper(data)}
    ; - })} -
    - ) : ( -
    No results yet
    - ); - - const resultsLength = - Object.getOwnPropertyNames(selectedExecution).length > 0 && - selectedExecution.results !== null - ? selectedExecution.results.length - : 0; - - const ExecutionDetails = () => { - var starttime = new Date(selectedExecution.started_at * 1000); - var endtime = new Date(selectedExecution.started_at * 1000); - - var parsedArgument = selectedExecution.execution_argument; - if ( - selectedExecution.execution_argument !== undefined && - selectedExecution.execution_argument.length > 0 - ) { - parsedArgument = replaceAll(parsedArgument, " None", ' "None"'); - } - - var arg = null; - if ( - selectedExecution.execution_argument !== undefined && - selectedExecution.execution_argument.length > 0 - ) { - var showResult = selectedExecution.execution_argument.trim(); - const validate = validateJson(showResult); - - arg = validate.valid ? ( - - ) : ( - showResult - ); - } - - var lastresult = null; - if ( - selectedExecution.result !== undefined && - selectedExecution.result.length > 0 - ) { - var showResult = selectedExecution.result.trim(); - const validate = validateJson(showResult); - lastresult = validate.valid ? ( - - ) : ( - showResult - ); - } - - /* -
    - ID: {selectedExecution.execution_id} -
    -
    - Last node: {selectedExecution.workflow.actions.find(data => data.id === selectedExecution.last_node).actions[0].label} -
    - */ - if ( - Object.getOwnPropertyNames(selectedExecution).length > 0 && - selectedExecution.workflow.actions !== null - ) { - return ( -
    -
    - Status: {selectedExecution.status} -
    -
    - Started: {starttime.toISOString()} -
    -
    - Finished: {endtime.toISOString()} -
    - {/* -
    - Last Result: {lastresult} -
    - */} -
    {arg}
    - - {resultsHandler} -
    - ); - } - - return executionLoading ? ( -
    - -
    - ) : ( -

    - There are no executiondetails yet. Click "execute" to run your first - one. -

    - ); - }; - - const ExecutionsView = () => { - if (workflowExecutions.length > 0) { - const sortedWorkflows = workflowExecutions - .sort((a, b) => a.started_at - b.started_at) - .reverse(); - - return ( -
    - {sortedWorkflows.map((data) => { - return executionPaper(data); - })} -
    - ); - } - return executionLoading ? ( -
    - -
    - ) : ( -

    - Executions have been moved to the Workflow itself.
    - - Click here to see them - -

    - ); - }; - - // Can create and set workflows - const setNewWorkflow = ( - name, - description, - tags, - editingWorkflow, - redirect - ) => { - var method = "POST"; - var extraData = ""; - var workflowdata = {}; - - if (editingWorkflow.id !== undefined) { - console.log("Building original workflow"); - method = "PUT"; - extraData = "/" + editingWorkflow.id; - workflowdata = editingWorkflow; - - console.log("REMOVING OWNER"); - workflowdata["owner"] = ""; - // FIXME: Loop triggers and turn them off? - } - - workflowdata["name"] = name; - workflowdata["description"] = description; - if (tags !== undefined) { - workflowdata["tags"] = tags; - } - //console.log(workflowdata) - //return - - return fetch(globalUrl + "/api/v1/workflows" + extraData, { - method: method, - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(workflowdata), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for workflows :O!"); - return; - } - return response.json(); - }) - .then((responseJson) => { - if (method === "POST" && redirect) { - window.location.pathname = "/workflows/" + responseJson["id"]; - } else if (!redirect) { - // Update :) - getAvailableWorkflows(); - } else { - alert.info("Successfully changed basic info for workflow"); - } - - return responseJson; - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; - - const importFiles = (event) => { - console.log("Importing!"); - const file = event.target.value; - if (event.target.files.length > 0) { - for (var key in event.target.files) { - const file = event.target.files[key]; - if (file.type !== "application/json") { - if (file.type !== undefined) { - alert.error("File has to contain valid json"); - } - - continue; - } - - const reader = new FileReader(); - // Waits for the read - reader.addEventListener("load", (event) => { - var data = reader.result; - try { - data = JSON.parse(reader.result); - } catch (e) { - alert.error("Invalid JSON: " + e); - return; - } - - // Initialize the workflow itself - const ret = setNewWorkflow( - data.name, - data.description, - data.tags, - {}, - false - ) - .then((response) => { - if (response !== undefined) { - // SET THE FULL THING - data.id = response.id; - - // Actually create it - const ret = setNewWorkflow( - data.name, - data.description, - data.tags, - data, - false - ).then((response) => { - if (response !== undefined) { - alert.success("Successfully imported " + data.name); - } - }); - } - }) - .catch((error) => { - alert.error("Import error: " + error.toString()); - }); - }); - - // Actually reads - reader.readAsText(file); - } - } - - setLoadWorkflowsModalOpen(false); - }; - - const modalView = modalOpen ? ( - { - setModalOpen(false); - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: "800px", - }, - }} - > - -
    - {editingWorkflow.id !== undefined ? "Editing" : "New"} workflow -
    - - - -
    -
    -
    - - - setNewWorkflowName(event.target.value)} - InputProps={{ - style: { - color: "white", - }, - }} - color="primary" - placeholder="Name" - margin="dense" - defaultValue={newWorkflowName} - fullWidth - /> - setNewWorkflowDescription(event.target.value)} - InputProps={{ - style: { - color: "white", - }, - }} - color="primary" - defaultValue={newWorkflowDescription} - placeholder="Description" - margin="dense" - fullWidth - /> - { - newWorkflowTags.push(chip); - setNewWorkflowTags(newWorkflowTags); - }} - onDelete={(chip, index) => { - newWorkflowTags.splice(index, 1); - setNewWorkflowTags(newWorkflowTags); - setUpdate("delete " + chip); - }} - /> - - - - - - -
    - ) : null; - - const viewSize = { - workflowView: 4, - executionsView: 3, - executionResults: 4, - }; - - const workflowViewStyle = { - flex: viewSize.workflowView, - marginLeft: "10px", - marginRight: "10px", - }; - - if (viewSize.workflowView === 0) { - workflowViewStyle.display = "none"; - } - - const workflowButtons = ( - - {view === "grid" && ( - - - - )} - {view === "list" && ( - - - - )} - {workflows.length > 0 ? ( - - - - ) : null} - - - - (upload = ref)} - onChange={importFiles} - /> - {workflows.length > 0 ? ( - - - - ) : null} - - - - - ); - - const useStyles = makeStyles((theme) => ({ - root: { - border: 0, - "& .MuiDataGrid-columnsContainer": { - backgroundColor: theme.palette.type === "light" ? "#fafafa" : "#1d1d1d", - }, - "& .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", - }, - }, - })); - const classes = useStyles(); - - const WorkflowGridView = () => { - let workflowData = ""; - if (workflows.length > 0) { - const columns = [ - { field: "title", headerName: "Title", width: 330 }, - { - field: "actions", - headerName: "Actions", - width: 200, - sortable: false, - disableClickEventBubbling: true, - renderCell: (params) => { - const data = params.row.record; - let [triggers, schedules, webhooks, subflows] = - getWorkflowMeta(data); - - return ( - - - - - - - - - executeWorkflow(data.id)} - /> - - - - - {webhooks > 0 ? ( - - - - ) : null} - {schedules > 0 ? ( - - - - ) : null} - - ); - }, - }, - { - field: "tags", - headerName: "Tags", - width: 390, - sortable: false, - disableClickEventBubbling: true, - renderCell: (params) => { - const data = params.row.record; - return ( - - {data.tags !== undefined - ? data.tags.map((tag, index) => { - if (index >= 3) { - return null; - } - - return ( - - ); - }) - : null} - - ); - }, - }, - ]; - let rows = []; - rows = workflows.map((data, index) => { - let obj = { id: index + 1, title: data.name, record: data }; - return obj; - }); - workflowData = ( - - ); - } - return
    {workflowData}
    ; - }; - - const WorkflowView = () => { - if (workflows.length === 0) { - return ( -
    - -
    -

    Welcome to Shuffle

    -
    -
    -

    - Shuffle is a flexible, easy to use, automation platform - allowing users to integrate their services and devices freely. - It's made to significantly reduce the amount of manual labor, - and is focused on security applications.{" "} - - Click here to learn more. - -

    -
    -
    - If you want to jump straight into it, click here to create your - first workflow: -
    -
    - - - - ..OR - - {workflowButtons} - -
    -
    -
    - ); - } - - return ( -
    -
    -
    -
    -

    Workflows

    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    {workflows.length}
    -
    ACTIVE WORKFLOWS
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    {workflows.length}
    -
    AVAILABE WORKFLOWS
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    {workflows.length}
    -
    NOTIFICATIONS
    -
    -
    -
    -
    - -
    -
    - - This is your workflow view.{" "} - - Learn more about Workflows - - -
    -
    {workflowButtons}
    -
    -
    - {view === "grid" && ( - - {workflows.map((data, index) => { - return ; - })} - - )} - - {view === "list" && } - -
    -
    -
    - ); - }; - - const importWorkflowsFromUrl = (url) => { - console.log("IMPORT WORKFLOWS FROM ", downloadUrl); - - const parsedData = { - url: url, - field_3: downloadBranch || "master", - }; - - if (field1.length > 0) { - parsedData["field_1"] = field1; - } - - if (field2.length > 0) { - parsedData["field_2"] = field2; - } - - alert.success("Getting specific workflows from your URL."); - var cors = "cors"; - fetch(globalUrl + "/api/v1/workflows/download_remote", { - method: "POST", - mode: "cors", - headers: { - Accept: "application/json", - }, - body: JSON.stringify(parsedData), - credentials: "include", - }) - .then((response) => { - if (response.status === 200) { - alert.success("Successfully loaded workflows from " + downloadUrl); - getAvailableWorkflows(); - } - - return response.json(); - }) - .then((responseJson) => { - console.log("DATA: ", responseJson); - if (!responseJson.success) { - if (responseJson.reason !== undefined) { - alert.error("Failed loading: " + responseJson.reason); - } else { - alert.error("Failed loading"); - } - } - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; - - const handleGithubValidation = () => { - importWorkflowsFromUrl(downloadUrl); - setLoadWorkflowsModalOpen(false); - }; - - const workflowDownloadModalOpen = loadWorkflowsModalOpen ? ( - {}} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: "800px", - minHeight: "320px", - }, - }} - > - -
    - Load workflows from github repo -
    - - - -
    -
    -
    - - Repository (supported: github, gitlab, bitbucket) - 0 - ? userdata.active_org.defaults.workflow_download_repo - : downloadUrl - } - InputProps={{ - style: { - color: "white", - height: "50px", - fontSize: "1em", - }, - }} - onChange={(e) => setDownloadUrl(e.target.value)} - placeholder="https://github.com/frikky/shuffle-apps" - fullWidth - /> - - Branch (default value is "master"): - -
    - 0 - ? userdata.active_org.defaults.workflow_download_branch - : downloadBranch - } - InputProps={{ - style: { - color: "white", - height: "50px", - fontSize: "1em", - }, - }} - onChange={(e) => setDownloadBranch(e.target.value)} - placeholder="master" - fullWidth - /> -
    - - Authentication (optional - private repos etc): - -
    - setField1(e.target.value)} - type="username" - placeholder="Username / APIkey (optional)" - fullWidth - /> - setField2(e.target.value)} - type="password" - placeholder="Password (optional)" - fullWidth - /> -
    -
    - - - - -
    - ) : null; - - const loadedCheck = - isLoaded && isLoggedIn && workflowDone ? ( -
    - - {modalView} - {deleteModal} - {workflowDownloadModalOpen} -
    - ) : ( -
    - - Loading Workflows -
    - ); - - // Maybe use gridview or something, idk - return
    {loadedCheck}
    ; -}; - -export default MyView; diff --git a/frontend/src/views/RegisterLink.jsx b/frontend/src/views/RegisterLink.jsx deleted file mode 100755 index 1e4827c6..00000000 --- a/frontend/src/views/RegisterLink.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useParams } from "react-router-dom"; - -import Paper from "@material-ui/core/Paper"; - -const bodyDivStyle = { - margin: "auto", - textAlign: "center", - width: "768px", -}; - -//const tmpdata = { -// "username": "frikky", -// "firstname": "fred", -// "lastname": "ode", -// "title": "topkek", -// "companyname": "company here", -// "email": "your email pls", -// "phone": "PHONE!!", -//} - -// FIXME - add fetch for data fields -// FIXME - remove tmpdata -// FIXME: Use isLoggedIn :) -const Settings = (defaultprops) => { - const { globalUrl, isLoaded, surfaceColor } = defaultprops; - - const params = useParams(); - var props = JSON.parse(JSON.stringify(defaultprops)) - props.match = {} - props.match.params = params - - const [firstRequest, setFirstRequest] = useState(true); - const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: surfaceColor, - color: "white", - display: "flex", - flexDirection: "column", - }; - - const registerCall = () => { - const url = globalUrl + "/api/v1/register/" + props.match.params.key; - fetch(url, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then((response) => - response.json().then((responseJson) => { - console.log(responseJson); - }) - ) - .catch((error) => { - console.log("SOMETHING WRONG"); - }); - }; - - // This should "always" have data - useEffect(() => { - if (firstRequest) { - setFirstRequest(false); - registerCall(); - } - }); - - // Random names for type & autoComplete. Didn't research :^) - const landingpageData = ( -
    - -

    Registration verification

    -

    Thanks for verifying, redirecting you to our login!

    -
    -
    - ); - - const loadedCheck = isLoaded ? ( -
    {landingpageData}
    - ) : ( -
    - ); - - return
    {loadedCheck}
    ; -}; -export default Settings; diff --git a/frontend/src/views/RegisterPage.jsx b/frontend/src/views/RegisterPage.jsx deleted file mode 100755 index 34df7666..00000000 --- a/frontend/src/views/RegisterPage.jsx +++ /dev/null @@ -1,176 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { useState } from "react"; - -import DialogTitle from "@material-ui/core/DialogTitle"; -import Dialog from "@material-ui/core/Dialog"; -import TextField from "@material-ui/core/TextField"; -import Button from "@material-ui/core/Button"; - -const LoginDialog = (props) => { - const { - classes, - onClose, - open, - globalUrl, - isLoggedIn, - setIsLoggedIn, - ...other - } = props; - - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - //const [selectedValue, setSelectedValue] = useState(false); - - // Used to swap from login to register. True = login, false = register - const [loginCheck, setLoginCheck] = useState(true); - - // Error messages etc - const [loginInfo, setLoginInfo] = useState(""); - - const handleValidateForm = () => { - return username.length > 1 && password.length > 8; - }; - - const onSubmit = (e) => { - e.preventDefault(); - - // Just use this one? - var data = - '{"username": "' + username + '", "password": "' + password + '"}'; - var baseurl = globalUrl; - if (loginCheck) { - var url = baseurl + "/login"; - fetch(url, { - method: "POST", - body: data, - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => - response.json().then((responseJson) => { - console.log(responseJson); - //console.log(e) - if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]); - } else { - setLoginInfo("Successful login :)"); - onClose(); - setIsLoggedIn(true); - } - }) - ) - .catch((error) => { - setLoginInfo("Error in userdata"); - }); - } else { - url = baseurl + "/register"; - fetch(url, { - method: "POST", - body: data, - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]); - } else { - setLoginInfo("Successful register. Please check your mail :)"); - onClose(); - setIsLoggedIn(true); - } - }) - ) - .catch((error) => { - setLoginInfo("Error in userdata"); - }); - } - }; - - const onChangeUser = (e) => { - setUsername(e.target.value); - }; - - const onChangePass = (e) => { - setPassword(e.target.value); - }; - - const onClickRegister = () => { - setLoginCheck(!loginCheck); - }; - - //var loginChange = loginCheck ? (

    Want to register? Click here.

    ) : (

    Go back to login? Click here.

    ); - var formtitle = loginCheck ?
    Login
    :
    Register
    ; - var formButton = loginCheck ? ( -
    Click to Register
    - ) : ( -
    Click to Login
    - ); - - return ( - - {formtitle} -
    - Username -
    - -
    - Password -
    - -
    -
    - - - -
    - {loginInfo} -
    -
    - -
    -
    - ); -}; - -export default LoginDialog; diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx index ebc1cad6..a8046fc2 100644 --- a/frontend/src/views/RunWorkflow.jsx +++ b/frontend/src/views/RunWorkflow.jsx @@ -3,7 +3,7 @@ import React, {useState, useEffect} from 'react'; import ReactDOM from "react-dom" import { useInterval } from "react-powerhooks"; -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@mui/material/styles'; import { useNavigate, Link, useParams } from "react-router-dom"; import {isMobile} from "react-device-detect"; import theme from '../theme.jsx'; @@ -20,7 +20,7 @@ import { Paper, Typography, Divider, -} from '@material-ui/core'; +} from '@mui/material'; import { Preview as PreviewIcon, @@ -66,10 +66,10 @@ const RunWorkflow = (defaultprops) => { marginBottom: 150, } - const params = useParams(); - var props = JSON.parse(JSON.stringify(defaultprops)) - props.match = {} - props.match.params = params + const params = useParams(); + var props = JSON.parse(JSON.stringify(defaultprops)) + props.match = {} + props.match.params = params const defaultTitle = "Run Workflow" if (document != undefined && document.title != defaultTitle) { @@ -126,12 +126,14 @@ const RunWorkflow = (defaultprops) => { } const executionMargin = 20 - const defaultReturn = + const defaultReturn = null + /*
    No results yet
    + */ if (executionData.results === undefined || executionData.results === null) { return defaultReturn @@ -271,7 +273,7 @@ const RunWorkflow = (defaultprops) => { width: 30, }} onClick={() => { - navigate(`?execution_highlight=${parsed_url}`) + //navigate(`?execution_highlight=${parsed_url}`) }} > @@ -314,10 +316,16 @@ const RunWorkflow = (defaultprops) => { ) } - const onSubmit = (execution_id, authorization, answer) => { + const onSubmit = (event, execution_id, authorization, answer) => { + if (event !== null) { + event.preventDefault() + } + + console.log("In submit!") + stop() - setMessage("") - setExecutionLoading(true) + setMessage("") + setExecutionLoading(true) setExecutionData({}) setExecutionInfo("") @@ -327,13 +335,13 @@ const RunWorkflow = (defaultprops) => { var url = `${globalUrl}/api/v1/workflows/${props.match.params.key}/execute` var fetchBody = { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, mode: 'cors', credentials: 'include', crossDomain: true, withCredentials: true, - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, } if (answer !== undefined && execution_id !== undefined && authorization !== undefined) { @@ -345,8 +353,10 @@ const RunWorkflow = (defaultprops) => { fetchBody.body = JSON.stringify(data) } + console.log("Pre request: ", url, fetchBody) fetch(url, fetchBody) .then((response) => { + console.log("Got answer 1") if (response.status !== 200 && response.status !== 201) { if (answer !== undefined && execution_id !== undefined && authorization !== undefined) { @@ -362,9 +372,11 @@ const RunWorkflow = (defaultprops) => { } } + console.log("Got answer 2") return response.json(); }) .then(responseJson => { + console.log("Got answer 3") setExecutionLoading(false) if (responseJson["success"] === false) { console.log("Failed sending execution request") @@ -379,6 +391,7 @@ const RunWorkflow = (defaultprops) => { start(); } } + console.log("Got answer 4") }) .catch(error => { //setExecutionInfo("Error in workflow startup: " + error) @@ -387,14 +400,14 @@ const RunWorkflow = (defaultprops) => { } const getWorkflow = (workflow_id) => { - fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) + fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for workflows :O!"); @@ -441,35 +454,35 @@ const RunWorkflow = (defaultprops) => { return } - console.log("Got response: ", responseJson) + //console.log("Got response: ", responseJson) - ReactDOM.unstable_batchedUpdates(() => { - if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) { - // FIXME: If another is selected, don't edit.. - // Doesn't work because this is some async garbage - if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) { - if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) { - console.log("Updating data!") - setExecutionData(responseJson) + ReactDOM.unstable_batchedUpdates(() => { + if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) { + // FIXME: If another is selected, don't edit.. + // Doesn't work because this is some async garbage + if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) { + if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) { + //console.log("Updating data!") + setExecutionData(responseJson) - 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) - } + 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"]) - } - - break - } + console.log("Newresult: ", validate.result) + if (validate.result["information"] !== undefined && validate.result["information"] !== null) { + setWorkflowQuestion(validate.result["information"]) } + + break + } + } } else { console.log("NOT updating executiondata state."); } @@ -518,9 +531,9 @@ const RunWorkflow = (defaultprops) => { if (responseJson.sync_features === undefined || responseJson.sync_features === null) { } - if (document != undefined && document.title != defaultTitle) { - document.title = responseJson.name + " - " + defaultTitle - } + if (document != undefined && document.title != defaultTitle) { + document.title = responseJson.name + " - " + defaultTitle + } setSelectedOrganization(responseJson) } }) @@ -530,6 +543,11 @@ const RunWorkflow = (defaultprops) => { }; const fetchUpdates = (execution_id, authorization, getorg) => { + if (execution_id === undefined || execution_id === null || execution_id === "") { + stop() + return + } + const innerRequest = { "execution_id": execution_id, "authorization": authorization @@ -594,7 +612,7 @@ const RunWorkflow = (defaultprops) => { 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) + //console.log("execdata: ", executionData) const disabledButtons = message.length > 0 || executionData.status === "FINISHED" || executionData.status === "ABORTED" const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : "Unknown" @@ -603,7 +621,7 @@ const RunWorkflow = (defaultprops) => { const image = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.image !== undefined && selectedOrganization.image !== null && selectedOrganization.image !== "" ? selectedOrganization.image : theme.palette.defaultImage - console.log("IMG: ", image, "ORG: ", selectedOrganization) + //console.log("IMG: ", image, "ORG: ", selectedOrganization) if (!disabledButtons && answer !== undefined && answer !== null && organization !== "Unknown" && buttonClicked.length === 0) { console.log("Finding button!") @@ -627,7 +645,7 @@ const RunWorkflow = (defaultprops) => { const basedata =
    -
    {onSubmit()}} style={{margin: "15px 15px 15px 15px"}}> + {onSubmit(e)}} style={{margin: "15px 15px 15px 15px"}}> {workflow.name} { {answer !== undefined && answer !== null ? null : - Execution Argument + Runtime Argument
    { {executionRunning ? - - Status: {executionData.status} - + + {executionData.status !== undefined && executionData.status !== null && executionData.status !== "" ? + + Status: {executionData.status} + + : null} : @@ -723,7 +744,7 @@ const RunWorkflow = (defaultprops) => { }
    - - - - - - -
    - -
    - ); - }; - - console.log(schedules); - console.log(schedules); - console.log(schedules.schedules); - const schedulemap = - Object.getOwnPropertyNames(schedules).length > 0 && - schedules.schedules && - schedules.schedules.length > 0 ? ( -
    {schedules.schedules.map((data) => schedulePaper(data))}
    - ) : ( -
    - -
    - ); - - const scheduleView = - Object.getOwnPropertyNames(schedules).length > 0 ? ( -
    - - {schedulemap} -
    - ) : null; - - // Maybe use gridview or something, idk - return
    {scheduleView}
    ; -}; - -export default Schedules; diff --git a/frontend/src/views/Search.jsx b/frontend/src/views/Search.jsx index 942a1ec9..ffa2061f 100644 --- a/frontend/src/views/Search.jsx +++ b/frontend/src/views/Search.jsx @@ -11,14 +11,14 @@ import { useNavigate } from "react-router-dom"; import { Tabs, Tab, -} from "@material-ui/core"; +} from "@mui/material"; import { Apps as AppsIcon, - Polymer as PolymerIcon, + Code as CodeIcon, EmojiObjects as EmojiObjectsIcon, Description as DescriptionIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; const bodyDivStyle = { @@ -137,7 +137,7 @@ const Search = (props) => { /> - Workflows + Workflows
    /> { diff --git a/frontend/src/views/SetAuthenticationSSO.jsx b/frontend/src/views/SetAuthenticationSSO.jsx index 371eedb8..1b46bacd 100755 --- a/frontend/src/views/SetAuthenticationSSO.jsx +++ b/frontend/src/views/SetAuthenticationSSO.jsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import { Typography, CircularProgress } from "@material-ui/core"; +import { Typography, CircularProgress } from "@mui/material"; const SetAuthentication = (props) => { const { globalUrl } = props; diff --git a/frontend/src/views/SettingsPage.jsx b/frontend/src/views/SettingsPage.jsx index b0a7c038..68acae51 100755 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; +import theme from '../theme.jsx'; import { Grid, Typography, @@ -8,16 +9,13 @@ import { Button, Divider, TextField, -} from "@material-ui/core"; -import { useAlert } from "react-alert"; -import { useTheme } from "@material-ui/core/styles"; - -import detectEthereumProvider from "@metamask/detect-provider"; +} from "@mui/material"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" const Settings = (props) => { const { globalUrl, isLoaded, userdata, setUserData } = props; - const theme = useTheme(); - const alert = useAlert(); + //const alert = useAlert(); let navigate = useNavigate(); const [username, setUsername] = useState(""); @@ -145,7 +143,7 @@ const Settings = (props) => { if (responseJson["success"] === false) { setPasswordFormMessage(responseJson["reason"]); } else { - alert.success("Changed password!"); + toast("Changed password!"); setPasswordFormMessage(""); } }) @@ -297,22 +295,22 @@ const Settings = (props) => { // detectEthereumProvider().then((provider) => { // if (provider) { // if (!provider.isMetaMask) { - // alert.error("Only MetaMask is supported as of now."); + // toast("Only MetaMask is supported as of now."); // return; // } // // Find the ethereum network // // Get the users' account(s) - // //alert.info("Connecting to MetaMask") + // //toast("Connecting to MetaMask") // //console.log("Connected: ", provider.isConnected()) // if (!provider.isConnected()) { - // alert.error("Metamask is not connected."); + // toast("Metamask is not connected."); // return; // } // provider.on("message", (event) => { - // alert.info("Ethereum message: ", event); + // toast("Ethereum message: ", event); // }); // provider.on("chainChanged", (chainId) => { @@ -333,12 +331,12 @@ const Settings = (props) => { // console.log("INFO: ", userdata); // setUserData(userdata); // } else { - // alert.error("Couldn't find balance: ", result); + // toast("Couldn't find balance: ", result); // } // }) // .catch((error) => { // // If the request fails, the Promise will reject with an error. - // alert.error("Failed getting info from ethereum API: " + error); + // toast("Failed getting info from ethereum API: " + error); // }); // }); // } @@ -732,28 +730,28 @@ const Settings = (props) => {

    {passwordFormMessage}

    -

    Platform Earnings

    +

    Creator Incentive Program

    -
    - {isCloud ? - - - By connecting your Github account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. Support: support@shuffler.io - - - - : null} -
    +
    + {isCloud ? + + + By joining the Creator Incentive Program and connecting your Github account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. Support: support@shuffler.io + + + + : null} +
    {userdata.eth_info !== undefined && @@ -815,65 +813,6 @@ const Settings = (props) => { ) : null}
    - {/*userdata !== undefined && - userdata.eth_info !== undefined && - userdata.eth_info.account !== undefined && - userdata.eth_info.account.length > 0 ? ( -
    - Network: TBD - - Address: {userdata.eth_info.account} - - {loadedWorkflowCollections.length > 0 ? ( - - Collections:  - {loadedWorkflowCollections.map((data, index) => { - var collectionname = data.toLowerCase(); - collectionname = collectionname.replaceAll("#", ""); - collectionname = collectionname.replaceAll(" ", "-"); - - return ( - - - {data} - -   - - ); - })} - - ) : null} - -
    - ) : ( - - )*/}
    @@ -920,7 +859,7 @@ const Settings = (props) => { }) .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed updating user: " + responseJson.reason); + toast("Failed updating user: " + responseJson.reason); } }) .catch((error) => { @@ -987,137 +926,6 @@ const Settings = (props) => { //saveWorkflow(workflow); } - const handleEthereumTokenCreation = async () => { - const provider = await detectEthereumProvider(); - if (!provider) { - console.log("Please install MetaMask!"); - alert.error( - "Please download the MetaMask browser extension to authenticate fully!" - ); - return; - } - - if (!provider.isMetaMask) { - alert.error("Only MetaMask is supported as of now."); - return; - } - - if (!provider.isConnected()) { - alert.error("Metamask is not connected."); - return; - } - - console.log("Should make a token"); - }; - - const handleEthereumConnection = async () => { - const provider = await detectEthereumProvider(); - if (!provider) { - console.log("Please install MetaMask!"); - alert.error( - "Please download the MetaMask browser extension to authenticate fully!" - ); - return; - } - - if (!provider.isMetaMask) { - alert.error("Only MetaMask is supported as of now."); - return; - } - - // Find the ethereum network - // Get the users' account(s) - //alert.info("Connecting to MetaMask") - //console.log("Connected: ", provider.isConnected()) - - if (!provider.isConnected()) { - alert.error("Metamask is not connected."); - return; - } - - provider.on("message", (event) => { - alert.info("Ethereum message: ", event); - }); - - /* - params: [ - { - from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155', - to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567', - gas: '0x76c0', // 30400 - gasPrice: '0x9184e72a000', // 10000000000000 - value: '0x9184e72a', // 2441406250 - data: - '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', - }, - ] - */ - - // https://docs.metamask.io/guide/rpc-api.html - // Gets accounts - requires previous permissions - //const method = "eth_accounts" - //const params = [] - // - // Asks for permission, and gets the accounts - var method = "eth_requestAccounts"; - var params = []; - provider - .request({ - method: method, - params, - }) - .then((result) => { - if (result !== undefined && result !== null && result.length > 0) { - userdata.eth_info.account = result[0]; - - // Getting and setting balance for the current user - method = "eth_getBalance"; - params = [userdata.eth_info.account, "latest"]; - provider - .request({ - method: method, - params, - }) - .then((result) => { - if ( - result !== undefined && - result !== null && - result.length > 0 - ) { - userdata.eth_info.balance = result; - userdata.eth_info.parsed_balance = result / 1000000000000000000; - console.log(userdata.eth_info); - setUserData(userdata.eth_info); - - // Updating - //if (userdata.eth_info !== userdata.userdata.eth_info) { - //} - - setUser(userdata.id, "eth_info", userdata.eth_info); - userdata.userdata.eth_info = userdata.eth_info; - } else { - alert.error("Couldn't find balance: ", result); - } - // The result varies by RPC method. - // For example, this method will return a transaction hash hexadecimal string on success. - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - //setEthInfo(userdata.eth_info) - alert.error("Failed getting info from ethereum API: " + error); - }); - } else { - alert.error("Couldn't find any user: ", result); - } - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error("Failed getting info from ethereum API: " + error); - }); - - // Gets the users' balance in WEI (one quintilionth ETH) - }; - const loadedCheck = isLoaded && !firstrequest ? (
    {landingpageData}
    diff --git a/frontend/src/views/TempDashboard.jsx b/frontend/src/views/TempDashboard.jsx deleted file mode 100644 index 4d4ab368..00000000 --- a/frontend/src/views/TempDashboard.jsx +++ /dev/null @@ -1,298 +0,0 @@ -import React from "react"; -import { Grid, Container, Divider } from "@mui/material"; - -import { makeStyles } from "@material-ui/core/styles"; -import Card from "@material-ui/core/Card"; -import CardContent from "@material-ui/core/CardContent"; -import Typography from "@material-ui/core/Typography"; - -import Table from "@material-ui/core/Table"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableContainer from "@material-ui/core/TableContainer"; -import TableHead from "@material-ui/core/TableHead"; -import TableRow from "@material-ui/core/TableRow"; -import Paper from "@material-ui/core/Paper"; - -import { LineChart, LineSeries, BarChart } from "reaviz"; -import { GridStripe } from "reaviz"; -//import { GridlineSeries } from "reaviz"; - -import InputLabel from '@material-ui/core/InputLabel'; -import FormControl from '@material-ui/core/FormControl'; -import Select from '@material-ui/core/Select'; -import MenuItem from '@material-ui/core/MenuItem'; - -const data = [ - { - key: new Date("11/29/2019"), - data: 10, - }, - { - key: new Date("11/30/2019"), - data: 14, - }, - { - key: new Date("12/01/2019"), - data: 5, - }, - { - key: new Date("12/02/2019"), - data: 18, - }, -]; - -const useStyles1 = makeStyles((theme) => ({ - formControl: { - margin: theme.spacing(1), - minWidth: 120, - }, - selectEmpty: { - marginTop: theme.spacing(2), - }, -})); - - -const useStyles = makeStyles({ - table: { - minWidth: 650, - }, - root: { - minWidth: 275, - }, - bullet: { - display: "inline-block", - margin: "0 2px", - transform: "scale(0.8)", - }, - title: { - fontSize: 14, - }, - pos: { - marginBottom: 12, - }, -}); - -function createData(name, calories, fat, carbs, protein) { - return { name, calories, fat, carbs, protein }; -} - -const rows = [ - createData("Frozen yoghurt", 159, 6.0, 24, 4.0), - createData("Ice cream sandwich", 237, 9.0, 37, 4.3), - createData("Eclair", 262, 16.0, 24, 6.0), - createData("Cupcake", 305, 3.7, 67, 4.3), - createData("Gingerbread", 356, 16.0, 49, 3.9), -]; - -const DashboardPage = () => { - const classes = useStyles(); - const classes1 = useStyles1(); - - const [age, setAge] = React.useState(0); - - const handleChange = (event) => { - setAge(event.target.value); - - }; - - return ( - - - -
    - - Dashboard - -
    - - Organization - - -
    -
    -
    - -
    - - - - - - Total workflows executions - - - 456 - - - - - - - - - Total Apps executions - - - 587 - - - - - - - - - Total failed executions - - - 999 - - - - - - - - - - - } - series={} - /> - - - - - - - - - Dessert (100g serving) - Calories - Fat (g) - Carbs (g) - Protein (g) - - - - {rows.map((row) => ( - - - {row.name} - - {row.calories} - {row.fat} - {row.carbs} - {row.protein} - - ))} - -
    -
    -
    -
    -
    - ); -}; - -export default DashboardPage; diff --git a/frontend/src/views/UpdateAuthentication.jsx b/frontend/src/views/UpdateAuthentication.jsx index c4d671b5..054244c3 100644 --- a/frontend/src/views/UpdateAuthentication.jsx +++ b/frontend/src/views/UpdateAuthentication.jsx @@ -5,9 +5,10 @@ import { Typography, CircularProgress, Button, -} from "@material-ui/core"; +} from "@mui/material"; import theme from '../theme.jsx'; -import { useAlert } from "react-alert"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; import AuthenticationWindow from "../components/AuthenticationWindow.jsx"; @@ -22,10 +23,10 @@ const SetAuthentication = (props) => { const [appAuthentication, setAppAuthentication] = React.useState([]); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - const alert = useAlert(); + //const alert = useAlert(); const parseIncomingOpenapiData = (data) => { - if (data.app === undefined || data.app === null) { + if (data.app === undefined || data.app === null) { return } @@ -42,11 +43,11 @@ const SetAuthentication = (props) => { parsedapp.name = parsedapp.name.replaceAll("_", " "); setApp(parsedapp); - document.title = parsedapp.name + " App Auth"; + document.title = parsedapp.name + " App Auth"; } - console.log("App: ", app) + console.log("App: ", app) const getApp = (appid) => { if (serverside === true) { @@ -63,28 +64,28 @@ const SetAuthentication = (props) => { }) .then((response) => { if (response.status !== 200) { - if (isCloud) { - ReactGA.event({ - category: "appauth", - action: `app_not_found`, - label: appid, - }); - } + if (isCloud) { + ReactGA.event({ + category: "appauth", + action: `app_not_found`, + label: appid, + }); + } } else { - if (isCloud) { - ReactGA.event({ - category: "appauth", - action: `app_found`, - label: appid, - }); - } + if (isCloud) { + ReactGA.event({ + category: "appauth", + action: `app_found`, + label: appid, + }); + } } return response.json(); }) .then((responseJson) => { if (responseJson.success === false || responseJson.success === undefined) { - alert.error("Failed to get the app. Does it exist?") + toast("Failed to get the app. Does it exist?") setIsAppLoaded(true) return; } @@ -92,7 +93,7 @@ const SetAuthentication = (props) => { parseIncomingOpenapiData(responseJson); }) .catch((error) => { - alert.error("Error in app fetch: " + error.toString()); + toast("Error in app fetch: " + error.toString()); }); }; diff --git a/frontend/src/views/Webhooks.jsx b/frontend/src/views/Webhooks.jsx deleted file mode 100755 index 64a463b9..00000000 --- a/frontend/src/views/Webhooks.jsx +++ /dev/null @@ -1,316 +0,0 @@ -import React, { useEffect } from "react"; - -import Paper from "@material-ui/core/Paper"; -import Grid from "@material-ui/core/Grid"; -import ButtonBase from "@material-ui/core/ButtonBase"; -import Button from "@material-ui/core/Button"; -import List from "@material-ui/core/List"; -import ListItem from "@material-ui/core/ListItem"; -import TextField from "@material-ui/core/TextField"; -import Select from "@material-ui/core/Select"; -import MenuItem from "@material-ui/core/MenuItem"; - -import Dialog from "@material-ui/core/Dialog"; -import DialogTitle from "@material-ui/core/DialogTitle"; -import DialogActions from "@material-ui/core/DialogActions"; -import DialogContent from "@material-ui/core/DialogContent"; - -import WebhookImage from "../assets/img/webhook.png"; -import KafkaImage from "../assets/img/kafka.png"; - -const Webhooks = (props) => { - const { globalUrl, isLoaded } = props; - const validtypes = ["webhook"]; - - //const [hooks, setSchedules] = React.useState(hookdata); - const [hooks, setHooks] = React.useState([]); - const [modalOpen, setModalOpen] = React.useState(false); - const [newHookName, setNewHookName] = React.useState(""); - const [newHookDescription, setNewHookDescription] = React.useState(""); - const [newHookType, setNewHookType] = React.useState(""); - const [firstrequest, setFirstrequest] = React.useState(true); - const [, setModalError] = React.useState(""); - - useEffect(() => { - if (firstrequest) { - setFirstrequest(false); - getAvailableHooks(); - } - }); - - const newHook = () => { - if (newHookName.length === 0) { - setModalError("Missing name in modal"); - return; - } - - if (!validtypes.includes(newHookType)) { - setModalError( - newHookType + " is not a valid type. Try this: " + validtypes - ); - } - - fetch(globalUrl + "/api/v1/hooks/new", { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - name: newHookName, - description: newHookDescription, - type: newHookType, - }), - credentials: "include", - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson); - setHooks([]); - }) - .catch((error) => { - console.log(error); - }); - }; - - const getAvailableHooks = () => { - fetch(globalUrl + "/api/v1/hooks", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => response.json()) - .then((responseJson) => { - setHooks(responseJson); - }) - .catch((error) => { - console.log(error); - // window.location.pathname = "/" - }); - }; - - const deleteHook = (id) => { - if (id === undefined) { - return; - } - - fetch(globalUrl + "/api/v1/hooks/" + id + "/delete", { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => response.json()) - .then((responseJson) => { - setHooks([]); - }) - .catch((error) => { - console.log(error); - }); - }; - - const bodyDivStyle = { - marginLeft: "20px", - marginRight: "20px", - width: "1350px", - minWidth: "1350px", - maxWidth: "1350px", - }; - - const hookApp = (app) => { - // Might be more options, but should be webhook or MQ - const appPicture = - app.type === "webhook" ? ( - webhook - ) : ( - MQ - ); - - return ( - - - {appPicture} - - {splitter} - - - -
    -

    {app.info.name}

    -
    -
    Desc: {app.info.description}
    -
    Status: {app.status}
    -
    - {app.action} -
    -
    -
    - ); - }; - - const splitter = ( -
    - ); - - const hrefStyle = { - color: "#385f71", - textDecoration: "none", - }; - - // FIXME - add Schedule modal - const hookPaper = (hook) => { - return ( -
    - -
    {hookApp(hook)}
    - {splitter} -
    - - - - - - - - - - -
    -
    -
    - ); - }; - - const modalView = modalOpen ? ( - { - setModalOpen(false); - }} - > - Hook configuration - - { - setNewHookName(event.target.value); - }} - color="primary" - placeholder="Name" - margin="dense" - fullWidth - /> - { - setNewHookDescription(event.target.value); - }} - color="primary" - placeholder="Description" - margin="dense" - fullWidth - /> - - - - - - - - - ) : null; - - const hookmap = - hooks.length > 0 ? ( -
    {hooks.map((data) => hookPaper(data))}
    - ) : ( -
    - -
    - ); - - const hookView = ( -
    - - {hookmap} -
    - ); - - const loadedCheck = isLoaded ? ( -
    - {modalView} - {hookView} -
    - ) : ( -
    - ); - - // Maybe use gridview or something, idk - return
    {loadedCheck}
    ; -}; - -export default Webhooks; diff --git a/frontend/src/views/Welcome.jsx b/frontend/src/views/Welcome.jsx index 22c571c9..97fed9f4 100644 --- a/frontend/src/views/Welcome.jsx +++ b/frontend/src/views/Welcome.jsx @@ -1,11 +1,12 @@ import React, { useState, useEffect } from 'react'; import ReactGA from 'react-ga4'; import WelcomeForm2 from "../components/WelcomeForm2.jsx"; -import Stepper from "@material-ui/core/Stepper"; -import Step from "@material-ui/core/Step"; -import StepLabel from "@material-ui/core/StepLabel"; import AppFramework from "../components/AppFramework.jsx"; -import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; + +import { + ArrorForwardIos as ArrowForwardIosIcon, +} from '@mui/icons-material'; + import { Grid, Container, @@ -16,32 +17,35 @@ import { Card, CardContent, CardActionArea, + Stepper, + Step, + StepLabel, } from '@mui/material'; import theme from '../theme.jsx'; import { useNavigate, Link } from "react-router-dom"; +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; import Drift from "react-driftjs"; const Welcome = (props) => { - const { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded, serverside } = props; + const { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded, serverside, checkLogin } = props; const [skipped, setSkipped] = React.useState(new Set()); const [inputUsecase, setInputUsecase] = useState({}); const [frameworkData, setFrameworkData] = useState(undefined); const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined); const [activeStep, setActiveStep] = React.useState(1); const [apps, setApps] = React.useState([]); - const [defaultSearch, setDefaultSearch] = React.useState("") - const [selectionOpen, setSelectionOpen] = React.useState(false) - const [showWelcome, setShowWelcome] = React.useState(false) + const [defaultSearch, setDefaultSearch] = React.useState("") + const [selectionOpen, setSelectionOpen] = React.useState(false) + const [showWelcome, setShowWelcome] = React.useState(false) const [usecases, setUsecases] = React.useState([]); const [workflows, setWorkflows] = React.useState([]); - let navigate = useNavigate(); - //if (serverside === false && isLoaded === true && isLoggedIn === false) { - // console.log("Redirecting to login?") - // console.log(window.location.pathname) - // console.log(window.location) - // navigate(`/login?view=${window.location.pathname}${window.location.search}`) - //} + let navigate = useNavigate(); + useEffect(() => { + if (checkLogin !== undefined) { + checkLogin() + } + }, [activeStep]) const isCloud = window.location.host === "localhost:3002" || @@ -191,9 +195,9 @@ const Welcome = (props) => { setFrameworkData({}) if (responseJson.reason !== undefined) { - //alert.error("Failed loading: " + responseJson.reason) + //toast("Failed loading: " + responseJson.reason) } else { - //alert.error("Failed to load framework for your org.") + //toast("Failed to load framework for your org.") } } else { setFrameworkData(responseJson) @@ -275,7 +279,7 @@ const Welcome = (props) => { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); const foundTab = params["tab"]; - if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) { + if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab) && foundTab >= 1 && foundTab <= 3) { setShowWelcome(true) if (foundTab === 3 || foundTab === "3") { handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase) @@ -283,8 +287,7 @@ const Welcome = (props) => { setActiveStep(foundTab-1) } else { - //navigate(`/welcome?tab=1`) - navigate(`/welcome?tab=2`) + navigate(`/welcome?tab=2`) } } }, []) @@ -297,44 +300,47 @@ const Welcome = (props) => { flex: 1, padding: 0, textAlign: "center", - maxWidth: 300, - minWidth: 300, + maxWidth: 275, + minWidth: 275, backgroundColor: theme.palette.surfaceColor, color: "white", borderRadius: theme.palette.borderRadius, } const actionObject = { - padding: "35px", - maxHeight: 300, - minHeight: 300, + padding: "25px", + maxHeight: 280, + minHeight: 280, borderRadius: theme.palette.borderRadius, } const imageStyle = { - width: 150, + width: 70, // height: 150, // margin: "auto", // marginTop: 10, - borderRadius: 75, + marginBottom: 18, + // borderRadius: 75, objectFit: "scale-down", } const buttonStyle = { - borderRadius: 8, + borderRadius: 200, height: 51, width: 464, fontSize: 16, - background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)", + // background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)", + background: "linear-gradient(90deg, #F86744 0%, #F34475 100%)", padding: "16px 24px", - top: 75, + top: 105, margin: "auto", itemAlign: "center", + marginLeft: "65px", } const defaultImage = "/images/experienced.png" const experienced_image = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : defaultImage return ( -
    +
    {/*
    @@ -342,7 +348,7 @@ const Welcome = (props) => { */} {showWelcome === true ?
    -
    + {/*
    { ) })} -
    - +
    */} +
    {/* @@ -388,6 +394,7 @@ const Welcome = (props) => { /> */} { />
    - {frameworkData === undefined || window.location.href.includes("tab=1") || window.location.href.includes("tab=3") ? null : + {/* {frameworkData === undefined || window.location.href.includes("tab=1") || window.location.href.includes("tab=3") ? null :
    App Framework @@ -426,7 +433,7 @@ const Welcome = (props) => { isLoggedIn={true} globalUrl={globalUrl} size={0.78} - color={theme.palette.platformColor} + color={theme.palette.backgroundColor} discoveryWrapper={discoveryWrapper} setDiscoveryWrapper={setDiscoveryWrapper} apps={apps} @@ -435,32 +442,35 @@ const Welcome = (props) => { />
    - } + } */}
    : -
    - {/* -
    - { - navigate("/login") +
    + + {/*
    + { + navigate("/workflows") }}/> - { - navigate("/login") + { + navigate("/workflows") }}> Back -
    - */} - +
    */} + + Help us get to know you - - We will use this information to personalize your automation + + Let us help you create a smoother journey. + {/* + We will use this information to personalize your automation + */}
    -
    +
    { if (isCloud) { ReactGA.event({ @@ -502,7 +512,7 @@ const Welcome = (props) => { navigate("/workflows?message=Skipped intro") }}> - + Experienced @@ -513,17 +523,24 @@ const Welcome = (props) => {
    - {/* -
    + +
    - */} + -
    { + + {/*
    { if (window.drift !== undefined) { window.drift.api.startInteraction({ interactionId: 340045 }) } else { @@ -533,7 +550,7 @@ const Welcome = (props) => { Want a demo instead? -
    +
    */}
    } diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 1e5e5414..67998211 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1,27 +1,23 @@ import React, { useEffect, useContext } from "react"; import ReactDOM from "react-dom" -import { makeStyles } from "@material-ui/core/styles"; +import { makeStyles } from "@mui/styles"; import { Navigate } from "react-router-dom"; -//import { Redirect } from "react-router-dom"; - - import SecurityFramework from '../components/SecurityFramework.jsx'; import EditWorkflow from "../components/EditWorkflow.jsx" -import { ShepherdTour, ShepherdTourContext } from 'react-shepherd' import Priority from "../components/Priority.jsx"; import { isMobile } from "react-device-detect" import { Badge, - Divider, + Divider, Avatar, Drawer, Grid, - InputLabel, - Select, - ListSubheader, + InputLabel, + Select, + ListSubheader, Paper, Tooltip, Button, @@ -32,7 +28,6 @@ import { MenuItem, Chip, Typography, - Zoom, CircularProgress, Dialog, DialogTitle, @@ -41,11 +36,10 @@ import { Checkbox, LinearProgress, ListItemText, -} from "@material-ui/core"; - -import { AvatarGroup, -} from "@mui/material" + + Zoom, +} from "@mui/material"; import { GridOn as GridOnIcon, @@ -79,26 +73,19 @@ import { RadioButtonUnchecked as RadioButtonUncheckedIcon, ArrowLeft as ArrowLeftIcon, ArrowRight as ArrowRightIcon, -} from "@material-ui/icons"; +} from "@mui/icons-material"; -//import NestedMenuItem from "material-ui-nested-menu-item"; -//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons'; - -//https://next.material-ui.com/components/material-icons/ import { DataGrid, GridToolbar } from "@mui/x-data-grid"; -//import JSONPretty from 'react-json-pretty'; -//import JSONPrettyMon from 'react-json-pretty/dist/monikai' import Dropzone from "../components/Dropzone.jsx"; import { useNavigate, Link } from "react-router-dom"; -import { useAlert } from "react-alert"; -import ChipInput from "material-ui-chip-input"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" +import { MuiChipsInput } from "mui-chips-input"; import { v4 as uuidv4 } from "uuid"; import theme from "../theme.jsx"; -const inputColor = "#383B40"; -const surfaceColor = "#27292D"; const svgSize = 24; const imagesize = 22; @@ -406,19 +393,22 @@ export const validateJson = (showResult) => { if (typeof showResult === 'string') { showResult = showResult.split(" False").join(" false") showResult = showResult.split(" True").join(" true") + + showResult.replaceAll("False,", "false,") + showResult.replaceAll("True,", "true,") } if (typeof showResult === "object" || typeof showResult === "array") { - return { - valid: true, - result: showResult, - } + return { + valid: true, + result: showResult, + } } if (showResult[0] === "\"") { - return { - valid: false, - result: showResult, + return { + valid: false, + result: showResult, } } @@ -427,10 +417,10 @@ export const validateJson = (showResult) => { if (!showResult.includes("{") && !showResult.includes("[")) { jsonvalid = false - return { - valid: jsonvalid, - result: showResult, - }; + return { + valid: jsonvalid, + result: showResult, + }; } } catch (e) { showResult = showResult.split("'").join('"'); @@ -543,7 +533,6 @@ const Workflows = (props) => { document.title = "Shuffle - Workflows"; let navigate = useNavigate(); - const alert = useAlert(); const classes = useStyles(theme); const imgSize = 60; @@ -600,6 +589,9 @@ const Workflows = (props) => { const [drawerOpen, setDrawerOpen] = React.useState(false) const [videoViewOpen, setVideoViewOpen] = React.useState(false) const [gettingStartedItems, setGettingStartedItems] = React.useState([]) + const [selectedWorkflowIndexes, setSelectedWorkflowIndexes] = React.useState([]) + + const [apps, setApps] = React.useState([]); const drawerWidth = drawerOpen ? 325 : 0 @@ -648,7 +640,7 @@ const Workflows = (props) => { 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) @@ -666,6 +658,12 @@ 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 (found.every((v) => v !== true)) { found = filters.map((filter) => { if (filter === undefined || filter === null) { @@ -676,7 +674,7 @@ const Workflows = (props) => { if (curWorkflow.name.toLowerCase().includes(filter.toLowerCase())) { return true; - } else if (curWorkflow.tags !== undefined && curWorkflow.tags !== null && curWorkflow.tags.includes(filter)) { + } else if (curWorkflow.tags !== undefined && curWorkflow.tags !== null && curWorkflow.tags.includes(filter.toLowerCase())) { return true; } else if (curWorkflow.owner === filter) { return true; @@ -684,17 +682,17 @@ const Workflows = (props) => { 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 - } + 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 @@ -729,6 +727,30 @@ const Workflows = (props) => { } }; + const getApps = () => { + 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") @@ -780,7 +802,7 @@ const Workflows = (props) => { }} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: 500, padding: 30, @@ -835,7 +857,7 @@ const Workflows = (props) => { }} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: 500, padding: 50, @@ -892,16 +914,18 @@ const Workflows = (props) => { }} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: 500, + padding: 50, }, }} >
    - Are you sure you want to delete this workflow?
    - Other workflows relying on this one may stop working + Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId).name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}?
    + + Other workflows relying on {selectedWorkflowIndexes.length > 0 ? "them" : "it"} one will stop working
    { setTimeout(() => { getAvailableWorkflows(); }, 1000); - } + } else if (selectedWorkflowIndexes.length > 0) { + // Do backwards so it doesn't change + 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); + } + } + + setTimeout(() => { + getAvailableWorkflows(); + }, 1000); + + setSelectedWorkflowIndexes([]); + } setDeleteModalOpen(false); }} color="primary" @@ -943,7 +981,7 @@ const Workflows = (props) => { const files = isDropzone ? e.dataTransfer.files : e.target.files; const reader = new FileReader(); - alert.info("Starting upload. Please wait while we validate the workflows"); + toast("Starting upload. Please wait while we validate the workflows"); try { reader.addEventListener("load", (e) => { @@ -952,7 +990,7 @@ const Workflows = (props) => { try { data = JSON.parse(reader.result); } catch (e) { - alert.error("Invalid JSON: " + e); + toast("Invalid JSON: " + e); return; } @@ -986,13 +1024,13 @@ const Workflows = (props) => { data.status ).then((response) => { if (response !== undefined) { - alert.success(`Successfully imported ${data.name}`); + toast(`Successfully imported ${data.name}`); } }); } }) .catch((error) => { - alert.error("Import error: " + error.toString()); + toast("Import error: " + error.toString()); }); }); } catch (e) { @@ -1030,9 +1068,9 @@ const Workflows = (props) => { if (responseJson.success === false) { setAppFramework({}) if (responseJson.reason !== undefined) { - //alert.error("Failed loading: " + responseJson.reason) + //toast("Failed loading: " + responseJson.reason) } else { - //alert.error("Failed to load framework for your org.") + //toast("Failed to load framework for your org.") } } else { setAppFramework(responseJson) @@ -1060,7 +1098,7 @@ const Workflows = (props) => { // navigate("/search?tab=workflows") //} - alert.info("Failed getting workflows. Are you logged in?"); + toast("Failed getting workflows. Are you logged in?"); return; } @@ -1119,20 +1157,20 @@ const Workflows = (props) => { // Ensures the zooming happens only once per load setTimeout(() => { - fetchUsecases(newarray) - setFirstLoad(false) - }, 100) + fetchUsecases(newarray) + setFirstLoad(false) + }, 100) } else { if (isLoggedIn) { - alert.error("An error occurred while loading workflows"); + toast("An error occurred while loading workflows"); } return; } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -1182,7 +1220,7 @@ const Workflows = (props) => { setUsecases(newcategories) } else { - setUsecases(categorydata) + setUsecases(categorydata) } } @@ -1212,7 +1250,7 @@ const Workflows = (props) => { } }) .catch((error) => { - //alert.error("ERROR: " + error.toString()); + //toast("ERROR: " + error.toString()); console.log("ERROR: " + error.toString()); setWorkflows(workflows); setWorkflowDone(true); @@ -1227,8 +1265,9 @@ const Workflows = (props) => { setView(tmpView); } + getApps() getAvailableWorkflows(); - getFramework() + getFramework() } }, []) @@ -1253,16 +1292,16 @@ const Workflows = (props) => { width: "100%", height: "250px", color: "white", - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, display: "flex", flexDirection: "column", }; //flexDirection: !isMobile ? "column" : "row", const paperAppContainer = { - display: "flex", - flexWrap: "wrap", - alignContent: "space-between", + //display: "flex", + //flexWrap: "wrap", + //alignContent: "space-between", }; const paperAppStyle = { @@ -1271,19 +1310,20 @@ const Workflows = (props) => { overflow: "hidden", width: "100%", color: "white", - backgroundColor: surfaceColor, padding: "12px 12px 0px 15px", borderRadius: 5, display: "flex", boxSizing: "border-box", position: "relative", + backgroundColor: theme.palette.surfaceColor, }; const gridContainer = { height: "auto", color: "white", margin: "10px", - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, + position: "relative", }; const workflowActionStyle = { @@ -1302,7 +1342,7 @@ const Workflows = (props) => { }, i * 200); } - alert.info(`exporting and keeping original for all ${allWorkflows.length} workflows`); + toast(`exporting and keeping original for all ${allWorkflows.length} workflows`); }; const deduplicateIds = (data, skip_sanitize) => { @@ -1343,7 +1383,7 @@ const Workflows = (props) => { trigger.parameters[1].value = "webhook_" + trigger.id; // FIXME: Add auth here? } else { - alert.info("Something is wrong with the webhook in the copy"); + toast("Something is wrong with the webhook in the copy"); } } @@ -1463,7 +1503,7 @@ const Workflows = (props) => { data = sanitizeWorkflow(data); if (data.subflows !== null && data.subflows !== undefined) { - alert.info( + toast( "Not exporting with subflows when sanitizing. Please manually export them." ); data.subflows = []; @@ -1491,7 +1531,7 @@ const Workflows = (props) => { const publishWorkflow = (data) => { data = JSON.parse(JSON.stringify(data)); data = sanitizeWorkflow(data); - alert.info("Sanitizing and publishing " + data.name); + toast("Sanitizing and publishing " + data.name); // This ALWAYS talks to Shuffle cloud fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish", { @@ -1508,9 +1548,9 @@ const Workflows = (props) => { console.log("Status not 200 for workflow publish :O!"); } else { if (isCloud) { - alert.success("Successfully published workflow"); + toast("Successfully published workflow"); } else { - alert.success( + toast( "Successfully published workflow to https://shuffler.io" ); } @@ -1520,20 +1560,20 @@ const Workflows = (props) => { }) .then((responseJson) => { if (responseJson.reason !== undefined) { - alert.error("Failed publishing: ", responseJson.reason); + toast("Failed publishing: ", responseJson.reason); } getAvailableWorkflows(); }) .catch((error) => { - alert.error("Failed publishing: is the workflow valid? Remember to save the workflow first.") + toast("Failed publishing: is the workflow valid? Remember to save the workflow first.") console.log(error.toString()); }); }; const copyWorkflow = (data) => { data = JSON.parse(JSON.stringify(data)); - alert.success("Copying workflow " + data.name); + toast("Copying workflow " + data.name); data.id = ""; data.name = data.name + "_copy"; data = deduplicateIds(data, true); @@ -1560,7 +1600,7 @@ const Workflows = (props) => { }, 1000); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -1576,9 +1616,9 @@ const Workflows = (props) => { .then((response) => { if (response.status !== 200) { console.log("Status not 200 for setting workflows :O!"); - alert.error("Failed deleting workflow. Do you have access?"); + toast("Failed deleting workflow. Do you have access?"); } else { - alert.success("Deleted workflow " + id); + toast("Deleted workflow " + id); } return response.json(); @@ -1589,7 +1629,7 @@ const Workflows = (props) => { }, 1000); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -1600,10 +1640,10 @@ const Workflows = (props) => { const NewWorkflowPaper = () => { const [hover, setHover] = React.useState(false); - const innerColor = "rgba(255,255,255,0.3)"; + const innerColor = "rgba(255,255,255,0.3)" const setupPaperStyle = { minHeight: paperAppStyle.minHeight, - maxWidth: "100%", + maxWidth: "100%", minWidth: paperAppStyle.width, color: innerColor, padding: paperAppStyle.padding, @@ -1622,9 +1662,9 @@ const Workflows = (props) => { square style={setupPaperStyle} onClick={() => { - setModalOpen(true) - setIsEditing(false) - }} + setModalOpen(true) + setIsEditing(false) + }} onMouseOver={() => { setHover(true); }} @@ -1695,7 +1735,7 @@ const Workflows = (props) => { } const actions = data.actions !== null ? data.actions.length : 0; - const appGroup = getWorkflowAppgroup(data) + const appGroup = getWorkflowAppgroup(data) const [triggers, subflows] = getWorkflowMeta(data); const workflowMenuButtons = ( @@ -1710,31 +1750,32 @@ const Workflows = (props) => { }} > { - event.stopPropagation() - ReactDOM.unstable_batchedUpdates(() => { - setModalOpen(true); - setEditingWorkflow(JSON.parse(JSON.stringify(data))); - setNewWorkflowName(data.name); - setNewWorkflowDescription(data.description); - setDefaultReturnValue(data.default_return_value); - if (data.tags !== undefined && data.tags !== null) { - setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))); - } + event.stopPropagation() + ReactDOM.unstable_batchedUpdates(() => { + setIsEditing(true) + setModalOpen(true); + setEditingWorkflow(JSON.parse(JSON.stringify(data))); + setNewWorkflowName(data.name); + setNewWorkflowDescription(data.description); + setDefaultReturnValue(data.default_return_value); + if (data.tags !== undefined && data.tags !== null) { + setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))); + } - if (data.usecase_ids !== undefined && data.usecase_ids !== null && data.usecase_ids.length > 0) { - setSelectedUsecases(data.usecase_ids) - } - }) - }} + if (data.usecase_ids !== undefined && data.usecase_ids !== null && data.usecase_ids.length > 0) { + setSelectedUsecases(data.usecase_ids) + } + }) + }} key={"change"} > {"Edit details"} { setSelectedWorkflow(data); setPublishModalOpen(true); @@ -1745,7 +1786,7 @@ const Workflows = (props) => { {"Publish Workflow"} { copyWorkflow(data); setOpen(false); @@ -1755,15 +1796,8 @@ const Workflows = (props) => { {"Duplicate Workflow"} - {/*= 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => { - //copyWorkflow(data) - //setOpen(false) - }} key={"duplicate"}> - - {"Copy to Child Org"} - */} { setExportModalOpen(true); @@ -1817,7 +1851,7 @@ const Workflows = (props) => { {"Export Workflow"} { setDeleteModalOpen(true); setSelectedWorkflowId(data.id); @@ -1909,7 +1943,7 @@ const Workflows = (props) => { } return ( -
    +
    {selectedCategory !== "" ? @@ -2053,7 +2087,7 @@ const Workflows = (props) => { }} onClick={() => { if (subflows === 0) { - alert.info("No subflows for " + data.name); + toast("No subflows for " + data.name); return; } @@ -2142,18 +2176,18 @@ const Workflows = (props) => { : null} {data.actions !== undefined && data.actions !== null ? ( -
    - - - - {workflowMenuButtons} -
    +
    + + + + {workflowMenuButtons} +
    ) : null}
    @@ -2200,13 +2234,13 @@ const Workflows = (props) => { workflowdata["default_return_value"] = defaultReturnValue; } - if (currentUsecases !== undefined && currentUsecases !== null) { - workflowdata["usecase_ids"] = currentUsecases - //workflows[0].category = ["detect"] - //workflows[0].usecase_ids = ["Correlate tickets"] - } + if (currentUsecases !== undefined && currentUsecases !== null) { + workflowdata["usecase_ids"] = currentUsecases + //workflows[0].category = ["detect"] + //workflows[0].usecase_ids = ["Correlate tickets"] + } - const new_url = `${globalUrl}/api/v1/workflows${extraData}` + const new_url = `${globalUrl}/api/v1/workflows${extraData}` return fetch(new_url, { method: method, headers: { @@ -2226,16 +2260,16 @@ const Workflows = (props) => { return response.json(); }) .then((responseJson) => { - if (responseJson.success === false) { - if (responseJson.reason !== undefined) { - alert.error("Error setting workflow: ", responseJson.reason) - } else { - alert.error("Error setting workflow.") - } - - return + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Error setting workflow: ", responseJson.reason) + } else { + toast("Error setting workflow.") } + return + } + if (redirect) { //window.location.pathname = "/workflows/" + responseJson["id"]; navigate("/workflows/" + responseJson["id"]) @@ -2244,18 +2278,18 @@ const Workflows = (props) => { // Update :) setTimeout(() => { getAvailableWorkflows(); - }, 2500); + }, 4000); setSubmitLoading(false) setModalOpen(false); } else { - //alert.info("Successfully changed basic info for workflow"); + //toast("Successfully changed basic info for workflow"); setModalOpen(false); } return responseJson; }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); setSubmitLoading(false) setModalOpen(false); setSubmitLoading(false); @@ -2264,7 +2298,7 @@ const Workflows = (props) => { const importFiles = (event) => { console.log("Importing!"); - setSubmitLoading(true) + setSubmitLoading(true) if (event.target.files.length > 0) { console.log("Files: !", event.target.files.length); @@ -2272,7 +2306,7 @@ const Workflows = (props) => { const file = event.target.files[key]; if (file.type !== "application/json") { if (file.type !== undefined) { - alert.error("File has to contain valid json"); + toast("File has to contain valid json"); setSubmitLoading(false) } @@ -2286,7 +2320,7 @@ const Workflows = (props) => { try { data = JSON.parse(reader.result); } catch (e) { - alert.error("Invalid JSON: " + e); + toast("Invalid JSON: " + e); setSubmitLoading(false) return; } @@ -2301,9 +2335,9 @@ const Workflows = (props) => { data.default_return_value, {}, false, - [], - "", - data.status, + [], + "", + data.status, ) .then((response) => { if (response !== undefined) { @@ -2321,18 +2355,18 @@ const Workflows = (props) => { data.default_return_value, data, false, - [], - "", - data.status, + [], + "", + data.status, ).then((response) => { if (response !== undefined) { - alert.success("Successfully imported " + data.name); + toast("Successfully imported " + data.name); } }); } }) .catch((error) => { - alert.error("Import error: " + error.toString()); + toast("Import error: " + error.toString()); }); }); @@ -2556,7 +2590,7 @@ const Workflows = (props) => { }} onClick={() => { if (subflows === 0) { - alert.info("No subflows for " + data.name); + toast("No subflows for " + data.name); return; } @@ -2665,6 +2699,7 @@ const Workflows = (props) => { renderCell: (params) => {}, }, ]; + let rows = []; rows = filteredWorkflows.map((data, index) => { let obj = { @@ -2682,17 +2717,55 @@ const Workflows = (props) => { className={classes.datagrid} rows={rows} columns={columns} - pageSize={20} + pageSize={25} checkboxSelection autoHeight density="standard" + onSelectionModelChange={(newSelection) => { + //setSelectedWorkflows(newSelection.selectionModel); + console.log(newSelection) + + setSelectedWorkflowIndexes(newSelection) + }} + selectionModel={selectedWorkflowIndexes} components={{ Toolbar: GridToolbar, }} /> ); } - return
    {workflowData}
    ; + return ( +
    + + { + setModalOpen(true) + setIsEditing(false) + }} + > + + + + {filteredWorkflows.length === 0 ? null : + { + setDeleteModalOpen(true) + }} + > + + + + + } + {workflowData} +
    + ) }; var total_count = 0 @@ -2704,7 +2777,7 @@ const Workflows = (props) => { }} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: isMobile ? "90%" : "800px", maxWidth: isMobile ? "90%" : "800px", @@ -2760,7 +2833,7 @@ const Workflows = (props) => { fullWidth />
    - { selectedUsecases.push(subcase.name) } - setUpdate(Math.random()); + setUpdate(Math.random()); setSelectedUsecases(selectedUsecases) }}> @@ -3163,70 +3236,20 @@ const Workflows = (props) => { const WorkflowView = () => { if (workflows.length === 0) { - // Not going there yet - //if ((userdata.tutorials !== undefined && userdata.tutorials !== null && !userdata.tutorials.includes("getting-started")) || userdata.tutorials === null) { - // return ; - //} - //return ( - //
    - // - //
    - //

    Welcome to Shuffle

    - //
    - //
    - //

    - // Shuffle is a flexible, easy to use, automation platform - // allowing users to integrate their services and devices freely. - // It's made to significantly reduce the amount of manual labor, - // and is focused on security applications.{" "} - // - // Click here to learn more. - // - //

    - //
    - //
    - // If you want to jump straight into it, click here to create your - // first workflow: - //
    - //
    - // - // - // - // ..OR - // - // {workflowButtons} - // - //
    - //
    - //
    - //) } - var workflowDelay = -150 - var appDelay = -75 + var workflowDelay = -150 + var appDelay = -75 - const foundPriority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) + const foundPriority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) return (
    - - Workflows - + + Workflows +
    {/*
    @@ -3245,7 +3268,7 @@ const Workflows = (props) => { {isMobile ? null :
    - { minWidth: 275, }, }} - placeholder="Add Filter" + rows={1} + placeholder="Filter Workflows" color="primary" fullWidth value={filters} - onAdd={(chip) => { - addFilter(chip); - }} - onDelete={(_, index) => { - removeFilter(index); + onChange={(chips) => { + console.log("CHANGE: ", chips); + setFilters(chips); + findWorkflow(chips); }} + //onAdd={(chip) => { + // console.log("ADd: ", chip); + // addFilter(chip); + //}} + //onDelete={(_, index) => { + // console.log("Remove: ", index); + // removeFilter(index); + //}} />
    @@ -3272,53 +3303,6 @@ const Workflows = (props) => { {workflowButtons}
    - {/* -
    -
    -
    -
    -
    -
    {workflows.length}
    -
    ACTIVE WORKFLOWS
    -
    -
    -
    -
    -
    -
    -
    -
    {workflows.length}
    -
    AVAILABE WORKFLOWS
    -
    -
    -
    -
    -
    -
    -
    -
    {workflows.length}
    -
    NOTIFICATIONS
    -
    -
    -
    -
    - */} - - {/* - chipRenderer={({ value, isFocused, isDisabled, handleClick, handleRequestDelete }, key) => { - console.log("VALUE: ", value) - - return ( - - {value} - - ) - }} - */}
    {!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ? @@ -3326,6 +3310,7 @@ const Workflows = (props) => { {usecases.map((usecase, index) => { //console.log(usecase) const percentDone = usecase.matches.length > 0 ? parseInt(usecase.matches.length/usecase.list.length*100) : 0 + //console.log("Usecase Matches: ", usecase.matches, ", Percent: ", percentDone) return ( { > { } return ( - - {returnData} - + + {/**/} + {returnData} + {/**/} + ); })}
    ) : null} - {userdata.priorities !== undefined && userdata.priorities !== null && userdata.priorities.length > 0 && userdata.priorities[0].name.includes("CPU") ? -
    + {userdata.priorities !== undefined && userdata.priorities !== null && userdata.priorities.length > 0 && userdata.priorities[0].name.includes("CPU") && userdata.priorities[0].active === true ? +
    {userdata.priorities[0].name} - - {userdata.priorities[0].description} - +
    + + + {userdata.priorities[0].description} + + +
    -
    - {/* @@ -3496,15 +3490,16 @@ const Workflows = (props) => { globalUrl={globalUrl} priority={foundPriority} checkLogin={checkLogin} + appFramework={appFramework} /> : null} -
    +
    {view === "grid" ? ( - - - - + + {/**/} + + {/**/} {filteredWorkflows.map((data, index) => { // Shouldn't be a part of this list @@ -3523,11 +3518,13 @@ const Workflows = (props) => { } return ( - + + {/**/} - + {/**/} + ) })} @@ -3542,6 +3539,7 @@ const Workflows = (props) => { globalUrl={globalUrl} priority={foundPriority} checkLogin={checkLogin} + appFramework={appFramework} /> : null} @@ -3567,7 +3565,7 @@ const Workflows = (props) => { parsedData["field_2"] = field2; } - alert.success("Getting specific workflows from your URL."); + toast("Getting specific workflows from your URL."); fetch(globalUrl + "/api/v1/workflows/download_remote", { method: "POST", mode: "cors", @@ -3579,7 +3577,7 @@ const Workflows = (props) => { }) .then((response) => { if (response.status === 200) { - alert.success("Successfully loaded workflows from " + downloadUrl); + toast("Successfully loaded workflows from " + downloadUrl); setTimeout(() => { getAvailableWorkflows(); }, 1000); @@ -3590,14 +3588,14 @@ const Workflows = (props) => { .then((responseJson) => { if (!responseJson.success) { if (responseJson.reason !== undefined) { - alert.error("Failed loading: " + responseJson.reason); + toast("Failed loading: " + responseJson.reason); } else { - alert.error("Failed loading"); + toast("Failed loading"); } } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -3612,7 +3610,7 @@ const Workflows = (props) => { onClose={() => {}} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: "800px", minHeight: "320px", @@ -3639,7 +3637,7 @@ const Workflows = (props) => { Repository (supported: github, gitlab, bitbucket) {
    {
    { fullWidth /> { } const gettingStartedDrawer = - { }} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: 560, minHeight: 415, @@ -3927,16 +3925,18 @@ const Workflows = (props) => { {modalOpen === true ? : null} diff --git a/functions/kubernetes/.gitignore b/functions/kubernetes/.gitignore new file mode 100644 index 00000000..a2661ad0 --- /dev/null +++ b/functions/kubernetes/.gitignore @@ -0,0 +1 @@ +certs/ \ No newline at end of file diff --git a/functions/kubernetes/all-in-one.yaml b/functions/kubernetes/all-in-one.yaml new file mode 100644 index 00000000..746215e9 --- /dev/null +++ b/functions/kubernetes/all-in-one.yaml @@ -0,0 +1,799 @@ +--- + +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: shuffle-data +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer + +--- + +apiVersion: v1 +data: + BACKEND_HOSTNAME: shuffle-backend + BACKEND_PORT: "5001" + BASE_URL: http://shuffle-backend:5001 + DATASTORE_EMULATOR_HOST: shuffle-database:8000 + DB_LOCATION: /mnt/shuffle-data/open-search + DOCKER_API_VERSION: "1.40" + ENVIRONMENT_NAME: Shuffle + FRONTEND_PORT: "3001" + FRONTEND_PORT_HTTPS: "3443" + HTTP_PROXY: "" + HTTPS_PROXY: "" + ORBORUS_CONTAINER_NAME: "\t\t\t\t" + ORG_ID: Shuffle + OUTER_HOSTNAME: shuffle-backend + SHUFFLE_APP_DOWNLOAD_LOCATION: https://github.com/shuffle/python-apps + SHUFFLE_APP_FORCE_UPDATE: "false" + SHUFFLE_APP_HOTLOAD_FOLDER: /shuffle-apps + SHUFFLE_APP_HOTLOAD_LOCATION: ./shuffle-apps + SHUFFLE_BASE_IMAGE_NAME: shuffle + SHUFFLE_BASE_IMAGE_REGISTRY: ghcr.io + SHUFFLE_BASE_IMAGE_TAG_SUFFIX: -1.0.0 + SHUFFLE_CHAT_DISABLED: "false" + SHUFFLE_CONTAINER_AUTO_CLEANUP: "false" + SHUFFLE_DEFAULT_APIKEY: "" + SHUFFLE_DEFAULT_PASSWORD: "" + SHUFFLE_DEFAULT_USERNAME: "" + SHUFFLE_DOWNLOAD_AUTH_BRANCH: "" + SHUFFLE_DOWNLOAD_AUTH_PASSWORD: "" + SHUFFLE_DOWNLOAD_AUTH_USERNAME: "" + SHUFFLE_DOWNLOAD_WORKFLOW_BRANCH: "" + SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION: "" + SHUFFLE_DOWNLOAD_WORKFLOW_PASSWORD: "" + SHUFFLE_DOWNLOAD_WORKFLOW_USERNAME: "" + SHUFFLE_ELASTIC: "true" + SHUFFLE_ENCRYPTION_MODIFIER: "" + SHUFFLE_FILE_LOCATION: /shuffle-files + SHUFFLE_LOGS_DISABLED: "false" + SHUFFLE_OPENSEARCH_APIKEY: "" + SHUFFLE_OPENSEARCH_CERTIFICATE_FILE: "" + SHUFFLE_OPENSEARCH_CLOUDID: "" + SHUFFLE_OPENSEARCH_INDEX_PREFIX: "" + SHUFFLE_OPENSEARCH_PASSWORD: admin + SHUFFLE_OPENSEARCH_PROXY: "" + SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY: "true" + SHUFFLE_OPENSEARCH_URL: https://opensearch:9200 + SHUFFLE_OPENSEARCH_USERNAME: admin + SHUFFLE_ORBORUS_STARTUP_DELAY: "\t\t" + SHUFFLE_PASS_APP_PROXY: "FALSE" + SHUFFLE_PASS_WORKER_PROXY: "TRUE" + SHUFFLE_RERUN_SCHEDULE: "300" + SSO_REDIRECT_URL: "" + TZ: "Europe/Amsterdam \t\t\t\t\t" + IS_KUBERNETES: "true" + REGISTRY_URL: "192.168.29.16:5000" + 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 + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: shuffle + name: pod-manager +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "create", "update", "delete"] +- apiGroups: ["batch"] + resources: ["jobs"] + verbs: ["create", "get", "list", "watch", "delete"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: pod-manager-binding + namespace: shuffle +subjects: +- kind: ServiceAccount + name: default + namespace: shuffle +roleRef: + kind: Role + name: pod-manager + apiGroup: rbac.authorization.k8s.io + +--- + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: shuffle-os-pv +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: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: opensearch-claim0 + name: opensearch-claim0 +spec: + accessModes: + - ReadWriteOnce + storageClassName: shuffle-data + resources: + requests: + storage: 500Mi +status: {} + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: opensearch + name: opensearch +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: opensearch + strategy: {} + template: + metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.network/shuffle: "true" + io.kompose.service: opensearch + spec: + # securityContext: + # runAsUser: 1000 # UID + # fsGroup: 1000 # GID + # nodeSelector: + # node: worker1 + initContainers: + - name: volume-permissions + image: busybox + command: ["sh", "-c", "chown -R 1000:1000 /usr/share/opensearch/data"] + volumeMounts: + - name: opensearch-claim0 + mountPath: /usr/share/opensearch/data + containers: + - env: + - name: OPENSEARCH_JAVA_OPTS + value: -Xms1024m -Xmx1024m + #- name: bootstrap.memory_lock + #value: "true" + - name: cluster.initial_master_nodes + value: shuffle-opensearch + - name: cluster.name + value: shuffle-cluster + - name: cluster.routing.allocation.disk.threshold_enabled + value: "false" + - name: discovery.seed_hosts + value: shuffle-opensearch + - name: node.name + value: shuffle-opensearch + - name: node.store.allow_mmap + value: "false" + - name: DB_LOCATION + valueFrom: + configMapKeyRef: + name: env + key: DB_LOCATION + image: opensearchproject/opensearch:2.5.0 + name: shuffle-opensearch + ports: + - containerPort: 9200 + resources: {} + volumeMounts: + - mountPath: /usr/share/opensearch/data + name: opensearch-claim0 + hostname: shuffle-opensearch + restartPolicy: Always + volumes: + - name: opensearch-claim0 + persistentVolumeClaim: + claimName: opensearch-claim0 +status: {} + +--- + +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: opensearch + name: opensearch +spec: + ports: + - name: "9200" + port: 9200 + targetPort: 9200 + selector: + io.kompose.service: opensearch +status: + loadBalancer: {} + +--- + +apiVersion: v1 +kind: PersistentVolume +metadata: + 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: + 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 + metadata: + creationTimestamp: null + labels: + io.kompose.service: backend-files-claim + name: backend-files-claim + spec: + accessModes: + - ReadWriteOnce + storageClassName: shuffle-data + resources: + requests: + storage: 5Gi +# status: {} + +--- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: backend-apps-claim + name: backend-apps-claim +spec: + accessModes: + - ReadWriteOnce + storageClassName: shuffle-data + resources: + requests: + storage: 5Gi +# status: {} + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: backend + name: backend +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: backend + strategy: + type: Recreate + template: + metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.network/shuffle: "true" + io.kompose.service: backend + app: shuffle-backend + name: shuffle-backend + spec: + volumes: + - name: shuffle-files + persistentVolumeClaim: + claimName: backend-files-claim + - name: shuffle-apps + persistentVolumeClaim: + claimName: backend-apps-claim +# nodeSelector: +# node: master + containers: + - env: + - name: BACKEND_HOSTNAME + valueFrom: + configMapKeyRef: + key: BACKEND_HOSTNAME + name: env + - name: BACKEND_PORT + valueFrom: + configMapKeyRef: + key: BACKEND_PORT + name: env + - name: BASE_URL + valueFrom: + configMapKeyRef: + key: BASE_URL + name: env + - name: DATASTORE_EMULATOR_HOST + valueFrom: + configMapKeyRef: + key: DATASTORE_EMULATOR_HOST + name: env + - name: DB_LOCATION + valueFrom: + configMapKeyRef: + key: DB_LOCATION + name: env + - name: DOCKER_API_VERSION + valueFrom: + configMapKeyRef: + key: DOCKER_API_VERSION + name: env + - name: ENVIRONMENT_NAME + valueFrom: + configMapKeyRef: + key: ENVIRONMENT_NAME + name: env + - name: FRONTEND_PORT + valueFrom: + configMapKeyRef: + key: FRONTEND_PORT + name: env + - name: FRONTEND_PORT_HTTPS + valueFrom: + configMapKeyRef: + key: FRONTEND_PORT_HTTPS + name: env + - name: HTTPS_PROXY + valueFrom: + configMapKeyRef: + key: HTTPS_PROXY + name: env + - name: HTTP_PROXY + valueFrom: + configMapKeyRef: + key: HTTP_PROXY + name: env + - name: ORBORUS_CONTAINER_NAME + valueFrom: + configMapKeyRef: + key: ORBORUS_CONTAINER_NAME + name: env + - name: ORG_ID + valueFrom: + configMapKeyRef: + key: ORG_ID + name: env + - name: OUTER_HOSTNAME + valueFrom: + configMapKeyRef: + key: OUTER_HOSTNAME + name: env + - name: SHUFFLE_APP_DOWNLOAD_LOCATION + valueFrom: + configMapKeyRef: + key: SHUFFLE_APP_DOWNLOAD_LOCATION + name: env + - name: SHUFFLE_APP_FORCE_UPDATE + valueFrom: + configMapKeyRef: + key: SHUFFLE_APP_FORCE_UPDATE + name: env + - name: SHUFFLE_APP_HOTLOAD_FOLDER + valueFrom: + configMapKeyRef: + key: SHUFFLE_APP_HOTLOAD_FOLDER + name: env + - name: SHUFFLE_APP_HOTLOAD_LOCATION + valueFrom: + configMapKeyRef: + key: SHUFFLE_APP_HOTLOAD_LOCATION + name: env + - name: SHUFFLE_BASE_IMAGE_NAME + valueFrom: + configMapKeyRef: + key: SHUFFLE_BASE_IMAGE_NAME + name: env + - name: SHUFFLE_BASE_IMAGE_REGISTRY + valueFrom: + configMapKeyRef: + key: SHUFFLE_BASE_IMAGE_REGISTRY + name: env + - name: SHUFFLE_BASE_IMAGE_TAG_SUFFIX + valueFrom: + configMapKeyRef: + key: SHUFFLE_BASE_IMAGE_TAG_SUFFIX + name: env + - name: SHUFFLE_CHAT_DISABLED + valueFrom: + configMapKeyRef: + key: SHUFFLE_CHAT_DISABLED + name: env + - name: SHUFFLE_CONTAINER_AUTO_CLEANUP + valueFrom: + configMapKeyRef: + key: SHUFFLE_CONTAINER_AUTO_CLEANUP + name: env + - name: SHUFFLE_DEFAULT_APIKEY + valueFrom: + configMapKeyRef: + key: SHUFFLE_DEFAULT_APIKEY + name: env + - name: SHUFFLE_DEFAULT_PASSWORD + valueFrom: + configMapKeyRef: + key: SHUFFLE_DEFAULT_PASSWORD + name: env + - name: SHUFFLE_DEFAULT_USERNAME + valueFrom: + configMapKeyRef: + key: SHUFFLE_DEFAULT_USERNAME + name: env + - name: SHUFFLE_DOWNLOAD_AUTH_BRANCH + valueFrom: + configMapKeyRef: + key: SHUFFLE_DOWNLOAD_AUTH_BRANCH + name: env + - name: SHUFFLE_DOWNLOAD_AUTH_PASSWORD + valueFrom: + configMapKeyRef: + key: SHUFFLE_DOWNLOAD_AUTH_PASSWORD + name: env + - name: SHUFFLE_DOWNLOAD_AUTH_USERNAME + valueFrom: + configMapKeyRef: + key: SHUFFLE_DOWNLOAD_AUTH_USERNAME + name: env + - name: SHUFFLE_DOWNLOAD_WORKFLOW_BRANCH + valueFrom: + configMapKeyRef: + key: SHUFFLE_DOWNLOAD_WORKFLOW_BRANCH + name: env + - name: SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION + valueFrom: + configMapKeyRef: + key: SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION + name: env + - name: SHUFFLE_DOWNLOAD_WORKFLOW_PASSWORD + valueFrom: + configMapKeyRef: + key: SHUFFLE_DOWNLOAD_WORKFLOW_PASSWORD + name: env + - name: SHUFFLE_DOWNLOAD_WORKFLOW_USERNAME + valueFrom: + configMapKeyRef: + key: SHUFFLE_DOWNLOAD_WORKFLOW_USERNAME + name: env + - name: SHUFFLE_ELASTIC + valueFrom: + configMapKeyRef: + key: SHUFFLE_ELASTIC + name: env + - name: SHUFFLE_ENCRYPTION_MODIFIER + valueFrom: + configMapKeyRef: + key: SHUFFLE_ENCRYPTION_MODIFIER + name: env + - name: SHUFFLE_FILE_LOCATION + valueFrom: + configMapKeyRef: + key: SHUFFLE_FILE_LOCATION + name: env + - name: SHUFFLE_LOGS_DISABLED + valueFrom: + configMapKeyRef: + key: SHUFFLE_LOGS_DISABLED + name: env + - name: SHUFFLE_OPENSEARCH_APIKEY + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_APIKEY + name: env + - name: SHUFFLE_OPENSEARCH_CERTIFICATE_FILE + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_CERTIFICATE_FILE + name: env + - name: SHUFFLE_OPENSEARCH_CLOUDID + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_CLOUDID + name: env + - name: SHUFFLE_OPENSEARCH_INDEX_PREFIX + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_INDEX_PREFIX + name: env + - name: SHUFFLE_OPENSEARCH_PASSWORD + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_PASSWORD + name: env + - name: SHUFFLE_OPENSEARCH_PROXY + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_PROXY + name: env + - name: SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY + name: env + - name: SHUFFLE_OPENSEARCH_URL + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_URL + name: env + - name: SHUFFLE_OPENSEARCH_USERNAME + valueFrom: + configMapKeyRef: + key: SHUFFLE_OPENSEARCH_USERNAME + name: env + - name: SHUFFLE_ORBORUS_STARTUP_DELAY + valueFrom: + configMapKeyRef: + key: SHUFFLE_ORBORUS_STARTUP_DELAY + name: env + - name: SHUFFLE_PASS_APP_PROXY + valueFrom: + configMapKeyRef: + key: SHUFFLE_PASS_APP_PROXY + name: env + - name: SHUFFLE_PASS_WORKER_PROXY + valueFrom: + configMapKeyRef: + key: SHUFFLE_PASS_WORKER_PROXY + name: env + - name: SHUFFLE_RERUN_SCHEDULE + valueFrom: + configMapKeyRef: + key: SHUFFLE_RERUN_SCHEDULE + name: env + - name: SSO_REDIRECT_URL + valueFrom: + configMapKeyRef: + key: SSO_REDIRECT_URL + name: env + - name: TZ + valueFrom: + configMapKeyRef: + key: TZ + name: env + - name: IS_KUBERNETES + valueFrom: + configMapKeyRef: + key: IS_KUBERNETES + name: env + - name: REGISTRY_URL + valueFrom: + configMapKeyRef: + key: REGISTRY_URL + name: env + - name: REGISTRY_AUTH + valueFrom: + configMapKeyRef: + key: REGISTRY_AUTH + name: env + image: ghcr.io/shuffle/shuffle-backend:nightly + imagePullPolicy: Always + name: shuffle-backend + ports: + - containerPort: 5001 + resources: {} + volumeMounts: + - name: shuffle-apps + mountPath: /app/generated + - name: shuffle-files + mountPath: /shuffle-files + restartPolicy: Always +status: {} + +--- + +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: backend + name: shuffle-backend +spec: + ports: + - name: "5001" + port: 5001 + targetPort: 5001 + selector: + io.kompose.service: backend +status: + loadBalancer: {} + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: frontend + strategy: {} + template: + metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.network/shuffle: "true" + io.kompose.service: frontend + spec: + containers: + - env: + - name: BACKEND_HOSTNAME + image: ghcr.io/shuffle/shuffle-frontend:nightly + name: shuffle-frontend + ports: + - containerPort: 80 + - containerPort: 443 + resources: {} + hostname: shuffle-frontend + restartPolicy: Always +status: {} + +--- + +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: frontend + name: frontend +spec: + type: NodePort + ports: + - name: "80" + port: 80 + targetPort: 80 + nodePort: 30007 + - name: "443" + port: 443 + targetPort: 443 + nodePort: 30008 + selector: + io.kompose.service: frontend +# status: +# loadBalancer: {} + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: orborus + name: orborus +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: orborus + strategy: {} + template: + metadata: + annotations: + kompose.cmd: kompose convert -f docker-compose.yml + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.network/shuffle: "true" + io.kompose.service: orborus + spec: + containers: + - env: + - name: BASE_URL + value: "http://shuffle-backend:5001" + - name: DOCKER_API_VERSION + value: "1.40" + - name: ENVIRONMENT_NAME + value: Shuffle + - name: ORG_ID + value: Shuffle + - name: SHUFFLE_APP_SDK_VERSION + value: nightly + - name: SHUFFLE_SCALE_REPLICAS + value: "5" + #- name: SHUFFLE_SWARM_CONFIG + #value: run + - name: SHUFFLE_WORKER_VERSION + value: nightly + - name: IS_KUBERNETES + valueFrom: + configMapKeyRef: + key: IS_KUBERNETES + name: env + - name: REGISTRY_URL + valueFrom: + configMapKeyRef: + key: REGISTRY_URL + name: env + - name: SHUFFLE_KUBERNETES_WORKER + valueFrom: + configMapKeyRef: + key: SHUFFLE_KUBERNETES_WORKER + name: env + + image: ghcr.io/shuffle/shuffle-orborus:nightly + #imagePullPolicy: Never + name: shuffle-orborus + resources: {} + hostname: shuffle-orborus + restartPolicy: Always +status: {} diff --git a/functions/kubernetes/generate_certs.sh b/functions/kubernetes/generate_certs.sh new file mode 100644 index 00000000..cb316a72 --- /dev/null +++ b/functions/kubernetes/generate_certs.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Check if ifconfig is present and use it to get the default IP +if command -v ifconfig &> /dev/null; then + default_ip=$(ifconfig | grep 'inet ' | grep -v 127.0.0.1 | awk '{print $2}') +# Check if ip is present and use it if ifconfig is not available +elif command -v ip &> /dev/null; then + default_ip=$(ip addr show | grep -oP 'inet \K[\d.]+' | sed -n '2p') +# If both tools are not available, error out +else + echo "Error: Neither ifconfig nor ip command found in the machine. Exiting.." + exit 1 +fi + + +read -p "Enter your node IP to use for cert generation (default is $default_ip): " custom_ip +# Use localhost as the default value +node_ip=${custom_ip:-$default_ip} + +echo "Using node IP: $node_ip to generate SSL certs!" + +mkdir -p certs + +# Generate CA key +openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/reg.key -x509 -days 365 -out certs/reg.crt -subj "/CN=$node_ip" + +# generate a random string +random_string=$(openssl rand -hex 3) + +echo "Starting docker registry with name shuffle-local-registry-$random_string.." + +docker run -d -p 5000:5000 --restart=always --name "shuffle-local-registry-$random_string" \ + -v $(pwd)/certs:/certs \ + -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/reg.crt \ + -e REGISTRY_HTTP_TLS_KEY=/certs/reg.key \ + registry:2 + +echo "Set up certs and launched docker registry successfully!" + +echo "Please put $node_ip:5000 as the REGISTRY_URL in all-in-one.yaml file" \ No newline at end of file diff --git a/functions/kubernetes/kubernetes.md b/functions/kubernetes/kubernetes.md new file mode 100644 index 00000000..e8e315af --- /dev/null +++ b/functions/kubernetes/kubernetes.md @@ -0,0 +1,36 @@ +## How to deploy Shuffle on Kubernetes? + +### Prerequisites: +- Clone the https://github.com/shuffle/shuffle repository using Git then, navigate to the functions/kubernetes directory, which contains all the necessary Kubernetes configuration files for deployment. +- [Running a Kubernetes cluster](https://kubernetes.io/docs/setup/). You can do that with either minikube or run the cluster locally. +- Ensure you have a local Docker registry set up to store and manage Docker images for applications built with Shuffle. While the registry is crucial for handling custom-built apps, you’ll still be able to run workflows without it. To setup a docker registry, if you have docker installed on one of your node run following commands. + + + ``` + chmod +x generate_certs.sh + ./setup_registry.sh + ``` + + > This will give you a NODE_IP which is you're local IP if you're not sure about what to use. + + > **Make sure that port 5000 is not exposed to the internet!** + +- 8 GB RAM and 4 CPUs are recommended as **minimum configs** for running Shuffle on Kubernetes. K8s is a resource-intensive application, and you may experience performance issues if you run it on a machine with fewer resources. + +- If you've used the above commands to set up a registry, you'll need to skip an SSL verification for your registry. If you're using Containerd as a runtime + add the following lines in /etc/containerd/config.toml + ``` + [plugins."io.containerd.grpc.v1.cri".registry.mirrors.""] + endpoint = ["https://"] + + [plugins."io.containerd.grpc.v1.cri".registry.configs."".tls] + insecure_skip_verify = true + ``` + +### Instructions +Step 1: Create a namespace called shuffle in a cluster by running ```kubectl create ns shuffle```. + +Step 2: Open the ```all-in-one.yaml``` file and review the configuration values. Change the value of REGISTRY_URL with ':5000' where the registry is at. Adjust other variables as per your deployment requirements; otherwise, the application will deploy using the default settings provided within the file. Then apply the configmap and deploy with ```kubectl apply -f all-in-one.yaml -n shuffle``` + +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. + diff --git a/functions/kubernetes/setup_registry.sh b/functions/kubernetes/setup_registry.sh new file mode 100755 index 00000000..7b24b3e1 --- /dev/null +++ b/functions/kubernetes/setup_registry.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Check if ifconfig is present and use it to get the default IP +if command -v ifconfig &> /dev/null; then + default_ip=$(ifconfig | grep 'inet ' | grep -v 127.0.0.1 | awk '{print $2}') +# Check if ip is present and use it if ifconfig is not available +elif command -v ip &> /dev/null; then + default_ip=$(ip addr show | grep -oP 'inet \K[\d.]+' | sed -n '2p') +# If both tools are not available, error out +else + echo "Error: Neither ifconfig nor ip command found in the machine. Exiting.." + exit 1 +fi + + +read -p "Enter your node IP to use for cert generation (default is $default_ip): " custom_ip +# Use localhost as the default value +node_ip=${custom_ip:-$default_ip} + +echo "Using node IP: $node_ip to generate SSL certs!" + +mkdir -p certs + +# Generate CA key +openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/reg.key -x509 -days 365 -out certs/reg.crt -subj "/CN=$node_ip" + +# generate a random string +random_string=$(openssl rand -hex 3) + +echo "Starting docker registry with name shuffle-local-registry-$random_string.." + +docker run -d -p 5000:5000 --restart=always --name "shuffle-local-registry-$random_string" \ + -v $(pwd)/certs:/certs \ + -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/reg.crt \ + -e REGISTRY_HTTP_TLS_KEY=/certs/reg.key \ + registry:2 + +echo "Done!" + diff --git a/functions/onprem/orborus/Dockerfile b/functions/onprem/orborus/Dockerfile index 3a8eb059..9eed284a 100755 --- a/functions/onprem/orborus/Dockerfile +++ b/functions/onprem/orborus/Dockerfile @@ -23,4 +23,11 @@ FROM alpine:3.15.0 RUN apk add --no-cache bash tzdata COPY --from=builder /app/ / +ENV ENVIRONMENT_NAME=Shuffle +ENV BASE_URL=http://shuffle-backend:5001 +ENV DOCKER_API_VERSION=1.39 +ENV SHUFFLE_OPENSEARCH_URL=https://opensearch:9200 + + + CMD ["./orborus"] diff --git a/functions/onprem/orborus/build.sh b/functions/onprem/orborus/build.sh index 4af6ba6c..bbec5067 100755 --- a/functions/onprem/orborus/build.sh +++ b/functions/onprem/orborus/build.sh @@ -1,5 +1,5 @@ NAME=shuffle-orborus -VERSION=1.2.1 +VERSION=1.3.0 echo "Running docker build with $NAME:$VERSION" #docker rmi frikky/shuffle:$NAME --force diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod index 30e1ff30..f74e495b 100644 --- a/functions/onprem/orborus/go.mod +++ b/functions/onprem/orborus/go.mod @@ -9,7 +9,10 @@ require ( github.com/mackerelio/go-osstat v0.2.3 github.com/satori/go.uuid v1.2.0 github.com/shirou/gopsutil v3.21.11+incompatible - github.com/shuffle/shuffle-shared v0.4.17 + github.com/shuffle/shuffle-shared v0.4.62 + k8s.io/api v0.28.1 + k8s.io/apimachinery v0.28.1 + k8s.io/client-go v0.28.1 ) require ( @@ -22,50 +25,75 @@ require ( github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/frikky/go-elasticsearch/v8 v8.13.1 // indirect github.com/frikky/kin-openapi v0.41.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/swag v0.19.5 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.4.3 // indirect - github.com/google/go-cmp v0.5.5 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-github/v28 v28.1.1 // indirect github.com/google/go-querystring v1.0.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/jstemmer/go-junit-report v0.9.1 // indirect - github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opensearch-project/opensearch-go v1.1.0 // indirect + github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/numcpus v0.6.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect go.opencensus.io v0.22.5 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.11.0 // indirect golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.13.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.8.0 // indirect google.golang.org/api v0.36.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595 // indirect google.golang.org/grpc v1.34.1 // indirect - google.golang.org/protobuf v1.25.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.4.0 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum index 7e955ccd..70af6a61 100644 --- a/functions/onprem/orborus/go.sum +++ b/functions/onprem/orborus/go.sum @@ -50,6 +50,20 @@ github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ= github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= +github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4= +github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= +github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 h1:hjXJeBcAMS1WGENGqDpzvmgS43oECTx8UXq31UBu0Jw= github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= @@ -61,6 +75,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -72,6 +87,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -86,12 +103,21 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -99,6 +125,8 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -121,8 +149,13 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -134,10 +167,16 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -155,6 +194,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -162,6 +203,14 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 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.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -169,22 +218,39 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mackerelio/go-osstat v0.2.3 h1:jAMXD5erlDE39kdX2CU7YwCGRcxIO33u/p8+Fhe5dJw= github.com/mackerelio/go-osstat v0.2.3/go.mod h1:DQbPOnsss9JHIXgBStc/dnhhir3gbd3YH+Dbdi7ptMA= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M= +github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= +github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ= +github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -200,14 +266,28 @@ github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKl github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shuffle/shuffle-shared v0.4.17 h1:56ll366bdmIJu/7GFqNC2XTjjl0SGBf430PSq+EB6Ro= github.com/shuffle/shuffle-shared v0.4.17/go.mod h1:jQrYySmvp/0De5ftrAaY6xwwr7TMfqBmBxQ2AX9yrjQ= +github.com/shuffle/shuffle-shared v0.4.57 h1:o+mMPRY4ourkE3R0qdi80jg6RlCtvAJ/VVrPk4y75Hk= +github.com/shuffle/shuffle-shared v0.4.57/go.mod h1:X613gbo0dT3fnYvXDRwjQZyLC+T49T2nSQOrCV5QMlI= +github.com/shuffle/shuffle-shared v0.4.59 h1:5Sv8aorgQJFZr3cCKltfycdXzp9v5zlF2l3GZXjrTEo= +github.com/shuffle/shuffle-shared v0.4.59/go.mod h1:X613gbo0dT3fnYvXDRwjQZyLC+T49T2nSQOrCV5QMlI= +github.com/shuffle/shuffle-shared v0.4.62 h1:L76zWCD/7gIBuhr3feWZwzT4I8VCiLRd8ZAub/3EiO0= +github.com/shuffle/shuffle-shared v0.4.62/go.mod h1:X613gbo0dT3fnYvXDRwjQZyLC+T49T2nSQOrCV5QMlI= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= @@ -216,6 +296,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -232,8 +313,11 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -268,8 +352,10 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -300,8 +386,14 @@ golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -312,6 +404,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423 h1:/hEknzWkMPCjTo7StMHRrBRa8YBbXuBWfck8680k3RE= golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -321,7 +415,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -354,22 +448,41 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -421,8 +534,10 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -524,18 +639,26 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -545,6 +668,24 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= +k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= +k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY= +k8s.io/apimachinery v0.28.1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= +k8s.io/client-go v0.28.1 h1:pRhMzB8HyLfVwpngWKE8hDcXRqifh1ga2Z/PU9SXVK8= +k8s.io/client-go v0.28.1/go.mod h1:pEZA3FqOsVkCc07pFVzK076R+P/eXqsgx5zuuRWukNE= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 21e42e9e..8b6846c2 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -27,6 +27,11 @@ import ( "strconv" "strings" "time" + "sync" + "math" + + //"os/signal" + //"syscall" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -39,13 +44,26 @@ import ( uuid "github.com/satori/go.uuid" //"github.com/mackerelio/go-osstat/disk" - "github.com/mackerelio/go-osstat/memory" - "github.com/shirou/gopsutil/cpu" + //"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" ) // Starts jobs in bulk, so this could be increased -var sleepTime = 3 -var maxConcurrency = 15 +var sleepTime = 2 + +// Making it work on low-end machines even during busy times :) +// May cause some things to run slowly +var maxConcurrency = 3 // Timeout if something rashes var workerTimeoutEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_TIMEOUT") @@ -55,6 +73,8 @@ var workerVersion = os.Getenv("SHUFFLE_WORKER_VERSION") var newWorkerImage = os.Getenv("SHUFFLE_WORKER_IMAGE") var dockerSwarmBridgeMTU = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_MTU") var dockerSwarmBridgeInterface = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_INTERFACE") +var isKubernetes = os.Getenv("IS_KUBERNETES") +var maxCPUPercent = 95 // var baseimagename = "docker.pkg.github.com/shuffle/shuffle" // var baseimagename = "ghcr.io/frikky" @@ -216,16 +236,18 @@ func deployServiceWorkers(image string) { log.Printf("[ERROR] Failed to get network interfaces: %s", err) } - mtu, err := strconv.Atoi(dockerSwarmBridgeMTU) // by default - bridgeName := dockerSwarmBridgeInterface - - if bridgeName == "" { - bridgeName = "eth0" + mtu := 1500 + if len(dockerSwarmBridgeMTU) == 0 { + mtu, err = strconv.Atoi(dockerSwarmBridgeMTU) // by default + if err != nil { + log.Printf("[DEBUG] Failed to convert the default MTU to int: %s. Using 1500 instead. Input: %s", err, dockerSwarmBridgeMTU) + mtu = 1500 + } } - if err != nil { - log.Printf("[ERROR] Failed to convert the default MTU to int: %s. Using 1500 instead", err) - mtu = 1500 + bridgeName := dockerSwarmBridgeInterface + if bridgeName == "" { + bridgeName = "eth0" } // Check if there is at least one interface @@ -233,11 +255,11 @@ func deployServiceWorkers(image string) { // this assumes that the machine should have at least 2 network // interfaces. If not, we will use the default MTU. // interface 1 is the loopback interface - // interface 2 is eth0, The eth0 interface inside a + // interface 2 is eth0, The eth0 interface inside a // Docker container corresponds to the virtual Ethernet // interface that connects the container to the docker0 log.Printf("[ERROR] Failed to get enough network interfaces") - } else { + } else { // Get the preferred interface for _, iface := range interfaces { if strings.Contains(iface.Name, bridgeName) { @@ -247,7 +269,7 @@ func deployServiceWorkers(image string) { break } } - } + } // Create the network options with the specified MTU options := make(map[string]string) @@ -443,6 +465,7 @@ func deployServiceWorkers(image string) { fmt.Sprintf("SHUFFLE_APP_REPLICAS=%d", cnt), fmt.Sprintf("TZ=%s", timezone), fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", os.Getenv("SHUFFLE_LOGS_DISABLED")), + fmt.Sprintf("DEBUG_MEMORY=%s", os.Getenv("DEBUG_MEMORY")), }, //Hosts: []string{ // innerContainerName, @@ -559,8 +582,72 @@ func deployServiceWorkers(image string) { // Deploys the internal worker whenever something happens // https://docs.docker.com/engine/api/sdk/examples/ + +func buildEnvVars(envMap map[string]string) []corev1.EnvVar { + var envVars []corev1.EnvVar + for key, value := range envMap { + envVars = append(envVars, corev1.EnvVar{Name: key, Value: value}) + } + return envVars +} + func deployWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) error { - // Binds is the actual "-v" volume. + + if isKubernetes == "true" { + if len(os.Getenv("REGISTRY_URL")) > 0 && os.Getenv("REGISTRY_URL") != "" { + env = append(env, fmt.Sprintf("REGISTRY_URL=%s", os.Getenv("REGISTRY_URL"))) + env = append(env, fmt.Sprintf("IS_KUBERNETES=%s", os.Getenv("IS_KUBERNETES"))) + } + + image = os.Getenv("SHUFFLE_KUBERNETES_WORKER") + log.Printf("[DEBUG] using worker image:", image) + // 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] + } + } + + clientset, err := getKubernetesClient() + if err != nil { + log.Printf("[ERROR] Error getting kubernetes client:", err) + return err + } + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: identifier, + Labels: map[string]string{"app": "shuffle-worker"}, + }, + Spec: corev1.PodSpec{ + RestartPolicy: "Never", + // once images is pushed, we can remove this + // keep this when running locally + // NodeSelector: map[string]string{ + // "node": "master", + // }, + Containers: []corev1.Container{ + { + Name: identifier, + Image: image, + Env: buildEnvVars(envMap), + }, + }, + }, + } + + createdPod, err := clientset.CoreV1().Pods("shuffle").Create(context.Background(), pod, metav1.CreateOptions{}) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating pod: %v\n", err) + } + + log.Printf("[INFO] Created pod %q in namespace %q\n", createdPod.Name, createdPod.Namespace) + } else { + + // Binds is the actual "-v" volume. // Max 20% CPU every second //CPUQuota: 25000, @@ -586,7 +673,7 @@ func deployWorker(image string, identifier string, env []string, executionReques hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) - if strings.ToLower(cleanupEnv) == "true" { + if strings.ToLower(cleanupEnv) != "false" { hostConfig.AutoRemove = true } @@ -718,6 +805,9 @@ func deployWorker(image string, identifier string, env []string, executionReques log.Printf("[INFO] Worker Container %s was created under environment %s: docker logs %s", cont.ID, environment, cont.ID) } + return nil + } + return nil } @@ -860,7 +950,54 @@ func checkSwarmService(ctx context.Context) { log.Printf("[DEBUG] Swarm info: %s\n\n", ret) } -func getOrborusStats() shuffle.OrborusStats { +func getContainerResourceUsage(ctx context.Context, cli *dockerclient.Client, containerID string) (float64, float64, error) { + // Get container stats + stats, err := cli.ContainerStats(ctx, containerID, false) + if err != nil { + return 0, 0, err + } + + defer stats.Body.Close() + // Parse and return CPU and memory utilization + cpuUsage, memoryUsage, err := parseResourceUsage(stats.Body) + if err != nil { + return 0, 0, err + } + + return cpuUsage, memoryUsage, nil +} + +func parseResourceUsage(body io.Reader) (float64, float64, error) { + var stats types.StatsJSON + + // Decode the stream of stats as JSON + decoder := json.NewDecoder(body) + if err := decoder.Decode(&stats); err != nil { + return 0, 0, err + } + + //log.Printf("[DEBUG] CPU : %d", stats.CPUStats.CPUUsage.TotalUsage) + //log.Printf("[DEBUG] CPU2: %d", stats.PreCPUStats.CPUUsage.TotalUsage) + if stats.CPUStats.CPUUsage.TotalUsage == 0 || stats.PreCPUStats.CPUUsage.TotalUsage == 0 { + //log.Printf("[DEBUG] BODY: %#v", stats) + return 0, 0, nil + } + + // Calculate time difference between current and previous stats in nanoseconds + timeDelta := float64(stats.Read.Sub(stats.PreRead).Nanoseconds()) + + // Calculate CPU usage percentage + cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage) + cpuUsage := (cpuDelta / timeDelta) * 100.0 + + // Calculate memory usage percentage + memoryUsage := float64(stats.MemoryStats.Usage) / float64(stats.MemoryStats.Limit) * 100.0 + + return cpuUsage, memoryUsage, nil + +} + +func getOrborusStats(ctx context.Context) shuffle.OrborusStats { newStats := shuffle.OrborusStats{ OrgId: org, Environment: environment, @@ -872,25 +1009,111 @@ func getOrborusStats() shuffle.OrborusStats { newStats.Swarm = true } - if runningMode == "kubernetes" || runningMode == "k8s" { - newStats.Kubernetes = true - } + + // Run this 1/10 times + //if rand.Intn(10) != 1 { + // return newStats + //} newStats.PollTime = sleepTime newStats.MaxQueue = maxConcurrency newStats.Queue = executionCount - // Get CPU usage and max CPU - /* - before, err := cpu.Get() - if err != nil { - log.Printf("[ERROR] Failed getting CPU stats: %s", err) - } else { - newStats.CPU = int(before.User) - newStats.MaxCPU = int(before.Total) - } - */ + if isKubernetes == "true" || runningMode == "kubernetes" || runningMode == "k8s" { + newStats.Kubernetes = true + return newStats + } + // Use the docker API to get the CPU usage of the docker engine machine + pers, err := dockercli.Info(ctx) + if err != nil { + log.Printf("[ERROR] Failed getting docker info: %s", err) + return newStats + } else { + newStats.TotalContainers = pers.Containers + newStats.StoppedContainers = pers.ContainersStopped + + // Calculate the amount of CPU utilization on the host + newStats.CPU = int(pers.NCPU) + newStats.MaxCPU = int(pers.NCPU) + newStats.Memory = int(pers.MemTotal) + newStats.MaxMemory = int(pers.MemTotal) + } + + + // Get list of all running containers + containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{}) + if err != nil { + log.Printf("[ERROR] Failed getting container list: %s", err) + return newStats + } + + // Use a WaitGroup to wait for all goroutines to finish + var wg sync.WaitGroup + + // Channel to collect results + resultCh := make(chan struct { + containerID string + cpuUsage float64 + memoryUsage float64 + }) + + // Iterate through containers and start a goroutine for each container + for _, container := range containers { + // Check if container is running + if container.State != "running" { + continue + } + + wg.Add(1) + go func(container types.Container) { + defer wg.Done() + + // Get CPU and memory usage for the container + cpuUsage, memoryUsage, err := getContainerResourceUsage(ctx, dockercli, container.ID) + if err != nil { + //log.Printf("[DEBUG] Error getting resource usage for container %s: %v\n", container.ID, err) + } + + // Send the result to the channel + resultCh <- struct { + containerID string + cpuUsage float64 + memoryUsage float64 + }{container.ID, cpuUsage, memoryUsage} + }(container) + } + + // Close the result channel after all goroutines are done + go func() { + wg.Wait() + close(resultCh) + }() + + // Collect results from the channel + + // Iterate through containers and get CPU usage + totalCPU := float64(0.0) + memUsage := float64(0.0) + for result := range resultCh { + //log.Printf("[DEBUG] Container %s CPU utilization: %.2f%%, Memory utilization: %.2f%%\n", result.containerID, result.cpuUsage, result.memoryUsage) + + // check if it's NaN or Inf + if !math.IsNaN(result.cpuUsage) { + totalCPU += float64(result.cpuUsage) + } + + if !math.IsNaN(result.memoryUsage) { + memUsage += float64(result.memoryUsage) + } + } + + newStats.CPUPercent = totalCPU/float64(newStats.CPU) + newStats.MemoryPercent = memUsage + + //log.Printf("[DEBUG] CPU: %.2f, Memory: %.2f", newStats.CPUPercent, newStats.MemoryPercent) + + /* cpuPercent, err := cpu.Percent(250*time.Millisecond, false) if err == nil && len(cpuPercent) > 0 { newStats.CPUPercent = cpuPercent[0] @@ -905,6 +1128,7 @@ func getOrborusStats() shuffle.OrborusStats { newStats.Memory = int(memory.Used) newStats.MaxMemory = int(memory.Total) } + */ // Get disk usage /* @@ -930,8 +1154,57 @@ func getOrborusStats() 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, error) { + 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 + } else { + 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 cleanup() { + log.Printf("[INFO] Cleaning up during shutdown") + ctx := context.Background() + cleanupExistingNodes(ctx) + zombiecheck(ctx, 600) + os.Exit(0) +} + // Initial loop etc func main() { + //sigCh := make(chan os.Signal, 1) + //signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + //defer cleanup() + + // Block until a signal is received + if isRunningInCluster() { + log.Printf("[INFO] Running inside k8s cluster") + } + startupDelay := os.Getenv("SHUFFLE_ORBORUS_STARTUP_DELAY") if len(startupDelay) > 0 { log.Printf("[DEBUG] Setting startup delay to %#v", startupDelay) @@ -946,7 +1219,7 @@ func main() { log.Println("[INFO] Setting up execution environment") - //FIXME + // //FIXME if baseUrl == "" { baseUrl = "https://shuffler.io" //baseUrl = "http://localhost:5001" @@ -976,10 +1249,8 @@ func main() { } } - // Handle Cleanup - // var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP")) - // SHUFFLE_CONTAINER_AUTO_CLEANUP=false - if strings.ToLower(os.Getenv("SHUFFLE_CONTAINER_AUTO_CLEANUP")) == "true" { + // Handle Cleanup - made it cleanup by default + if strings.ToLower(os.Getenv("SHUFFLE_CONTAINER_AUTO_CLEANUP")) != "false" { cleanupEnv = "true" } @@ -1015,7 +1286,8 @@ func main() { ctx := context.Background() // Run by default from now - zombiecheck(ctx, workerTimeout) + //commenting for now as its stoppoing minikube + // zombiecheck(ctx, workerTimeout) log.Printf("[INFO] Running towards %s (BASE_URL) with environment name %s", baseUrl, environment) @@ -1091,24 +1363,41 @@ func main() { req.Header.Add("X-Orborus-Runmode", "Docker Swarm") } + if os.Getenv("SHUFFLE_MAX_CPU") != "" { + // parse + tmpInt, err := strconv.Atoi(os.Getenv("SHUFFLE_MAX_CPU")) + if err == nil { + maxCPUPercent = tmpInt + } + } + log.Printf("[INFO] Waiting for executions at %s with Environment %#v", fullUrl, environment) hasStarted := false for { if req.Method == "POST" { // Should find data to send (memory etc.) - orborusStats := getOrborusStats() + // Create timeout of max 4 seconds just in case + ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) + defer cancel() + // Marshal and set body + orborusStats := getOrborusStats(ctx) jsonData, err := json.Marshal(orborusStats) if err == nil { req.Body = ioutil.NopCloser(bytes.NewBuffer(jsonData)) } else { - log.Printf("[ERROR] Failed marshalling json: %s", err) + log.Printf("[ERROR] Failed marshalling. Maybe max 4 second timeout? %s", err) + } + + if int(orborusStats.CPUPercent) > maxCPUPercent { + log.Printf("[DEBUG] CPU usage is at %f%%. This is more than the max limit the machine should be running at (%d). Waiting before continue.", orborusStats.CPUPercent, maxCPUPercent) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue } } newresp, err := client.Do(req) - //log.Printf("[DEBUG] Postrequest - queue") if err != nil { log.Printf("[WARNING] Failed making request to %s: %s", fullUrl, err) @@ -1145,7 +1434,7 @@ func main() { // FIXME - add check for StatusCode if newresp.StatusCode != 200 { - log.Printf("[ERROR] Backend configuration missing (%d): %s", newresp.StatusCode, string(body)) + log.Printf("[ERROR] Backend connection failed, or is missing (%d): %s", newresp.StatusCode, string(body)) } else { if !hasStarted { log.Printf("[DEBUG] Starting iteration on environment %#v (default = Shuffle). Got statuscode %d from backend on first request", environment, newresp.StatusCode) @@ -1220,13 +1509,15 @@ func main() { } if shuffle.ArrayContains(executionIds, execution.ExecutionId) { - log.Printf("[INFO] Execution already handled: %s", execution.ExecutionId) + log.Printf("[INFO] Execution already handled (rerun of old executions?): %s", execution.ExecutionId) toBeRemoved.Data = append(toBeRemoved.Data, execution) - continue + + if swarmConfig != "run" && swarmConfig != "swarm" { + continue + } } // Now, how do I execute this one? - // FIXME - if error, check the status of the running one. If it's bad, send data back. containerName := fmt.Sprintf("worker-%s", execution.ExecutionId) env := []string{ fmt.Sprintf("AUTHORIZATION=%s", execution.Authorization), @@ -1336,77 +1627,108 @@ func main() { if len(toBeRemoved.Data) == len(executionRequests.Data) { //log.Println("Should remove ALL!") } else { - log.Printf("[INFO] NOT IMPLEMENTED: Should remove %d workflows from backend because they're executed!", len(toBeRemoved.Data)) + //log.Printf("[INFO] NOT IMPLEMENTED: Should remove %d workflows from backend because they're executed!", len(toBeRemoved.Data)) } } time.Sleep(time.Duration(sleepTime) * time.Second) } + } // 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) - containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{ - All: true, - }) + counter := 0 + if isKubernetes == "true" { + log.Printf("[INFO] getting running workers in kubernetes") - // Automatically updates the version - if err != nil { - log.Printf("[ERROR] Error getting containers: %s", err) + thresholdTime := time.Now().Add(time.Duration(-workerTimeout) * time.Second) - newVersionSplit := strings.Split(fmt.Sprintf("%s", err), "version is") - if len(newVersionSplit) > 1 { - //dockerApiVersion = strings.TrimSpace(newVersionSplit[1]) - log.Printf("[DEBUG] WANT to change the API version to default to %s?", strings.TrimSpace(newVersionSplit[1])) + clientset, err := getKubernetesClient() + if err != nil { + log.Printf("[ERROR] Failed getting kubernetes client: %s", err) + return 0 } - return maxConcurrency - } + labelSelector := "app=shuffle-worker" + pods, podErr := clientset.CoreV1().Pods("shuffle").List(ctx, metav1.ListOptions{ + LabelSelector: labelSelector, + }) + if podErr != nil { + log.Printf("[ERROR] Failed getting running workers: %s", podErr) + return 0 + } - currenttime := time.Now().Unix() - counter := 0 - for _, container := range containers { - // Skip random containers. Only handle things related to Shuffle. - if !strings.Contains(container.Image, baseimagename) { - shuffleFound := false - for _, item := range container.Labels { - if item == "shuffle" { - shuffleFound = true + for _, pod := range pods.Items { + if pod.Status.Phase == "Running" && pod.CreationTimestamp.Time.After(thresholdTime) { + counter++ + } + } + } else { + + containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{ + All: true, + }) + + // Automatically updates the version + if err != nil { + log.Printf("[ERROR] Error getting containers: %s", err) + + newVersionSplit := strings.Split(fmt.Sprintf("%s", err), "version is") + if len(newVersionSplit) > 1 { + //dockerApiVersion = strings.TrimSpace(newVersionSplit[1]) + log.Printf("[DEBUG] WANT to change the API version to default to %s?", strings.TrimSpace(newVersionSplit[1])) + } + + return maxConcurrency + } + + currenttime := time.Now().Unix() + + for _, container := range containers { + // Skip random containers. Only handle things related to Shuffle. + if !strings.Contains(container.Image, baseimagename) { + shuffleFound := false + for _, item := range container.Labels { + if item == "shuffle" { + shuffleFound = true + break + } + } + + // Check image name + if !shuffleFound { + continue + } + //} else { + // log.Printf("NAME: %s", container.Image) + } + + for _, name := range container.Names { + // FIXME - add name_version_uid_uid regex check as well + if !strings.HasPrefix(name, "/worker") { + continue + } + + //log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout)) + if container.State == "running" && currenttime-container.Created < int64(workerTimeout) { + counter += 1 break } } - - // Check image name - if !shuffleFound { - continue - } - //} else { - // log.Printf("NAME: %s", container.Image) - } - - for _, name := range container.Names { - // FIXME - add name_version_uid_uid regex check as well - if !strings.HasPrefix(name, "/worker") { - continue - } - - //log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout)) - if container.State == "running" && currenttime-container.Created < int64(workerTimeout) { - counter += 1 - break - } } } - return counter } // FIXME - add this to remove exited workers // Should it check what happened to the execution? idk func zombiecheck(ctx context.Context, workerTimeout int) error { + isK8s := isKubernetes == "true" + executionIds = []string{} - if swarmConfig == "run" || swarmConfig == "swarm" { + if swarmConfig == "run" || swarmConfig == "swarm" || isK8s { //log.Printf("[DEBUG] Skipping Zombie check due to new execution model (swarm)") return nil } @@ -1517,7 +1839,6 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { baseUrlSplit := strings.Split(baseUrl, ":") if len(baseUrlSplit) >= 3 { parsedBaseurl = strings.Join(baseUrlSplit[0:2], ":") - //parsedRequest.BaseUrl = fmt.Sprintf("%s:33333", parsedBaseurl) } } @@ -1527,18 +1848,20 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { return err } - //log.Printf("[DEBUG] Data: %s", string(data)) - streamUrl := fmt.Sprintf("http://shuffle-workers:33333/api/v1/execute") if containerId == "" || containerId == "shuffle-orborus" { streamUrl = fmt.Sprintf("%s:33333/api/v1/execute", parsedBaseurl) } - // var workerServerUrl = os.Getenv("SHUFFLE_WORKER_SERVER_URL") - if len(workerServerUrl) > 0 { + if len(workerServerUrl) > 0 { streamUrl = fmt.Sprintf("%s:33333/api/v1/execute", workerServerUrl) } + if strings.Contains(streamUrl, "localhost") || strings.Contains(streamUrl, "shuffle-backend") { + log.Printf("[INFO] Using default worker server url as previous is invalid: %s", streamUrl) + streamUrl = fmt.Sprintf("http://shuffle-workers:33333/api/v1/execute") + } + client := &http.Client{} req, err := http.NewRequest( "POST", @@ -1601,6 +1924,6 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { _ = body - log.Printf("[DEBUG] Ran worker from request with execution ID: %s. Worker URL: %s. DEBUGGING: docker service logs shuffle-workers 2&>1 | grep %s", workflowExecution.ExecutionId, streamUrl, workflowExecution.ExecutionId) + 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) return nil } diff --git a/functions/onprem/worker/Dockerfile b/functions/onprem/worker/Dockerfile index 3b89d290..b1bbbe43 100755 --- a/functions/onprem/worker/Dockerfile +++ b/functions/onprem/worker/Dockerfile @@ -33,6 +33,10 @@ ENV SHUFFLE_BASE_IMAGE_REGISTRY=docker.io ENV SHUFFLE_BASE_IMAGE_NAME=frikky/shuffle ENV SHUFFLE_BASE_IMAGE_TAG_SUFFIX=0.8.70 +#for k8s +ENV SHUFFLE_OPENSEARCH_URL=https://opensearch:9200 +ENV SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true + RUN apk add --no-cache bash tzdata COPY --from=builder /app/ / diff --git a/functions/onprem/worker/build.sh b/functions/onprem/worker/build.sh index 28d6a47b..ad97c321 100755 --- a/functions/onprem/worker/build.sh +++ b/functions/onprem/worker/build.sh @@ -1,5 +1,5 @@ NAME=shuffle-worker -VERSION=1.2.0 +VERSION=1.3.0 echo "Running docker build with $NAME:$VERSION" #CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin . @@ -11,10 +11,10 @@ docker build . -t frikky/shuffle:$NAME -t frikky/shuffle:$NAME_$VERSION -t docke #docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION #docker tag frikky/shuffle:0.8.51 ghcr.io/frikky/shuffle-worker:0.8.5 #docker tag frikky/shuffle:$NAME ghcr.io/frikky/shuffle-worker:0.8.52 -docker push frikky/shuffle:$NAME -docker push ghcr.io/frikky/$NAME:$VERSION -docker push ghcr.io/frikky/$NAME:nightly - -docker push shuffle/shuffle:$NAME -docker push ghcr.io/shuffle/$NAME:$VERSION -docker push ghcr.io/shuffle/$NAME:nightly +#docker push frikky/shuffle:$NAME +#docker push ghcr.io/frikky/$NAME:$VERSION +#docker push ghcr.io/frikky/$NAME:nightly +# +#docker push shuffle/shuffle:$NAME +#docker push ghcr.io/shuffle/$NAME:$VERSION +#docker push ghcr.io/shuffle/$NAME:nightly diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod index 2e0499d6..64a5e930 100644 --- a/functions/onprem/worker/go.mod +++ b/functions/onprem/worker/go.mod @@ -11,7 +11,10 @@ require ( github.com/gorilla/mux v1.8.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.4.17 + github.com/shuffle/shuffle-shared v0.4.57 + k8s.io/api v0.28.3 + k8s.io/apimachinery v0.28.3 + k8s.io/client-go v0.28.3 ) require ( @@ -25,46 +28,79 @@ require ( github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/frikky/go-elasticsearch/v8 v8.13.1 // indirect github.com/frikky/kin-openapi v0.41.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/swag v0.19.5 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-github/v28 v28.1.1 // indirect github.com/google/go-querystring v1.0.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect - github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opensearch-project/opensearch-go v1.1.0 // indirect + github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + github.com/spf13/pflag v1.0.5 // indirect go.opencensus.io v0.24.0 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect golang.org/x/crypto v0.14.0 // indirect +<<<<<<< HEAD golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.6.0 // indirect +======= + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.8.0 // indirect +>>>>>>> 1.3.0 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.106.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect google.golang.org/grpc v1.51.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.4.0 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/functions/onprem/worker/go.sum b/functions/onprem/worker/go.sum index 38beba80..f26caf60 100644 --- a/functions/onprem/worker/go.sum +++ b/functions/onprem/worker/go.sum @@ -60,17 +60,35 @@ github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ= github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= +github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4= +github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= +github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 h1:hjXJeBcAMS1WGENGqDpzvmgS43oECTx8UXq31UBu0Jw= github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -82,10 +100,13 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frikky/go-elasticsearch/v8 v8.13.1 h1:GB+Wr0Yx8efG7D1jc9fGGiqjjRRngWJTcMSua3QIDaM= github.com/frikky/go-elasticsearch/v8 v8.13.1/go.mod h1:RPq0JXPQVVSFHTlPwj/go8BZ1hegRf+StaSpT2iGIoQ= @@ -96,10 +117,19 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -107,6 +137,8 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -131,8 +163,12 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -144,17 +180,22 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -165,6 +206,7 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -181,26 +223,50 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 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.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M= +github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= +github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ= +github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -214,8 +280,14 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shuffle/shuffle-shared v0.4.17 h1:56ll366bdmIJu/7GFqNC2XTjjl0SGBf430PSq+EB6Ro= github.com/shuffle/shuffle-shared v0.4.17/go.mod h1:jQrYySmvp/0De5ftrAaY6xwwr7TMfqBmBxQ2AX9yrjQ= +github.com/shuffle/shuffle-shared v0.4.50 h1:fJLfhWIJ5mYap4JwHnD/B5aaLyIULwylFSl3FoWlajM= +github.com/shuffle/shuffle-shared v0.4.50/go.mod h1:X613gbo0dT3fnYvXDRwjQZyLC+T49T2nSQOrCV5QMlI= +github.com/shuffle/shuffle-shared v0.4.57 h1:o+mMPRY4ourkE3R0qdi80jg6RlCtvAJ/VVrPk4y75Hk= +github.com/shuffle/shuffle-shared v0.4.57/go.mod h1:X613gbo0dT3fnYvXDRwjQZyLC+T49T2nSQOrCV5QMlI= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -227,10 +299,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -246,6 +320,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -281,8 +359,11 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -314,6 +395,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -327,6 +416,8 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -336,7 +427,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -368,21 +459,43 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -434,8 +547,11 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -544,10 +660,15 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -565,6 +686,24 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= +k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= +k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= +k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= +k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= +k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index 7a1570a7..427a4bee 100755 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -36,6 +36,16 @@ import ( // No necessary outside shared "cloud.google.com/go/datastore" "cloud.google.com/go/storage" + + //k8s deps + 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" + // "k8s.io/client-go/util/retry" ) // This is getting out of hand :) @@ -78,6 +88,7 @@ var downloadedImages []string // Images to be autodeployed in the latest version of Shuffle. var autoDeploy = map[string]string{ "http:1.3.0": "frikky/shuffle:http_1.3.0", + "http:1.4.0": "frikky/shuffle:http_1.4.0", "shuffle-tools:1.2.0": "frikky/shuffle:shuffle-tools_1.2.0", "shuffle-subflow:1.0.0": "frikky/shuffle:shuffle-subflow_1.0.0", "shuffle-subflow:1.1.0": "frikky/shuffle:shuffle-subflow_1.1.0", @@ -171,111 +182,267 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason os.Exit(3) } +// } + +func isRunningInCluster() bool { + _, existsHost := os.LookupEnv("KUBERNETES_SERVICE_HOST") + _, existsPort := os.LookupEnv("KUBERNETES_SERVICE_PORT") + return existsHost && existsPort +} + +func buildEnvVars(envMap map[string]string) []corev1.EnvVar { + var envVars []corev1.EnvVar + for key, value := range envMap { + envVars = append(envVars, corev1.EnvVar{Name: key, Value: value}) + } + return envVars +} + +func getKubernetesClient() (*kubernetes.Clientset, error) { + 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 + } else { + 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 + } +} + // Deploys the internal worker whenever something happens func deployApp(cli *dockerclient.Client, image string, identifier string, env []string, workflowExecution shuffle.WorkflowExecution, action shuffle.Action) error { - // form basic hostConfig - ctx := context.Background() + // log.Printf("################################### new call to deployApp ###################################") + // log.Printf("image: %s", image) + // log.Printf("identifier: %s", identifier) + // log.Printf("execution: %+v", workflowExecution) - if action.AppName == "shuffle-subflow" { - // Automatic replacement of URL - for paramIndex, param := range action.Parameters { - if param.Name != "backend_url" { - continue - } + if os.Getenv("IS_KUBERNETES") == "true" { - if strings.Contains(param.Value, "shuffle-backend") { - // Automatic replacement as this is default - action.Parameters[paramIndex].Value = os.Getenv("BASE_URL") - log.Printf("[DEBUG][%s] Replaced backend_url with %s", workflowExecution.ExecutionId, os.Getenv("BASE_URL")) + namespace := "shuffle" + localRegistry := os.Getenv("REGISTRY_URL") + + envMap := make(map[string]string) + for _, envStr := range env { + parts := strings.SplitN(envStr, "=", 2) + if len(parts) == 2 { + envMap[parts[0]] = parts[1] } } - } - // Max 10% CPU every second - //CPUShares: 128, - //CPUQuota: 10000, - //CPUPeriod: 100000, - hostConfig := &container.HostConfig{ - LogConfig: container.LogConfig{ - Type: "json-file", - Config: map[string]string{ - "max-size": "10m", + clientset, err := getKubernetesClient() + if err != nil { + fmt.Println("[ERROR]Error getting kubernetes client:", err) + // os.Exit(1) + } + + log.Printf("[DEBUG] Got kubernetes client") + str := strings.ToLower(identifier) + strSplit := strings.Split(str, "_") + value := strSplit[0] + value = strings.ReplaceAll(value, "_", "-") + + // checking if app is generated or not + appDetails := strings.Split(image, ":")[1] + appDetailsSplit := strings.Split(appDetails, "_") + appName := strings.Join(appDetailsSplit[:len(appDetailsSplit)-1], "_") + appVersion := appDetailsSplit[len(appDetailsSplit)-1] + + // log.Printf("APP VERSION IS: %s", appVersion) + + for _, app := range workflowExecution.Workflow.Actions { + // log.Printf("[DEBUG] App: %s, Version: %s", appName, appVersion) + // log.Printf("[DEBUG] Checking app %s with version %s", app.AppName, app.AppVersion) + if app.AppName == appName && app.AppVersion == appVersion { + if app.Generated == true { + log.Printf("[DEBUG] Generated app, setting local registry") + image = fmt.Sprintf("%s/%s", localRegistry, image) + break + } else { + log.Printf("[DEBUG] Not generated app, setting shuffle registry") + } + } + } + + //fix naming convention + podUuid := uuid.NewV4().String() + podName := fmt.Sprintf("%s-%s", value, podUuid) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Labels: map[string]string{ + "app": "shuffle-app", + "executionId": workflowExecution.ExecutionId, + }, + }, + Spec: corev1.PodSpec{ + // NodeName: "worker1" + RestartPolicy: "Never", + Containers: []corev1.Container{ + { + Name: value, + Image: image, + Env: buildEnvVars(envMap), + // ImagePullPolicy: corev1.PullAlways, + }, + }, }, - }, - Resources: container.Resources{}, - } - - hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:worker-%s", workflowExecution.ExecutionId)) - - // Removing because log extraction should happen first - if cleanupEnv == "true" { - hostConfig.AutoRemove = true - } - - // FIXME: Add proper foldermounts here - //log.Printf("\n\nPRE FOLDERMOUNT\n\n") - //volumeBinds := []string{"/tmp/shuffle-mount:/rules"} - //volumeBinds := []string{"/tmp/shuffle-mount:/rules"} - volumeBinds := []string{} - if len(volumeBinds) > 0 { - log.Printf("[DEBUG] Setting up binds for container!") - hostConfig.Binds = volumeBinds - hostConfig.Mounts = []mount.Mount{} - for _, bind := range volumeBinds { - if !strings.Contains(bind, ":") || strings.Contains(bind, "..") || strings.HasPrefix(bind, "~") { - log.Printf("[WARNING] Bind %s is invalid.", bind) - continue - } - - log.Printf("[DEBUG] Appending bind %s", bind) - bindSplit := strings.Split(bind, ":") - sourceFolder := bindSplit[0] - destinationFolder := bindSplit[0] - hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{ - Type: mount.TypeBind, - Source: sourceFolder, - Target: destinationFolder, - }) } + + createdPod, err := clientset.CoreV1().Pods(namespace).Create(context.Background(), pod, metav1.CreateOptions{}) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating pod: %v\n", err) + // os.Exit(1) + } + fmt.Printf("[DEBUG] Created pod %q in namespace %q\n", createdPod.Name, createdPod.Namespace) } else { - //log.Printf("[WARNING] Not mounting folders") - } + // form basic hostConfig + ctx := context.Background() - config := &container.Config{ - Image: image, - Env: env, - } + if action.AppName == "shuffle-subflow" { + // Automatic replacement of URL + for paramIndex, param := range action.Parameters { + if param.Name != "backend_url" { + continue + } - // Checking as late as possible, just in case. - newExecId := fmt.Sprintf("%s_%s", workflowExecution.ExecutionId, action.ID) - _, err := shuffle.GetCache(ctx, newExecId) - if err == nil { - log.Printf("\n\n[DEBUG] Result for %s already found - returning\n\n", newExecId) + if strings.Contains(param.Value, "shuffle-backend") { + // Automatic replacement as this is default + action.Parameters[paramIndex].Value = os.Getenv("BASE_URL") + log.Printf("[DEBUG][%s] Replaced backend_url with %s", workflowExecution.ExecutionId, os.Getenv("BASE_URL")) + } + } + } + + // Max 10% CPU every second + //CPUShares: 128, + //CPUQuota: 10000, + //CPUPeriod: 100000, + hostConfig := &container.HostConfig{ + LogConfig: container.LogConfig{ + Type: "json-file", + Config: map[string]string{ + "max-size": "10m", + }, + }, + Resources: container.Resources{}, + } + + hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:worker-%s", workflowExecution.ExecutionId)) + + // Removing because log extraction should happen first + if cleanupEnv == "true" { + hostConfig.AutoRemove = true + } + + // FIXME: Add proper foldermounts here + //log.Printf("\n\nPRE FOLDERMOUNT\n\n") + //volumeBinds := []string{"/tmp/shuffle-mount:/rules"} + //volumeBinds := []string{"/tmp/shuffle-mount:/rules"} + volumeBinds := []string{} + if len(volumeBinds) > 0 { + log.Printf("[DEBUG] Setting up binds for container!") + hostConfig.Binds = volumeBinds + hostConfig.Mounts = []mount.Mount{} + for _, bind := range volumeBinds { + if !strings.Contains(bind, ":") || strings.Contains(bind, "..") || strings.HasPrefix(bind, "~") { + log.Printf("[WARNING] Bind %s is invalid.", bind) + continue + } + + log.Printf("[DEBUG] Appending bind %s", bind) + bindSplit := strings.Split(bind, ":") + sourceFolder := bindSplit[0] + destinationFolder := bindSplit[0] + hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{ + Type: mount.TypeBind, + Source: sourceFolder, + Target: destinationFolder, + }) + } + } else { + //log.Printf("[WARNING] Not mounting folders") + } + + config := &container.Config{ + Image: image, + Env: env, + } + + // Checking as late as possible, just in case. + newExecId := fmt.Sprintf("%s_%s", workflowExecution.ExecutionId, action.ID) + _, err := shuffle.GetCache(ctx, newExecId) + if err == nil { + log.Printf("\n\n[DEBUG] Result for %s already found - returning\n\n", newExecId) + return nil + } + + cacheData := []byte("1") + err = shuffle.SetCache(ctx, newExecId, cacheData, 30) + if err != nil { + log.Printf("[WARNING] Failed setting cache for action %s: %s", newExecId, err) + } else { + log.Printf("[DEBUG] Adding %s to cache. Name: %s", newExecId, action.Name) + } + + if action.ExecutionDelay > 0 { + log.Printf("[DEBUG] Running app %s in docker with delay of %d", action.Name, action.ExecutionDelay) + waitTime := time.Duration(action.ExecutionDelay) * time.Second + + time.AfterFunc(waitTime, func() { + DeployContainer(ctx, cli, config, hostConfig, identifier, workflowExecution, newExecId) + }) + } else { + log.Printf("[DEBUG] Running app %s in docker NORMALLY as there is no delay set with identifier %s", action.Name, identifier) + returnvalue := DeployContainer(ctx, cli, config, hostConfig, identifier, workflowExecution, newExecId) + log.Printf("[DEBUG] Normal deploy ret: %s", returnvalue) + return returnvalue + } return nil } + return nil +} - cacheData := []byte("1") - err = shuffle.SetCache(ctx, newExecId, cacheData, 30) +func cleanupExecution(clientset *kubernetes.Clientset, workflowExecution shuffle.WorkflowExecution, namespace string) error { + + workerName := fmt.Sprintf("worker-%s", workflowExecution.ExecutionId) + labelSelector := fmt.Sprintf("app=shuffle-app,executionId=%s", workflowExecution.ExecutionId) + + podList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: labelSelector, + }) if err != nil { - log.Printf("[WARNING] Failed setting cache for action %s: %s", newExecId, err) - } else { - log.Printf("[DEBUG] Adding %s to cache. Name: %s", newExecId, action.Name) + return fmt.Errorf("[ERROR]failed to list apps with label selector %s: %v", labelSelector, err) } - if action.ExecutionDelay > 0 { - log.Printf("[DEBUG] Running app %s in docker with delay of %d", action.Name, action.ExecutionDelay) - waitTime := time.Duration(action.ExecutionDelay) * time.Second - - time.AfterFunc(waitTime, func() { - DeployContainer(ctx, cli, config, hostConfig, identifier, workflowExecution, newExecId) - }) - } else { - log.Printf("[DEBUG] Running app %s in docker NORMALLY as there is no delay set with identifier %s", action.Name, identifier) - returnvalue := DeployContainer(ctx, cli, config, hostConfig, identifier, workflowExecution, newExecId) - log.Printf("[DEBUG] Normal deploy ret: %s", returnvalue) - return returnvalue + for _, pod := range podList.Items { + err := clientset.CoreV1().Pods(namespace).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{}) + if err != nil { + return fmt.Errorf("failed to delete app %s: %v", pod.Name, err) + } + fmt.Printf("App %s in namespace %s deleted.\n", pod.Name, namespace) } + podErr := clientset.CoreV1().Pods(namespace).Delete(context.TODO(), workerName, metav1.DeleteOptions{}) + if podErr != nil { + return fmt.Errorf("[ERROR] failed to delete the worker %s in namespace %s: %v", workerName, namespace, podErr) + } + fmt.Printf("[DEBUG] %s in namespace %s deleted.\n", workerName, namespace) return nil } @@ -869,7 +1036,17 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { log.Printf("[INFO][%s] BREAKING BECAUSE RESULTS IS SAME LENGTH AS ACTIONS. SHOULD CHECK ALL RESULTS FOR WHETHER THEY'RE DONE", workflowExecution.ExecutionId) validateFinished(workflowExecution) log.Printf("[DEBUG][%s] Shutting down (17)", workflowExecution.ExecutionId) - shutdown(workflowExecution, "", "", true) + if os.Getenv("IS_KUBERNETES") == "true" { + // log.Printf("workflow execution: %#v", workflowExecution) + clientset, err := getKubernetesClient() + if err != nil { + fmt.Println("[ERROR]Error getting kubernetes client:", err) + os.Exit(1) + } + cleanupExecution(clientset, workflowExecution, "shuffle") + } else { + shutdown(workflowExecution, "", "", true) + } return } } @@ -1086,14 +1263,35 @@ func handleDefaultExecution(client *http.Client, req *http.Request, workflowExec if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" { log.Printf("[INFO][%s] Workflow execution is finished. Exiting worker.", workflowExecution.ExecutionId) log.Printf("[DEBUG] Shutting down (20)") - shutdown(workflowExecution, "", "", true) + //handle workerssssssssss + if os.Getenv("IS_KUBERNETES") == "true" { + // log.Printf("workflow execution: %#v", workflowExecution) + clientset, err := getKubernetesClient() + if err != nil { + fmt.Println("[ERROR]Error getting kubernetes client:", err) + os.Exit(1) + } + cleanupExecution(clientset, workflowExecution, "shuffle") + } else { + shutdown(workflowExecution, "", "", true) + } } log.Printf("[INFO][%s] Status: %s, Results: %d, actions: %d", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extra) if workflowExecution.Status != "EXECUTING" { log.Printf("[WARNING][%s] Exiting as worker execution has status %s!", workflowExecution.ExecutionId, workflowExecution.Status) log.Printf("[DEBUG] Shutting down (21)") - shutdown(workflowExecution, "", "", true) + if os.Getenv("IS_KUBERNETES") == "true" { + // log.Printf("workflow execution: %#v", workflowExecution) + clientset, err := getKubernetesClient() + if err != nil { + fmt.Println("[ERROR]Error getting kubernetes client:", err) + os.Exit(1) + } + cleanupExecution(clientset, workflowExecution, "shuffle") + } else { + shutdown(workflowExecution, "", "", true) + } } setWorkflowExecution(ctx, workflowExecution, false)