Merge branch '1.3.0'

This commit is contained in:
Aditya
2023-11-01 01:39:30 +05:30
132 changed files with 14828 additions and 19881 deletions
+10 -9
View File
@@ -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
+1 -1
View File
@@ -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
+13 -11
View File
@@ -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 }}
+1 -1
View File
@@ -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"]
+58 -29
View File
@@ -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
+1 -1
View File
@@ -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
+20
View File
@@ -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
```
+217 -58
View File
@@ -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
}
+22 -5
View File
@@ -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
)
+887
View File
@@ -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=
+135 -1279
View File
File diff suppressed because it is too large Load Diff
+8 -160
View File
@@ -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
}
+16 -6
View File
@@ -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
+5 -2
View File
@@ -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
+2 -2
View File
@@ -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;
+45 -43
View File
@@ -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"
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
<defs>
<style>
.cls-1 {
fill: #fff;
}
.cls-2 {
fill-rule: evenodd;
}
</style>
</defs>
<path class="cls-1" d="m23.82.13l32.97.16,23.2,23.42-.16,32.97-23.42,23.2-32.97-.16L.24,56.31l.16-32.97L23.82.13Z"/>
<g>
<path class="cls-2" d="m52.34,43.3c0-.05-.02-.1-.03-.15,0-.05.03-.09.03-.14,0-1.03-.36-1.86-.96-2.43-.59-.56-1.36-.84-2.12-.84-.59,0-1.19.17-1.7.51-.1-.8-.42-1.46-.91-1.94-.59-.56-1.36-.84-2.12-.84-.66,0-1.32.22-1.87.64-.16-.46-.41-.86-.74-1.18-.59-.56-1.36-.84-2.12-.84-.57,0-1.15.16-1.66.48v-3.12c0-1.67-1.42-2.95-3.08-2.95s-3.08,1.28-3.08,2.95v11.68s-.03.04-.05.08c-.11.17-.27.4-.46.68-.36.56-.82,1.31-1.2,2.05-.35.71-.71,1.49-.77,2.33-.06.89.23,1.76.99,2.63.65.75,1.81,2.14,2.81,3.35.5.6.96,1.16,1.3,1.57l.55.66s.04.05.06.07c.74.69,1.76,1.07,2.81,1.07h7.29c4.12,0,7.02-2.98,7.02-6.13h-.71.71v-10.2Zm-1.42,0v10.2c0,2.27-2.15,4.71-5.6,4.71h-7.29c-.7,0-1.35-.25-1.81-.66l-.52-.63c-.34-.41-.8-.97-1.3-1.57-1-1.21-2.18-2.61-2.83-3.37-.55-.63-.67-1.14-.64-1.61.03-.52.26-1.07.62-1.79.13-.27.29-.53.44-.8v2.11c0,.39.32.71.71.71s.71-.32.71-.71v-16.44c0-.81.7-1.53,1.66-1.53s1.66.72,1.66,1.53v5.91s0,0,0,0v5.38c0,.39.32.71.71.71,0,0,0,0,0,0h0s0,0,0,0c.1,0,.19-.02.27-.06.06-.02.11-.07.15-.1.02-.02.05-.03.07-.05.05-.05.08-.1.11-.16.01-.02.03-.04.04-.06.04-.09.06-.18.06-.28v-5.38c0-.67.23-1.12.52-1.4.3-.29.71-.44,1.14-.44s.84.15,1.13.44c.29.28.52.73.52,1.41v1.37s0,0,0,0v1.7s0,0,0,0v2.3c0,.39.32.71.71.71s.71-.32.71-.71v-4.01c0-.67.23-1.12.52-1.4.3-.29.71-.44,1.14-.44s.84.15,1.14.44c.29.28.52.73.52,1.41v2.26s0,0,0,0v.85s0,0,0,0v1.07c0,.39.32.71.71.71s.71-.32.71-.71v-1.93c0-.67.23-1.12.52-1.4.3-.29.71-.44,1.14-.44s.84.15,1.14.44c.29.28.52.73.52,1.41,0,.05.02.1.03.15,0,.05-.03.09-.03.14Z"/>
<path class="cls-2" d="m51.35,22.81c-2.53,0-4.6,1.88-4.95,4.31h-4.84c-.82-3.03-3.18-5.57-6.42-6.44-4.85-1.3-9.84,1.59-11.14,6.43-1.29,4.83.86,9.61,5.72,10.91.41.11.83-.14.94-.54.11-.41-.14-.83-.54-.94-4.01-1.07-5.72-4.99-4.63-9.03,1.08-4.03,5.23-6.43,9.26-5.35,4.03,1.08,6.43,5.23,5.35,9.26-.11.41.14.83.54.94.41.11.83-.14.94-.54.29-1.1.36-2.2.25-3.27h4.58c.35,2.43,2.42,4.31,4.95,4.31,2.78,0,5.03-2.25,5.03-5.03s-2.25-5.03-5.03-5.03Zm-26.02,4.66c-1.1,4.1.63,8.11,4.74,9.21.33.09.53.44.44.77-.07.25-.28.42-.52.46.24-.03.45-.21.52-.46.09-.33-.11-.68-.44-.77-4.1-1.1-5.83-5.1-4.74-9.21.76-2.82,2.99-4.86,5.65-5.5-2.66.64-4.89,2.68-5.65,5.5Zm9.76-6.65c-1.39-.37-2.8-.39-4.12-.11,1.32-.27,2.73-.26,4.12.11,0,0,0,0,.02,0,0,0-.01,0-.02,0Zm6.31,6.3h0s0,0,0-.01c0,0,0,0,0,.01Zm.02,4.67c-.07.25-.28.42-.52.46.24-.03.45-.21.52-.46.08-.28.13-.56.18-.85-.05.28-.11.56-.18.85Zm.25-3.23h0s0,.01,0,.02c0,0,0-.01,0-.02Zm9.68,2.87c-1.98,0-3.59-1.61-3.59-3.59s1.61-3.59,3.59-3.59,3.59,1.61,3.59,3.59-1.61,3.59-3.59,3.59Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80px" height="80px" viewBox="0 0 80 80" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 23.820312 0.128906 L 56.789062 0.289062 L 79.988281 23.710938 L 79.828125 56.679688 L 56.410156 79.878906 L 23.441406 79.71875 L 0.238281 56.308594 L 0.398438 23.339844 Z M 23.820312 0.128906 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 52.339844 43.300781 C 52.339844 43.25 52.320312 43.199219 52.308594 43.148438 C 52.308594 43.101562 52.339844 43.058594 52.339844 43.011719 C 52.339844 41.980469 51.980469 41.148438 51.378906 40.578125 C 50.789062 40.019531 50.019531 39.738281 49.261719 39.738281 C 48.671875 39.738281 48.070312 39.910156 47.558594 40.25 C 47.460938 39.449219 47.140625 38.789062 46.648438 38.308594 C 46.058594 37.75 45.289062 37.46875 44.53125 37.46875 C 43.871094 37.46875 43.210938 37.691406 42.660156 38.109375 C 42.5 37.648438 42.25 37.25 41.921875 36.929688 C 41.328125 36.371094 40.558594 36.089844 39.800781 36.089844 C 39.230469 36.089844 38.648438 36.25 38.140625 36.570312 L 38.140625 33.449219 C 38.140625 31.78125 36.71875 30.5 35.058594 30.5 C 33.398438 30.5 31.980469 31.78125 31.980469 33.449219 L 31.980469 45.128906 C 31.980469 45.128906 31.949219 45.171875 31.929688 45.210938 C 31.820312 45.378906 31.660156 45.609375 31.46875 45.890625 C 31.109375 46.449219 30.648438 47.199219 30.269531 47.941406 C 29.921875 48.648438 29.558594 49.429688 29.5 50.269531 C 29.441406 51.160156 29.730469 52.03125 30.488281 52.898438 C 31.140625 53.648438 32.300781 55.039062 33.300781 56.25 C 33.800781 56.851562 34.261719 57.410156 34.601562 57.820312 L 35.148438 58.480469 C 35.148438 58.480469 35.191406 58.53125 35.210938 58.550781 C 35.949219 59.238281 36.96875 59.621094 38.019531 59.621094 L 45.308594 59.621094 C 49.429688 59.621094 52.328125 56.640625 52.328125 53.488281 L 51.621094 53.488281 L 52.328125 53.488281 L 52.328125 43.289062 Z M 50.921875 43.300781 L 50.921875 53.5 C 50.921875 55.769531 48.769531 58.210938 45.320312 58.210938 L 38.03125 58.210938 C 37.328125 58.210938 36.679688 57.960938 36.21875 57.550781 L 35.699219 56.921875 C 35.359375 56.511719 34.898438 55.949219 34.398438 55.351562 C 33.398438 54.140625 32.21875 52.738281 31.570312 51.980469 C 31.019531 51.351562 30.898438 50.839844 30.929688 50.371094 C 30.960938 49.851562 31.191406 49.300781 31.550781 48.578125 C 31.679688 48.308594 31.839844 48.050781 31.988281 47.78125 L 31.988281 49.890625 C 31.988281 50.28125 32.308594 50.601562 32.699219 50.601562 C 33.089844 50.601562 33.410156 50.28125 33.410156 49.890625 L 33.410156 33.449219 C 33.410156 32.640625 34.109375 31.921875 35.070312 31.921875 C 36.03125 31.921875 36.730469 32.640625 36.730469 33.449219 L 36.730469 44.738281 C 36.730469 45.128906 37.050781 45.449219 37.441406 45.449219 C 37.539062 45.449219 37.628906 45.429688 37.710938 45.390625 C 37.769531 45.371094 37.820312 45.320312 37.859375 45.289062 C 37.878906 45.269531 37.910156 45.261719 37.929688 45.238281 C 37.980469 45.191406 38.011719 45.140625 38.039062 45.078125 C 38.050781 45.058594 38.070312 45.039062 38.078125 45.019531 C 38.121094 44.929688 38.140625 44.839844 38.140625 44.738281 L 38.140625 39.359375 C 38.140625 38.691406 38.371094 38.238281 38.660156 37.960938 C 38.960938 37.671875 39.371094 37.519531 39.800781 37.519531 C 40.230469 37.519531 40.640625 37.671875 40.929688 37.960938 C 41.21875 38.238281 41.449219 38.691406 41.449219 39.371094 L 41.449219 44.738281 C 41.449219 45.128906 41.769531 45.449219 42.160156 45.449219 C 42.550781 45.449219 42.871094 45.128906 42.871094 44.738281 L 42.871094 40.730469 C 42.871094 40.058594 43.101562 39.609375 43.390625 39.328125 C 43.691406 39.039062 44.101562 38.890625 44.53125 38.890625 C 44.960938 38.890625 45.371094 39.039062 45.671875 39.328125 C 45.960938 39.609375 46.191406 40.058594 46.191406 40.738281 L 46.191406 44.921875 C 46.191406 45.308594 46.511719 45.628906 46.898438 45.628906 C 47.289062 45.628906 47.609375 45.308594 47.609375 44.921875 L 47.609375 42.988281 C 47.609375 42.320312 47.839844 41.871094 48.128906 41.589844 C 48.429688 41.300781 48.839844 41.148438 49.269531 41.148438 C 49.699219 41.148438 50.109375 41.300781 50.410156 41.589844 C 50.699219 41.871094 50.929688 42.320312 50.929688 43 C 50.929688 43.050781 50.949219 43.101562 50.960938 43.148438 C 50.960938 43.199219 50.929688 43.238281 50.929688 43.289062 Z M 50.921875 43.300781 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 51.351562 22.808594 C 48.820312 22.808594 46.75 24.691406 46.398438 27.121094 L 41.558594 27.121094 C 40.738281 24.089844 38.378906 21.550781 35.140625 20.679688 C 30.289062 19.378906 25.300781 22.269531 24 27.109375 C 22.710938 31.941406 24.859375 36.71875 29.71875 38.019531 C 30.128906 38.128906 30.550781 37.878906 30.660156 37.480469 C 30.769531 37.070312 30.519531 36.648438 30.121094 36.539062 C 26.109375 35.46875 24.398438 31.550781 25.488281 27.511719 C 26.570312 23.480469 30.71875 21.078125 34.75 22.160156 C 38.78125 23.238281 41.179688 27.390625 40.101562 31.421875 C 39.988281 31.828125 40.238281 32.25 40.640625 32.359375 C 41.050781 32.46875 41.46875 32.21875 41.578125 31.820312 C 41.871094 30.71875 41.941406 29.621094 41.828125 28.550781 L 46.410156 28.550781 C 46.761719 30.980469 48.828125 32.859375 51.359375 32.859375 C 54.140625 32.859375 56.390625 30.609375 56.390625 27.828125 C 56.390625 25.050781 54.140625 22.800781 51.359375 22.800781 Z M 25.328125 27.46875 C 24.230469 31.570312 25.960938 35.578125 30.070312 36.679688 C 30.398438 36.769531 30.601562 37.121094 30.511719 37.449219 C 30.441406 37.699219 30.230469 37.871094 29.988281 37.910156 C 30.230469 37.878906 30.441406 37.699219 30.511719 37.449219 C 30.601562 37.121094 30.398438 36.769531 30.070312 36.679688 C 25.96875 35.578125 24.238281 31.578125 25.328125 27.46875 C 26.089844 24.648438 28.320312 22.609375 30.980469 21.96875 C 28.320312 22.609375 26.089844 24.648438 25.328125 27.46875 Z M 35.089844 20.820312 C 33.699219 20.449219 32.289062 20.429688 30.96875 20.710938 C 32.289062 20.441406 33.699219 20.449219 35.089844 20.820312 C 35.089844 20.820312 35.089844 20.820312 35.109375 20.820312 C 35.109375 20.820312 35.101562 20.820312 35.089844 20.820312 Z M 41.398438 27.121094 C 41.398438 27.121094 41.398438 27.121094 41.398438 27.109375 C 41.398438 27.109375 41.398438 27.109375 41.398438 27.121094 Z M 41.421875 31.789062 C 41.351562 32.039062 41.140625 32.210938 40.898438 32.25 C 41.140625 32.21875 41.351562 32.039062 41.421875 31.789062 C 41.5 31.511719 41.550781 31.230469 41.601562 30.941406 C 41.550781 31.21875 41.488281 31.5 41.421875 31.789062 Z M 41.671875 28.558594 C 41.671875 28.558594 41.671875 28.570312 41.671875 28.578125 C 41.671875 28.578125 41.671875 28.570312 41.671875 28.558594 Z M 51.351562 31.429688 C 49.371094 31.429688 47.761719 29.820312 47.761719 27.839844 C 47.761719 25.859375 49.371094 24.25 51.351562 24.25 C 53.328125 24.25 54.941406 25.859375 54.941406 27.839844 C 54.941406 29.820312 53.328125 31.429688 51.351562 31.429688 Z M 51.351562 31.429688 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

+6 -6
View File
@@ -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
+46 -195
View File
@@ -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" ? (
<div>
<Routes>
<Route
exact
path="/home"
render={(props) => <LandingPageNew isLoaded={isLoaded} {...props} />}
/>
</Routes>
</div>
) : (
<div
style={{
backgroundColor: "#1F2023",
backgroundColor: theme.palette.backgroundColor,
color: "rgba(255, 255, 255, 0.65)",
minHeight: "100vh",
}}
@@ -453,6 +326,7 @@ const App = (message, props) => {
path="/usecases"
element={
<Dashboard
userdata={userdata}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
@@ -472,7 +346,7 @@ const App = (message, props) => {
/>
}
/>
<Route exact path="/apps/authentication" element={<UpdateAuthentication serverside={serverside} userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
<Route exact path="/apps/authentication" element={<UpdateAuthentication serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
<Route
exact
path="/apps"
@@ -503,7 +377,7 @@ const App = (message, props) => {
path="/workflows"
element={
<Workflows
checkLogin={checkLogin}
checkLogin={checkLogin}
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
@@ -545,8 +419,8 @@ const App = (message, props) => {
/>
}
/>
<Route exact path="/workflows/:key/run" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } />
<Route exact path="/workflows/:key/execute" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } />
<Route exact path="/workflows/:key/run" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } />
<Route exact path="/workflows/:key/execute" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } />
<Route
exact
path="/docs/:key"
@@ -585,28 +459,6 @@ const App = (message, props) => {
/>
}
/>
<Route
exact
path="/introduction"
element={
<Introduction
isLoaded={isLoaded}
globalUrl={globalUrl}
{...props}
/>
}
/>
<Route
exact
path="/introduction/:key"
element={
<Introduction
isLoaded={isLoaded}
globalUrl={globalUrl}
{...props}
/>
}
/>
<Route
exact
path="/set_authentication"
@@ -656,17 +508,6 @@ const App = (message, props) => {
{...props}
/>
}
/>
<Route
exact
path="/testdashboard"
element={
<DashboardPage
isLoaded={isLoaded}
globalUrl={globalUrl}
{...props}
/>
}
/>
<Route
exact
@@ -680,22 +521,23 @@ const App = (message, props) => {
/>
}
/>
<Route
exact
path="/welcome"
element={
<Welcome
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
cookies={cookies}
userdata={userdata}
{...props}
/>
}
<Route
exact
path="/welcome"
element={
<Welcome
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
cookies={cookies}
userdata={userdata}
checkLogin={checkLogin}
{...props}
/>
}
/>
<Route
exact
path="/"
@@ -714,21 +556,30 @@ const App = (message, props) => {
/>
</Routes>
</div>
);
// <div style={{backgroundColor: "rgba(21, 32, 43, 1)", color: "#fffff", minHeight: "100vh"}}>
// backgroundColor: "#213243",
// This is a mess hahahah
return (
<MuiThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
<CssBaseline />
<CookiesProvider>
<BrowserRouter>
<Provider template={AlertTemplate} {...options}>
{includedData}
</Provider>
</BrowserRouter>
<ToastContainer
position="bottom-center"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="dark"
/>
</CookiesProvider>
</MuiThemeProvider>
</ThemeProvider>
);
};
+10 -5
View File
@@ -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)",
+156 -29
View File
@@ -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,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" /></svg>`),
"CASES": encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M15.6408 8.39233H18.0922V10.0287H15.6408V8.39233ZM0.115234 8.39233H2.56663V10.0287H0.115234V8.39233ZM9.92083 0.21051V2.66506H8.28656V0.21051H9.92083ZM3.31839 2.25596L5.05889 4.00687L3.89856 5.16051L2.15807 3.42596L3.31839 2.25596ZM13.1485 3.99869L14.8808 2.25596L16.0493 3.42596L14.3088 5.16051L13.1485 3.99869ZM9.10369 4.30142C10.404 4.30142 11.651 4.81863 12.5705 5.73926C13.4899 6.65989 14.0065 7.90854 14.0065 9.21051C14.0065 11.0269 13.0178 12.6141 11.5551 13.4651V14.9378C11.5551 15.1548 11.469 15.3629 11.3158 15.5163C11.1625 15.6698 10.9547 15.756 10.738 15.756H7.46943C7.25271 15.756 7.04487 15.6698 6.89163 15.5163C6.73839 15.3629 6.6523 15.1548 6.6523 14.9378V13.4651C5.18963 12.6141 4.2009 11.0269 4.2009 9.21051C4.2009 7.90854 4.71744 6.65989 5.63689 5.73926C6.55635 4.81863 7.80339 4.30142 9.10369 4.30142ZM10.738 16.5741V17.3923C10.738 17.6093 10.6519 17.8174 10.4986 17.9709C10.3454 18.1243 10.1375 18.2105 9.92083 18.2105H8.28656C8.06984 18.2105 7.862 18.1243 7.70876 17.9709C7.55552 17.8174 7.46943 17.6093 7.46943 17.3923V16.5741H10.738ZM8.28656 14.1196H9.92083V12.3769C11.3345 12.0169 12.3722 10.7323 12.3722 9.21051C12.3722 8.34253 12.0279 7.5101 11.4149 6.89634C10.8019 6.28259 9.97056 5.93778 9.10369 5.93778C8.23683 5.93778 7.40546 6.28259 6.79249 6.89634C6.17953 7.5101 5.83516 8.34253 5.83516 9.21051C5.83516 10.7323 6.87292 12.0169 8.28656 12.3769V14.1196Z" /></svg>`),
@@ -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 (
<div style={{margin: "auto", backgroundColor: bgColor, position: "relative", }}>
<div style={{position: "absolute"}}>
@@ -1995,11 +2122,11 @@ const AppFramework = (props) => {
{
Object.getOwnPropertyNames(discoveryData).length > 0 ?
<Paper style={{width: 275, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
<Paper style={{width: 300, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 25, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
{paperTitle.length > 0 ?
<span>
<Typography variant="h6" style={{textAlign: "center"}}>
{paperTitle}
{paperTitle.replace("_", " ", -1)}
</Typography>
<Divider style={{marginTop: 5, marginBottom: 5 }} />
</span>
@@ -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) => {
?
<span>
<Typography variant="body2" color="textSecondary" style={{marginTop: 10}}>
Click an app below to select it
Search to find your app
</Typography>
</span>
:
@@ -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}
+25 -18
View File
@@ -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 (
<form noValidate action="" role="search">
<TextField
defaultValue={defaultSearch}
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
InputProps={{
@@ -118,10 +125,12 @@ const AppGrid = props => {
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 (
<Grid container spacing={2}>
{hits.map((data, index) => {
-377
View File
@@ -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 (
<form noValidate action="" role="search">
<input
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
InputProps={{
style:{
color: "white",
fontSize: "1em",
height: 50,
},
startAdornment: (
<InputAdornment position="start">
<SearchIcon style={{marginLeft: 5}}/>
</InputAdornment>
),
}}
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' : ''*/}
</form>
)
}
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 (
<Grid container spacing={2}>
{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 (
<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>
<Grid item xs={xs} key={index}>
<Link to={`/apps/${data.objectID}?queryID=${data.__queryID}`} style={{textDecoration: "none", color: "#f85a3e"}}>
<Paper elevation={0} style={paperStyle} onMouseOver={() => {
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,
}
])
}}>
<ButtonBase style={{padding: 5, borderRadius: 3, minHeight: 100, minWidth: 100,}}>
<img alt={data.name} src={data.image_url} style={{width: "100%", maxWidth: 100, minWidth: 100, minHeight: 100, maxHeight: 100, display: "block", margin: "0 auto"}} />
</ButtonBase>
<div/>
{index === mouseHoverIndex || showName === true ?
parsedname
:
null
}
{data.generated ?
<Tooltip title={"Created with App editor"} style={{marginTop: "28px", width: "100%"}} aria-label={data.name}>
{data.invalid ?
<CloudQueueIcon style={{position: "absolute", top: 1, left: 3, height: 16, width: 16, color: theme.palette.primary.main }}/>
:
<CloudQueueIcon style={{position: "absolute", top: 1, left: 3, height: 16, width: 16, color: "rgba(255,255,255,0.95)",}}/>
}
</Tooltip>
:
<Tooltip title={"Created with python (custom app)"} style={{marginTop: "28px", width: "100%"}} aria-label={data.name}>
<CodeIcon style={{position: "absolute", top: 1, left: 3, height: 16, width: 16, color: "rgba(255,255,255,0.95)",}}/>
</Tooltip>
}
</Paper>
</Link>
</Grid>
</Zoom>
)
})}
</Grid>
)
}
const CustomSearchBox = connectSearchBox(SearchBox)
const CustomHits = connectHits(Hits)
//const CustomHits = connectHitInsights(aa)(Hits)
const selectButtonStyle = {
minWidth: 150,
maxWidth: 150,
minHeight: 50,
}
return (
<div style={{width: "100%", textAlign: "center", position: "relative", height: "100%", display: "flex"}}>
{/*
<div style={{padding: 10, }}>
<Button
style={selectButtonStyle}
variant="outlined"
onClick={() => {
const searchField = document.createElement("shuffle_search_field")
console.log("Field: ", searchField)
if (searchField !== null & searchField !== undefined) {
console.log("Set field.")
searchField.value = "WHAT WABALABA"
searchField.setAttribute("value", "WHAT WABALABA")
}
}}
>
Cases
</Button>
</div>
*/}
<div style={{width: "100%", position: "relative", height: "100%",}}>
<InstantSearch searchClient={searchClient} indexName="appsearch">
<div style={{maxWidth: 450, margin: "auto", marginTop: 15, marginBottom: 15, }}>
<CustomSearchBox />
</div>
<CustomHits hitsPerPage={5}/>
<Configure clickAnalytics />
</InstantSearch>
{showSuggestion === true ?
<div style={{paddingTop: 0, maxWidth: isMobile ? "100%" : "60%", margin: "auto"}}>
<Typography variant="h6" style={{color: "white", marginTop: 50,}}>
Can't find what you're looking for?
</Typography>
<div style={{flex: "1", display: "flex", flexDirection: "row", textAlign: "center",}}>
<TextField
required
style={{flex: "1", marginRight: "15px", backgroundColor: theme.palette.inputColor}}
InputProps={{
style:{
color: "#ffffff",
},
}}
color="primary"
fullWidth={true}
placeholder="Email (optional)"
type="email"
id="email-handler"
autoComplete="email"
margin="normal"
variant="outlined"
onChange={e => setFormMail(e.target.value)}
/>
<TextField
required
style={{flex: "1", backgroundColor: theme.palette.inputColor}}
InputProps={{
style:{
color: "#ffffff",
},
}}
color="primary"
fullWidth={true}
placeholder="What apps do you want to see?"
type=""
id="standard-required"
margin="normal"
variant="outlined"
autoComplete="off"
onChange={e => setMessage(e.target.value)}
/>
</div>
<Button
variant="contained"
color="primary"
style={buttonStyle}
disabled={message.length === 0}
onClick={() => {
submitContact(formMail, message)
}}
>
Submit
</Button>
<Typography style={{color: "white"}} variant="body2">{formMessage}</Typography>
</div>
: null
}
<span style={{position: "absolute", display: "flex", textAlign: "right", float: "right", right: 0, bottom: 120, }}>
<Typography variant="body2" color="textSecondary" style={{}}>
Search by
</Typography>
<a rel="noopener noreferrer" href="https://www.algolia.com/" target="_blank" style={{textDecoration: "none", color: "white"}}>
<img src={"/images/logo-algolia-nebula-blue-full.svg"} alt="Algolia logo" style={{height: 17, marginLeft: 5, marginTop: 3,}} />
</a>
</span>
</div>
</div>
)
}
export default AppGrid1;
@@ -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 (
<Grid item xs={11} style={{ display: "flex" }}>
{/*<FormLabel style={{ color: "#B9B9BA" }}>Find your integrations!</FormLabel>*/}
{/* <div style={{ display: "flex", width: 510, height:100 }}>
<Button
disabled={finishedApps.includes("CASES")}
variant={
defaultSearch === "CASES" ? "contained" : "outlined"
}
color="secondary"
style={{
flex: 1,
width: "100%",
padding: 25,
margin: buttonMargin,
fontSize: 18,
color: "var(--White-text, #F1F1F1)",
fontWeight: 400,
background: "rgba(33, 33, 33, 1)",
borderRadius: 8,
textTransform: 'capitalize',
border: "1px solid rgba(33, 33, 33, 1)" ,
}}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
// startIcon = {defaultSearch === "CASES" ? newSelectedApp.image_url : <LightbulbIcon/>}
onClick={(event) => {
onNodeSelect("CASES");
setDefaultSearch(discoveryData.label)
}}
>
{appFramework === undefined || appFramework.cases === undefined || appFramework.cases.large_image === undefined ||
appFramework === null || appFramework.cases === null || appFramework.cases.large_image === null || appFramework.cases.large_image.length === 0 ?
<div style={{width: 40, border: "1px solid rgba(33, 33, 33, 1) !importent", height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign:"center"}}>
<LightbulbIcon style={{ marginTop: 6 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={AppImage} />}
<div style={{marginLeft: 8, }}>
<Typography style={{display:"flex",border:"none"}} >Case Management</Typography>
{appFramework === undefined || appFramework.cases === undefined || appFramework.cases.name === undefined ||
appFramework === null || appFramework.cases === null || appFramework.cases.name === null || appFramework.cases.name.length === 0 ?
"":<Typography style={{fontSize: 12, textAlign:"left", color:"var(--label-grey-text, #9E9E9E)" }} >{AppName}</Typography>}
</div>
</Button>
</div>
<div style={{ display: "flex", width: 510, height: 100 }}>
<Button
disabled={finishedApps.includes("SIEM")}
variant={
defaultSearch === "SIEM" ? "contained" : "outlined"
}
style={buttonStyle}
// startIcon={<SearchIcon />}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
color="secondary"
onClick={(event) => {
onNodeSelect("SIEM");
setDefaultSearch(discoveryData.label)
}}
>
{appFramework === undefined || appFramework.siem === undefined || appFramework.siem.large_image === undefined ||
appFramework === null || appFramework.siem === null || appFramework.siem.large_image === null || appFramework.siem.large_image.length === 0 ?
<div style={{width: 40, border: "1px solid rgba(33, 33, 33, 1) !importent", height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign:"center"}}>
<SearchIcon style={{ marginTop: 6 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={appFramework.siem.large_image} />}
<div style={{marginLeft: 8,}}>
<Typography style={{display:"flex",}}>SIEM</Typography>
{appFramework === undefined || appFramework.siem === undefined || appFramework.siem.name === undefined ||
appFramework === null || appFramework.siem === null || appFramework.siem.name === null || appFramework.siem.name.length === 0 ?
"":<Typography style={{fontSize: 12, textAlign:"left", color:"var(--label-grey-text, #9E9E9E)" }} >{appFramework.siem.name.split('_').join(' ')}</Typography>}
</div>
</Button>
<Button
disabled={
finishedApps.includes("EDR & AV") ||
finishedApps.includes("ERADICATION")
}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
variant={
defaultSearch === "Eradication" ? "contained" : "outlined"
}
style={buttonStyle}
// startIcon={<NewReleasesIcon />}
color="secondary"
onClick={(event) => {
onNodeSelect("ERADICATION");
}}
>
{appFramework === undefined || appFramework.edr === undefined || appFramework.edr.large_image === undefined ||
appFramework === null || appFramework.edr === null || appFramework.edr.large_image === null || appFramework.edr.large_image.length === 0 ?
<div style={{width: 40, border: "1px solid rgba(33, 33, 33, 1) !importent", height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign:"center"}}>
<NewReleasesIcon style={{marginTop: 8 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={appFramework.edr.large_image} />}
<div style={{marginLeft: 8,}}>
<Typography style={{display:"flex",}}>Endpoint</Typography>
{appFramework === undefined || appFramework.edr === undefined || appFramework.edr.name === undefined ||
appFramework === null || appFramework.edr === null || appFramework.edr.name === null || appFramework.edr.name.length === 0 ?
"":<Typography style={{fontSize: 12, textAlign:"left", color:"var(--label-grey-text, #9E9E9E)" }} >{appFramework.edr.name.split('_').join(' ')}</Typography>}
</div>
</Button>
</div>
<div style={{ display: "flex", width: 510, height: 100 }}>
<Button
disabled={finishedApps.includes("INTEL")}
variant={
defaultSearch === "INTEL" ? "contained" : "outlined"
}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
style={buttonStyle}
// startIcon={<ExtensionIcon />}
color="secondary"
onClick={(event) => {
onNodeSelect("INTEL");
}}
>
{appFramework === undefined || appFramework.intel === undefined || appFramework.intel.large_image === undefined ||
appFramework === null || appFramework.intel === null || appFramework.intel.large_image === null || appFramework.intel.large_image.length === 0 ?
<div style={{width: 40, border: "1px solid rgba(33, 33, 33, 1) !importent", height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign:"center"}}>
<ExtensionIcon style={{ marginTop: 8 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={appFramework.intel.large_image} />}
<div style={{marginLeft: 8,}}>
<Typography style={{display:"flex",}}>Intel</Typography>
{appFramework === undefined || appFramework.intel === undefined || appFramework.intel.name === undefined ||
appFramework === null || appFramework.intel === null || appFramework.intel.name === null || appFramework.intel.name.length === 0 ?
"":<Typography style={{fontSize: 12, textAlign:"left", color:"var(--label-grey-text, #9E9E9E)" }} >{appFramework.intel.name.split('_').join(' ')}</Typography>}
</div>
</Button>
<Button
disabled={
finishedApps.includes("COMMS") ||
finishedApps.includes("EMAIL")
}
variant={
defaultSearch === "EMAIL" ? "contained" : "outlined"
}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
style={buttonStyle}
// startIcon={<EmailIcon />}
color="secondary"
onClick={(event) => {
onNodeSelect("EMAIL");
}}
>
{appFramework === undefined || appFramework.communication === undefined || appFramework.communication.large_image === undefined ||
appFramework === null || appFramework.communication === null || appFramework.communication.large_image === null || appFramework.communication.large_image.length === 0 ?
<div style={{width: 40, border: "1px solid rgba(33, 33, 33, 1) !importent", height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign:"center"}}>
<EmailIcon style={{ marginTop: 8 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={appFramework.communication.large_image} />}
<div style={{marginLeft: 8,}}>
<Typography style={{display:"flex",}}>Email</Typography>
{appFramework === undefined || appFramework.communication === undefined || appFramework.communication.name === undefined ||
appFramework === null || appFramework.communication === null || appFramework.communication.name === null || appFramework.communication.name.length === 0 ?
"":<Typography style={{fontSize: 12, textAlign:"left", color:"var(--label-grey-text, #9E9E9E)" }} >{appFramework.communication.name.split('_').join(' ')}</Typography>}
</div>
</Button>
</div>
{moreButton ? (
<div style={{ display: "flex", width: 510, height: 100, marginBottom: 20 }}>
<Button
disabled={finishedApps.includes("NETWORK")}
variant={
defaultSearch === "NETWORK" ? "contained" : "outlined"
}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
style={buttonStyle}
// startIcon={<ExtensionIcon />}
color="secondary"
onClick={(event) => {
onNodeSelect("NETWORK");
}}
>
{appFramework === undefined || appFramework.network === undefined || appFramework.network.large_image === undefined ||
appFramework === null || appFramework.network === null || appFramework.network.large_image === null || appFramework.network.large_image.length === 0 ?
<div style={{width: 40, border: "1px solid rgba(33, 33, 33, 1) !importent", height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign:"center"}}>
<ShowChartIcon style={{ marginTop: 8 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={appFramework.network.large_image} />}
<div style={{marginLeft: 8,}}>
<Typography style={{display:"flex",}}>Network</Typography>
{appFramework === undefined || appFramework.network === undefined || appFramework.network.name === undefined ||
appFramework === null || appFramework.network === null || appFramework.network.name === null || appFramework.network.name.length === 0 ?
"":<Typography style={{fontSize: 10, textAlign:"left", color:"var(--label-grey-text, #9E9E9E)" }} >{appFramework.network.name}</Typography>}
</div>
</Button>
<Button
disabled={
finishedApps.includes("ASSETS")
}
variant={
defaultSearch === "ASSETS" ? "contained" : "outlined"
}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
style={buttonStyle}
// startIcon={<EmailIcon />}
color="secondary"
onClick={(event) => {
onNodeSelect("ASSETS");
}}
>
{appFramework === undefined || appFramework.assets === undefined || appFramework.assets.large_image === undefined ||
appFramework === null || appFramework.assets === null || appFramework.assets.large_image === null || appFramework.assets.large_image.length === 0 ?
<div style={{width: 40, border: "1px solid rgba(33, 33, 33, 1) !importent", height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign:"center"}}>
<ExploreIcon style={{ marginTop: 8 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={appFramework.assets.large_image} />}
<div style={{marginLeft: 8,}}>
<Typography style={{display:"flex",}}>Assets</Typography>
{appFramework === undefined || appFramework.assets === undefined || appFramework.assets.name === undefined ||
appFramework === null || appFramework.assets === null || appFramework.assets.name === null || appFramework.assets.name.length === 0 ?
"":<Typography style={{fontSize: 10, textAlign:"left", color:"var(--label-grey-text, #9E9E9E)" }} >{appFramework.assets.name}</Typography>}
</div>
</Button>
<Button
disabled={
finishedApps.includes("IAM")
}
variant={
defaultSearch === "IAM" ? "contained" : "outlined"
}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
style={buttonStyle}
// startIcon={<EmailIcon />}
color="secondary"
onClick={(event) => {
onNodeSelect("IAM");
}}
>
{appFramework === undefined || appFramework.iam === undefined || appFramework.iam.large_image === undefined ||
appFramework === null || appFramework.iam === null || appFramework.iam.large_image === null || appFramework.iam.large_image.length === 0 ?
<div style={{width: 40, border: "1px solid rgba(33, 33, 33, 1) !importent", height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign:"center"}}>
<FingerprintIcon style={{ marginTop: 8 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={appFramework.iam.large_image} />}
<div style={{marginLeft: 8,}}>
<Typography style={{display:"flex",}}>IAM</Typography>
{appFramework === undefined || appFramework.iam === undefined || appFramework.iam.name === undefined ||
appFramework === null || appFramework.iam === null || appFramework.iam.name === null || appFramework.iam.name.length === 0 ?
"":<Typography style={{fontSize: 8, textAlign:"left", color:"var(--label-grey-text, #9E9E9E)" }} >{appFramework.iam.name}</Typography>}
</div>
</Button>
</div>
)
:
<div style={{ display: "flex", width: 510, paddingLeft: 165, }}>
<Button
style={{ color: "#f86a3e", textTransform: 'capitalize', border: 2, backgroundColor: "var(--Background-color, #1A1A1A)" }}
className="btn btn-primary"
onClick={(event) => {
setMoreButton(true);
}}
>
<Typography style={{ textDecorationLine: 'underline', }}>
See more Categories
</Typography>
</Button>
</div>} */}
<div style={{ display: "flex", width: 510, height: 64, borderRadius: 8, background: "var(--Container, #212121)" }}
>
<div
onMouseOver={mouseOver}
onMouseOut={mouseOut}
disabled={finishedApps.includes("CASES")}
variant={
defaultSearch === "CASES" ? "contained" : "outlined"
}
color="secondary"
style={{
flex: 1,
width: "100%",
margin: buttonMargin,
fontSize: 18,
color: "var(--White-text, #F1F1F1)",
fontWeight: 400,
background: "rgba(33, 33, 33, 1)",
borderRadius: 8,
textTransform: 'capitalize',
}}
// startIcon = {defaultSearch === "CASES" ? newSelectedApp.image_url : <LightbulbIcon/>}
onClick={(event) => {
onNodeSelect("CASES");
setDefaultSearch(discoveryData.label)
}}
>
<div style={{marginLeft: 20, display:"flex", textAlign:"center", alignItems:"center", marginLeft: 100, width: 320, marginRight: "auto" }}>
{AppImage === undefined || AppImage === undefined ||
AppImage === null || AppImage === null || AppImage.length === 0 ?
<div style={{ width: 40, height: 40, borderRadius: 9999, backgroundColor: "#2F2F2F", textAlign: "center" }}>
<LightbulbIcon style={{ marginTop: 6 }} />
</div>
: <img style={{ marginRight: 8, width: 40, height: 40, flexShrink: 0, borderRadius: 40, }} src={AppImage} />}
<div style={{ marginLeft: 8, }}>
<Typography style={{ display: "flex", border: "none" }} >Case Management</Typography>
{appName === undefined || appName === undefined ||
appName === null || appName === null || appName.length === 0 ?
""
:
<Typography style={{ fontSize: 12, textAlign: "left", color: "var(--label-grey-text, #9E9E9E)" }} >{appName}</Typography>}
</div>
</div>
</div>
</div>
</Grid>
)
}
export default AppSearchButtons
+433
View File
@@ -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 (
<Collapse in={true}>
<div
style={{
minHeight: sizing,
maxHeight: sizing,
marginTop: 10,
width: 500,
}}
>
{selectionOpen ? (
<div
style={{
width: 319,
height: 395,
flexShrink: 0,
marginLeft: 70,
marginTop: 68,
position: "absolute",
zIndex: 100,
borderRadius: 6,
border: "1px solid var(--Container-Stroke, #494949)",
background: "var(--Container, #212121)",
boxShadow: "8px 8px 32px 24px rgba(0, 0, 0, 0.16)",
}}
>
<div style={{ display: "flex" }}>
<div style={{ display: "flex", textAlign: "center", textTransform: "capitalize" }}>
<Typography style={{ padding: 16, color: "#FFFFFF", textTransform: "capitalize" }}> {discoveryData} </Typography>
</div>
<div style={{ display: "flex" }}>
<Tooltip
title="Close"
placement="top"
style={{ zIndex: 10011 }}
>
<IconButton
style={{
flex: 1,
// width: 224,
marginLeft: discoveryData === ('ERADICATION') ? 120 : 177,
width: "100%",
marginBottom: 23,
fontSize: 16,
background: "rgba(33, 33, 33, 1)",
borderColor: "rgba(33, 33, 33, 1)",
borderRadius: 8,
}}
onClick={() => {
setSelectionOpen(false)
}}
>
<CloseIcon style={{ width: 16 }} />
</IconButton>
</Tooltip>
<Tooltip
title="Delete app"
placement="bottom"
style={{ zIndex: 10011 }}
>
<IconButton
style={{ zIndex: 12501, position: "absolute", top: 32, right: 16 }}
onClick={(e) => {
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)
}}
>
<DeleteIcon style={{ color: "white", height: 15, width: 15, }} />
</IconButton>
</Tooltip>
</div>
</div>
<div
style={{ width: "100%", border: "1px #494949 solid" }}
/>
<AppSearch
defaultSearch={defaultSearch}
newSelectedApp={newSelectedApp}
setNewSelectedApp={setNewSelectedApp}
userdata={userdata}
// cy={cy}
/>
</div>
) : null}
<Typography
variant="h4"
style={{
marginLeft: 8,
marginTop: 40,
marginRight: 30,
marginBottom: 0,
}}
color="rgba(241, 241, 241, 1)"
>
Find your apps
</Typography>
<Typography
variant="body2"
style={{
marginLeft: 8,
marginTop: 10,
marginRight: 30,
marginBottom: 40,
}}
color="rgba(158, 158, 158, 1)"
>
Select the apps you work with and we will connect the for you.
</Typography>
{appButtons.map((appData, index) => {
const appName = appData.name
const AppImage = appData.large_image
const appType = appData.type
return (
<AppSearchButtons
appFramework={appFramework}
appName={appName}
appType = {appType}
AppImage={AppImage}
defaultSearch={defaultSearch}
finishedApps={finishedApps}
onNodeSelect={onNodeSelect}
discoveryData={discoveryData}
setDiscoveryData={setDiscoveryData}
setDefaultSearch={setDefaultSearch}
apps={apps}
/>
)
})}
</div>
<div style={{ flexDirection: "row", }}>
<Button variant="contained" type="submit" fullWidth style={bottomButtonStyle} onClick={() => {
navigate("/welcome?tab=3")
setActiveStep(2)
}}>
Continue
</Button>
</div>
</Collapse>
)
}
export default AppSelection;
+25 -96
View File
@@ -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 => {
<form noValidate action="" role="search">
<TextField
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, width: "100%",}}
style={{backgroundColor: "#2F2F2F", borderRadius: borderRadius, width: "100%",}}
InputProps={{
style:{
color: "white",
fontSize: "1em",
height: 50,
},
startAdornment: (
endAdornment: (
<InputAdornment position="start">
<SearchIcon style={{marginLeft: 5}}/>
</InputAdornment>
@@ -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 => {
<Grid container spacing={0} style={{border: "1px solid rgba(255,255,255,0.2)", maxHeight: 250, minHeight: 250, overflowY: "auto", overflowX: "hidden", }}>
{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 => {
}
}}>
<div style={{display: "flex"}}>
<img alt={data.name} src={data.image_url} style={{width: "100%", maxWidth: 30, minWidth: 30, minHeight: 30, maxHeight: 30, display: "block", }} />
<img alt={data.name} src={data.image_url} style={{width: "100%", maxWidth: 30, minWidth: 30, minHeight: 30, borderRadius: 40, maxHeight: 30, display: "block", }} />
<Typography variant="body1" style={{marginTop: 2, marginLeft: 10, }}>
{parsedname}
</Typography>
@@ -272,7 +201,7 @@ const Appsearch = props => {
const CustomHits = connectHits(InputHits)
return (
<div style={{width: "100%", textAlign: "center", position: "relative", height: "100%",}}>
<div style={{width: 287, height: 295, padding: "16px 16px 267px 16px", alignItems: "center", gap: 138,}}>
<InstantSearch searchClient={searchClient} indexName="appsearch">
{/* showSearch === false ? null :
<div style={{maxWidth: 450, margin: "auto", }}>
+2 -2
View File
@@ -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 {
+11 -11
View File
@@ -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());
});
};
@@ -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"
+469
View File
@@ -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 (
<DialogContent style={{ textAlign: "center", marginTop: 50 }}>
<Typography variant="h4" id="draggable-dialog-title" style={{ cursor: "move", }}>
{selectedApp.name} does not require authentication
</Typography>
</DialogContent>
);
}
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 (
<div key={index} style={{ marginTop: 10 }}>
<div style={{display: "flex"}}>
<LockOpenIcon style={{ marginRight: 10, }} />
<Typography variant="body1" style={{}}>
{data.name.replace("_basic", "", -1).replace("_", " ", -1)}
</Typography>
</div>
{data.schema !== undefined &&
data.schema !== null &&
data.schema.type === "bool" ? (
<Select
MenuProps={{
disableScrollLock: true,
}}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
defaultValue={"false"}
fullWidth
onChange={(e) => {
console.log("Value: ", e.target.value);
authenticationOption.fields[data.name] = e.target.value;
}}
style={{
backgroundColor: theme.palette.surfaceColor,
color: "white",
height: 50,
}}
>
<MenuItem
key={"false"}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={"false"}
>
false
</MenuItem>
<MenuItem
key={"true"}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={"true"}
>
true
</MenuItem>
</Select>
) : (
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {
color: "white",
fontSize: "1em",
},
disableUnderline: true,
}}
fullWidth
type={
data.example !== undefined && data.example.includes("***")
? "password"
: "text"
}
color="primary"
defaultValue={
data.value !== undefined && data.value !== null
? data.value
: ""
}
placeholder={data.example}
onChange={(event) => {
authenticationOption.fields[data.name] =
event.target.value;
}}
/>
)}
</div>
);
})
const authenticationButtons = <span>
<Button
style={{ borderRadius: theme.palette.borderRadius, marginTop: authFieldsOnly ? 20 : 0 }}
onClick={() => {
setAuthenticationOptions(authenticationOption);
handleSubmitCheck();
}}
variant={"contained"}
disabled={submitSuccessful}
color="primary"
>
Submit
</Button>
{authFieldsOnly === true ? null :
<Button
style={{ borderRadius: 0 }}
onClick={() => {
setAuthenticationModalOpen(false);
}}
color="primary"
>
Cancel
</Button>
}
</span>
// Check if only the auth items should show
if (authFieldsOnly === true) {
return (
<div>
{submitSuccessful === true ?
<Typography variant="h6" style={{ marginTop: 10 }}>
App succesfully configured! You may close this window.
</Typography>
:
<span>
{authenticationParameters}
{authenticationButtons}
</span>
}
</div>
)
}
return (
<Dialog
PaperComponent={PaperComponent}
aria-labelledby="draggable-dialog-title"
hideBackdrop={true}
disableEnforceFocus={true}
disableBackdropClick={true}
style={{ pointerEvents: "none" }}
open={authenticationModalOpen}
onClose={() => {
//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,
},
}}
>
<IconButton
style={{
zIndex: 5000,
position: "absolute",
top: 14,
right: 18,
color: "grey",
}}
onClick={() => {
setAuthenticationModalOpen(false);
if (configureWorkflowModalOpen === true) {
setSelectedAction({});
}
}}
>
<CloseIcon />
</IconButton>
<DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
<div style={{ color: "white" }}>
Authentication for {selectedApp.name}
</div>
</DialogTitle>
<DialogContent>
<a
target="_blank"
rel="noopener noreferrer"
href="https://shuffler.io/docs/apps#authentication"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
What is app authentication?
</a>
<div />
These are required fields for authenticating with {selectedApp.name}
<div style={{ marginTop: 15 }} />
<b>Name - what is this used for?</b>
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {
color: "white",
fontSize: "1em",
},
}}
fullWidth
color="primary"
placeholder={"Auth july 2020"}
defaultValue={`Auth for ${selectedApp.name}`}
onChange={(event) => {
authenticationOption.label = event.target.value;
}}
/>
<Divider
style={{
marginTop: 15,
marginBottom: 15,
backgroundColor: "rgb(91, 96, 100)",
}}
/>
<div />
{authenticationParameters}
</DialogContent>
<DialogActions>
{authenticationButtons}
</DialogActions>
</Dialog>
);
};
export default AuthenticationData;
File diff suppressed because it is too large Load Diff
+280
View File
@@ -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 (
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: 30, marginTop: 15, }}>
<Typography variant="h6" style={{marginBotton: 15, }}>
{inputname}
</Typography>
<BarChart
width={"100%"}
height={height}
data={inputdata}
gridlines={
<GridlineSeries line={<Gridline direction="all" />} />
}
/>
</div>
)
}
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 = (
<div className="content" style={{width: "100%", margin: "auto", }}>
<Typography variant="body1" style={{margin: "auto", marginLeft: 10, marginBottom: 20, }}>
All Stat widgets are monthly and gathered from <a
href={`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`}
target="_blank"
style={{ textDecoration: "none", color: "#f85a3e",}}
>Your Organization Statistics. </a>
This is a feature to help give you more insight into Shuffle, and will be populating over time.
</Typography>
{statistics !== undefined ?
<div style={{display: "flex", textAlign: "center",}}>
<Paper style={paperStyle}>
<Typography variant="h4">
{statistics.monthly_workflow_executions}
</Typography>
<Typography variant="h6">
Workflow Runs
</Typography>
</Paper>
<Paper style={paperStyle}>
<Typography variant="h4">
{statistics.monthly_app_executions}
</Typography>
<Typography variant="h6">
App Runs
</Typography>
</Paper>
</div>
: null}
{appRuns === undefined ?
null
:
<LineChartWrapper keys={appRuns} height={300} width={"100%"} inputname={"Daily App Runs"}/>
}
{workflowRuns === undefined ?
null
:
<LineChartWrapper keys={workflowRuns} height={300} width={"100%"} inputname={"Daily Workflow Runs (including subflows)"}/>
}
{subflowRuns === undefined ?
null
:
<LineChartWrapper keys={subflowRuns} height={300} width={"100%"} inputname={"Subflow Runs"}/>
}
</div>
)
const dataWrapper = (
<div style={{ maxWidth: 1366, margin: "auto" }}>{data}</div>
);
return dataWrapper;
}
export default AppStats;
+107 -24
View File
@@ -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 (
<div>
<Typography variant="h6" style={{ marginTop: 20, marginBottom: 10 }}>
Branding
</Typography>
<h2>
Branding
</h2>
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more.
</Typography>
<Divider style={{marginTop: 50, marginBottom: 50, }} />
<h2>
Creator Network
Creator Incentive Program
</h2>
<div style={{ display: "flex", width: 700, }}>
<div style={{ display: "flex", width: 900, }}>
<div>
<span>
<Typography variant="body1" color="textSecondary">
By changing publishing settings, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Terms of Service</a>, and acknowledge that your organization's non-sensitive data will be turned into a <a target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}} href="https://shuffler.io/creators">creator account</a>. Support: support@shuffler.io
By changing publishing settings, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Terms of Service</a>, and acknowledge that your organization's non-sensitive data will be added as a <a target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}} href="https://shuffler.io/creators">creator account</a>. 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.<div/>Support: <a href="mailto:support@shuffler.io"target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>support@shuffler.io</a>
</Typography>
{selectedOrganization.creator_id == "" ?
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
&nbsp;
</Typography>
:
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
<a href={`/creators/${selectedOrganization.creator_id}`} target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Modify your creator organization</a>
</Typography>
}
<Button
style={{ height: 40, marginTop: 10, width: 300, }}
variant="outlined"
color="primary"
disabled={() => {
return isOrganizationReady()
}}
variant={selectedOrganization.creator_id == "" ? "contained" : "outlined"}
color={selectedOrganization.creator_id == "" ? "primary" : "secondary"}
disabled={!isOrganizationReady()}
onClick={() => {
handleChangePublishing();
}}
>
Join Creator Network
{selectedOrganization.creator_id == "" ? "Join" : "Leave"} Creators
</Button>
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "white", }}>
{publishingInfo}
</Typography>
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
{publishRequirements.map((item) => {
return (
<div>
Required: {item}
</div>
)
})}
</Typography>
</span>
</div>
</div>
+14 -14
View File
@@ -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());
});
};
File diff suppressed because it is too large Load Diff
+21 -23
View File
@@ -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 (
<form noValidate action="" role="search">
<TextField
defaultValue={defaultSearch}
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
InputProps={{
@@ -132,10 +130,10 @@ const CreatorGrid = props => {
autoComplete='off'
type="search"
color="primary"
value={currentRefinement}
placeholder="Find Creators..."
id="shuffle_search_field"
onChange={(event) => {
removeQuery("q")
refine(event.currentTarget.value)
}}
/>
+19 -18
View File
@@ -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 (
<form noValidate action="" role="search">
<TextField
defaultValue={defaultSearch}
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
InputProps={{
@@ -117,10 +118,10 @@ const DocsGrid = props => {
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 = <PolymerIcon />
const baseImage = <CodeIcon/>
const avatar = data.image_url === undefined ?
baseImage
:
+12 -2
View File
@@ -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);
+241 -156
View File
@@ -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 (
<Dialog
<Drawer
open={modalOpen}
onClose={() => {
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,
},
}}
>
<DialogTitle style={{padding: 30, paddingBottom: 0, zIndex: 1000,}}>
<div style={{display: "flex"}}>
<div style={{display: "flex"}}>
<div style={{flex: 1, color: "rgba(255,255,255,0.9)" }}>
<div style={{display: "flex"}}>
<Typography variant="h6" style={{flex: 9, }}>
{newWorkflow ? "New" : "Editing"} workflow
</Typography>
{newWorkflow === true ? null :
<div style={{ marginLeft: 5, flex: 1 }}>
<Tooltip title="Open Workflow Form for 'normal' users">
<a
rel="noopener noreferrer"
href={`/workflows/${workflow.id}/run`}
target="_blank"
style={{
textDecoration: "none",
color: "#f85a3e",
marginLeft: 5,
marginTop: 10,
}}
>
<OpenInNewIcon />
</a>
</Tooltip>
</div>
}
<div style={{display: "flex"}}>
<Typography variant="h4" style={{flex: 9, }}>
{newWorkflow ? "New" : "Editing"} workflow
</Typography>
{newWorkflow === true ? null :
<div style={{ marginLeft: 5, flex: 1 }}>
<Tooltip title="Open Workflow Form for 'normal' users">
<a
rel="noopener noreferrer"
href={`/workflows/${workflow.id}/run`}
target="_blank"
style={{
textDecoration: "none",
color: "#f85a3e",
marginLeft: 5,
marginTop: 10,
}}
>
<OpenInNewIcon />
</a>
</Tooltip>
</div>
<Typography variant="body2" color="textSecondary" style={{maxWidth: 440,}}>
Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
</Typography>
{showUpload === true ?
<div style={{ float: "right" }}>
<Tooltip color="primary" title={"Import manually"} placement="top">
<Button
color="primary"
style={{}}
variant="text"
onClick={() => upload.click()}
>
<PublishIcon />
</Button>
</Tooltip>
</div>
: null}
}
</div>
<Typography variant="body2" color="textSecondary" style={{marginTop: 20, maxWidth: 440,}}>
Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
</Typography>
{showUpload === true ?
<div style={{ float: "right" }}>
<Tooltip color="primary" title={"Import manually"} placement="top">
<Button
color="primary"
style={{}}
variant="text"
onClick={() => upload.click()}
>
<PublishIcon />
</Button>
</Tooltip>
</div>
: null}
</div>
{/*newWorkflow === true ?
<div style={{flex: 1, marginLeft: 45, }}>
@@ -211,12 +228,12 @@ const EditWorkflow = (props) => {
</div>
</DialogTitle>
<FormControl>
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 350, zIndex: 1001, }}>
<div style={{minWidth: newWorkflow ? 450 : 500, maxWidth: newWorkflow ? 450 : 500, }}>
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, }}>
<div style={{minWidth: newWorkflow ? 500 : 550, maxWidth: newWorkflow ? 450 : 500, }}>
<TextField
onBlur={(event) => {
setName(event.target.value)
}}
onChange={(event) => {
setName(event.target.value)
}}
InputProps={{
style: {
color: "white",
@@ -231,27 +248,29 @@ const EditWorkflow = (props) => {
autoFocus
fullWidth
/>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
InputProps={{
style: {
color: "white",
},
}}
maxRows={4}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
<div style={{display: "flex", }}>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
InputProps={{
style: {
color: "white",
},
}}
maxRows={4}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
</div>
<div style={{display: "flex", marginTop: 10, }}>
<ChipInput
style={{ flex: 1, maxHeight: 40, marginTop: 12, overflow: "auto", }}
<MuiChipsInput
style={{ flex: 1, maxHeight: 40, }}
InputProps={{
style: {
color: "white",
@@ -261,13 +280,20 @@ const EditWorkflow = (props) => {
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 ?
<span style={{marginTop: 25, }}>
<div style={{display: "flex"}}>
<FormControl style={{marginTop: 15, }}>
<FormLabel id="demo-row-radio-buttons-group-label">Status</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.status}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
setInnerWorkflow(innerWorkflow)
}}
>
<FormControlLabel value="test" control={<Radio />} label="Test" />
<FormControlLabel value="production" control={<Radio />} label="Production" />
<FormControl style={{marginTop: 15, }}>
<FormLabel id="demo-row-radio-buttons-group-label">Status</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.status}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
setInnerWorkflow(innerWorkflow)
</RadioGroup>
</FormControl>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
sx={{
marginTop: 3,
marginLeft: 3,
}}
>
<FormControlLabel value="test" control={<Radio />} label="Test" />
<FormControlLabel value="production" control={<Radio />} label="Production" />
</RadioGroup>
</FormControl>
value={dueDate}
label="Due Date"
format="YYYY-MM-DD"
onChange={(newValue) => {
setDueDate(newValue)
}}
/>
</LocalizationProvider>
</div>
<div />
<FormControl style={{marginTop: 15, }}>
@@ -430,38 +471,28 @@ const EditWorkflow = (props) => {
</span>
: null}
<Tooltip color="primary" title={"Add more details"} placement="top">
<IconButton
style={{ color: "white", margin: "auto", marginTop: 10, textAlign: "center", width: 50,}}
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
>
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>}
</IconButton>
</Tooltip>
</div>
{/*newWorkflow === true ?
<div style={{marginLeft: 50, maxWidth: 400, minWidth: 400, position: "relative",}}>
<UsecaseSearch
globalUrl={globalUrl}
appFramework={appFramework}
defaultSearch={undefined}
apps={undefined}
setFoundWorkflowId={setFoundWorkflowId}
userdata={userdata}
/>
</div>
: null*/}
<IconButton
style={{ color: "white", margin: "auto", marginTop: 10, textAlign: "center", width: 50,}}
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
>
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>}
</IconButton>
</Tooltip>
</div>
</DialogContent>
<DialogActions>
<DialogActions style={{paddingRight: 100, }}>
<Button
style={{}}
onClick={() => {
if (setNewWorkflow !== undefined) {
setWorkflow({})
}
if (setNewWorkflow !== undefined) {
setWorkflow({})
}
setModalOpen(false)
setModalOpen(false)
}}
color="primary"
>
@@ -470,46 +501,100 @@ const EditWorkflow = (props) => {
<Button
variant="contained"
style={{}}
disabled={name.length === 0}
disabled={name.length === 0 || submitLoading === true}
onClick={() => {
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags
}
setSubmitLoading(true)
if (selectedUsecases.length > 0) {
innerWorkflow.usecase_ids = selectedUsecases
}
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags
}
if (selectedUsecases.length > 0) {
innerWorkflow.usecase_ids = selectedUsecases
}
if (setNewWorkflow !== undefined) {
setNewWorkflow(
innerWorkflow.name,
innerWorkflow.description,
innerWorkflow.tags,
innerWorkflow.default_return_value,
innerWorkflow,
newWorkflow,
innerWorkflow.usecase_ids,
innerWorkflow.blogpost,
innerWorkflow.status,
)
setWorkflow({})
} else {
setWorkflow(innerWorkflow)
console.log("editing workflow: ", innerWorkflow)
}
setModalOpen(false)
if (dueDate > 0) {
innerWorkflow.due_date = new Date(`${dueDate["$y"]}-${dueDate["$M"]+1}-${dueDate["$D"]}`).getTime()/1000
}
if (setNewWorkflow !== undefined) {
setNewWorkflow(
innerWorkflow.name,
innerWorkflow.description,
innerWorkflow.tags,
innerWorkflow.default_return_value,
innerWorkflow,
newWorkflow,
innerWorkflow.usecase_ids,
innerWorkflow.blogpost,
innerWorkflow.status,
)
setWorkflow({})
} else {
setWorkflow(innerWorkflow)
console.log("editing workflow: ", innerWorkflow)
}
setSubmitLoading(true)
// If new workflow, don't close it
if (isEditing) {
setModalOpen(false)
}
}}
color="primary"
>
{submitLoading ? <CircularProgress color="secondary" /> : "Submit"}
{submitLoading ? <CircularProgress color="secondary" /> : "Done"}
</Button>
</DialogActions>
{newWorkflow === true ?
<span style={{marginTop: 30, }}>
<Typography variant="h6" style={{marginLeft: 30, paddingBottom: 0, }}>
Relevant Workflows
</Typography>
{priority === null || priority === undefined ? null :
<div style={{marginLeft: 30, }}>
<WorkflowTemplatePopup
userdata={userdata}
globalUrl={globalUrl}
srcapp={priority.description.split("&").length > 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}
/>
</div>
}
</span>
: null}
{newWorkflow === true && name.length > 2 ?
<div style={{marginLeft: 30, }}>
<WorkflowGrid
maxRows={1}
globalUrl={globalUrl}
showSuggestions={false}
isMobile={isMobile}
userdata={userdata}
inputsearch={name+description+newWorkflowTags.join(" ")}
parsedXs={6}
alternativeView={false}
onlyResults={true}
/>
</div>
: null}
</FormControl>
</Dialog>
</Drawer>
)
}
+382
View File
@@ -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),
<Dialog
open={modalOpen}
onClose={() => {
setModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: "800px",
minHeight: "320px",
},
}}
>
<DialogTitle style={{}}>
<div style={{ color: "white", display: "flex", alignItems: "center", justifyContent: "center" }}>
<CheckBoxSharpIcon sx={{ borderRadius: 4, color: "rgba(255, 132, 68, 1)" }} style={{ width: 24 }} />
<span style={{ marginLeft: 8, color: "rgba(255, 132, 68, 1)", fontSize: 16, width: 60 }}>Sign Up</span>
<div style={{ borderTop: "1px solid rgba(255, 132, 68, 1)", width: 85, marginLeft: 8, marginRight: 8 }} />
<CheckBoxSharpIcon sx={{ borderRadius: 4, color: "rgba(255, 132, 68, 1)" }} style={{ width: 24 }} />
<span style={{ marginLeft: 8, color: "rgba(255, 132, 68, 1)", fontSize: 16, width: 60 }}>Setup</span>
<div style={{ borderTop: "1px solid rgba(255, 132, 68, 1)", width: 85, marginRight: 8 }} />
<CheckBoxSharpIcon sx={{ borderRadius: 4, color: "rgba(255, 132, 68, 1)" }} style={{ width: 24 }} />
<span style={{ marginLeft: 8, color: "rgba(255, 132, 68, 1)", fontSize: 16, width: 60 }}>Explore</span>
</div>
</DialogTitle>
<Typography style={{ fontSize: 16, width: 252, marginLeft: 167 }}>
Heres a recommended workflow:
</Typography>
{/* <div style={{ marginTop: 0, maxWidth: 700, minWidth: 700, margin: "auto", minHeight: sizing, maxHeight: sizing, }}>
<div style={{ marginTop: 0, }}>
<div className="thumbs" style={{ display: "flex" }}>
<Tooltip title={"Previous usecase"}>
<IconButton
style={{
// backgroundColor: thumbIndex === 0 ? "inherit" : "white",
zIndex: 5000,
minHeight: 50,
maxHeight: 50,
color: "grey",
marginTop: 150,
borderRadius: 50,
border: "1px solid rgba(255,255,255,0.3)",
}}
onClick={() => {
slidePrev()
}}
>
<ArrowBackIosNewIcon />
</IconButton>
</Tooltip>
<div style={{ minWidth: 554, maxWidth: 554, borderRadius: theme.palette.borderRadius, }}>
<AliceCarousel
style={{ backgroundColor: theme.palette.surfaceColor, minHeight: 750, maxHeight: 750, }}
items={formattedCarousel}
activeIndex={thumbIndex}
infiniteLoop
mouseTracking={false}
responsive={responsive}
// activeIndex={activeIndex}
controlsStrategy="responsive"
autoPlay={false}
infinite={true}
animationType="fadeout"
animationDuration={800}
disableButtonsControls
/>
</div>
<Tooltip title={"Next usecase"}>
<IconButton
style={{
backgroundColor: thumbIndex === usecaseButtons.length - 1 ? "inherit" : "white",
zIndex: 5000,
minHeight: 50,
maxHeight: 50,
color: "grey",
marginTop: 150,
borderRadius: 50,
border: "1px solid rgba(255,255,255,0.3)",
}}
onClick={() => {
slideNext()
}}
>
<ArrowForwardIosIcon />
</IconButton>
</Tooltip>
</div>
</div>
</div> */}
<DialogActions style={{ paddingLeft: "30px", paddingRight: '30px' }}>
<Button
style={{ borderRadius: "0px" }}
onClick={() => setModalOpen(false)}
color="primary"
>
Cancel
</Button>
<Button
variant="contained"
style={{ borderRadius: "0px" }}
onClick={() => {
console.log("hello")
}}
color="primary"
>
Submit
</Button>
</DialogActions>
</Dialog>
);
return (
<div style={{ marginTop: 0, margin: "auto", minHeight: sizing, maxHeight: sizing, }}>
{modalView}
<Typography variant="h4" style={{ marginLeft: 8, marginTop: 40, marginRight: 30, marginBottom: 0, }} color="rgba(241, 241, 241, 1)">
Start using workflows
</Typography>
<Typography variant="body2" style={{ marginLeft: 8, marginTop: 10, marginRight: 30, marginBottom: 40, }} color="rgba(158, 158, 158, 1)">
Based on what you selected workflows, here are our recommendations! You will see more of these later.
</Typography>
<div style={{ marginTop: 0, }}>
<div className="thumbs" style={{ display: "flex" }}>
<div style={{ minWidth: 554, maxWidth: 554, borderRadius: theme.palette.borderRadius, }}>
<Grid item xs={11} style={{}}>
{suggestedUsecases.length === 0 && usecasesSet ?
<Typography variant="h6" style={{ marginTop: 30, marginBottom: 50, }} color="rgba(158, 158, 158, 1)">
All Workflows are already added for your current apps!
</Typography>
:
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 (
<WorkflowTemplatePopup
userdata={userdata}
globalUrl={globalUrl}
img1={image1}
srcapp={srcapp}
img2={image2}
dstapp={dstapp}
title={name}
description={description}
apps={apps}
/>
)
})}
</Grid>
<div>
<div style={{ marginTop: 32 }}>
<Typography variant="body2" style={{ fontSize: 16, marginTop: 24 }} color="rgba(158, 158, 158, 1)">
<Button variant="contained" type="submit"
fullWidth style={{
borderRadius: 200,
height: 51,
width: 464,
fontSize: 16,
padding: "16px 24px",
margin: "auto",
itemAlign: "center",
background: activeUsecases === 0 ? "rgba(47, 47, 47, 1)" : "linear-gradient(90deg, #F86744 0%, #F34475 100%)",
color: activeUsecases === 0? "rgba(158, 158, 158, 1)" : "rgba(241, 241, 241, 1)",
border: activeUsecases === 0 ? "1px solid rgba(158, 158, 158, 1)" : "none",
}}
onClick={() => {
navigate("/workflows?message="+activeUsecases+" workflows added")
}}>
Continue to workflows
</Button>
</Typography>
</div>
<Typography variant="body2" style={{ fontSize: 16, marginTop: 24 }} color="rgba(158, 158, 158, 1)">
<Link style={{ color: "#f86a3e", marginLeft: 145 }} to="/usecases" className="btn btn-primary">
Explore usecases
</Link>
</Typography>
</div>
</div>
</div>
</div>
</div>
)
}
export default ExploreWorkflow
+21 -20
View File
@@ -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) => {
<ListItemText
primary=<span style={{ display:"inline"}}>
<Tooltip
title={`Edit File (${allowedFileTypes.join(", ")})`}
title={`Edit File (${allowedFileTypes.join(", ")}). Max size 2MB`}
style={{}}
aria-label={"Edit"}
>
@@ -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");
}
}}
>
-54
View File
@@ -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 (
<div style={FooterStyle}>
<div style={FooterInfo}>
<Box />
</div>
</div>
);
};
const Box = (props) => {
return (
<div style={{ display: "flex" }}>
<div style={{ flex: "1" }}>
<a style={hrefStyle} href="/about">
<h1>About</h1>
</a>
</div>
<div style={{ flex: "1" }}>
<a style={hrefStyle} href="/privacy-policy">
<h1>Privacy Policy</h1>
</a>
</div>
</div>
);
};
export default Footer;
-883
View File
@@ -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 (
<Paper
style={{
backgroundColor: theme.palette.surfaceColor,
width: notificationWidth,
padding: 25,
borderBottom: "1px solid rgba(255,255,255,0.4)",
}}
>
{/*<Typography variant="h6">
{new Date(data.updated_at).toISOString()}
</Typography >*/}
{data.reference_url !== undefined &&
data.reference_url !== null &&
data.reference_url.length > 0 ? (
<Link
to={data.reference_url}
style={{ color: "#f86a3e", textDecoration: "none" }}
>
<Typography variant="h6">{data.title}</Typography>
</Link>
) : (
<Typography variant="h6">{data.title}</Typography>
)}
{data.image !== undefined &&
data.image !== null &&
data.image.length > 0 ? (
<img
alt={data.title}
src={data.image}
style={{ height: 100, width: 100 }}
/>
) : null}
<Typography variant="body1">{data.description}</Typography>
{/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ?
data.tags.map((tag, index) => {
return (
<Chip
key={index}
style={chipStyle}
label={tag}
onClick={() => {
}}
variant="outlined"
color="primary"
/>
)
})
: null */}
{data.read === false ? (
<Button
color="primary"
variant="contained"
style={{ marginTop: 15 }}
onClick={() => {
dismissNotification(data.id);
}}
>
Dismiss
</Button>
) : null}
</Paper>
);
};
const notificationMenu = (
<span style={{ zIndex: 10001 }}>
<IconButton
color="primary"
style={{ zIndex: 10001, marginRight: 15 }}
aria-controls="simple-menu"
aria-haspopup="true"
onClick={(event) => {
setAnchorEl(event.currentTarget);
}}
>
<Badge badgeContent={notifications.length} color="primary">
<NotificationsIcon
color="secondary"
style={{ height: 30, width: 30 }}
alt="Your username here"
src=""
/>
</Badge>
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
style={{
zIndex: 10002,
maxHeight: "90vh",
overflowX: "hidden",
overflowY: "auto",
}}
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
},
}}
onClose={() => {
handleClose();
}}
>
<Paper
style={{
backgroundColor: theme.palette.surfaceColor,
width: notificationWidth,
padding: 25,
borderBottom: "3px solid rgba(255,255,255,0.4)",
}}
>
<div style={{ display: "flex", marginBottom: 5 }}>
<Typography variant="h6">
Your Notifications ({notifications.length})
</Typography>
{notifications.length > 1 ? (
<Button
color="primary"
variant="contained"
style={{ marginLeft: 30 }}
onClick={() => {
clearNotifications();
}}
>
Flush
</Button>
) : null}
</div>
<Typography variant="body2">
Notifications are made by Shuffle to help you discover issues or
improvements.
</Typography>
</Paper>
{notifications.map((data, index) => {
return <NotificationItem data={data} key={index} />;
})}
</Menu>
</span>
);
// Should be based on some path
const avatarMenu = (
<span style={{ zIndex: 10001 }}>
<IconButton
color="primary"
style={{ zIndex: 10001, marginRight: 15 }}
aria-controls="simple-menu"
aria-haspopup="true"
onClick={(event) => {
setAnchorElAvatar(event.currentTarget);
}}
>
<Avatar
style={{ height: 30, width: 30 }}
alt="Your username here"
src=""
/>
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorElAvatar}
keepMounted
open={Boolean(anchorElAvatar)}
style={{ zIndex: 10012 }}
onClose={() => {
handleClose();
}}
>
<MenuItem
onClick={(event) => {
event.preventDefault();
handleClose();
}}
>
<Link to="/docs" style={hrefStyle}>
<HelpOutlineIcon style={{marginRight: 5 }}/> About
</Link>
</MenuItem>
<MenuItem
onClick={(event) => {
event.preventDefault();
handleClose();
}}
>
<Link to="/getting-started" style={hrefStyle}>
<AnalyticsIcon style={{marginRight: 5 }}/> Get Started
</Link>
</MenuItem>
<MenuItem
onClick={(event) => {
event.preventDefault();
handleClose();
}}
>
<Link to="/usecases" style={hrefStyle}>
<LightbulbIcon style={{marginRight: 5 }}/> Use Cases
</Link>
</MenuItem>
<MenuItem
onClick={(event) => {
event.preventDefault();
handleClose();
}}
>
<Link to="/settings" style={hrefStyle}>
<SettingsIcon style={{marginRight: 5 }}/> Settings
</Link>
</MenuItem>
<MenuItem
style={{ color: "white" }}
onClick={(event) => {
event.preventDefault();
handleClose();
handleClickLogout();
}}
>
<MeetingRoomIcon style={{marginRight: 5 }}/> &nbsp;Logout
</MenuItem>
</Menu>
</span>
);
// Handle top bar or something
const logoCheck = !homePage ? null : null;
//<div style={{position: "fixed", top: 0, left: 0, display: "flex"}}>
const loginTextBrowser = !isLoggedIn ? (
<div style={{ display: "flex" }}>
<List style={{ flex: 1, display: "flex", flexDirect: "row" }} component="nav">
<ListItem style={{ textAlign: "center", marginLeft: "0px" }}>
<Link to="/docs" style={hrefStyle}>
<div
onMouseOver={handleSoarHover}
onMouseOut={handleSoarHoverOut}
style={{ color: SoarHoverColor, cursor: "pointer" }}
>
About
</div>
</Link>
</ListItem>
</List>
{!isLoaded ? null :
userdata.chat_disabled === true ? null :
<div style={{flex: 1, }}>
<SearchField serverside={false} userdata={userdata} />
</div>
}
<div style={{ flex: 1, display: "flex", flexDirection: "row-reverse" }}>
<List
style={{ display: "flex", flexDirection: "row-reverse" }}
component="nav"
>
<ListItem style={{ flex: "1", textAlign: "center" }}>
<Link to="/login" style={hrefStyle}>
<div
onMouseOver={handleLoginHover}
onMouseOut={handleLoginHoverOut}
style={{ color: LoginHoverColor, cursor: "pointer" }}
>
Login
</div>
</Link>
</ListItem>
</List>
</div>
</div>
) : (
<div style={{ display: "flex" }}>
<div style={{ flex: 1, flexDirection: "row" }}>
<List
style={{ display: "flex", flexDirect: "row", flex: "1" }}
component="nav"
>
<ListItem style={{ textAlign: "center", maxWidth: 140, }}>
<Link to="/workflows" style={hrefStyle}>
<div
onMouseOver={handleSoarHover}
onMouseOut={handleSoarHoverOut}
style={{
color: SoarHoverColor,
cursor: "pointer",
display: "flex",
}}
>
<PolymerIcon style={{ marginRight: "5px" }} />
<span style={{ marginTop: 2 }}>Workflows</span>
</div>
</Link>
</ListItem>
<ListItem style={{ textAlign: "center", maxWidth: 100, }}>
<Link to="/apps" style={hrefStyle}>
<div
onMouseOver={handleHelpHover}
onMouseOut={handleHelpHoverOut}
style={{
color: HelpHoverColor,
cursor: "pointer",
display: "flex",
}}
>
<AppsIcon style={{ marginRight: "5px" }} />
<span style={{ marginTop: 2 }}>Apps</span>
</div>
</Link>
</ListItem>
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/dashboard" style={hrefStyle}>
<div onMouseOver={handleDocsHover} onMouseOut={handleDocsHoverOut} style={{color: DocsHoverColor, cursor: "pointer"}}>Dashboard</div>
</Link>
</ListItem>
*/}
<ListItem style={{ textAlign: "center", maxWidth: 120, }}>
<Link to="/docs" style={hrefStyle}>
<div
onMouseOver={handleDocsHover}
onMouseOut={handleDocsHoverOut}
style={{
color: DocsHoverColor,
cursor: "pointer",
display: "flex",
}}
>
<DescriptionIcon style={{ marginRight: "5px" }} />
<span style={{ marginTop: 2 }}>Docs</span>
</div>
</Link>
</ListItem>
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/pricing" style={hrefStyle}>
<div onMouseOver={handleDocsHover} onMouseOut={handleDocsHoverOut} style={{color: DocsHoverColor, cursor: "pointer", display: "flex"}}>
<DescriptionIcon style={{marginRight: "5px"}} />
<span style={{marginTop: 2}}>Pricing</span>
</div>
</Link>
</ListItem>
*/}
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/configurations" style={hrefStyle}>
<div onMouseOver={handleCredentialHover} onMouseOut={handleCredentialHoverOut} style={{color: CredentialHoverColor, cursor: "pointer"}}>Configure</div>
</a>
</ListItem>
*/}
</List>
</div>
{!isLoaded ? null :
userdata.chat_disabled === true ? null :
<div style={{flex: 1, }}>
<SearchField serverside={false} userdata={userdata} />
</div>
}
<div
style={{ flex: 1, display: "flex", flexDirection: "row-reverse" }}
>
{avatarMenu}
{notificationMenu}
{userdata === undefined ||
userdata.admin === undefined ||
userdata.admin === null ||
!userdata.admin ? null : (
<Link to="/admin" style={hrefStyle}>
<Button
color="primary"
variant="outlined"
style={{ marginRight: 15, marginTop: 12 }}
>
Admin
</Button>
</Link>
)}
{userdata === undefined ||
userdata.orgs === undefined ||
userdata.orgs === null ||
userdata.orgs.length <= 1 ? null : (
<Select
SelectDisplayProps={{
style: {
marginLeft: 10,
maxWidth: 200,
overflow: "hidden",
},
}}
value={userdata.active_org.id}
fullWidth
style={{
zIndex: 10012,
marginTop: 5,
backgroundColor: theme.palette.surfaceColor,
marginRight: 15,
color: "white",
height: 50,
width: 200,
}}
MenuProps={{
style: { zIndex: 10012 },
}}
onChange={(e) => {
handleClickChangeOrg(e.target.value);
}}
>
{userdata.orgs.map((data, index) => {
if (
data.name === undefined ||
data.name === null ||
data.name.length === 0
) {
return null;
}
const imagesize = 22
//if (data.creator_org !== undefined && data.creator_org !== null && data.creator_org.length > 0 && data.fixed !== true) {
var skipOrg = false
if (data.creator_org !== undefined && data.creator_org !== null && data.creator_org.length > 0) {
// Finds the parent org
for (var key in userdata.child_orgs) {
if (data.child_orgs[key].id === data.creator_org) {
skipOrg = true
break
}
}
if (skipOrg) {
return null
}
}
// Reordering to have suborgs with access under original org
if (data.child_orgs !== undefined && data.child_orgs !== null) {
var cnt = 0
for (var key in data.child_orgs) {
const childorg = data.child_orgs[key]
const foundIndex = userdata.orgs.findIndex(item => item.id === childorg.id)
if (foundIndex !== -1) {
const newindex = parseInt(index)+parseInt(cnt)
var newitem = userdata.orgs[foundIndex]
newitem.fixed = true
userdata.orgs.splice(newindex+1, 0, newitem)
userdata.orgs.splice(foundIndex+1, 1)
} else {
console.log("ORG NOT FOUND IN LIST: ", childorg)
}
// This is stupid :)
cnt += 1
}
}
//console.log("ORG: ", data)
const imageStyle = {
width: imagesize,
height: imagesize,
pointerEvents: "none",
marginRight: 10,
marginLeft: data.creator_org !== undefined && data.creator_org !== null && data.creator_org.length > 0 ? 20 : 0,
}
const parsedTitle = data.creator_org !== undefined && data.creator_org !== null && data.creator_org.length > 0 ? `Suborg of ${data.creator_org}` : ""
const image = data.image === "" ?
<img alt={data.name} src={theme.palette.defaultImage} style={imageStyle} />
:
<img alt={data.name} src={data.image} style={imageStyle} />
return (
<MenuItem key={index} disabled={data.id === userdata.active_org.id} style={{backgroundColor: theme.palette.inputColor, color: "white", height: 40,}} value={data.id}>
<Tooltip color="primary" title={parsedTitle} placement="left">
<div style={{display: "flex"}}>
{image} {data.name}
</div>
</Tooltip>
</MenuItem>
)
})}
</Select>
)}
</div>
</div>
);
//console.log("USR: ", userdata.orgs)
const loginTextMobile = !isLoggedIn ? (
<div style={{ display: "flex" }}>
<List style={{ display: "flex", flexDirection: "row" }} component="nav">
<ListItem style={{ textAlign: "center" }}>
<Link to="/" style={hrefStyle}>
<div
onMouseOver={handleHomeHover}
onMouseOut={handleHomeHoverOut}
style={{ color: HomeHoverColor, cursor: "pointer" }}
>
<Grid container direction="row" alignItems="center">
<Grid item>
<HomeIcon style={{ marginTop: "3px", marginRight: "5px" }} />
</Grid>
</Grid>
</div>
</Link>
</ListItem>
<ListItem style={{ textAlign: "center" }}>
<Link to="/docs" style={hrefStyle}>
<div
onMouseOver={handleSoarHover}
onMouseOut={handleSoarHoverOut}
style={{ color: SoarHoverColor, cursor: "pointer" }}
>
About
</div>
</Link>
</ListItem>
</List>
</div>
) : (
<div style={{ display: "flex" }}>
<div style={{ flex: "1", flexDirection: "row" }}>
<List
style={{ display: "flex", flexDirect: "row", flex: "1" }}
component="nav"
>
<ListItem style={{ textAlign: "center" }}>
<Link to="/" style={hrefStyle}>
<div
onMouseOver={handleHomeHover}
onMouseOut={handleHomeHoverOut}
style={{ color: HomeHoverColor, cursor: "pointer" }}
>
Shuffle
</div>
</Link>
</ListItem>
<ListItem style={{ textAlign: "center" }}>
<Link to="/workflows" style={hrefStyle}>
<div
onMouseOver={handleSoarHover}
onMouseOut={handleSoarHoverOut}
style={{ color: SoarHoverColor, cursor: "pointer" }}
>
Workflows
</div>
</Link>
</ListItem>
<ListItem style={{ textAlign: "center" }}>
<Link to="/apps" style={hrefStyle}>
<div
onMouseOver={handleHelpHover}
onMouseOut={handleHelpHoverOut}
style={{ color: HelpHoverColor, cursor: "pointer" }}
>
Apps
</div>
</Link>
</ListItem>
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/configurations" style={hrefStyle}>
<div onMouseOver={handleCredentialHover} onMouseOut={handleCredentialHoverOut} style={{color: CredentialHoverColor, cursor: "pointer"}}>Configure</div>
</a>
</ListItem>
*/}
</List>
</div>
<div
style={{ flex: "10", display: "flex", flexDirection: "row-reverse" }}
>
{avatarMenu}
</div>
</div>
);
// <Divider style={{height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
const loadedCheck = (
<div style={{ minHeight: 60 }}>
<BrowserView>{loginTextBrowser}</BrowserView>
<MobileView>{loginTextMobile}</MobileView>
</div>
);
// <div style={{backgroundImage: "linear-gradient(-90deg,#342f78 0,#29255e 50%,#1b1947 100%"}}>
return (
<div
style={{
width: "100%",
position: "fixed",
minHeight: 60,
maxHeight: 60,
top: 0,
zIndex: 10000,
backgroundColor: "inherit",
}}
>
{loadedCheck}
</div>
);
};
export default Header;
+51 -29
View File
@@ -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 ?
<Link to={data.reference_url} style={{color: "#f86a3e", textDecoration: "none",}}>
<Typography variant="body1">
{data.title}
{data.title} ({data.amount})
</Typography >
</Link>
:
@@ -274,7 +278,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
:
null
}
<Typography variant="body2">
<Typography variant="body2" style={{maxHeight: 200, overflowX: "hidden", overflowY: "auto", }}>
{data.description}
</Typography >
{/*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 ?
<div style={{display: "flex", minWidth: 1250, maxWidth: 1250, margin: "auto", textAlign: "center",}}>
<div style={{display: "flex", flex: 1, }}>
@@ -668,7 +673,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
</div>
</div>
:
<div style={{display: "flex", backgroundColor: "#1f2023",}}>
<div style={{display: "flex", }}>
<div style={{minWidth: 1250, maxWidth: 1250, display: "flex", margin: "auto", }}>
<div style={{flex: 1, flexDirection: "row"}}>
<List style={{height: 56, marginTop: "auto", marginBottom: "auto", display: "flex", flexDirect: "row", alignItems: "baseline", maxWidth: 340, }} component="nav">
@@ -690,9 +695,9 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
<Link to="/workflows" style={hrefStyle}>
<div onMouseOver={handleSoarHover} onMouseOut={handleSoarHoverOut} style={{color: SoarHoverColor, cursor: "pointer", display: "flex"}}>
{/*
<PolymerIcon style={{marginRight: "5px"}} />
<PolylineIcon style={{marginRight: "5px"}} />
*/}
<span style={{marginTop: 0, marginRight: 8, }}>Workflows</span>
<Typography style={{marginTop: defaultTop, marginRight: 8, }}>Workflows</Typography>
</div>
</Link>
</ListItem>
@@ -702,7 +707,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
{/*
<AppsIcon style={{marginRight: "5px"}} />
*/}
<span style={{marginTop: 0, marginRight: 5, }}>Apps</span>
<Typography style={{marginTop: defaultTop, marginRight: 5, }}>Apps</Typography>
</div>
</Link>
</ListItem>
@@ -719,7 +724,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
{/*
<DescriptionIcon style={{marginRight: "5px"}} />
*/}
<span style={{marginTop: 0,}}>Docs</span>
<Typography style={{marginTop: defaultTop,}}>Docs</Typography>
</div>
</Link>
</ListItem>
@@ -793,8 +798,6 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
</ListItem>
: 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
<Tooltip color="primary" title={parsedTitle} placement="left">
<div style={{display: "flex"}}>
<Typography variant="body2" style={{borderRadius: theme.palette.borderRadius, float: "left", margin: "0 0 0 0", marginRight: 25, }}>{regiontag}</Typography> {image} <span style={{marginLeft: 8}}>{data.name}</span>
{isCloud?<Typography variant="body2" style={{borderRadius: theme.palette.borderRadius, float: "left", margin: "0 0 0 0", marginRight: 25, }}>{regiontag}</Typography>:null} {image} <span style={{marginLeft: 8}}>{data.name}</span>
</div>
</Tooltip>
</MenuItem>
@@ -895,13 +898,32 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
</span>
}
{/* 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) ?
<ListItem style={{textAlign: "center", marginLeft: 0, marginRight: 7, marginTop: 3, }}>
<Link to ="/pricing?tab=cloud&highlight=true" style={hrefStyle}>
<Button variant="contained" color="primary" style={{textTransform: "none"}} onClick={() => {
ReactGA.event({
category: "header",
action: "pricing_upgrade_click",
label: "",
})
}}>
Upgrade
</Button>
</Link>
</ListItem>
: null}
{userdata === undefined || userdata.app_execution_limit === undefined || userdata.app_execution_usage === undefined || userdata.app_execution_usage < 1000 ?
null
:
<Tooltip title={`Amount of executions left: ${userdata.app_execution_usage} / ${userdata.app_execution_limit}. When the limit is reached, you can still use Shuffle normally, but your Workflow triggers may stop working. Reach out to support@shuffler.io to extend this limit.`}>
<div style={{maxHeight: 30, minHeight: 30, padding: 8, textAlign: "center", cursor: "pointer", borderRadius: theme.palette.borderRadius, marginRight: 10, marginTop: 12, backgroundColor: theme.palette.surfaceColor, minWidth: 60, maxWidth: 60, }} onClick={() => {
<div style={{maxHeight: 30, minHeight: 30, padding: 8, textAlign: "center", cursor: "pointer", borderRadius: theme.palette.borderRadius, marginRight: 10, marginTop: 12, backgroundColor: theme.palette.surfaceColor, minWidth: 60, maxWidth: 60, border: userdata.app_execution_usage/userdata.app_execution_limit >= 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 =
<div style={{minHeight: 68}}>
<BrowserView>
{loginTextBrowser}
{loginTextBrowser}
</BrowserView>
<MobileView>
{loginTextMobile}
{loginTextMobile}
</MobileView>
</div>
// <div style={{backgroundImage: "linear-gradient(-90deg,#342f78 0,#29255e 50%,#1b1947 100%"}}>
return (
<div style={{backgroundColor: props.color === "undefined" ? "inherit" : props.color}}>
{loadedCheck}
<div style={{backgroundColor: theme.palette.backgroundColor, }}>
{loadedCheck}
</div>
)
}
@@ -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 = [
{
-176
View File
@@ -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 ? (<div><p onClick={setLoginCheck(false)}>Want to register? Click here.</p></div>) : (<div><p onClick={setLoginCheck(true)}>Go back to login? Click here.</p></div>);
var formtitle = loginCheck ? <div>Login</div> : <div>Register</div>;
var formButton = loginCheck ? (
<div>Click to Register</div>
) : (
<div>Click to Login</div>
);
return (
<Dialog modal open={open} onClose={onClose} {...other}>
<DialogTitle>{formtitle}</DialogTitle>
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px" }}>
Username
<div>
<TextField
required
id="standard-required"
autoComplete="username"
margin="normal"
variant="outlined"
onChange={onChangeUser}
/>
</div>
Password
<div>
<TextField
id="outlined-password-input"
type="password"
autoComplete="current-password"
margin="normal"
variant="outlined"
onChange={onChangePass}
/>
</div>
<div style={{ display: "flex", marginTop: "15px" }}>
<Button
color="secondary"
variant="contained"
type="submit"
style={{ flex: "1", marginRight: "5px" }}
disabled={!handleValidateForm()}
>
SUBMIT
</Button>
<Button
color="primary"
variant="contained"
type="button"
style={{ flex: "1" }}
onClick={onClose}
>
Cancel
</Button>
</div>
{loginInfo}
</form>
<div style={{ display: "flex" }}>
<Button
color="secondary"
variant="contained"
onClick={onClickRegister}
type="button"
style={{ flex: "1" }}
>
{formButton}
</Button>
</div>
</Dialog>
);
};
export default LoginDialog;
-213
View File
@@ -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";
//<MenuItemProps, 'button'>
//export interface NestedMenuItemProps {
// /**
// * Open state of parent `<Menu />`, 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 `<MenuItem/>`
// * element.
// */
// label: React.ReactNode;
// /**
// * @default <ArrowRight />
// */
// rightIcon: React.ReactNode;
// /**
// * Props passed to container element.
// */
// ContainerProps: React.HTMLAttributes;
// //<HTMLElement> &React.RefAttributes<HTMLElement | null>
// /**
// * Props passed to sub `<Menu/>` element
// */
// MenuProps: Omit<MenuProps, 'children'>;
// /**
// * @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 `<MenuItem>` when you need to add cascading
* menu elements as children to this component.
*/
//const NestedMenuItem = React.forwardRef<NestedMenuItemProps>(
const NestedMenuItem = (props, ref) => {
console.log(props, ref);
//function NestedMenuItem(props, ref) {
const {
parentMenuOpen,
component = "div",
label,
rightIcon = <ArrowRight />,
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<HTMLElement>) => {
setIsSubMenuOpen(true);
if (ContainerProps?.onMouseEnter) {
ContainerProps.onMouseEnter(event);
}
};
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
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<HTMLElement>) => {
if (event.target === containerRef.current) {
setIsSubMenuOpen(true);
}
if (ContainerProps?.onFocus) {
ContainerProps.onFocus(event);
}
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
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 (
<div
{...ContainerProps}
ref={containerRef}
onFocus={handleFocus}
tabIndex={tabIndex}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onKeyDown={handleKeyDown}
>
<MenuItem
{...MenuItemProps}
className={clsx(menuItemClasses.root, className)}
ref={menuItemRef}
>
{label}
{rightIcon}
</MenuItem>
<Menu
// Set pointer events to 'none' to prevent the invisible Popover div
// from capturing events for clicks and hovers
style={{ pointerEvents: "none" }}
anchorEl={menuItemRef.current}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={open}
autoFocus={false}
disableAutoFocus
disableEnforceFocus
onClose={() => {
setIsSubMenuOpen(false);
}}
>
<div ref={menuContainerRef} style={{ pointerEvents: "auto" }}>
{children}
</div>
</Menu>
</div>
);
};
export default NestedMenuItem;
-202
View File
@@ -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<MenuItemProps, 'button'> {
/**
* Open state of parent `<Menu />`, 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 `<MenuItem/>`
* element.
*/
label?: React.ReactNode
/**
* @default <ArrowRight />
*/
rightIcon?: React.ReactNode
/**
* Props passed to container element.
*/
ContainerProps?: React.HTMLAttributes<HTMLElement> &
React.RefAttributes<HTMLElement | null>
/**
* Props passed to sub `<Menu/>` element
*/
MenuProps?: Omit<MenuProps, 'children'>
/**
* @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 `<MenuItem>` 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 = <ArrowRight />,
children,
className,
tabIndex: tabIndexProp,
MenuProps = {},
ContainerProps: ContainerPropsProp = {},
...MenuItemProps
} = props
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)
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
setIsSubMenuOpen(true)
if (ContainerProps?.onMouseEnter) {
ContainerProps.onMouseEnter(event)
}
}
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
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<HTMLElement>) => {
if (event.target === containerRef.current) {
setIsSubMenuOpen(true)
}
if (ContainerProps?.onFocus) {
ContainerProps.onFocus(event)
}
}
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
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 (
<div
{...ContainerProps}
ref={containerRef}
onFocus={handleFocus}
tabIndex={tabIndex}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onKeyDown={handleKeyDown}
>
<MenuItem
{...MenuItemProps}
className={clsx(menuItemClasses.root, className)}
ref={menuItemRef}
>
{label}
{rightIcon}
</MenuItem>
<Menu
// Set pointer events to 'none' to prevent the invisible Popover div
// from capturing events for clicks and hovers
style={{pointerEvents: 'none'}}
anchorEl={menuItemRef.current}
anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
open={open}
autoFocus={false}
disableAutoFocus
disableEnforceFocus
onClose={() => {
setIsSubMenuOpen(false)
}}
>
<div ref={menuContainerRef} style={{pointerEvents: 'auto'}}>
{children}
</div>
</Menu>
</div>
)
})
export default NestedMenuItem
+106 -102
View File
@@ -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 (
<div style={{margin: "auto", color: "white", textAlign: "center",}}>
<Typography variant="h4" style={{marginTop: 35,}}>
Security Automation Newsletter
</Typography>
<Typography variant="h6" style={{color: "#7d7f82", marginTop: 20, }}>
Defensive security is 99% noise. Join us to sift through it.
</Typography>
<div style={{}}>
<TextField
style={{minWidth: isMobile ? "90%" : 450, backgroundColor: theme.palette.inputColor, marginTop: 20, borderRadius: 10, }}
InputProps={{
style:{
borderRadius: 10,
height: 60,
color: "white",
},
}}
color="primary"
value={email}
onChange={(e) => {
setEmail(e.target.value)
}}
placeholder="Your email"
id="standard-required"
margin="normal"
variant="outlined"
/>
</div>
<Button
variant="contained"
color="primary"
style={buttonStyle}
disabled={!buttonActive}
onClick={() => {
newsletterSignup(email)
ReactGA.event({
category: "newsletter",
action: `signup_click`,
label: "",
})
}}
>
Sign up
</Button>
<div/>
{msg}
</div>
)
}
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 (
<div style={{margin: "auto", color: "white", textAlign: "center",}}>
<Typography variant="h4" style={{marginTop: 35,}}>
Security Automation Newsletter
</Typography>
<Typography variant="h6" style={{color: "#7d7f82", marginTop: 20, }}>
Defensive security is 99% noise. Join us to sift through it.
</Typography>
<div style={{}}>
<TextField
style={{minWidth: isMobile ? "90%" : 450, backgroundColor: theme.palette.inputColor, marginTop: 20, borderRadius: 10, }}
InputProps={{
style:{
borderRadius: 10,
height: 60,
color: "white",
},
}}
color="primary"
value={email}
onChange={(e) => {
setEmail(e.target.value)
}}
placeholder="Your email"
id="standard-required"
margin="normal"
variant="outlined"
/>
</div>
<Button
variant="contained"
color="primary"
style={buttonStyle}
disabled={!buttonActive}
onClick={() => {
newsletterSignup(email)
ReactGA.event({
category: "newsletter",
action: `signup_click`,
label: "",
})
}}
>
Sign up
</Button>
<div/>
{msg}
</div>
)
}
export default Newsletter;
+22 -29
View File
@@ -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) => {
<div>
<DialogTitle>
<div style={{ color: "white" }}>
Authentication for {selectedApp.name}
Authenticate {selectedApp.name.replaceAll("_", " ")}
</div>
</DialogTitle>
<DialogContent>
@@ -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
<Select
multiple
underline={false}
value={selectedScopes}
style={{
backgroundColor: theme.palette.inputColor,
File diff suppressed because one or more lines are too long
+20 -17
View File
@@ -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) => {
<Tooltip title="Save any unsaved data" placement="bottom">
<Button
style={{ width: 150, height: 55, flex: 1 }}
variant="contained"
variant="outlined"
color="primary"
disabled={
userdata === undefined || userdata === null || userdata.admin !== "true"
@@ -205,13 +208,13 @@ const OrgHeader = (props) => {
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");
return;
}
}
if (e.target.value.length > 100) {
alert.error("Choose a shorter name.");
toast("Choose a shorter name.");
return;
}
+213 -42
View File
@@ -1,8 +1,8 @@
import React, { useEffect } from "react";
import { makeStyles } from "@material-ui/styles";
import { useTheme } from "@material-ui/core/styles";
import { useAlert } from "react-alert";
import { makeStyles } from "@mui/styles";
import theme from '../theme.jsx';
import { toast } from "react-toastify"
import {
FormControl,
@@ -23,12 +23,15 @@ import {
Tabs,
Tab,
Grid,
} from "@material-ui/core";
IconButton,
Autocomplete,
} from "@mui/material";
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 {
ExpandLess as ExpandLessIcon,
ExpandMore as ExpandMoreIcon,
Save as SaveIcon,
} from "@mui/icons-material";
const useStyles = makeStyles({
notchedOutline: {
@@ -46,8 +49,6 @@ const OrgHeaderexpanded = (props) => {
adminTab,
} = props;
const theme = useTheme();
const alert = useAlert();
const classes = useStyles();
const defaultBranch = "master";
@@ -154,6 +155,47 @@ const OrgHeaderexpanded = (props) => {
: selectedOrganization.sso_config.openid_token
)
const [workflows, setWorkflows] = React.useState([])
const [workflow, setWorkflow] = React.useState({})
const getAvailableWorkflows = (trigger_index) => {
fetch(globalUrl + "/api/v1/workflows", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!");
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson !== undefined) {
setWorkflows(responseJson)
if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) {
const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow)
if (workflow !== undefined && workflow !== null) {
setWorkflow(workflow)
}
}
}
})
.catch((error) => {
console.log("Error getting workflows: " + error);
})
}
useEffect(() => {
getAvailableWorkflows()
}, [])
const handleEditOrg = (
name,
description,
@@ -187,19 +229,32 @@ const OrgHeaderexpanded = (props) => {
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
alert.error("Failed updating org: ", responseJson.reason);
toast("Failed updating org: ", responseJson.reason);
} else {
alert.success("Successfully edited org!");
toast("Successfully edited org!");
}
})
)
.catch((error) => {
alert.error("Err: " + error.toString());
toast("Err: " + error.toString());
});
};
const handleWorkflowSelectionUpdate = (e, isUserinput) => {
if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
console.log("Returning as there's no id")
return null
}
setWorkflow(e.target.value)
setNotificationWorkflow(e.target.value.id)
toast("Updated notification workflow. Don't forget to save!")
}
const orgSaveButton = (
<Tooltip title="Save any unsaved data" placement="bottom">
<div>
<Button
style={{ width: 150, height: 55, flex: 1 }}
variant="contained"
@@ -236,6 +291,7 @@ const OrgHeaderexpanded = (props) => {
>
<SaveIcon />
</Button>
</div>
</Tooltip>
);
@@ -244,34 +300,149 @@ const OrgHeaderexpanded = (props) => {
<Grid container spacing={3} style={{ textAlign: "left" }}>
<Grid item xs={12} style={{}}>
<span>
<Typography>Notification Workflow ID</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
placeholder="ID of the workflow to receive notifications"
value={notificationWorkflow}
onChange={(e) => {
setNotificationWorkflow(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
<Typography>Notification Workflow</Typography>
{/*
<Typography variant="body2" color="textSecondary">
Add a Workflow that receives notifications from Shuffle when an error occurs in one of your workflows
</Typography>
*/}
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
{workflows !== undefined && workflows !== null && workflows.length > 0 ?
<Autocomplete
id="notification_workflow_search"
autoHighlight
freeSolo
//autoSelect
value={workflow}
classes={{ inputRoot: classes.inputRoot }}
ListboxProps={{
style: {
backgroundColor: theme.palette.inputColor,
color: "white",
},
}}
getOptionLabel={(option) => {
if (
option === undefined ||
option === null ||
option.name === undefined ||
option.name === null
) {
return "No Workflow Selected";
}
const newname = (
option.name.charAt(0).toUpperCase() + option.name.substring(1)
).replaceAll("_", " ");
return newname;
}}
options={workflows}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
}}
onChange={(event, newValue) => {
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 (
<Tooltip arrow placement="left" title={
<span style={{}}>
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
<img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
: null}
<Typography>
Choose {data.name}
</Typography>
</span>
} placement="bottom">
<MenuItem
style={{
backgroundColor: theme.palette.inputColor,
color: data.id === workflow.id ? "red" : "white",
}}
value={data}
onClick={(e) => {
var parsedinput = { target: { value: data } }
handleWorkflowSelectionUpdate(parsedinput)
}}
>
{data.name}
</MenuItem>
</Tooltip>
)
}}
renderInput={(params) => {
return (
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find a notification workflow"
variant="outlined"
/>
);
}}
/>
:
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
placeholder="ID of the workflow to receive notifications"
value={notificationWorkflow}
onChange={(e) => {
setNotificationWorkflow(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
}
<div style={{minWidth: 150, maxWidth: 150, marginTop: 5, marginLeft: 10, }}>
{orgSaveButton}
</div>
</div>
</span>
</Grid>
<Grid item xs={12} style={{}}>
@@ -699,4 +870,4 @@ const OrgHeaderexpanded = (props) => {
)
}
export default OrgHeaderexpanded;
export default OrgHeaderexpanded;
+1 -1
View File
@@ -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 (
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -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}
/>
)
})
+104 -56
View File
@@ -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 (
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", height: 70, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", minHeight: isCloud ? 70 : 100, maxHeight: isCloud ? 70 : 100, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
<div style={{flex: 2, overflow: "hidden",}}>
<span style={{display: "flex", }}>
{priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null}
@@ -77,17 +112,17 @@ const Priority = (props) => {
</span>
{priority.type === "usecase" && priority.description.includes("&") ?
<span style={{display: "flex", marginTop: 10, }}>
<img src={priority.description.split("&")[1]} alt={priority.name} style={{height: 30, width: 30, marginRight: 5, borderRadius: theme.palette.borderRadius, marginRight: 10, }} />
<img src={newdescription.split("&")[1]} alt={priority.name} style={{height: "auto", width: 30, marginRight: realigned ? -10 : 10, borderRadius: theme.palette.borderRadius, marginTop: realigned ? 5 : 0 }} />
<Typography variant="body2" color="textSecondary" style={{marginTop: 3, }}>
{priority.description.split("&")[0]}
{newdescription.split("&")[0]}
</Typography>
{priority.description.split("&").length > 3 ?
{newdescription.split("&").length > 3 ?
<span style={{display: "flex", }}>
<ArrowForwardIcon style={{marginLeft: 15, marginRight: 15, }}/>
<img src={priority.description.split("&")[3]} alt={priority.name+"2"} style={{height: 30, width: 30, borderRadius: theme.palette.borderRadius, marginRight: 10, }} />
<img src={newdescription.split("&")[3]} alt={priority.name+"2"} style={{height: "auto", width: 30, marginRight: realigned ? -10 : 10, borderRadius: theme.palette.borderRadius, marginTop: realigned ? 5 : 0 }} />
<Typography variant="body2" color="textSecondary" style={{marginTop: 3}}>
{priority.description.split("&")[2]}
{newdescription.split("&")[2]}
</Typography>
</span>
: null}
@@ -100,17 +135,30 @@ const Priority = (props) => {
}
</div>
<div style={{flex: 1, display: "flex", marginLeft: 30, }}>
<Button style={{height: 50, borderRadius: 25, marginTop: 8, width: 175, marginRight: 10, color: priority.active === false ? "white" : "black", backgroundColor: priority.active === false ? theme.palette.inputColor : "white", }} variant="contained" color="secondary" onClick={() => {
/*
ReactGA.event({
category: "",
action: `partner_${partner.name}_click`,
label: "",
})
*/
<Button style={{height: 50, borderRadius: 25, marginTop: 8, width: 175, marginRight: 10, color: priority.active === false ? "white" : "black", backgroundColor: priority.active === false ? theme.palette.inputColor : "rgba(255,255,255,0.8)", }} variant="contained" color="secondary" onClick={() => {
if (isCloud) {
ReactGA.event({
category: "recommendation",
action: `click_${priority.name}`,
label: "",
})
}
navigate(priority.url)
if (setAdminTab !== undefined && setCurTab !== undefined) {
if (priority.description.toLowerCase().includes("notification workflow")) {
setCurTab(0)
setAdminTab(0)
}
if (priority.description.toLowerCase().includes("hybrid shuffle")) {
setCurTab(6)
}
}
}}>
explore
Explore
</Button>
{priority.active === true ?
<Button style={{borderRadius: 25, width: 100, height: 50, marginTop: 8, }} variant="text" color="secondary" onClick={() => {
+15 -1
View File
@@ -2,6 +2,20 @@ import { useEffect } from "react";
//import { withRouter } from "react-router-dom";
import { useLocation } from "react-router-dom";
export const removeQuery = (query) => {
const urlSearchParams = new URLSearchParams(window.location.search)
const params = Object.fromEntries(urlSearchParams.entries())
if (params[query] !== undefined) {
delete params[query]
} else {
return
}
const queryString = Object.keys(params).map(key => key + '=' + params[key]).join('&')
const newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + queryString
window.history.pushState({path:newurl},'',newurl)
}
// ensures scrolling happens in the right way on different pages and when changing
function ScrollToTop({ getUserNotifications, curpath, setCurpath, history }) {
let location = useLocation();
@@ -9,7 +23,7 @@ function ScrollToTop({ getUserNotifications, curpath, setCurpath, history }) {
useEffect(() => {
// Custom handler for certain scroll mechanics
//
console.log("OLD: ", curpath, "NeW: ", window.location.pathname)
//console.log("OLD: ", curpath, "NeW: ", window.location.pathname)
if (curpath === window.location.pathname && curpath === "/usecases") {
} else {
-648
View File
@@ -1,648 +0,0 @@
import React, {useState, useEffect, useRef} from 'react';
import { useNavigate, Link, useParams } from "react-router-dom";
import { useTheme } from '@material-ui/core/styles';
import SearchIcon from '@material-ui/icons/Search';
import {
Chip,
IconButton,
TextField,
InputAdornment,
List,
Card,
ListItem,
ListItemAvatar,
ListItemText,
Avatar,
Typography,
Tooltip,
} from '@material-ui/core';
import {
AvatarGroup,
} from "@mui/material"
import {Close as CloseIcon, Folder as FolderIcon, Polymer as PolymerIcon, LibraryBooks as LibraryBooksIcon} from '@material-ui/icons'
import algoliasearch from 'algoliasearch/lite';
import aa from 'search-insights'
import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom';
//import { InstantSearch, SearchBox, Hits, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom';
// https://www.algolia.com/doc/api-reference/widgets/search-box/react/
const chipStyle = {
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
}
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
const SearchField = props => {
const { serverside, userdata } = props
const theme = useTheme();
let navigate = useNavigate();
const borderRadius = 3
const node = useRef()
const [searchOpen, setSearchOpen] = useState(false)
const [oldPath, setOldPath] = useState("")
if (serverside === true) {
return null
}
if (window !== undefined && window.location !== undefined && window.location.pathname === "/search") {
return null
}
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
if (window.location.pathname !== oldPath) {
setSearchOpen(false)
setOldPath(window.location.pathname)
}
//useEffect(() => {
// if (searchOpen) {
// var tarfield = document.getElementById("shuffle_search_field")
// tarfield.focus()
// }
//}, searchOpen)
const SearchBox = ({currentRefinement, refine, isSearchStalled, } ) => {
/*
endAdornment: (
<InputAdornment position="end" style={{textAlign: "right", zIndex: 5001, cursor: "pointer", width: 100, }} onMouseOver={(event) => {
event.preventDefault()
}}>
<CloseIcon style={{marginRight: 5,}} onClick={() => {
setSearchOpen(false)
}} />
</InputAdornment>
),
*/
return (
<form id="search_form" noValidate type="searchbox" action="" role="search" style={{margin: 0, }} onClick={() => {
}}>
<TextField
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
InputProps={{
style:{
color: "white",
fontSize: "1em",
height: 50,
margin: 0,
},
startAdornment: (
<InputAdornment position="start">
<SearchIcon style={{marginLeft: 5}}/>
</InputAdornment>
),
}}
autoComplete='off'
type="search"
color="primary"
placeholder="Find Public Apps, Workflows, Documentation and more"
value={currentRefinement}
id="shuffle_search_field"
onClick={(event) => {
if (!searchOpen) {
setSearchOpen(true)
setTimeout(() => {
var tarfield = document.getElementById("shuffle_search_field")
//console.log("TARFIELD: ", tarfield)
tarfield.focus()
}, 100)
}
}}
onBlur={(event) => {
setTimeout(() => {
setSearchOpen(false)
}, 500)
}}
onChange={(event) => {
//if (event.currentTarget.value.length > 0 && !searchOpen) {
// setSearchOpen(true)
//}
refine(event.currentTarget.value)
}}
limit={5}
/>
{/*isSearchStalled ? 'My search is stalled' : ''*/}
</form>
)
}
const WorkflowHits = ({ hits }) => {
const [mouseHoverIndex, setMouseHoverIndex] = useState(0)
var tmp = searchOpen
if (!searchOpen) {
return null
}
const positionInfo = document.activeElement.getBoundingClientRect()
const outerlistitemStyle = {
width: "100%",
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
}
if (hits.length > 4) {
hits = hits.slice(0, 4)
}
var type = "workflows"
const baseImage = <PolymerIcon />
return (
<Card elevation={0} style={{position: "relative", marginLeft: 10, marginRight: 10, position: "absolute", color: "white", zIndex: 1002, backgroundColor: theme.palette.inputColor, width: 405, height: 408, left: 75, boxShadows: "none",}}>
<Typography variant="h6" style={{margin: "10px 10px 0px 20px", }}>
Workflows
</Typography>
<List style={{backgroundColor: theme.palette.inputColor, }}>
{hits.length === 0 ?
<ListItem style={outerlistitemStyle}>
<ListItemAvatar onClick={() => console.log(hits)}>
<Avatar>
<FolderIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={"No workflows found."}
secondary={"Try a broader search term"}
/>
</ListItem>
:
hits.map((hit, index) => {
const innerlistitemStyle = {
width: positionInfo.width+35,
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit",
cursor: "pointer",
marginLeft: 5,
marginRight: 5,
maxHeight: 75,
minHeight: 75,
maxWidth: 420,
minWidth: "100%",
}
const name = hit.name === undefined ?
hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title :
(hit.name.charAt(0).toUpperCase()+hit.name.slice(1)).replaceAll("_", " ")
const secondaryText = hit.description !== undefined && hit.description !== null && hit.description.length > 3 ? hit.description.slice(0, 40)+"..." : ""
const appGroup = hit.action_references === undefined || hit.action_references === null ? [] : hit.action_references
const avatar = baseImage
var parsedUrl = isCloud ? `/workflows/${hit.objectID}` : `https://shuffler.io/workflows/${hit.objectID}`
parsedUrl += `?queryID=${hit.__queryID}`
// <a rel="noopener noreferrer" href="https://www.algolia.com/" target="_blank" style={{textDecoration: "none", color: "white"}}>
return (
<Link key={hit.objectID} to={{ pathname: parsedUrl }} rel="noopener noreferrer" style={{textDecoration: "none", color: "white",}} onClick={(event) => {
//console.log("CLICK")
setSearchOpen(true)
aa('init', {
appId: searchClient.appId,
apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"]
})
const timestamp = new Date().getTime()
aa('sendEvents', [
{
eventType: 'click',
eventName: 'Workflow Clicked',
index: 'workflows',
objectIDs: [hit.objectID],
timestamp: timestamp,
queryID: hit.__queryID,
positions: [hit.__position],
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
if (!isCloud) {
event.preventDefault()
window.open(parsedUrl, '_blank');
}
}}>
<ListItem key={hit.objectID} style={innerlistitemStyle} onMouseOver={() => {
setMouseHoverIndex(index)
}}>
<ListItemAvatar>
{avatar}
</ListItemAvatar>
<div style={{}}>
<ListItemText
primary={name}
/>
<AvatarGroup max={10} style={{flexDirection: "row", padding: 0, margin: 0, itemAlign: "left", textAlign: "left",}}>
{appGroup.map((app, index) => {
// Putting all this in secondary of ListItemText looked weird.
return (
<div
key={index}
style={{
height: 24,
width: 24,
filter: "brightness(0.6)",
cursor: "pointer",
}}
onClick={() => {
navigate("/apps/"+app.id)
}}
>
<Tooltip color="primary" title={app.name} placement="bottom">
<Avatar alt={app.name} src={app.image_url} style={{width: 24, height: 24}}/>
</Tooltip>
</div>
)
})}
</AvatarGroup>
</div>
{/*
<ListItemSecondaryAction>
<IconButton edge="end" aria-label="delete">
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
*/}
</ListItem>
</Link>
)})
}
</List>
{/*
<span style={{display: "flex", textAlign: "left", float: "left", position: "absolute", left: 15, bottom: 10, }}>
<Link to="/search" style={{textDecoration: "none", color: "#f85a3e"}}>
<Typography variant="body2" style={{}}>
See all workflows
</Typography>
</Link>
</span>
*/}
</Card>
)
}
const AppHits = ({ hits }) => {
const [mouseHoverIndex, setMouseHoverIndex] = useState(0)
var tmp = searchOpen
if (!searchOpen) {
return null
}
const positionInfo = document.activeElement.getBoundingClientRect()
const outerlistitemStyle = {
width: "100%",
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
}
if (hits.length > 4) {
hits = hits.slice(0, 4)
}
var type = "app"
const baseImage = <LibraryBooksIcon />
return (
<Card elevation={0} style={{position: "relative", marginLeft: 10, marginRight: 10, position: "absolute", color: "white", zIndex: 1001, backgroundColor: theme.palette.inputColor, width: 1155, height: 408, left: -305, boxShadows: "none",}}>
<IconButton style={{zIndex: 5000, position: "absolute", right: 14, color: "grey"}} onClick={() => {
setSearchOpen(false)
}}>
<CloseIcon />
</IconButton>
<Typography variant="h6" style={{margin: "10px 10px 0px 20px", }}>
Apps
</Typography>
<List style={{backgroundColor: theme.palette.inputColor, }}>
{hits.length === 0 ?
<ListItem style={outerlistitemStyle}>
<ListItemAvatar onClick={() => console.log(hits)}>
<Avatar>
<FolderIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={"No apps found."}
secondary={"Try a broader search term"}
/>
</ListItem>
:
hits.map((hit, index) => {
const innerlistitemStyle = {
width: positionInfo.width+35,
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit",
cursor: "pointer",
marginLeft: 5,
marginRight: 5,
maxHeight: 75,
minHeight: 75,
maxWidth: 420,
minWidth: "100%",
}
const name = hit.name === undefined ?
hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title :
(hit.name.charAt(0).toUpperCase()+hit.name.slice(1)).replaceAll("_", " ")
var secondaryText = hit.data !== undefined ? hit.data.slice(0, 40)+"..." : ""
const avatar = hit.image_url === undefined ?
baseImage
:
<Avatar
src={hit.image_url}
variant="rounded"
/>
//console.log(hit)
if (hit.categories !== undefined && hit.categories !== null && hit.categories.length > 0) {
secondaryText = hit.categories.slice(0,3).map((data, index) => {
if (index === 0) {
return data
}
return ", "+data
/*
<Chip
key={index}
style={chipStyle}
label={data}
onClick={() => {
//handleChipClick
}}
variant="outlined"
color="primary"
/>
*/
})
}
var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}`
parsedUrl += `?queryID=${hit.__queryID}`
return (
<Link key={hit.objectID} to={{ pathname: parsedUrl }} style={{textDecoration: "none", color: "white",}} onClick={(event) => {
console.log("CLICK")
setSearchOpen(true)
aa('init', {
appId: searchClient.appId,
apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"]
})
const timestamp = new Date().getTime()
aa('sendEvents', [
{
eventType: 'click',
eventName: 'App Clicked',
index: 'appsearch',
objectIDs: [hit.objectID],
timestamp: timestamp,
queryID: hit.__queryID,
positions: [hit.__position],
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
if (!isCloud) {
event.preventDefault()
window.open(parsedUrl, '_blank');
}
}}>
<ListItem key={hit.objectID} style={innerlistitemStyle} onMouseOver={() => {
setMouseHoverIndex(index)
}}>
<ListItemAvatar>
{avatar}
</ListItemAvatar>
<ListItemText
primary={name}
secondary={secondaryText}
/>
{/*
<ListItemSecondaryAction>
<IconButton edge="end" aria-label="delete">
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
*/}
</ListItem>
</Link>
)})
}
</List>
<span style={{display: "flex", textAlign: "left", float: "left", position: "absolute", left: 15, bottom: 10, }}>
<Link to="/search" style={{textDecoration: "none", color: "#f85a3e"}}>
<Typography variant="body1" style={{}}>
See more
</Typography>
</Link>
</span>
</Card>
)
}
const DocHits = ({ hits }) => {
const [mouseHoverIndex, setMouseHoverIndex] = useState(0)
var tmp = searchOpen
if (!searchOpen) {
return null
}
const positionInfo = document.activeElement.getBoundingClientRect()
const outerlistitemStyle = {
width: "100%",
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
}
if (hits.length > 4) {
hits = hits.slice(0, 4)
}
const type = "documentation"
const baseImage = <LibraryBooksIcon />
//console.log(type, hits.length, hits)
return (
<Card elevation={0} style={{position: "relative", marginLeft: 10, marginRight: 10, position: "absolute", color: "white", zIndex: 1002, backgroundColor: theme.palette.inputColor, width: 405, height: 408, left: 470, boxShadows: "none",}}>
<IconButton style={{zIndex: 5000, position: "absolute", right: 14, color: "grey"}} onClick={() => {
setSearchOpen(false)
}}>
<CloseIcon />
</IconButton>
<Typography variant="h6" style={{margin: "10px 10px 0px 20px", }}>
Documentation
</Typography>
{/*
<IconButton edge="end" aria-label="delete" style={{position: "absolute", top: 5, right: 15,}} onClick={() => {
setSearchOpen(false)
}}>
<DeleteIcon />
</IconButton>
*/}
<List style={{backgroundColor: theme.palette.inputColor, }}>
{hits.length === 0 ?
<ListItem style={outerlistitemStyle}>
<ListItemAvatar onClick={() => console.log(hits)}>
<Avatar>
<FolderIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={"No documentation."}
secondary={"Try a broader search term"}
/>
</ListItem>
:
hits.map((hit, index) => {
const innerlistitemStyle = {
width: positionInfo.width+35,
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit",
cursor: "pointer",
marginLeft: 5,
marginRight: 5,
maxHeight: 75,
minHeight: 75,
maxWidth: 420,
minWidth: "100%",
}
var name = hit.name === undefined ?
hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title
:
(hit.name.charAt(0).toUpperCase()+hit.name.slice(1)).replaceAll("_", " ")
if (name.length > 30) {
name = name.slice(0, 30)+"..."
}
const secondaryText = hit.data !== undefined ? hit.data.slice(0, 40)+"..." : ""
const avatar = hit.image_url === undefined ?
baseImage
:
<Avatar
src={hit.image_url}
variant="rounded"
/>
var parsedUrl = hit.urlpath !== undefined ? hit.urlpath : ""
parsedUrl += `?queryID=${hit.__queryID}`
if (parsedUrl.includes("/apps/")) {
const extraHash = hit.url_hash === undefined ? "" : `#${hit.url_hash}`
parsedUrl = `/apps/${hit.filename}?tab=docs&queryID=${hit.__queryID}${extraHash}`
}
return (
<Link key={hit.objectID} to={parsedUrl} style={{textDecoration: "none", color: "white",}} onClick={(event) => {
aa('init', {
appId: searchClient.appId,
apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"]
})
const timestamp = new Date().getTime()
aa('sendEvents', [
{
eventType: 'click',
eventName: 'Document Clicked',
index: 'documentation',
objectIDs: [hit.objectID],
timestamp: timestamp,
queryID: hit.__queryID,
positions: [hit.__position],
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
console.log("CLICK")
setSearchOpen(true)
}}>
<ListItem key={hit.objectID} style={innerlistitemStyle} onMouseOver={() => {
setMouseHoverIndex(index)
}}>
<ListItemAvatar>
{avatar}
</ListItemAvatar>
<ListItemText
primary={name}
secondary={secondaryText}
/>
{/*
<ListItemSecondaryAction>
<IconButton edge="end" aria-label="delete">
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
*/}
</ListItem>
</Link>
)})
}
</List>
{type === "documentation" ?
<span style={{display: "flex", textAlign: "right", position: "absolute", right: 15, bottom: 10,}}>
<Typography variant="body2" style={{}}>
Search by
</Typography>
<a rel="noopener noreferrer" href="https://www.algolia.com/" target="_blank" style={{textDecoration: "none", color: "white"}}>
<img src={"/images/logo-algolia-nebula-blue-full.svg"} alt="Algolia logo" style={{height: 17, marginLeft: 5, marginTop: 3,}} />
</a>
</span>
: null}
</Card>
)
}
const CustomSearchBox = connectSearchBox(SearchBox)
const CustomAppHits = connectHits(AppHits)
const CustomWorkflowHits = connectHits(WorkflowHits)
const CustomDocHits = connectHits(DocHits)
return (
<div ref={node} style={{width: "100%", maxWidth: 425, margin: "auto", position: "relative", zIndex: 12500,}}>
<InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
console.log("CLICKED")
}}>
<Configure clickAnalytics />
<CustomSearchBox />
<Index indexName="appsearch">
<CustomAppHits />
</Index>
<Index indexName="documentation">
<CustomDocHits />
</Index>
<Index indexName="workflows">
<CustomWorkflowHits />
</Index>
</InstantSearch>
</div>
)
}
export default SearchField;
+18 -10
View File
@@ -1,8 +1,7 @@
import React, {useState, useEffect, useRef} from 'react';
import theme from '../theme.jsx';
import { useNavigate, Link, useParams } from "react-router-dom";
import { useTheme } from '@material-ui/core/styles';
import SearchIcon from '@material-ui/icons/Search';
import {
Chip,
@@ -17,13 +16,13 @@ import {
Avatar,
Typography,
Tooltip,
} from '@material-ui/core';
} from '@mui/material';
import {
AvatarGroup,
} from "@mui/material"
import {Close as CloseIcon, Folder as FolderIcon, Polymer as PolymerIcon, LibraryBooks as LibraryBooksIcon} from '@material-ui/icons'
import {Search as SearchIcon, Close as CloseIcon, Folder as FolderIcon, Code as CodeIcon, LibraryBooks as LibraryBooksIcon} from '@mui/icons-material'
import algoliasearch from 'algoliasearch/lite';
import aa from 'search-insights'
@@ -39,12 +38,12 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
const SearchField = props => {
const { serverside, userdata } = props
const theme = useTheme();
let navigate = useNavigate();
const borderRadius = 3
const node = useRef()
const [searchOpen, setSearchOpen] = useState(false)
const [oldPath, setOldPath] = useState("")
const [value, setValue] = useState("");
if (serverside === true) {
return null
@@ -69,7 +68,13 @@ const SearchField = props => {
//}, searchOpen)
const SearchBox = ({currentRefinement, refine, isSearchStalled, } ) => {
const keyPressHandler = (e) => {
// e.preventDefault();
if (e.which === 13) {
// alert("You pressed enter!");
navigate("/search?q=" + currentRefinement, { state: value, replace: true });
}
};
/*
endAdornment: (
<InputAdornment position="end" style={{textAlign: "right", zIndex: 5001, cursor: "pointer", width: 100, }} onMouseOver={(event) => {
@@ -110,6 +115,7 @@ const SearchField = props => {
color="primary"
placeholder="Find Public Apps, Workflows, Documentation..."
value={currentRefinement}
onKeyDown={keyPressHandler}
id="shuffle_search_field"
onClick={(event) => {
if (!searchOpen) {
@@ -162,7 +168,7 @@ const SearchField = props => {
}
var type = "workflows"
const baseImage = <PolymerIcon />
const baseImage = <CodeIcon />
return (
<Card elevation={0} style={{position: "relative", marginLeft: 10, marginRight: 10, position: "absolute", color: "white", zIndex: 1002, backgroundColor: theme.palette.inputColor, width: 405, height: 408, left: 75, boxShadows: "none",}}>
@@ -208,11 +214,12 @@ const SearchField = props => {
const avatar = baseImage
var parsedUrl = isCloud ? `/workflows/${hit.objectID}` : `https://shuffler.io/workflows/${hit.objectID}`
parsedUrl += `?queryID=${hit.__queryID}`
// <a rel="noopener noreferrer" href="https://www.algolia.com/" target="_blank" style={{textDecoration: "none", color: "white"}}>
return (
<Link key={hit.objectID} to={{ pathname: parsedUrl }} rel="noopener noreferrer" style={{textDecoration: "none", color: "white",}} onClick={(event) => {
<Link key={hit.objectID} to={parsedUrl} rel="noopener noreferrer" style={{textDecoration: "none", color: "white",}} onClick={(event) => {
//console.log("CLICK")
setSearchOpen(true)
@@ -404,7 +411,7 @@ const SearchField = props => {
parsedUrl += `?queryID=${hit.__queryID}`
return (
<Link key={hit.objectID} to={{ pathname: parsedUrl }} style={{textDecoration: "none", color: "white",}} onClick={(event) => {
<Link key={hit.objectID} to={parsedUrl} style={{textDecoration: "none", color: "white",}} onClick={(event) => {
console.log("CLICK")
setSearchOpen(true)
@@ -560,7 +567,8 @@ const SearchField = props => {
if (parsedUrl.includes("/apps/")) {
const extraHash = hit.url_hash === undefined ? "" : `#${hit.url_hash}`
parsedUrl = `/apps/${hit.filename}?tab=docs&queryID=${hit.__queryID}${extraHash}`
parsedUrl = `/apps/${hit.filename}`
parsedUrl += `?tab=docs&queryID=${hit.__queryID}${extraHash}`
}
return (
@@ -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 = [
{
-177
View File
@@ -1,177 +0,0 @@
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";
import Divider from "@material-ui/core/Divider";
const SettingsDialog = (props) => {
const {
classes,
onClose,
settingsOpen,
settingsData,
globalUrl,
isLoggedIn,
setIsLoggedIn,
...other
} = props;
const [password1, setPassword1] = useState("");
const [password2, setPassword2] = useState("");
const [password3, setPassword3] = useState("");
const handleValidateForm = () => {
var passlength = 10;
if (
password1 === password2 &&
password1.length >= passlength &&
password3.length >= passlength
) {
return true;
}
return false;
};
const onChangePass1 = (e) => {
setPassword1(e.target.value);
};
const onChangePass2 = (e) => {
setPassword2(e.target.value);
};
const onChangePass3 = (e) => {
setPassword3(e.target.value);
};
const onSubmitPassReset = () => {
console.log("Should change password");
// Rofl, this can't possibly be typesafe
var data =
'{"password1": "' +
password1 +
'", "password2": "' +
password2 +
'", "password3": "' +
password3 +
'"}';
fetch(globalUrl + "/passwordreset", {
body: data,
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
if (responseJson.status === true) {
console.log("SUCCESS");
}
})
.catch((error) => {
console.log(error);
});
};
//PaperProps={{style: {minWidth: "500px"}}
return (
<Dialog open={settingsOpen} onClose={() => onClose()} {...other}>
<DialogTitle>Settings</DialogTitle>
<Divider />
<div style={{ marginLeft: "15px", marginRight: "15px" }}>
<h3>Username</h3>
{settingsData.username}
</div>
<div
style={{
marginLeft: "15px",
marginRight: "15px",
marginBottom: "15px",
}}
>
<h3>ApiKey</h3>
<TextField
id="outlined-read-only-input"
defaultValue={settingsData.apikey}
value={settingsData.apikey}
style={{ width: 320 }}
InputProps={{
readOnly: true,
}}
variant="outlined"
/>
</div>
<Divider />
<form style={{ margin: "15px 15px 15px 15px" }}>
<h3>Change password</h3>
<div>
<TextField
id="standard-password-input"
label="Current password"
type="password"
name="password"
style={{ width: 320 }}
placeholder="********************************"
autoComplete="current-password"
margin="normal"
variant="outlined"
onChange={onChangePass1}
/>
</div>
<div>
<TextField
label="Confirm current password"
type="password"
placeholder="********************************"
name="password"
style={{ width: 320 }}
autoComplete="current-password"
margin="normal"
variant="outlined"
onChange={onChangePass2}
/>
</div>
<div>
<TextField
label="New password"
type="password"
name="password"
placeholder="********************************"
style={{ width: 320 }}
margin="normal"
variant="outlined"
onChange={onChangePass3}
/>
</div>
<div style={{ display: "flex", marginTop: "10px" }}>
<Button
color="secondary"
variant="contained"
onClick={onSubmitPassReset}
type="button"
style={{ flex: "1", marginRight: "5px" }}
disabled={!handleValidateForm()}
>
SUBMIT
</Button>
<Button
color="primary"
variant="contained"
type="button"
style={{ flex: "1" }}
onClick={onClose}
>
Cancel
</Button>
</div>
</form>
</Dialog>
);
};
export default SettingsDialog;
+112 -92
View File
@@ -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,
},
}}
>
<IconButton
style={{
zIndex: 5000,
position: "absolute",
top: 14,
right: 18,
color: "grey",
}}
onClick={() => {
setExpansionModalOpen(false)
}}
>
<CloseIcon />
</IconButton>
<div style={{display: "flex"}}>
<div style={{flex: 1, }}>
{ 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",
}}>
<CodeMirror
value = {localcodedata}
value={localcodedata}
height={isFileEditor ? 450 : 525}
width={isFileEditor ? 650 : 600}
style={{
@@ -1389,6 +1406,8 @@ const CodeEditor = (props) => {
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,
}}
/>
</span>
@@ -1553,7 +1567,7 @@ const CodeEditor = (props) => {
<IconButton disabled={executing} color="primary" style={{border: `1px solid ${theme.palette.primary.main}`, marginLeft: 100, padding: 8}} variant="contained" onClick={() => {
executeSingleAction(expOutput)
}}>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
{executing ? <CircularProgress style={{height: 18, width: 18, }} /> : <PlayArrowIcon style={{height: 18, width: 18, }} /> }
</Tooltip>
@@ -1664,34 +1678,29 @@ const CodeEditor = (props) => {
<div style={{display: 'flex',}}>
<button
<Button
style={{
color: "white",
background: "#383b49",
border: "none",
height: 35,
flex: 1,
marginLeft: 5,
marginTop: 5,
cursor: "pointer"
}}
variant="outlined"
color="secondary"
onClick={() => {
setExpansionModalOpen(false);
}}
>
Cancel
</button>
<button
</Button>
<Button
variant="contained"
color="primary"
style={{
color: "white",
background: "#f85a3e",
border: "none",
height: 35,
flex: 1,
marginLeft: 10,
marginTop: 5,
cursor: "pointer"
}}
onClick={(event) => {
// Take localcodedata through the Shuffle JSON parser just in case
@@ -1709,15 +1718,26 @@ const CodeEditor = (props) => {
runUpdateText(fixedcodedata);
setcodedata(fixedcodedata);
setExpansionModalOpen(false)
} else {
changeActionParameterCodeMirror(event, fieldCount, fixedcodedata)
} else if (changeActionParameterCodeMirror !== undefined) {
//changeActionParameterCodeMirror(event, fieldCount, fixedcodedata)
changeActionParameterCodeMirror(event, fieldCount, fixedcodedata, actionlist)
setExpansionModalOpen(false)
setcodedata(fixedcodedata)
}
// Check if fieldname is set, and try to find and inject the text
if (fieldname !== undefined && fieldname !== null && fieldname.length > 0) {
const foundfield = document.getElementById(fieldname)
if (foundfield !== undefined && foundfield !== null) {
foundfield.value = fixedcodedata
}
}
setExpansionModalOpen(false)
}}
>
Submit
</button>
</Button>
</div>
</Dialog>)
}
@@ -13,7 +13,7 @@ import {
CircularProgress,
Tooltip,
Dialog,
} from "@material-ui/core";
} from "@mui/material";
import {
Close as CloseIcon,
+22 -21
View File
@@ -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 (
<div key={curindex} style={{display: "flex", maxHeight: 40, minHeight: 40, borderTop: "1px solid rgba(255,255,255,0.3)", }} onClick={() => {
if (subdata.disabled === true) {
//alert.info("Usecase not available yet.")
//toast("Usecase not available yet.")
return
}
File diff suppressed because it is too large Load Diff
+63 -46
View File
@@ -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 (
<form noValidate action="" role="search">
{onlyResults !== true ?
<TextField
defaultValue={defaultSearch}
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
InputProps={{
style:{
color: "white",
fontSize: "1em",
height: 50,
},
startAdornment: (
<InputAdornment position="start">
@@ -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' : ''*/}
</form>
: null}
</form>
)
}
@@ -229,29 +241,33 @@ const AppGrid = props => {
var counted = 0
return (
<Grid container spacing={4} style={paperAppContainer}>
{hits.map((data, index) => {
workflowDelay += 50
<div>
{onlyResults === true && hits.length > 0 ?
null
: null}
<Grid container spacing={4} style={paperAppContainer}>
{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 (
<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>
<Grid item xs={xs} style={{ padding: "12px 10px 12px 10px" }}>
return (
<Grid item xs={xs} style={{ padding: "12px 10px 12px 10px",}}>
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
{alternativeView === true ?
<WorkflowPaperNew key={index} data={data} />
:
<WorkflowPaper key={index} data={data} />
}
</Grid>
</Zoom>
)
})}
</Grid>
)
})}
</Grid>
</div>
)
}
@@ -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)}
/>
</div>
<Button
@@ -354,15 +370,16 @@ const AppGrid = props => {
</div>
: null
}
<span style={{position: "absolute", display: "flex", textAlign: "right", float: "right", right: 0, bottom: 120, }}>
<Typography variant="body2" color="textSecondary" style={{}}>
Search by
</Typography>
<a rel="noopener noreferrer" href="https://www.algolia.com/" target="_blank" style={{textDecoration: "none", color: "white"}}>
<img src={"/images/logo-algolia-nebula-blue-full.svg"} alt="Algolia logo" style={{height: 17, marginLeft: 5, marginTop: 3,}} />
</a>
</span>
{onlyResults === true ? null :
<span style={{position: "absolute", display: "flex", textAlign: "right", float: "right", right: 0, bottom: 120, }}>
<Typography variant="body2" color="textSecondary" style={{}}>
Search by
</Typography>
<a rel="noopener noreferrer" href="https://www.algolia.com/" target="_blank" style={{textDecoration: "none", color: "white"}}>
<img src={"/images/logo-algolia-nebula-blue-full.svg"} alt="Algolia logo" style={{height: 17, marginLeft: 5, marginTop: 3,}} />
</a>
</span>
}
</div>
)
}
-291
View File
@@ -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 ? <Avatar alt={data.creator} src={data.creator_info.image} style={imageStyle}/> : <Avatar alt={"shuffle_image"} src={theme.palette.defaultImage} style={imageStyle}/>
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 (
<div style={{width: "100%", position: "relative",}}>
<Paper square style={paperAppStyle}>
<div
style={{
position: "absolute",
bottom: 1,
left: 1,
height: 12,
width: 12,
backgroundColor: boxColor,
borderRadius: "0 100px 0 0",
}}
/>
<Grid
item
style={{ display: "flex", flexDirection: "column", width: "100%" }}
>
<Grid item style={{ display: "flex", maxHeight: 34 }}>
<Tooltip title={`${creatorname}`} placement="bottom">
<div
style={{ cursor: data.creator_info !== undefined ? "pointer" : "inherit" }}
onClick={() => {
if (data.creator_info !== undefined) {
navigate("/creators/"+data.creator_info.username)
}
}}
>
{image}
</div>
</Tooltip>
<Tooltip title={`Edit ${data.name}`} placement="bottom">
<Typography
variant="body1"
style={{
marginBottom: 0,
paddingBottom: 0,
maxHeight: 30,
flex: 10,
}}
>
<Link
to={parsedUrl}
style={{ textDecoration: "none", color: "inherit" }}
>
{parsedName}
</Link>
</Typography>
</Tooltip>
</Grid>
<Grid item style={workflowActionStyle}>
{appGroup.length > 0 ?
<div style={{display: "flex", marginTop: 8, }}>
<AvatarGroup max={4} style={{marginLeft: 5, maxHeight: 24,}}>
{appGroup.map((app, index) => {
return (
<div
key={index}
style={{
height: 24,
width: 24,
filter: "brightness(0.6)",
cursor: "pointer",
}}
onClick={() => {
navigate("/apps/"+app.id)
}}
>
<Tooltip color="primary" title={app.name} placement="bottom">
<Avatar alt={app.name} src={app.image_url} style={{width: 24, height: 24}}/>
</Tooltip>
</div>
)
})}
</AvatarGroup>
</div>
:
<Tooltip color="primary" title="Action amount" placement="bottom">
<span style={{ color: "#979797", display: "flex" }}>
<BubbleChartIcon
style={{ marginTop: "auto", marginBottom: "auto" }}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{data.actions === undefined || data.actions === null ? 1 : data.actions.length}
</Typography>
</span>
</Tooltip>
}
<Tooltip
color="primary"
title="Trigger amount"
placement="bottom"
>
<span
style={{ marginLeft: 15, color: "#979797", display: "flex" }}
>
<RestoreIcon
style={{
color: "#979797",
marginTop: "auto",
marginBottom: "auto",
}}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{data.triggers === undefined || data.triggers === null ? 1 : data.triggers.length}
</Typography>
</span>
</Tooltip>
<Tooltip color="primary" title="Subflows used" placement="bottom">
<span
style={{
marginLeft: 15,
display: "flex",
color: "#979797",
cursor: "pointer",
}}
onClick={() => {
}}
>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{
color: "#979797",
marginTop: "auto",
marginBottom: "auto",
}}
>
<path
d="M0 0H15V15H0V0ZM16 16H18V18H16V16ZM16 13H18V15H16V13ZM16 10H18V12H16V10ZM16 7H18V9H16V7ZM16 4H18V6H16V4ZM13 16H15V18H13V16ZM10 16H12V18H10V16ZM7 16H9V18H7V16ZM4 16H6V18H4V16Z"
fill="#979797"
/>
</svg>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{0}
</Typography>
</span>
</Tooltip>
</Grid>
<Grid
item
style={{
justifyContent: "left",
overflow: "hidden",
marginTop: 5,
}}
>
{data.tags !== undefined && data.tags !== null
? data.tags.map((tag, index) => {
if (index >= 3) {
return null;
}
return (
<Chip
key={index}
style={chipStyle}
label={tag}
variant="outlined"
color="primary"
/>
);
})
: null}
</Grid>
</Grid>
</Paper>
</div>
)
}
export default WorkflowPaper
+3 -3
View File
@@ -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;
}
+2 -2
View File
@@ -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";
@@ -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 (
<Drawer
anchor={"left"}
open={modalOpen}
onClose={() => {
setModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: "black",
color: "white",
minWidth: 700,
maxWidth: 700,
paddingTop: 75,
itemAlign: "center",
},
}}
>
<IconButton
style={{
zIndex: 5000,
position: "absolute",
top: 14,
right: 14,
color: "white",
}}
onClick={() => {
setModalOpen(false);
}}
>
<CloseIcon />
</IconButton>
<DialogContent style={{marginTop: 0, marginLeft: 75, maxWidth: 470, }}>
<Typography variant="h4">
<b>Configure Workflow</b>
</Typography>
<Typography variant="body2" color="textSecondary" style={{marginTop: 25, }}>
Selected Workflow:
</Typography>
<div style={{marginBottom: 0, }} id="workflow-template">
<WorkflowTemplatePopup2
globalUrl={globalUrl}
img1={img1}
srcapp={srcapp}
img2={img2}
dstapp={dstapp}
title={title}
description={description}
visualOnly={true}
/>
</div>
{workflowLoading ?
<div style={{marginTop: 75, textAlign: "center", }}>
<Typography variant="h4"> Generating the Workflow...
</Typography>
<CircularProgress style={{marginLeft: 125, marginTop: 10, }}/>
</div>
:
<div>
<Typography variant="h6" style={{marginTop: 75, }}>
{errorMessage !== "" ? errorMessage : ""}
</Typography>
</div>
}
<ConfigureWorkflow
userdata={userdata}
theme={theme}
globalUrl={globalUrl}
workflow={workflow}
appAuthentication={appAuthentication}
setAppAuthentication={setAppAuthentication}
apps={apps}
/>
{errorMessage === "" ?
<Button
style={{marginTop: 50, }}
variant={isFinished() ? "contained" : "outlined"}
onClick={() => {
setModalOpen(false);
}}
>
Done
</Button>
: null}
</DialogContent>
</Drawer>
)
}
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 (
<div style={{ display: "flex", maxWidth: isCloud ? 470 : 450, minWidth: isCloud ? 470 : 450, height: 78, borderRadius: 8 }}>
<ModalView />
<div
// variant={isActive === 1 ? "contained" : "outlined"}
color="secondary"
disabled={visualOnly === true}
style={{
margin: 4,
width: "100%",
borderRadius: 8,
textTransform: "none",
backgroundColor: theme.palette.inputColor,
border: isActive ? errorMessage !== "" ? "1px solid red" : `2px solid ${theme.palette.green}` : isHovered ? "1px solid #f85a3e" : "1px solid rgba(33, 33, 33, 1)",
cursor: isActive ? errorMessage !== "" ? "not-allowed" : "pointer" : "pointer",
padding: "10px 20px 10px 20px",
position: "relative",
}}
onMouseEnter={() => {
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()
}
}}
>
<div style={{ display: "flex", itemAlign: "left", textAlign: "left", }}>
<div style={{display: "flex", flex: 1, marginTop: 3, }}>
{img1 !== undefined && img1 !== "" && srcapp !== undefined && srcapp !== "" ?
<Tooltip title={srcapp.replaceAll(":default", "").replaceAll("_", " ").replaceAll(" API", "")} placement="top">
<span style={srcapp !== undefined && srcapp.includes(":default") ? imagestyleWrapperDefault : imagestyleWrapper}>
<img src={img1} style={srcapp !== undefined && srcapp.includes(":default") ? imagestyleDefault : imagestyle} />
</span>
</Tooltip>
:
<span style={{width: 50, }} />
}
{img2 !== undefined && img2 !== "" && dstapp !== undefined && dstapp !== "" ?
<Tooltip title={dstapp.replaceAll(":default", "").replaceAll("_", " ").replaceAll(" API", "")} placement="top">
<span style={{display: "flex", }}>
<TrendingFlatIcon style={{ marginTop: 7, }} />
<span style={dstapp !== undefined && dstapp.includes(":default") ? imagestyleWrapperDefault : imagestyleWrapper}>
<img src={img2} style={dstapp !== undefined && dstapp.includes(":default") ? imagestyleDefault : imagestyle} />
</span>
</span>
</Tooltip>
:
<span style={{width: 50, }} />
}
</div>
<div style={{ flex: 3, marginLeft: 20, }}>
<Typography variant="body1" style={{ marginTop: parsedDescription.length === 0 ? 10 : 0, }} color="rgba(241, 241, 241, 1)">
{parsedTitle}
</Typography>
<Typography variant="body2" color="textSecondary" style={{ marginTop: 0, marginRight: 0, maxHeight: 16, overflow: "hidden",}} color="rgba(158, 158, 158, 1)">
{parsedDescription}
</Typography>
</div>
</div>
<div>
{isActive === true && errorMessage === "" ?
<CheckIcon color="primary" sx={{ borderRadius: 4 }} style={{ position: "absolute", color: theme.palette.green, top: 10, right: 10, }} />
: ""}
</div>
</div>
</div>
)
}
export default WorkflowTemplatePopup
+4 -5
View File
@@ -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)
}
-432
View File
@@ -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;
+13 -12
View File
@@ -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"],
},
},
{
+12 -8
View File
@@ -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(<App />, 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(
<React.Fragment>
<App />
</React.Fragment>
);
//reportWebVitals();
File diff suppressed because one or more lines are too long
+36 -43
View File
@@ -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;
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,6 @@
/* eslint-disable react/no-multi-comp */
import React, { useState } from "react";
import { makeStyles } from "@material-ui/styles";
import { makeStyles } from "@mui/styles";
import {
CircularProgress,
@@ -8,7 +8,7 @@ import {
Button,
Paper,
Typography,
} from "@material-ui/core";
} from "@mui/material";
const bodyDivStyle = {
margin: "auto",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-468
View File
@@ -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 = (
<div style={SideBar}>
<List>
<ListItem >
<ListItemText>
<span><Typography variant="primary">Categories</Typography></span>
</ListItemText>
</ListItem>
<span></span>
<Divider />
<ListItem>
<ListItemText>
<Button variant="primary">
ASSETS
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
CASES
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
COMMS
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
EDR & AV
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
IAM
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
INTEL
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
NETWORK
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
SIEM
</Button>
</ListItemText>
</ListItem>
</List>
</div>
)
return (
<div>
<div style={{ display: "flex" }}>
{catItems}
<div style={contentbar}>
<Grid>
<Grid item xl={8} style={{ "border": "20px" }}>
<Typography type="title" variant="h6">
Getting Started
</Typography>
<div style={{
paddingLeft: "50px",
}}>
</div>
</Grid>
</Grid>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 4 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(algoliaResult.length)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<a href={algoliaResult[0]["objectID"]} style={link}>
<Item>
<div class="row">
<div class="column" style={{ float: "left" }}>
<img src={algoliaResult[0]["image_url"]} alt="shuffle" width="50px" />
</div>
<div class="column " style={boxdata}>
<div style={boxdata}>
<Typography align="left" variant="body1">
{algoliaResult[0]["name"]}
</Typography>
</div>
<div style={boxdata}>
<Typography align="left" variant="body2">
{algoliaResult[0]["description"].substring(0, 20)}
</Typography>
</div>
</div>
</div>
</Item>
</a>
</Grid>
))}
</Grid>
</Box>
<Grid item xl={8} style={{ "border": "20px" }}>
<Typography type="title" variant="h6">
Most Popular
</Typography>
<div style={{
paddingLeft: "50px",
}}>
</div>
</Grid>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(3)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<a href="#" style={link}>
<Item>
<div class="row">
<div class="column" style={{ float: "left" }}>
<img src="/images/testing.png" alt="shuffle" width="50px" />
</div>
<div class="column " style={boxdata}>
<div style={boxdata}>
<Typography align="left" variant="body1">
App Name
</Typography>
</div>
<div style={boxdata}>
<Typography align="left" variant="body2">
Description
</Typography>
</div>
</div>
</div>
</Item>
</a>
</Grid>
))}
</Grid>
</Box>
<Grid item xl={8} style={{ "border": "20px" }}>
<Typography type="title" variant="h6">
Brand New
</Typography>
<div style={{
paddingLeft: "50px",
}}>
</div>
</Grid>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(3)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<a href="#" style={link}>
<Item>
<div class="row">
<div class="column" style={{ float: "left" }}>
<img src="/images/testing.png" alt="shuffle" width="50px" />
</div>
<div class="column " style={boxdata}>
<div style={boxdata}>
<Typography align="left" variant="body1">
App Name
</Typography>
</div>
<div style={boxdata}>
<Typography align="left" variant="body2">
Description
</Typography>
</div>
</div>
</div>
</Item>
</a>
</Grid>
))}
</Grid>
</Box>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(3)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<div className="row" >
<div className="column" style={{ float: "left", width: "33.33%", marginTop: "20px", marginBottom: "20px", }}>
<img src="/images/shuffle_logo.png" alt="shuffle" width="200px" />
</div>
</div>
</Grid>
))}
</Grid>
</Box>
<Grid item xl={8} style={{ "border": "20px" }}>
<Typography type="title" variant="h6">
Hybrid work
</Typography>
<div style={{
paddingLeft: "50px",
}}>
</div>
</Grid>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(3)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<a href="#" style={link}>
<Item>
<div class="row">
<div class="column" style={{ float: "left" }}>
<img src="/images/testing.png" alt="shuffle" width="50px" />
</div>
<div class="column " style={boxdata}>
<div style={boxdata}>
<Typography align="left" variant="body1">
App Name
</Typography>
</div>
<div style={boxdata}>
<Typography align="left" variant="body2">
Description
</Typography>
</div>
</div>
</div>
</Item>
</a>
</Grid>
))}
</Grid>
</Box>
<div className="row" style={{ display: "flex" }}>
<div className="col" style={{ width: "40%", marginTop: "50px" }}>
<Typography variant="h6">Don't see it? Build it!</Typography>
<Typography variant="body2">Use our APIs to create an app that makes your working life better.And maybe even share it with the world.</Typography>
<a href="#" target="_blank" rel="nonref"
style={{
background: "#FF4500",
borderRadius: "3.125rem",
color: "#fff",
display: "block",
fontSize: ".9375rem",
fontWeight: "500",
height: "1rem",
letterSpacing: "-.02em",
lineHeight: ".875rem",
marginTop: "1.5rem",
padding: "1.3125rem 1.375rem",
textAlign: "center",
textDecoration: "none",
width: "8.5rem"
}}><span>visit developer portal</span></a>
</div>
<div className="col" style={{ float: "right" }}>
<div className="row" style={{ float: "left", marginLeft: "90px" }}>
<div className="column" style={{ float: "left", margin: "60px 10px 20px 30px" }}>
<img src="/images/demo1.png" alt="shuffle" width="90px" />
</div>
<div className="column" style={{ float: "left", margin: "60px 10px 20px 30px" }}>
<img src="/images/demo1.png" alt="shuffle" width="90px" />
</div>
<div className="column" style={{ float: "left", margin: "60px 10px 20px 30px" }}>
<img src="/images/demo1.png" alt="shuffle" width="90px" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default AppExplorer;
-789
View File
@@ -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 = (
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
id={menuId}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={isMenuOpen}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}>Profile</MenuItem>
<MenuItem onClick={handleMenuClose}>My account</MenuItem>
</Menu>
);
const mobileMenuId = 'primary-search-account-menu-mobile';
const renderMobileMenu = (
<Menu
anchorEl={mobileMoreAnchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
id={mobileMenuId}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={isMobileMenuOpen}
onClose={handleMobileMenuClose}
>
<MenuItem>
<IconButton size="large" aria-label="show 4 new mails" color="inherit">
<Badge badgeContent={4} color="error">
<MailIcon />
</Badge>
</IconButton>
<p>Messages</p>
</MenuItem>
<MenuItem>
<IconButton
size="large"
aria-label="show 17 new notifications"
color="inherit"
>
<Badge badgeContent={17} color="error">
<NotificationsIcon />
</Badge>
</IconButton>
<p>Notifications</p>
</MenuItem>
<MenuItem onClick={handleProfileMenuOpen}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="primary-search-account-menu"
aria-haspopup="true"
color="inherit"
>
<AccountCircle />
</IconButton>
<p>Profile</p>
</MenuItem>
</Menu>
);
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="fixed" style={{ backgroundColor: "black", boxShadow: "unset" }}>
<Toolbar>
<img src="/images/Shuffle_logo.png" style={{ height: "3rem", width: "3rem" }} alt="shuffle img" />
<SearchField />
{/* <Box sx={{ flexGrow: 1 }} /> */}
</Toolbar>
</AppBar>
{renderMobileMenu}
{renderMenu}
</Box>
);
}
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 (
<div>
<Card>
<CardContent style={{ padding: 0 }}>
<div style={{
background: "url('/images/home-header-bg.png')", height: "450px", backgroundSize: "cover",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
position: "relative"
}}>
<div style={{ width: "95%", margin: "auto", position: "relative", height: "450px" }}>
<div>
<PrimarySearchAppBar />
</div>
<div style={{
position: "absolute",
bottom: "10%",
display: "flex",
alignItems: "flex-end",
justifyContent: "space-between",
width: "100%"
}}>
<div>
<img src="/images/Shuffle_logo.png" style={{ height: "4rem", width: "4rem" }} alt="shuffle img" />
<Typography type="title" variant="h1" color="#ef5d29">
SHUFFLE
</Typography>
</div>
<div>
<SearchField />
</div>
</div>
</div>
</div>
</CardContent>
</Card>
<div style={{ display: "flex" }}>
<div style={SideBar}>
<List>
<ListItem >
<ListItemText>
<span style={{ fontSize: "25px", fontFamily: "revert", fontWeight: "bold" }}>Categories</span>
</ListItemText>
</ListItem>
<span></span>
<Divider />
<ListItem>
<ListItemText>
<span style={{ fontSize: "25px", fontFamily: "revert" }}>Workflows</span>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<span style={{ fontSize: "25px", fontFamily: "revert" }}>Apps</span>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<span style={{ fontSize: "25px", fontFamily: "revert" }}>Docs</span>
</ListItemText>
</ListItem>
</List>
</div>
<div style={{ padding: "20px", width: "100%" }}>
<Typography type="title" variant="h2" color="black">
Workflow
</Typography>
<div style={{ width: "100%", minHeight: isMobile ? 0 : 71, maxHeight: isMobile ? 0 : 71, }}>
{!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ?
<div style={{ display: "flex", }}>
<Grid container spacing={2}>
{usecases.map((usecase, index) => {
//console.log(usecase)
return (
<Grid item xs={4}>
<Paper
key={usecase.name}
style={{
flex: 1,
backgroundColor: "transparent",
marginRight: index === usecases.length - 1 ? 0 : 10,
cursor: "pointer",
overflow: "hidden",
padding: 10,
border: "0.0625rem solid #b2b2b2",
borderRadius: "1.5625rem",
boxSizing: "content-box",
cursor: "pointer",
height: "70px",
}}
onClick={() => {
console.log("clicked...")
}}
>
<a href={`/usecases?selected=${usecase.name}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", }}>
<Typography variant="body1" color="textPrimary">
{usecase.name}
</Typography>
<Typography variant="body2" color="textSecondary">
In use: {usecase.matches.length}/{usecase.list.length}
</Typography>
</a>
</Paper>
</Grid>
)
})}
</Grid>
</div>
: null}
</div>
</div>
</div>
<Card>
<CardContent style={{ padding: 0 }}>
<Typography type="title" variant="h3" color="#ffffff" style={{
backgroundColor: "black", padding: "10px", height: "200px",
display: "flex",
justifyContent: "center",
alignItems: "center"
}}>
footer
</Typography>
</CardContent>
</Card>
</div>
);
};
export default AppHub;
-268
View File
@@ -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 = (
<div style={SideBar}>
<List>
<ListItem >
<ListItemText>
<span><Typography variant="primary">Categories</Typography></span>
</ListItemText>
</ListItem>
<span></span>
<Divider />
<ListItem>
<ListItemText>
<Button id="ASSETS" variant="primary" onClick={()=>{setAppCategory("assets")}}>
ASSETS
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("cases")}}>
CASES
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("comms")}}>
COMMS
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("edr av")}}>
EDR & AV
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("iam")}}>
IAM
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("intel")}}>
INTEL
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("network")}}>
NETWORK
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("siem")}}>
SIEM
</Button>
</ListItemText>
</ListItem>
</List>
</div>
)
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 =
<div style={{paddingBottom: hidemargins === true ? 0 : 100, color: "white", backgroundColor: theme.palette.surfacColor}}>
<div style={boxStyle}>
<Tabs
style={{width: 610, margin: "auto", marginTop: hidemargins === true ? 0 : 25, }}
value={curTab}
indicatorColor="primary"
textColor="secondary"
onChange={setConfig}
aria-label="disabled tabs example"
>
<Tab
label=<span>
<AppsIcon style={iconStyle} /> Apps
</span>
/>
</Tabs>
{curTab === 0 ?
<AppGrid1 maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} searchValue={appCategory} key={appCategory} />
:
curTab === 1 ?
window.location.pathname === "/search" ?
<WorkflowGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
:
<WorkflowGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
:
curTab === 2 ?
<DocsGrid maxRows={6} parsedXs={12} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
:
curTab === 3 ?
<CreatorGrid parsedXs={4} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
:
null}
</div>
</div>
//{/*alternativeView={true} />*/}
const loadedCheck = isLoaded ?
<div>
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
</div>
:
<div>
</div>
// #1f2023?
return(
<div style={{backgroundColor: "#1f2023", display: "flex"}}>
{catItems}
{loadedCheck}
</div>
)
}
export default Appdemo;
+406 -78
View File
@@ -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) => {
<Select
value={sharingConfiguration}
onChange={(event) => {
alert.info("Changing sharing to " + event.target.value);
setSharingConfiguration(event.target.value);
if (event.target.value === "you") {
toast("Changing sharing to " + event.target.value);
updateAppField(selectedApp.id, "sharing", false);
} else if (
event.target.value === "everyone" ||
event.target.value === "public"
) {
updateAppField(selectedApp.id, "sharing", true);
if (!isCloud) {
setPublishModalOpen(true)
} else {
updateAppField(selectedApp.id, "sharing", true);
}
} else {
console.log(
"Can't handle value for sharing: ",
@@ -1333,11 +1345,80 @@ const Apps = (props) => {
</div>
) : null;
const AppCreateButton = (props) => {
const { text, func, icon } = props;
const [hover, setHover] = React.useState(false);
return (
<Paper
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
onClick={func}
style={{
flex: 1,
padding: 15,
margin: 10,
backgroundColor: hover ? theme.palette.surfaceColor : "transparent",
border: hover ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)",
cursor: hover ? "pointer" : "default",
textAlign: "center",
minHeight: 150,
maxHeight: 150,
}}
>
{icon}
<Typography>
{text}
</Typography>
</Paper>
)
}
return (
<div style={{}}>
<Paper square style={uploadViewPaperStyle}>
<div style={{ margin: 25 }}>
<h2>App Creator</h2>
<h2 style={{
textAlign: "center",
}}>
App Creator
</h2>
<div style={{display: "flex"}}>
<AppCreateButton
text="Generate from OpenAPI/Swagger"
func={() => {
setOpenApiModal(true)
}}
icon={<PublishIcon style={{minHeight: 50, maxHeigth: 50, }} />}
/>
<AppCreateButton
text="Generate from Documentation"
func={() => {
setGenerateAppModal(true)
}}
icon={<AutoFixHighIcon style={{minHeight: 50, maxHeigth: 50, }} />}
/>
</div>
<Link
to="/apps/new"
style={{
marginLeft: 5,
textDecoration: "none",
color: "#f85a3e",
}}
>
<Button
variant="outlined"
component="label"
color="secondary"
style={{}}
fullWidth
>
Create from scratch
</Button>
</Link>
{/*
<a
rel="noopener noreferrer"
href="https://shuffler.io/docs/apps"
@@ -1420,6 +1501,7 @@ const Apps = (props) => {
</Button>
</Link>
</div>
*/}
</div>
</Paper>
<Paper square style={uploadViewPaperStyle}>
@@ -1459,6 +1541,30 @@ const Apps = (props) => {
//}
};
const uploadFileDocumentation = (e) => {
const isDropzone = e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
const files = isDropzone ? e.dataTransfer.files : e.target.files;
const reader = new FileReader();
try {
reader.addEventListener("load", (e) => {
const content = e.target.result;
setOpenApiData(content);
setIsDropzone(isDropzone);
setOpenApiModal(true);
});
} catch (e) {
console.log("Error in dropzone: ", e);
}
try {
reader.readAsText(files[0]);
} catch (error) {
toast("Failed to read file");
}
};
const uploadFile = (e) => {
const isDropzone =
e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
@@ -1480,7 +1586,7 @@ const Apps = (props) => {
try {
reader.readAsText(files[0]);
} catch (error) {
alert.error("Failed to read file");
toast("Failed to read file");
}
};
@@ -1579,9 +1685,9 @@ const Apps = (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.")
if (refresh === true) {
getApps()
@@ -1589,7 +1695,7 @@ const Apps = (props) => {
}
})
.catch(error => {
//alert.error(error.toString())
//toast(error.toString())
console.log("Activate app error: ", error.toString())
});
}
@@ -1693,13 +1799,13 @@ const Apps = (props) => {
return (
<div style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
//if (!isCloud) {
// alert.info("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.")
// toast("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.")
// setTimeout(() => {
// event.preventDefault()
// window.open(parsedUrl, '_blank')
// }, 2000)
//} else {
alert.info(`Activating ${name}`)
toast(`Activating ${name}`)
//}
console.log("CLICK: ", hit)
@@ -1868,12 +1974,6 @@ const Apps = (props) => {
style={{ backgroundColor: inputColor, borderRadius: 5 }}
InputProps={{
style: {
color: "white",
minHeight: "50px",
marginLeft: "5px",
maxWidth: "95%",
fontSize: "1em",
borderRadius: 5,
},
}}
disabled={
@@ -1894,27 +1994,27 @@ const Apps = (props) => {
filteredApps.length > 0 ? (
<div style={{ height: "75vh", overflowY: "auto" }}>
{filteredApps.map((app, index) => {
if (firstLoad) {
appDelay += 75
} else {
//return returnData
if (firstLoad) {
appDelay += 75
} else {
//return returnData
return <AppPaper app={app} />
}
}
return (
<Zoom key={index} in={true} style={{ transitionDelay: `${appDelay}ms` }}>
<span>
<AppPaper app={app} />
</span>
</Zoom>
)
<Zoom key={index} in={true} style={{ transitionDelay: `${appDelay}ms` }}>
<div>
<AppPaper app={app} />
</div>
</Zoom>
)
})}
{cursearch.length > 0
? null
: searchableApps.map((app, index) => {
return (
<AppPaper app={app} />
)
<AppPaper app={app} />
)
})}
</div>
) : (
@@ -2017,7 +2117,7 @@ const Apps = (props) => {
parsedData["force_update"] = forceUpdate;
alert.success("Getting specific apps from your URL.");
toast("Getting specific apps from your URL.");
var cors = "cors";
fetch(globalUrl + "/api/v1/apps/get_existing", {
method: "POST",
@@ -2030,7 +2130,7 @@ const Apps = (props) => {
})
.then((response) => {
if (response.status === 200) {
alert.success("Loaded existing apps!");
toast("Loaded existing apps!");
}
//stop()
@@ -2041,12 +2141,12 @@ const Apps = (props) => {
.then((responseJson) => {
console.log("DATA: ", responseJson);
if (responseJson.reason !== undefined) {
alert.error("Failed loading: " + responseJson.reason);
toast("Failed loading: " + responseJson.reason);
}
})
.catch((error) => {
console.log("ERROR: ", error.toString());
//alert.error(error.toString());
//toast(error.toString());
//stop()
setIsLoading(false);
@@ -2056,7 +2156,7 @@ const Apps = (props) => {
// Locally hotloads app from folder
const hotloadApps = () => {
alert.info("Hotloading apps from location in .env");
toast("Hotloading apps from location in .env");
setIsLoading(true);
fetch(globalUrl + "/api/v1/apps/run_hotload", {
mode: "cors",
@@ -2068,7 +2168,7 @@ const Apps = (props) => {
.then((response) => {
setIsLoading(false);
if (response.status === 200) {
//alert.success("Hotloaded apps!")
//toast("Hotloaded apps!")
getApps();
}
@@ -2076,14 +2176,14 @@ const Apps = (props) => {
})
.then((responseJson) => {
if (responseJson.success === true) {
alert.info("Successfully finished hotload");
toast("Successfully finished hotload");
} else {
alert.error("Failed hotload: ", responseJson.reason);
toast("Failed hotload: ", responseJson.reason);
//(responseJson.reason !== undefined && responseJson.reason.length > 0) {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2107,7 +2207,7 @@ const Apps = (props) => {
});
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2120,9 +2220,9 @@ const Apps = (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();
@@ -2145,7 +2245,7 @@ const Apps = (props) => {
responseJson.loop_versions = selectedApp.loop_versions;
}
//alert.info("Should set app to selected")
//toast("Should set app to selected")
if (
responseJson.actions !== undefined &&
responseJson.actions !== null &&
@@ -2156,15 +2256,16 @@ const Apps = (props) => {
setSelectedAction({});
}
setSelectedApp(responseJson);
setSharingConfiguration(responseJson.sharing === true ? "public" : "you")
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
const deleteApp = (appId) => {
alert.info("Attempting to delete app");
toast("Attempting to delete app");
fetch(globalUrl + "/api/v1/apps/" + appId, {
method: "DELETE",
headers: {
@@ -2174,16 +2275,16 @@ const Apps = (props) => {
})
.then((response) => {
if (response.status === 200) {
alert.success("Successfully deleted app");
toast("Successfully deleted app");
setTimeout(() => {
getApps();
}, 1000);
} else {
alert.error("Failed deleting app. Does it still exist?");
toast("Failed deleting app. Does it still exist?");
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2191,6 +2292,7 @@ const Apps = (props) => {
const data = {};
data[fieldname] = fieldvalue;
console.log("DATA: ", data);
fetch(globalUrl + "/api/v1/apps/" + app_id, {
@@ -2207,19 +2309,19 @@ const Apps = (props) => {
})
.then((responseJson) => {
//console.log(responseJson)
//alert.info(responseJson)
//toast(responseJson)
if (responseJson.success) {
alert.success("Successfully updated app configuration");
toast("Successfully updated app configuration");
} else {
if (responseJson.reason !== undefined && responseJson.reason !== null) {
alert.error("Error: "+responseJson.reason);
toast("Error: "+responseJson.reason);
} else {
alert.error("Error updating app configuration");
}
toast("Error updating app configuration. Are you the owner of this app?");
}
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2250,10 +2352,62 @@ const Apps = (props) => {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
const validateDocumentationUrl = () => {
setValidation(true);
// curl https://doc-to-openapi-stbuwivzoq-nw.a.run.app/doc_to_openapi -d '{"url": "https://gitlab.com/rhab/PyOTRS/-/raw/main/pyotrs/lib.py?ref_type=heads"}' -H "Content-Type: application/json"
const urldata = {
"url": openApi,
}
//fetch("http://localhost:8080/doc_to_openapi", {
fetch("https://doc-to-openapi-stbuwivzoq-nw.a.run.app/doc_to_openapi", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(urldata),
})
.then((response) => {
setValidation(false);
if (response.status !== 200) {
toast("Error in generation: "+response.status);
setOpenApiError("Error in generation - bad status: "+response.status);
return response.text();
}
return response.json();
})
.then((responseJson) => {
// Check if openapi or swagger in string of the json
try {
const parsedtext = JSON.stringify(responseJson);
if (parsedtext.indexOf("openapi") === -1 && parsedtext.indexOf("swagger") === -1) {
setValidation(false);
setOpenApiError("Error in generation: "+parsedtext);
return;
}
} catch (e) {
setValidation(false);
setOpenApiError("Error in generation (2): "+e.toString());
return;
}
console.log("Validating response!");
validateOpenApi(responseJson);
})
.catch((error) => {
setValidation(false);
toast(error.toString());
setOpenApiError(error.toString());
});
}
const validateRemote = () => {
setValidation(true);
@@ -2288,7 +2442,7 @@ const Apps = (props) => {
validateOpenApi(responseJson);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
setOpenApiError(error.toString());
});
};
@@ -2345,12 +2499,12 @@ const Apps = (props) => {
if (responseJson.reason !== undefined) {
setOpenApiError(responseJson.reason);
}
alert.error("An error occurred in the response");
toast("An error occurred in the response");
}
})
.catch((error) => {
setValidation(false);
alert.error(error.toString());
toast(error.toString());
setOpenApiError(error.toString());
});
};
@@ -2364,6 +2518,61 @@ const Apps = (props) => {
setLoadAppsModalOpen(false);
};
const publishModal = publishModalOpen ? (
<Dialog
open={publishModalOpen}
onClose={() => {
setPublishModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 500,
padding: 50,
},
}}
>
<DialogTitle style={{ marginBottom: 0 }}>
<div style={{ textAlign: "center", color: "rgba(255,255,255,0.9)" }}>
Are you sure you want to PUBLISH this app?
</div>
</DialogTitle>
<DialogContent
style={{ color: "rgba(255,255,255,0.65)", textAlign: "center" }}
>
<div>
<Typography variant="body1" style={{ marginBottom: 20 }}>
Before publishing, make sure to sanitize the App for anything you don't want public.
</Typography>
<Typography variant="body1" style={{ marginBottom: 20 }}>
The published App is yours, and you can always change your public Apps after they are released.
</Typography>
</div>
<Button
variant="contained"
style={{}}
onClick={() => {
updateAppField(selectedApp.id, "sharing", true);
setPublishModalOpen(false);
}}
color="primary"
>
Yes
</Button>
<Button
style={{}}
onClick={() => {
setPublishModalOpen(false);
}}
color="primary"
>
No
</Button>
</DialogContent>
</Dialog>
) : null;
const deleteModal = deleteModalOpen ? (
<Dialog
open={deleteModalOpen}
@@ -2414,6 +2623,7 @@ const Apps = (props) => {
const circularLoader = validation ? (
<CircularProgress color="primary" />
) : null;
const appsModalLoad = loadAppsModalOpen ? (
<Dialog
open={loadAppsModalOpen}
@@ -2550,14 +2760,13 @@ const Apps = (props) => {
</Dialog>
) : null;
const errorText =
openApiError.length > 0 ? (
<div style={{ marginTop: 10 }}>Error: {openApiError}</div>
) : null;
const modalView = openApiModal ? (
const errorText = openApiError.length > 0 ? ( <div style={{ marginTop: 10 }}>Error: {openApiError}</div>) : null;
const generateAppView = generateAppModal ? (
<Dialog
open={openApiModal}
open={generateAppModal}
onClose={() => {
setGenerateAppModal(false);
setOpenApiModal(false);
}}
PaperProps={{
@@ -2572,7 +2781,124 @@ const Apps = (props) => {
<FormControl>
<DialogTitle>
<div style={{ color: "rgba(255,255,255,0.9)" }}>
Create a new app
Generate an app based on documentation (beta)
</div>
</DialogTitle>
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
<Typography variant="body1">
Paste in a URL, and we will make it into an app for you. This may take multiple minutes based on the size of the documentation. <b>{isCloud ? "" : "Uses Shuffle Cloud (https://shuffler.io) for processing (for now)."}</b>
</Typography>
<TextField
style={{ backgroundColor: inputColor }}
variant="outlined"
margin="normal"
InputProps={{
style: {
color: "white",
height: "50px",
fontSize: "1em",
},
endAdornment: (
<Button
style={{
borderRadius: "0px",
marginTop: "0px",
height: "50px",
}}
variant="contained"
disabled={openApi.length === 0 || appValidation.length > 0}
color="primary"
onClick={() => {
setOpenApiError("");
validateDocumentationUrl();
}}
>
Validate
</Button>
),
}}
onChange={(e) => {
setOpenApi(e.target.value);
}}
helperText={
<span style={{ color: "white", marginBottom: "2px" }}>
Should be a documentation page containing an API.
</span>
}
placeholder="API Documentation URL"
fullWidth
/>
<p>Or upload document with the content (coming soon)</p>
<input
hidden
type="file"
ref={upload}
multiple={false}
onChange={uploadFileDocumentation}
/>
<Button
variant="contained"
color="primary"
disabled
onClick={() => upload.current.click()}
>
Upload
</Button>
{errorText}
</DialogContent>
<DialogActions>
{circularLoader}
<Button
style={{ borderRadius: "0px" }}
onClick={() => {
setGenerateAppModal(false);
setOpenApiModal(false);
setAppValidation("");
setOpenApiError("");
setOpenApi("");
setOpenApiData("");
}}
color="primary"
>
Cancel
</Button>
<Button
variant="contained"
style={{ borderRadius: "0px" }}
disabled={appValidation.length === 0}
onClick={() => {
redirectOpenApi();
}}
color="primary"
>
Continue
</Button>
</DialogActions>
</FormControl>
</Dialog>
) : null
const modalView = openApiModal ? (
<Dialog
open={openApiModal}
onClose={() => {
setOpenApiModal(false);
setGenerateAppModal(false);
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: "800px",
minHeight: "320px",
},
}}
>
<FormControl>
<DialogTitle>
<div style={{ color: "rgba(255,255,255,0.9)" }}>
Create a new app from OpenAPI / Swagger
</div>
</DialogTitle>
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
@@ -2677,6 +3003,8 @@ const Apps = (props) => {
<div>
{appView}
{modalView}
{publishModal}
{generateAppView}
{appsModalLoad}
{deleteModal}
</div>
-345
View File
@@ -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 =
<div>
<div style={bodyTextStyle}>
<h3 style={{ color: "#f85a3e" }}>Contact us</h3>
<h2>Lets talk!</h2>
</div>
<div style={{ display: "flex" }}>
<Paper style={boxStyle}>
<h2>Contact Details</h2>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
required
style={{ flex: "1", marginRight: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="First Name"
type="firstname"
id="standard-required"
autoComplete="firstname"
margin="normal"
variant="outlined"
onChange={e => setFirstname(e.target.value)}
/>
<TextField
style={{ flex: "1", marginLeft: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Last Name"
type="lastname"
id="standard"
autoComplete="lastname"
margin="normal"
variant="outlined"
onChange={e => setLastname(e.target.value)}
/>
</div>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
style={{ flex: "1", marginRight: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Job Title"
type="jobtitle"
id="standard-required"
autoComplete="jobtitle"
margin="normal"
variant="outlined"
onChange={e => setTitle(e.target.value)}
/>
<TextField
style={{ flex: "1", marginLeft: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
type="companyname"
placeholder="Company Name"
id="standard-required"
autoComplete="companyname"
margin="normal"
variant="outlined"
onChange={e => setCompanyname(e.target.value)}
/>
</div>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
required
style={{ flex: "1", marginRight: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Email"
type="email"
id="standard-required"
autoComplete="email"
margin="normal"
variant="outlined"
onChange={e => setEmail(e.target.value)}
/>
<TextField
style={{ flex: "1", marginLeft: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
type="phone"
placeholder="Phone number"
id="standard-required"
autoComplete="phone"
margin="normal"
variant="outlined"
onChange={e => setPhone(e.target.value)}
/>
</div>
<div style={{ flex: 1 }}>
<h2>Message</h2>
</div>
<div style={{ flex: 4 }}>
<TextField
multiline
InputProps={{
style: {
color: "white",
},
}}
color="primary"
style={{ flex: "1", backgroundColor: theme.palette.inputColor }}
rows="6"
fullWidth={true}
placeholder="What can we help you with?"
id="filled-multiline-static"
margin="normal"
variant="outlined"
onChange={e => setMessage(e.target.value)}
/>
</div>
<Button
disabled={email.length <= 0 || message.length <= 0}
style={{ width: "100%", height: "60px", marginTop: "10px" }}
variant="contained"
color="primary"
onClick={submitContact}
>
Submit
</Button>
<h3>{formMessage}</h3>
</Paper>
</div>
</div>
const landingpageDataMobile =
<div style={{ paddingBottom: "50px" }}>
<div style={{ color: "white", textAlign: "center" }}>
<h3 style={{ color: "#f85a3e" }}>Contact us</h3>
<h2>Lets talk!</h2>
</div>
<div style={{ display: "flex" }}>
<Paper style={boxStyle}>
<h2>Contact Details</h2>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
required
style={{ flex: "1", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Name"
type="firstname"
id="standard-required"
autoComplete="firstname"
margin="normal"
variant="outlined"
onChange={e => setFirstname(e.target.value)}
/>
</div>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
required
style={{ flex: "1", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Email"
type="email"
id="standard-required"
autoComplete="email"
margin="normal"
variant="outlined"
onChange={e => setEmail(e.target.value)}
/>
</div>
<div style={{ flex: 1 }}>
<h2>Message</h2>
</div>
<div style={{ flex: 4 }}>
<TextField
multiline
style={{ flex: "1", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
rows="6"
fullWidth={true}
placeholder="What can we help you with?"
id="filled-multiline-static"
margin="normal"
variant="outlined"
onChange={e => setMessage(e.target.value)}
/>
</div>
<Button
disabled={email.length <= 0 || message.length <= 0}
style={{ width: "100%", height: "60px", marginTop: "10px" }}
variant="contained"
color="primary"
onClick={submitContact}
>
Submit
</Button>
<h3>{formMessage}</h3>
</Paper>
</div>
</div>
const loadedCheck = isLoaded ?
<div>
<BrowserView>
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
</BrowserView>
<MobileView>
{landingpageDataMobile}
</MobileView>
</div>
:
<div>
</div>
return (
<div>
{loadedCheck}
</div>
)
}
export default Contact;
+286 -186
View File
@@ -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)
}
}}>
<Paper style={{padding: 25, minHeight: 75, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor,}} onClick={() => {
<Paper style={{padding: 25, minHeight: isCloud ? 75 : 122, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor,}} onClick={() => {
}}>
{!selectedItem ?
<div style={{textAlign: "left", position: "relative",}}>
@@ -637,24 +724,36 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
</div>
</div>
:
<div style={{flex: 1, textAlign: "left", marginRight: 10, }}>
<Typography variant="body1" color="textSecondary">
{subcase.description}
</Typography>
<div style={{flex: 1, textAlign: "left", marginRight: 10, }}>
<Typography variant="body1" color="textSecondary">
{subcase.description}
</Typography>
{workflows !== undefined && workflows !== null && workflows.length > 0 ?
<Typography variant="body1" style={{marginTop: 15, marginBottom: 10, }}>
Select relevant workflows
</Typography>
: null}
{workflows !== undefined && workflows !== null && workflows.length > 0 ?
<Typography variant="body1" style={{marginTop: 15, marginBottom: 10, }}>
Select relevant workflows
</Typography>
:
<span style={{display: "flex"}}>
<Typography variant="body1" style={{marginTop: 15, marginBottom: 10, }}>
Find workflows related to this usecase:
</Typography>
<a href={`https://shuffler.io/search?tab=workflows&q=${subcase.name}`} style={{textDecoration: "none", }} target="_blank" rel="noopener noreferrer">
<IconButton style={{paddingTop: 15, }}>
<OpenInNewIcon style={{color: "#f85a3e", }}/>
</IconButton>
</a>
</span>
}
{workflows !== undefined && workflows !== null && workflows.length > 0 ?
<Autocomplete
multiple
{workflows !== undefined && workflows !== null && workflows.length > 0 ?
<Autocomplete
multiple
id="workflow_matching"
options={workflows}
autoHighlight
value={selectedWorkflows}
value={selectedWorkflows}
classes={{ inputRoot: classes.inputRoot }}
ListboxProps={{
style: {
@@ -662,7 +761,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
color: "white",
},
}}
getOptionSelected={(option, value) => 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 (
<li {...props}>
<Tooltip arrow placement="left" title={
<span style={{}}>
{props.image !== undefined && props.image !== null && props.image.length > 0 ?
<img src={props.image} alt={newname} style={{backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
: null}
<Typography>
Choose {newname}
</Typography>
</span>
} placement="bottom">
<span>
<Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
checkedIcon={<CheckBoxIcon fontSize="small" />}
style={{ marginRight: 8 }}
checked={option.selected}
/>
{newname}
</span>
</Tooltip>
</li>
)
}}
if (newname.length > 2) {
newname = newname.charAt(0).toUpperCase() + newname.substring(1)
}
return (
<li {...props}>
<Tooltip arrow placement="left" title={
<span style={{}}>
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
<img src={data.image} alt={newname} style={{backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
: null}
<Typography>
Choose {newname}
</Typography>
</span>
} placement="bottom">
<span>
<Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
checkedIcon={<CheckBoxIcon fontSize="small" />}
style={{ marginRight: 8 }}
checked={selectedWorkflows.find(wf => wf.id === data.id) !== undefined}
/>
{newname}
</span>
</Tooltip>
</li>
)
}}
renderInput={(params) => {
return (
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find your workflows"
variant="outlined"
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find your workflows"
variant="outlined"
/>
);
}}
/>
: null}
: null}
<span style={{top: 30, position: "relative",}}>
<Typography
variant="body2"
color="textSecondary"
style={{marginTop: 0, marginLeft: 5, }}
onClick={() => {}}
>
Try it out:
</Typography>
<WorkflowTemplatePopup
userdata={userdata}
globalUrl={globalUrl}
img1={inputUsecase.srcimg}
srcapp={inputUsecase.srcapp}
img2={inputUsecase.dstimg}
dstapp={inputUsecase.dstapp}
title={inputUsecase.name}
description={inputUsecase.description}
apps={apps}
/>
</span>
{/*
{/*subcase.matches.length > 0 ?
<Grid container style={{maxWidth: 325, marginTop: 10, }}>
{subcase.matches.map((workflow, workflowindex) => {
return (
<Grid key={workflowindex} item index={workflowindex} xs={12}>
<WorkflowPaper key={workflowindex} data={workflow} />
</Grid>
)
})}
</Grid>
:
<div>
<Typography variant="body1" color="textSecondary">
No workflow selected yet.
</Typography>
</div>
*/}
{subcase.extra_buttons !== undefined && subcase.extra_buttons !== null && subcase.extra_buttons.length > 0 ?
<div style={{marginTop: 25, }}>
@@ -852,15 +956,9 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
<Typography variant="body1" style={{marginTop: 15, cursor: "pointer",}} onClick={() => {}}>
See other Public Workflows for {} <OpenInNewIcon style={{marginTop: 5, marginLeft: 15, }}/>
</Typography>
{/*
<div>
<Typography variant="body1" color="textSecondary">
No workflows yet.
</Typography>
</div>
*/}
</a>
</div>
*/}
</div>
}
<div style={{
@@ -868,6 +966,8 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
width: 350,
borderRadius: theme.palette.borderRadius,
border: "1px solid rgba(255,255,255,0.3)",
padding: 5,
backgroundColor: theme.palette.backgroundColor,
}}>
<AppFramework
inputUsecase={inputUsecase}
@@ -1026,7 +1126,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);
@@ -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());
});
};
+11 -11
View File
@@ -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());
});
};
+55 -27
View File
@@ -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 (
<a
@@ -386,23 +387,32 @@ const Docs = (defaultprops) => {
}
function Img(props) {
return <img style={{ borderRadius: theme.palette.borderRadius, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />;
return <img style={{ borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />;
}
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 (
<pre
<div
style={{
padding: 15,
minWidth: "50%",
maxWidth: "100%",
backgroundColor: theme.palette.inputColor,
overflowX: "auto",
overflowY: "hidden",
overflowY: "auto",
}}
>
<code>{props.value}</code>
</pre>
<code
style={{
// Wrap if larger than X
whiteSpace: "pre-wrap",
overflow: "auto",
}}
>{propvalue}</code>
</div>
);
}
@@ -435,7 +445,7 @@ const Docs = (defaultprops) => {
href={selectedMeta.link}
style={{ textDecoration: "none", color: "#f85a3e" }}
>
<Button style={{}} variant="outlined">
<Button style={{color: "white", }} variant="outlined" color="secondary">
<EditIcon /> &nbsp;&nbsp;Edit
</Button>
</a>
@@ -637,7 +647,7 @@ const Docs = (defaultprops) => {
<Typography variant="h6" style={headerStyle}>Why Shuffle?</Typography>
<Typography variant="body1">
<b>Security first.</b> 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 <a href="https://shuffler.io/pricing?tag=docs" target="_blank" style={hrefStyle2}>find usecases</a> that fit your your unique needs. Accessibility is key, and we intend to help every SOC globally use and share their usecases.
<b>Security first.</b> 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 <a href="https://shuffler.io/pricing?tag=docs" target="_blank" style={hrefStyle2}>find usecases</a> that fit your unique needs. Accessibility is key, and we intend to help every SOC globally use and share their usecases.
</Typography>
<Typography variant="h6" style={headerStyle}>Get help</Typography>
@@ -790,22 +800,31 @@ const Docs = (defaultprops) => {
:
<div id="markdown_wrapper_outer" style={markdownStyle}>
<ReactMarkdown
components={{
img: Img,
code: CodeHandler,
h1: Heading,
h2: Heading,
h3: Heading,
h4: Heading,
h5: Heading,
h6: Heading,
a: OuterLink,
}}
id="markdown_wrapper"
escapeHtml={false}
source={data}
style={{maxWidth: "100%", minWidth: "100%", }}
renderers={{
link: OuterLink,
image: Img,
code: CodeHandler,
heading: Heading,
style={{
maxWidth: "100%", minWidth: "100%",
}}
/>
>
{data}
</ReactMarkdown>
</div>
}
</div>
</div>
);
// remarkPlugins={[remarkGfm]}
const mobileStyle = {
color: "white",
@@ -868,16 +887,25 @@ const Docs = (defaultprops) => {
:
<div id="markdown_wrapper_outer" style={markdownStyle}>
<ReactMarkdown
components={{
img: Img,
code: CodeHandler,
h1: Heading,
h2: Heading,
h3: Heading,
h4: Heading,
h5: Heading,
h6: Heading,
a: OuterLink,
}}
id="markdown_wrapper"
escapeHtml={false}
source={data}
renderers={{
link: OuterLink,
image: Img,
code: CodeHandler,
heading: Heading,
style={{
maxWidth: "100%", minWidth: "100%",
}}
/>
>
{data}
</ReactMarkdown>
</div>
}
<Divider
File diff suppressed because it is too large Load Diff
-393
View File
@@ -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" ? (
<img src={WebhookImage} alt="webhook" width="100px" height="100px" />
) : (
<img src={KafkaImage} alt="MQ" width="100px" height="100px" />
);
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 (
<div>
Workflow select:
<Select
value={selectedWorkflows[counter.counter].name}
onChange={(event) => {
addNewWorkflow(event, counter.counter);
}}
displayEmpty
name="workflow"
>
{availableWorkflows.map((data) => (
<MenuItem key={data.name} value={data} name={data.name}>
{data.name}
</MenuItem>
))}
</Select>
</div>
);
};
const extraWorkflow =
workflows.length > 0 && availableWorkflows.length > 0 ? (
<WorkflowSelect counter={selectedWorkflows.length} />
) : null;
const multiWorkflowSelect =
workflows.length > 0 && selectedWorkflows.length > 0 ? (
<div>
{selectedWorkflows.map((data, count) => (
<WorkflowSelect key={count} counter={count} />
))}
{extraWorkflow}
</div>
) : (
<WorkflowSelect counter={0} />
);
const headerInfo =
Object.getOwnPropertyNames(webhookData).length > 0 ? (
<div>
<Paper style={headerPaperStyle}>
<div style={{ display: "flex", flex: "1" }}>
<div style={{ flex: "1" }}>{hookPicture}</div>
<div
style={{ display: "flex", flexDirection: "column", flex: "5" }}
>
<div style={{ flex: "1" }}>
<h1>Name: {webhookData.info.name}</h1>
</div>
</div>
</div>
<div style={{ flex: "4" }}>
Description: {webhookData.info.description}
<div>Id: {webhookData.id}</div>
<div>Url: {webhookData.info.url}</div>
<div>Type: {webhookData.type}</div>
<div>Status: {webhookData.status}</div>
<div>
CHOOSE ACTIONS:
{multiWorkflowSelect}
</div>
</div>
<Divider />
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<div style={{ flex: "1" }}>
<Button
disabled={
webhookData.running === true && webhookData.name !== ""
}
onClick={() => {
executeHook("start");
}}
style={{
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
}}
variant="outlined"
color="primary"
>
Start {webhookData.type}
</Button>
</div>
<div style={{ flex: "1" }}>
<Button
disabled={webhookData.running === false}
onClick={() => {
executeHook("stop");
}}
style={{
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
}}
variant="outlined"
color="primary"
>
Stop {webhookData.type}
</Button>
</div>
</div>
</Paper>
</div>
) : null;
// FIXME - needs refresh every time you add a new workflow
const workflowdata =
Object.getOwnPropertyNames(webhookData).length > 0 &&
selectedWorkflows.length > 0 ? (
<EditWorkflow
globalUrl={globalUrl}
inputworkflows={selectedWorkflows}
inputname={webhookData.info.name}
inputtype={webhookData.type}
/>
) : null;
const loadedCheck = isLoaded ? (
<div style={{ display: "flex", backgroundColor: "#f7f7f7" }}>
<div style={{ flex: 1 }}>{workflowdata}</div>
<div style={{ flex: 1 }}>{headerInfo}</div>
</div>
) : (
<div></div>
);
// FIXME: Use this for testing
// <EditWorkflow globalUrl={globalUrl} inputname={"Helo?"} inputtype={"webhook"} /> : null
return <div>{loadedCheck}</div>;
};
export default EditWebhook;
+3 -3
View File
@@ -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;
export default Faq;
-118
View File
@@ -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 = (
<div style={bodyDivStyle}>
<Paper style={boxStyle}>
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px" }}>
<h2>Password reset</h2>
<div>
<TextField
required
fullWidth={true}
color="primary"
style={{ backgroundColor: inputColor }}
InputProps={{
style: {
height: "50px",
color: "white",
fontSize: "1em",
},
}}
type="username"
placeholder="Username / Email"
id="standard-required"
autoComplete="username"
margin="normal"
variant="outlined"
onChange={onChangeUser}
/>
</div>
<div style={{ display: "flex", marginTop: "15px" }}>
<Button
color="primary"
variant="contained"
type="submit"
style={{ flex: "1", marginRight: "5px" }}
disabled={!handleValidateForm()}
>
SUBMIT
</Button>
</div>
<div style={{ marginTop: "20px" }}>{resetInfo}</div>
</form>
</Paper>
</div>
);
const loadedCheck = isLoaded ? <div>{data}</div> : <div></div>;
return <div>{loadedCheck}</div>;
};
export default ForgotPassword;
-136
View File
@@ -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 = (
<div style={{ display: "flex", marginTop: "80px" }}>
<Paper style={boxStyle}>
<h2>Password Reset</h2>
<div style={{ flex: "1", display: "flex", flexDirection: "column" }}>
<TextField
required
style={{ flex: "1" }}
fullWidth={true}
placeholder="New password"
type="password"
id="standard-required"
autoComplete="password"
margin="normal"
variant="outlined"
onChange={(e) => setNewPassword(e.target.value)}
/>
<TextField
required
style={{ flex: "1" }}
fullWidth={true}
type="password"
placeholder="Repeat new password"
id="standard-required"
margin="normal"
variant="outlined"
onChange={(e) => setNewPassword2(e.target.value)}
/>
</div>
<Button
disabled={
newPassword.length < 10 ||
newPassword2.length < 10 ||
newPassword !== newPassword2
}
style={{ width: "100%", height: "60px", marginTop: "10px" }}
variant="contained"
color="primary"
onClick={() => onPasswordChange()}
>
Submit password change
</Button>
<h3>{passwordFormMessage}</h3>
</Paper>
</div>
);
const loadedCheck = isLoaded ? (
<div style={bodyDivStyle}>{landingpageData}</div>
) : (
<div></div>
);
return <div>{loadedCheck}</div>;
};
export default Settings;
+7 -6
View File
@@ -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());
})
}
+45 -55
View File
@@ -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) => {
<FileCopyIcon style={{ marginLeft: 0, marginRight: 8 }} />
{"Duplicate Workflow"}
</MenuItem>
{/*<NestedMenuItem disabled={userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length === 1 || userdata.orgs.length >= 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
//copyWorkflow(data)
//setOpen(false)
}} key={"duplicate"}>
<FileCopyIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Copy to Child Org"}
</NestedMenuItem>*/}
<MenuItem
style={{ backgroundColor: inputColor, color: "white" }}
onClick={() => {
@@ -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
/>
<ChipInput
<MuiChipsInput
style={{ marginTop: 10 }}
InputProps={{
style: {
@@ -2179,7 +2169,7 @@ const GettingStarted = (props) => {
})
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</span>!
@@ -2444,7 +2434,7 @@ const GettingStarted = (props) => {
</Typography>
</div>
<div style={{ flex: 1, float: "right" }}>
<ChipInput
<MuiChipsInput
style={{}}
InputProps={{
style: {
@@ -2610,7 +2600,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());
});
};
+1 -1
View File
@@ -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";
-222
View File
@@ -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 (
<Grid
item
xs={4}
onClick={() => {
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()
}}
>
<Card style={baseStyle}>
<CardActionArea style={cardContentStyle}>
<CardContent>
<Typography variant="h4">{data.title}</Typography>
</CardContent>
</CardActionArea>
</Card>
</Grid>
);
};
const view1 =
curView === 0 ? (
<div>
<Typography variant="h4">What are you interested in?</Typography>
<Grid container style={outerGridView} spacing={3}>
{viewdata1.map((data) => {
return HandleSelection(data);
})}
</Grid>
{/*
<Button variant="contained" color="primary" style={{height: 50, width: 300, margin: "auto",}} onClick={() => {
setCurView(1)
}}>
Continue
</Button>
*/}
</div>
) : null;
const view2 =
curView === 1 ? (
<div>
<Typography variant="h4">Step 2.</Typography>
{/*
<Grid container style={outerGridView} spacing={3}>
{selectedItem.subitems === undefined ? null :
selectedItem.subitems.map(data => {
return (
<Grid item xs={4}>
<Card style={paperStyle}>
<CardActionArea style={cardContentStyle}>
<CardContent>
<Typography variant="h4">
{data.name}
</Typography>
<Typography variant="body1" style={{marginTop: 10}}>
{data.subtitle}
</Typography>
</CardContent>
</CardActionArea>
</Card>
</Grid>
)
})}
</Grid>
*/}
</div>
) : null;
const baseView = (
<div style={{ maxWidth: 1024, margin: "auto", paddingTop: 50 }}>
{view1}
{view2}
</div>
);
return <div>{baseView}</div>;
};
export default Workflows;
-205
View File
@@ -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 (
<Paper style={boxStyle}>
<a href={link} style={hrefStyle}>
<div style={{ flex: "1", color: "#FFFFFF" }}>
<h2>{header}</h2>
</div>
<Divider />
<div
style={{
flex: "3",
marginLeft: "10px",
marginRight: "10px",
marginTop: "10px",
color: textColor,
}}
>
{description}
</div>
<div style={{ margin: "auto" }}>{icon}</div>
<Divider style={{ marginTop: "20px", marginBottom: "20px" }} />
<div style={{ flex: "1", color: "#f85a3e" }}>
<div style={{}}>Learn more</div>
</div>
</a>
</Paper>
);
};
const listitems = [
GridLayout(
"Simple integrations",
"Easily use others' or create your own integration",
"/docs/apps",
<Web
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
GridLayout(
"Workflows",
"Access the power of automation within minutes, whether its on premise or in the cloud",
"/docs/workflows",
<AccountTree
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
GridLayout(
"Realtime actions",
"Beat the clock by leveraging our realtime triggers",
"/docs/triggers",
<ScheduleIcon
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
];
// The actual landing page
// <img style={{width: "400px"}} alt={"logo"} src={Default}/>
const landingpageDataBrowser = (
<div>
<div style={bodyTextStyle}>
<h1>Shuffle</h1>
<h3 style={{ color: "#8899A6" }}>
A general automation solution for Infosec and IT Professionals
</h3>
</div>
<a href="/register" style={hrefStyle}>
<Button
style={{ width: "180px", height: "50px", borderRadius: "0px" }}
variant="outlined"
color="primary"
>
Try it out
</Button>
</a>
<a href="/contact" style={hrefStyle}>
<Button
style={{ width: "180px", height: "50px", borderRadius: "0px" }}
variant="contained"
color="primary"
>
Contact
</Button>
</a>
<div style={{ display: "flex", marginTop: "100px" }}>
{listitems.map((item) => {
return <div>{item}</div>;
})}
</div>
</div>
);
const landingpageDataMobile = (
<div>
<div
style={{
color: "white",
textAlign: "center",
marginLeft: "10px",
marginRight: "10px",
}}
>
<h1>Shuffle</h1>
<h3>A general automation solution for Infosec and IT Professionals</h3>
<a href="/contact" style={hrefStyle}>
<Button
style={{ width: "220px", height: "60px", borderRadius: "0px" }}
variant="contained"
color="primary"
>
Contact
</Button>
</a>
</div>
<div
style={{ display: "flex", flexDirection: "column", marginTop: "100px" }}
>
<div>{listitems[0]}</div>
<div style={{ marginTop: "20px" }}>{listitems[1]}</div>
<div style={{ marginTop: "20px", marginBottom: "30px" }}>
{listitems[2]}
</div>
<div
style={{
marginTop: "20px",
marginBottom: "30px",
textAlign: "center",
}}
>
<a href="/contact" style={hrefStyle}>
<Button
style={{ width: "220px", height: "60px", borderRadius: "0px" }}
variant="contained"
color="primary"
>
Contact
</Button>
</a>
</div>
</div>
</div>
);
// Reroute if the user is logged in
// const landingSite = isLoggedIn ? <Workflows globalUrl={globalUrl}{...props} /> : <div style={bodyDivStyle}>{landingpageData}</div>
const landingSite = <div style={bodyDivStyle}>{landingpageDataBrowser}</div>;
const loadedCheck = isLoaded ? (
<div>
<BrowserView>{landingSite}</BrowserView>
<MobileView>{landingpageDataMobile}</MobileView>
</div>
) : (
<div></div>
);
return <div>{loadedCheck}</div>;
};
export default LandingPage;

Some files were not shown because too many files have changed in this diff Show More