diff --git a/.env b/.env index e139fdda..307c9d35 100644 --- a/.env +++ b/.env @@ -32,6 +32,7 @@ SHUFFLE_ENCRYPTION_MODIFIER= # Other configs BASE_URL=http://shuffle-backend:5001 +SSO_REDIRECT_URL=http://localhost:3001 BACKEND_HOSTNAME=shuffle-backend BACKEND_PORT=5001 FRONTEND_PORT=3001 @@ -50,16 +51,17 @@ SHUFFLE_PASS_WORKER_PROXY=TRUE 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 -SHUFFLE_BASE_IMAGE_NAME=frikky +SHUFFLE_BASE_IMAGE_NAME=shuffle SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io -SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-0.8.80" +SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.0.0" # Used for auto-cleanup of containers. REALLY important at scale. SHUFFLE_CONTAINER_AUTO_CLEANUP=false SHUFFLE_ELASTIC=true SHUFFLE_LOGS_DISABLED=false -SHUFFLE_CHAT_DISABLED=false +SHUFFLE_CHAT_DISABLED=false # Controls support chat SHUFFLE_RERUN_SCHEDULE=300 # DATABASE CONFIGURATIONS diff --git a/.github/install-guide.md b/.github/install-guide.md index c876f109..73550fca 100644 --- a/.github/install-guide.md +++ b/.github/install-guide.md @@ -87,9 +87,13 @@ http://localhost:5001 - REST API - requires [>=go1.13](https://golang.org/dl/) ```bash export SHUFFLE_OPENSEARCH_URL="http://localhost:9200" export SHUFFLE_ELASTIC=true +export SHUFFLE_OPENSEARCH_USERNAME=admin +export SHUFFLE_OPENSEARCH_PASSWORD=admin +export SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true cd backend/go-app -go run *.go +go run main.go walkoff.go docker.go ``` +**WINDOWS USERS:** Follow [this guide](https://www.wikihow.com/Create-an-Environment-Variable-in-Windows-10) to add environment variables in your machine. Large portions of the backend is written in another repository - [shuffle-shared](https://github.com/frikky/shuffle-shared). If you want to update any of this code and test in realtime, we recommend following these steps: 1. Clone shuffle-shared to a local repository @@ -101,8 +105,6 @@ Large portions of the backend is written in another repository - [shuffle-shared 4. Make the changes you want, then restart the backend server! 5. With your changes made, make a pull request :fire: -**WINDOWS USERS:** You'll have to to add the "export" part as an environment variable. - ## Database - Opensearch Make sure this is running through the docker-compose, and that the backend points to it with SHUFFLE_OPENSEARCH_URL defined @@ -122,6 +124,4 @@ export BASE_URL=http://YOUR-IP:5001 export DOCKER_API_VERSION=1.40 ``` -**WINDOWS USERS:** You'll have to to add the "export" part as an environment variable. - AND THAT's it - hopefully it worked. If it didn't please email [frikky@shuffler.io](mailto:frikky@shuffler.io) diff --git a/.github/push_nightly.sh b/.github/push_nightly.sh new file mode 100644 index 00000000..92d5320b --- /dev/null +++ b/.github/push_nightly.sh @@ -0,0 +1,47 @@ +# This can be done in the dockerpush workflow itself +# Done manually for now since GHCR isn't being pushed to easily with the current Github action CI. Nightly = Latest IF we run hotfixes on latest + +### Pull latest from ghcr CI/CD +#docker pull ghcr.io/shuffle/shuffle-app_sdk:nightly +#docker pull ghcr.io/shuffle/shuffle-worker:nightly +#docker pull ghcr.io/shuffle/shuffle-orborus:nightly +#docker pull ghcr.io/shuffle/shuffle-frontend:nightly +#docker pull ghcr.io/shuffle/shuffle-backend:nightly + +### LATEST releases: +#docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:latest +#docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:latest +#docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:latest +#docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:latest +#docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:latest +# +#docker push ghcr.io/shuffle/shuffle-app_sdk:latest +#docker push ghcr.io/shuffle/shuffle-worker:latest +#docker push ghcr.io/shuffle/shuffle-orborus:latest +#docker push ghcr.io/shuffle/shuffle-frontend:latest +#docker push ghcr.io/shuffle/shuffle-backend:latest + + +### 1.1.0 releases: +#docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:1.1.0 +#docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:1.1.0 +#docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:1.1.0 +#docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:1.1.0 +#docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:1.1.0 +# +#docker push ghcr.io/shuffle/shuffle-app_sdk:1.1.0 +#docker push ghcr.io/shuffle/shuffle-worker:1.1.0 +#docker push ghcr.io/shuffle/shuffle-orborus:1.1.0 +#docker push ghcr.io/shuffle/shuffle-frontend:1.1.0 +#docker push ghcr.io/shuffle/shuffle-backend:1.1.0 + + + +### Manage worker-scale upload (Requires auth) +# This is supposed to be unavailable, and only be downloadable by customers +docker pull ghcr.io/shuffle/shuffle-worker-scale:latest +docker save ghcr.io/shuffle/shuffle-worker-scale:latest -o shuffle-worker.zip +echo "1. Upload shuffle-worker.zip to the shuffler.io public repo. If in Github Dev env, download the file, and upload manually." +echo "2. Have customers download it with: $ wget URL" +echo "3. Have customers use with with: docker load shuffle-worker.zip" + diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml new file mode 100644 index 00000000..411a5ff5 --- /dev/null +++ b/.github/workflows/dockerbuild.yaml @@ -0,0 +1,75 @@ +name: dockerbuild + +on: + push: + branches: launch +jobs: + main: + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + strategy: + fail-fast: false + matrix: + include: + - app: frontend + path: frontend + version: nightly + experimental: true + - app: backend + path: backend + version: nightly + experimental: true + - app: app_sdk + path: backend/app_sdk + version: nightly + experimental: true + - app: orborus + path: functions/onprem/orborus + version: nightly + experimental: true + - app: worker + path: functions/onprem/worker + version: nightly + experimental: true + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to Ghcr + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Ghcr Build and push + id: docker_build + uses: docker/build-push-action@v3 + env: + BUILDX_NO_DEFAULT_LOAD: true + with: + logout: false + context: ${{ matrix.path }}/ + file: ${{ matrix.path }}/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + 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 + + - name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/snyk-container-analysis.yml b/.github/workflows/snyk-container-analysis.yml index b3a78761..f9a406b2 100644 --- a/.github/workflows/snyk-container-analysis.yml +++ b/.github/workflows/snyk-container-analysis.yml @@ -11,7 +11,6 @@ name: Snyk Container on: push: branches: - - master - launch pull_request: # The branches below must be a subset of the branches above @@ -25,9 +24,12 @@ jobs: snyk: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 + - name: Build a Docker image - run: docker build -t your/image-to-test . + run: docker build -t frontend . + - name: Run Snyk to check Docker image for vulnerabilities # Snyk can be used to break the build when it detects vulnerabilities. # In this case we want to upload the issues to GitHub Code Scanning @@ -41,6 +43,7 @@ jobs: with: image: your/image-to-test args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning uses: github/codeql-action/upload-sarif@v1 with: diff --git a/README.md b/README.md index 17c5c7b8..e937f939 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ Shuffle Automation +[![CodeQL](https://github.com/Shuffle/Shuffle/actions/workflows/codeql-analysis.yml/badge.svg?branch=launch)](https://github.com/Shuffle/Shuffle/actions/workflows/codeql-analysis.yml) +[![Autobuild](https://github.com/Shuffle/Shuffle/actions/workflows/dockerbuild.yaml/badge.svg?branch=launch)](https://github.com/Shuffle/Shuffle/actions/workflows/dockerbuild.yaml) +

[Shuffle](https://shuffler.io) is an automation platform for and by the community, focusing on accessibility for anyone to automate. Security operations is complex, but it doesn't have to be. diff --git a/backend/Dockerfile b/backend/Dockerfile index f09fe3b3..5bc61f64 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.2-buster as builder +FROM golang:1.19.3-buster as builder # Add files RUN mkdir /app @@ -15,14 +15,24 @@ ADD ./app_sdk/app_base.py /app_sdk ADD ./app_gen /app_gen RUN go get -v +RUN go mod tidy +RUN go clean -modcache -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webapp . +# From November 2022, CGO is enabled due to packages +# that we use requiring it. This is a temporary fix +# and makes us HAVE to install libc compatibility packages farther down. +RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o webapp . # Certificate build - gets required certs FROM alpine:latest as certs RUN apk --update add ca-certificates -FROM alpine:3.14.2 +# Sets up the final image +FROM alpine:3.17.0 + +# FIXME: Install cgo because CGO_ENABLED=1 during build +RUN apk add --no-cache libc6-compat +RUN apk add --no-cache libstdc++ COPY --from=builder /app/ /app COPY --from=builder /app_sdk/ /app_sdk diff --git a/backend/app_sdk/Dockerfile_blackarch b/backend/app_sdk/Dockerfile_blackarch index 02480aad..e7469166 100644 --- a/backend/app_sdk/Dockerfile_blackarch +++ b/backend/app_sdk/Dockerfile_blackarch @@ -1,4 +1,4 @@ -FROM peterclemenko/blackarch as base +FROM blackarchlinux/blackarch as base FROM base as builder diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 3c42c42e..2f23755f 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -17,6 +17,7 @@ import http.client import urllib.parse import jinja2 import datetime +import dateutil from io import StringIO as StringBuffer from io import BytesIO from liquid import Liquid, defaults @@ -104,6 +105,89 @@ def base64_decode(a): except: return base64.b64decode(a) +@shuffle_filters.register +def json_parse(a): + return json.loads(str(a)) + +@shuffle_filters.register +def as_object(a): + return json.loads(str(a)) + +@shuffle_filters.register +def ast(a): + return ast.literal_eval(str(a)) + +@shuffle_filters.register +def escape_string(a): + a = str(a) + return a.replace("\\\'", "\'", -1).replace("\\\"", "\"", -1).replace("'", "\\\'", -1).replace("\"", "\\\"", -1) + +@shuffle_filters.register +def json_escape(a): + a = str(a) + return a.replace("\\\'", "\'", -1).replace("\\\"", "\"", -1).replace("'", "\\\\\'", -1).replace("\"", "\\\\\"", -1) + +@shuffle_filters.register +def escape_json(a): + a = str(a) + return a.replace("\\\'", "\'", -1).replace("\\\"", "\"", -1).replace("'", "\\\\\'", -1).replace("\"", "\\\\\"", -1) + +# By default using json escape to add all backslashes +@shuffle_filters.register +def escape(a): + a = str(a) + return json_escape(a) + +@shuffle_filters.register +def flatten(a): + a = list(a) + + flat_list = [a for xs in xss for a in xs] + return flat_list + +@shuffle_filters.register +def csv_parse(a): + a = str(a) + splitdata = a.split("\n") + columns = [] + if len(splitdata) > 1: + columns = splitdata[0].split(",") + else: + return a.split("\n") + + allitems = [] + cnt = -1 + for item in splitdata[1:]: + cnt += 1 + commasplit = item.split(",") + + fullitem = {} + fullitem["unparsed"] = item + fullitem["index"] = cnt + fullitem["parsed"] = {} + if len(columns) != len(commasplit): + + if len(commasplit) > len(columns): + diff = len(commasplit)-len(columns) + + try: + commasplit = commasplit[0:len(commasplit)-diff] + except: + pass + else: + for item in range(0, len(columns)-len(commasplit)): + commasplit.append("") + + for key in range(len(columns)): + try: + fullitem["parsed"][columns[key]] = commasplit[key] + except: + continue + + allitems.append(fullitem) + + return allitems + #print(standard_filter_manager.filters) #print(shuffle_filters.filters) #print(Liquid("{{ '10' | plus: 1}}", filters=shuffle_filters.filters).render()) @@ -261,6 +345,36 @@ class AppBase: return new_input + def prepare_response(self, request): + try: + parsedheaders = {} + for key, value in request.headers.items(): + parsedheaders[key] = value + + cookies = {} + if request.cookies: + for key, value in request.cookies.items(): + cookies[key] = value + + + jsondata = request.text + try: + jsondata = json.loads(jsondata) + except: + pass + + return json.dumps({ + "success": True, + "status": request.status_code, + "url": request.url, + "headers": parsedheaders, + "body": jsondata, + "cookies":cookies, + }) + except Exception as e: + print(f"[WARNING] Failed in request: {e}") + return request.text + # FIXME: Add more info like logs in here. # Docker logs: https://forums.docker.com/t/docker-logs-inside-the-docker-container/68190/2 def send_result(self, action_result, headers, stream_path): @@ -330,7 +444,7 @@ class AppBase: # FIXME: Adding retries here. try: finished = False - for i in range (0, 5): + for i in range (0, 10): try: ret = requests.post(url, headers=headers, json=action_result, timeout=10) @@ -339,26 +453,36 @@ class AppBase: finished = True break else: - self.logger.info(f"[DEBUG] RESP: {ret.text}") + self.logger.info(f"[ERROR] RESP: {ret.text}") except requests.exceptions.RequestException as e: self.logger.info(f"[DEBUG] Request problem: {e}") + time.sleep(0.1) + #time.sleep(5) continue except TimeoutError as e: self.logger.info(f"[DEBUG] Timeout or request: {e}") + time.sleep(0.1) + #time.sleep(5) continue except requests.exceptions.ConnectionError as e: self.logger.info(f"[DEBUG] Connectionerror: {e}") + time.sleep(0.1) + #time.sleep(5) continue except http.client.RemoteDisconnected as e: self.logger.info(f"[DEBUG] Remote: {e}") + time.sleep(0.1) + #time.sleep(5) continue except urllib3.exceptions.ProtocolError as e: self.logger.info(f"[DEBUG] Protocol err: {e}") + time.sleep(0.1) + #time.sleep(5) continue @@ -367,17 +491,17 @@ class AppBase: if not finished: # Not sure why this would work tho :) action_result["status"] = "FAILURE" - action_result["result"] = f"POST failed to get info!" - self.logger.info(f"[DEBUG] Before typeerror stream result - NOT finished") + 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) 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: self.logger.info(f"[DEBUG] Unexpected ConnectionError happened: {e}") except TypeError as e: - #self.logger.exception(e) action_result["status"] = "FAILURE" - action_result["result"] = f"POST error: {e}" + action_result["result"] = json.dumps({"success": False, "reason": "Typeerror when sending to backend URL %s" % url}) + self.logger.info(f"[DEBUG] Before typeerror stream result: {e}") ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result) #self.logger.info(f"[DEBUG] Result: {ret.status_code}") @@ -971,6 +1095,31 @@ class AppBase: self.logger.info("\nLOOP: %s\nRESULTS: %s" % (loop_wrapper, results)) return results + # Downloads all files from a namespace + # Currently only working on local version of Shuffle + def get_file_category_ids(self, category): + org_id = self.full_execution["workflow"]["execution_org"]["id"] + + get_path = "/api/v1/files/namespaces/%s?execution_id=%s&ids=true" % (category, self.full_execution["execution_id"]) + headers = { + "Authorization": "Bearer %s" % self.authorization + } + + ret = requests.get("%s%s" % (self.url, get_path), headers=headers) + return ret.json() + #if ret1.status_code != 200: + # return { + # "success": False, + # "reason": "Status code is %d from backend for category %s" % category, + # "list": [], + # } + + #return { + # "success": True, + # "ids": ret1.json(), + #} + + # Downloads all files from a namespace # Currently only working on local version of Shuffle def get_file_namespace(self, namespace): @@ -1003,6 +1152,12 @@ class AppBase: return myzipfile + def get_file_namespace_ids(self, namespace): + return self.get_file_category_ids(self, namespace) + + def get_file_category(self, category): + return self.get_file_namespace(self, category) + # Things to consider for files: # - How can you download / stream a file? # - Can you decide if you want a stream or the files directly? @@ -1273,36 +1428,53 @@ class AppBase: if isinstance(self.full_execution, str) and len(self.full_execution) == 0: self.logger.info("[DEBUG] NO EXECUTION - LOADING!") try: - tmpdata = { - "authorization": self.authorization, - "execution_id": self.current_execution_id - } + failed = False + rettext = "" + for i in range(0, 5): + tmpdata = { + "authorization": self.authorization, + "execution_id": self.current_execution_id + } - self.logger.info("[DEBUG] Before FULLEXEC stream result") - ret = requests.post( - "%s/api/v1/streams/results" % (self.base_url), - headers=headers, - json=tmpdata - ) + self.logger.info("[ERROR] Before FULLEXEC stream result") + ret = requests.post( + "%s/api/v1/streams/results" % (self.base_url), + headers=headers, + json=tmpdata + ) - if ret.status_code == 200: - fullexecution = ret.json() - else: - try: - self.logger.info("[DEBUG] Error: Data: ", ret.json()) - self.logger.info("[DEBUG] Error with status code for results. Crashing because ACTION_RESULTS or WORKFLOW_VARIABLE can't be handled. Status: %d" % ret.status_code) - except json.decoder.JSONDecodeError: - pass + if ret.status_code == 200: + fullexecution = ret.json() + failed = False + break + elif ret.status_code == 500: + self.logger.info("[ERROR] (fails: %d) Error in app with status code %d for results (1). RETRYING because results can't be handled" % (i+1, ret.status_code)) + + rettext = ret.text + failed = True + time.sleep(10) + continue + + else: + self.logger.info("[ERROR] Error in app with status code %d for results (2). Crashing because results can't be handled" % ret.status_code) + + rettext = ret.text + failed = True + break + + if failed: self.action_result["result"] = json.dumps({ "success": False, "reason": f"Bad result from backend during startup of app: {ret.status_code}", - "extended_reason": f"{ret.text}" + "extended_reason": f"{rettext}" }) + self.send_result(self.action_result, headers, stream_path) return + except requests.exceptions.ConnectionError as e: - self.logger.info("[DEBUG] FullExec Connectionerror: %s" % e) + self.logger.info("[ERROR] FullExec Connectionerror: %s" % e) self.action_result["result"] = json.dumps({ "success": False, "reason": f"Connection error during startup: {e}" @@ -1315,7 +1487,7 @@ class AppBase: try: fullexecution = json.loads(self.full_execution) except json.decoder.JSONDecodeError as e: - self.logger.info("[WARNING] Json decode execution error: %s" % e) + self.logger.info("[ERROR] Json decode execution error: %s" % e) self.action_result["result"] = "Json error during startup: %s" % e self.send_result(self.action_result, headers, stream_path) return @@ -2012,7 +2184,6 @@ class AppBase: errors = False error_msg = "" try: - #self.logger.info("In liquid") if len(template) > 10000000: self.logger.info("[DEBUG] Skipping liquid - size too big (%d)" % len(template)) return template @@ -2042,10 +2213,87 @@ class AppBase: self.logger.info(f"[ERROR] Liquid Template error: {e}") error = True error_msg = e + + self.action["parameters"].append({ + "name": "liquid_template_error", + "value": f"There was a Liquid input error (1). Details: {e}", + }) + + self.action_result["action"] = self.action + except SyntaxError as e: + self.logger.info(f"[ERROR] Liquid Syntax error: {e}") + error = True + error_msg = e + + self.action["parameters"].append({ + "name": "liquid_python_syntax_error", + "value": f"There was a syntax error in your Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action + except IndentationError as e: + self.logger.info(f"[ERROR] Liquid IndentationError: {e}") + error = True + error_msg = e + + self.action["parameters"].append({ + "name": "liquid_indentiation_error", + "value": f"There was an indentation error in your Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action except jinja2.exceptions.TemplateSyntaxError as e: self.logger.info(f"[ERROR] Liquid Syntax error: {e}") error = True error_msg = e + + self.action["parameters"].append({ + "name": "liquid_syntax_error", + "value": f"There was a syntax error in your Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action + except json.decoder.JSONDecodeError as e: + self.logger.info(f"[ERROR] Liquid JSON Syntax error: {e}") + + replace = False + skip_next = False + newlines = [] + thisline = [] + for line in template.split("\n"): + #print("LINE: %s" % repr(line)) + if "\"\"\"" in line or "\'\'\'" in line: + if replace: + skip_next = True + else: + replace = not replace + + if replace == True: + thisline.append(line) + if skip_next == True: + if len(thisline) > 0: + #print(thisline) + newlines.append(" ".join(thisline)) + thisline = [] + + replace = False + else: + newlines.append(line) + + new_template = "\n".join(newlines) + if new_template != template: + #check_template(new_template) + return parse_liquid(new_template, self) + else: + error = True + error_msg = e + + self.action["parameters"].append({ + "name": "liquid_json_error", + "value": f"There was a syntax error in your input JSON(2). This is typically an issue with escaping newlines. Details: {e}", + }) + + self.action_result["action"] = self.action except TypeError as e: try: if "string as left operand" in f"{e}": @@ -2070,6 +2318,13 @@ class AppBase: except Exception as e: print(f"SubError in Liquid: {e}") + + self.action["parameters"].append({ + "name": "liquid_general_error", + "value": f"There was general error Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action #return template self.logger.info(f"[ERROR] Liquid TypeError error: {e}") @@ -2081,6 +2336,16 @@ class AppBase: error = True error_msg = e + self.action["parameters"].append({ + "name": "liquid_general_exception", + "value": f"There was general exception Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action + + if "fmt" in error_msg and "liquid_date" in error_msg: + return template + self.logger.info("Done in liquid") if error == True: self.action_result["status"] = "FAILURE" @@ -2089,6 +2354,7 @@ class AppBase: "reason": f"Failed to parse LiquidPy: {error_msg}", "input": template, } + try: self.action_result["result"] = json.dumps(data) except Exception as e: @@ -2227,7 +2493,6 @@ class AppBase: #self.logger.info("STATIC PARSED: %s" % actualitem) #self.logger.info("[INFO] Done with regex matching") if len(actualitem) > 0: - #self.logger.info("[DEBUG] Matches: ", actualitem) for replace in actualitem: try: to_be_replaced = replace[0] @@ -2458,6 +2723,12 @@ class AppBase: except KeyError: return True, "" + # Startnode should always run - no need to check incoming + try: + if action["id"] == fullexecution["start"]: + return True, "" + except Exception as error: + self.logger.info(f"[WARNING] Failed checking startnode: {error}") available_checks = [ "=", @@ -2527,7 +2798,7 @@ class AppBase: check, sourcevalue, is_loop = parse_params(action, fullexecution, condition["source"], self) if check: continue - return False, {"success": False, "reason": "Failed condition (1): %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)} + return False, {"success": False, "reason": "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)} #sourcevalue = sourcevalue.encode("utf-8") sourcevalue = parse_wrapper_start(sourcevalue, self) @@ -2536,7 +2807,7 @@ class AppBase: check, destinationvalue, is_loop = parse_params(action, fullexecution, condition["destination"], self) if check: continue - return False, {"success": False, "reason": "Failed condition (2): %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)} + return False, {"success": False, "reason": "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)} #destinationvalue = destinationvalue.encode("utf-8") destinationvalue = parse_wrapper_start(destinationvalue, self) @@ -2695,6 +2966,8 @@ class AppBase: if parameter["name"] == "body": bodyindex = counter #self.logger.info("PARAM: %s" % parameter) + + # FIXMe: This should also happen after liquid & param parsing.. try: values = parameter["value_replace"] if values != None: @@ -2702,16 +2975,24 @@ class AppBase: for val in values: replace_value = val["value"] replace_key = val["key"] + if (val["value"].startswith("{") and val["value"].endswith("}")) or (val["value"].startswith("[") and val["value"].endswith("]")): self.logger.info(f"""Trying to parse as JSON: {val["value"]}""") try: - value_replace = json.loads(val["value"]) - # If it gets here, remove the "" infront and behind the key as well since this is preventing the JSON from being loaded + newval = val["value"] + + # If it gets here, remove the "" infront and behind the key as well + # since this is preventing the JSON from being loaded + tmpvalue = json.loads(newval) replace_key = f"\"{replace_key}\"" except json.decoder.JSONDecodeError as e: - self.logger.info("Failed JSON replacement for OpenAPI %s", val["key"]) + self.logger.info("[WARNING] Failed JSON replacement for OpenAPI %s", val["key"]) + elif val["value"].lower() == "true" or val["value"].lower() == "false": replace_key = f"\"{replace_key}\"" + else: + if "\"" in replace_value and not "\\\"" in replace_value: + replace_value = replace_value.replace("\"", "\\\"", -1) action["parameters"][counter]["value"] = action["parameters"][counter]["value"].replace(replace_key, replace_value, 1) @@ -2768,6 +3049,9 @@ class AppBase: "exception": f"Value Error: {check}", })) + if parameter["name"] == "body": + self.logger.info(f"[INFO] Should debug field with liquid and other checks as it's BODY: {value}") + # Custom format for ${name[0,1,2,...]}$ #submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})" #self.logger.info(f"Returnedvalue: {value}") @@ -3107,6 +3391,13 @@ class AppBase: # FIXME: add this to Multi exec as well. try: for key, value in params.items(): + if "-" in key: + try: + newkey = key.replace("-", "_", -1).lower() + params[newkey] = params[key] + except Exception as e: + self.logger.info("[DEBUG] Failed updating key with dash in it: %s" % e) + try: if isinstance(value, str) and ((value.startswith("{") and value.endswith("}")) or (value.startswith("[") and value.endswith("]"))): params[key] = json.loads(value) @@ -3151,7 +3442,7 @@ class AppBase: errorstring = f"{e}" 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?") + self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (0)?") try: e = json.loads(f"{e}") except: @@ -3159,7 +3450,7 @@ class AppBase: newres = json.dumps({ "success": False, - "reason": "An exception occurred while running this function. See exception for more details and contact support if this persists (support@shuffler.io)", + "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, }) break @@ -3181,7 +3472,7 @@ class AppBase: }) break except Exception as e: - self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly?") + self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (1)?") try: e = json.loads(f"{e}") @@ -3190,7 +3481,7 @@ class AppBase: newres = json.dumps({ "success": False, - "reason": "An exception occurred while running this function. See exception for more details and contact support if this persists (support@shuffler.io)", + "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, }) break @@ -3322,12 +3613,22 @@ class AppBase: except TypeError as e: self.logger.info("[ERROR] TypeError issue: %s" % e) self.action_result["status"] = "FAILURE" - self.action_result["result"] = "TypeError: %s" % str(e) + self.action_result["result"] = json.dumps({ + "success": False, + "reason": f"Typeerror. Most likely due to a list that should've been a string. See details for more info.", + "details": e, + }) + #self.action_result["result"] = "TypeError: %s" % str(e) else: self.logger.info("[DEBUG] Function %s doesn't exist?" % action["name"]) self.logger.error(f"[ERROR] App {self.__class__.__name__}.{action['name']} is not callable") self.action_result["status"] = "FAILURE" - self.action_result["result"] = "Function %s is not callable." % actionname + #self.action_result["result"] = "Function %s is not callable." % actionname + + self.action_result["result"] = json.dumps({ + "success": False, + "reason": f"Function %s doesn't exist." % actionname, + }) # https://ptb.discord.com/channels/747075026288902237/882017498550112286/882043773138382890 except (requests.exceptions.RequestException, TimeoutError) as e: diff --git a/backend/app_sdk/build.sh b/backend/app_sdk/build.sh index 22416f8b..1ff45c75 100644 --- a/backend/app_sdk/build.sh +++ b/backend/app_sdk/build.sh @@ -2,16 +2,21 @@ ### DEFAULT NAME=shuffle-app_sdk -VERSION=0.9.70 +VERSION=1.1.0 docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force -docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly +docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly -t shuffle/shuffle:app_sdk -t shuffle/$NAME:$VERSION -t docker.pkg.github.com/shuffle/shuffle/$NAME:$VERSION -t ghcr.io/shuffle/$NAME:$VERSION -t ghcr.io/shuffle/$NAME:nightly docker push frikky/shuffle:app_sdk docker push ghcr.io/frikky/$NAME:$VERSION docker push ghcr.io/frikky/$NAME:nightly docker push ghcr.io/frikky/$NAME:latest +docker push shuffle/shuffle:app_sdk +docker push ghcr.io/shuffle/$NAME:$VERSION +docker push ghcr.io/shuffle/$NAME:nightly +docker push ghcr.io/shuffle/$NAME:latest + diff --git a/backend/app_sdk/requirements.txt b/backend/app_sdk/requirements.txt index fe0c6789..bacb3bc8 100644 --- a/backend/app_sdk/requirements.txt +++ b/backend/app_sdk/requirements.txt @@ -1,7 +1,8 @@ urllib3==1.26.5 requests==2.25.1 MarkupSafe==2.0.1 -liquidpy==0.7.3 +liquidpy==0.7.6 flask[async]==2.0.2 waitress==2.1.0 #flask==1.1.2 +python-dateutil==2.8.1 diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go index 9ca293bf..f9c27eef 100644 --- a/backend/go-app/docker.go +++ b/backend/go-app/docker.go @@ -2,15 +2,17 @@ package main // Docker import ( + "archive/tar" + "github.com/shuffle/shuffle-shared" - "archive/tar" //"bufio" "path/filepath" //"strconv" "bytes" "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -757,7 +759,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) { tagFound = version.Name } - buildSwaggerApp(resp, []byte(openApiApp.Body), user) + buildSwaggerApp(resp, []byte(openApiApp.Body), user, false) } } } @@ -803,6 +805,92 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) { //resp.WriteHeader(200) } +// Downloads and activates an app from shuffler.io if possible +func handleRemoteDownloadApp(resp http.ResponseWriter, ctx context.Context, user shuffle.User, appId string) { + url := fmt.Sprintf("https://shuffler.io/api/v1/apps/%s/config", appId) + log.Printf("Downloading API from %s", url) + req, err := http.NewRequest( + "GET", + url, + nil, + ) + + if err != nil { + log.Printf("[ERROR] Failed auto-downloading app %s: %s", appId, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + + httpClient := &http.Client{} + newresp, err := httpClient.Do(req) + if err != nil { + log.Printf("[ERROR] Failed running auto-download request for %s: %s", appId, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + log.Printf("[ERROR] Failed setting respbody for workflow download: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + + if len(respBody) > 0 { + type tmpapp struct { + Success bool `json:"success"` + OpenAPI string `json:"openapi"` + } + + app := tmpapp{} + err := json.Unmarshal(respBody, &app) + if err != nil || app.Success == false || len(app.OpenAPI) == 0 { + log.Printf("[ERROR] Failed app unmarshal during auto-download. Success%#v. Applength: %d: %s", app.Success, len(app.OpenAPI), err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + + key, err := base64.StdEncoding.DecodeString(app.OpenAPI) + if err != nil { + log.Printf("[ERROR] Failed auto-setting OpenAPI app: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + + cacheKey := fmt.Sprintf("workflowapps-sorted-100") + shuffle.DeleteCache(ctx, cacheKey) + cacheKey = fmt.Sprintf("workflowapps-sorted-500") + shuffle.DeleteCache(ctx, cacheKey) + cacheKey = fmt.Sprintf("workflowapps-sorted-1000") + shuffle.DeleteCache(ctx, cacheKey) + + newapp := shuffle.ParsedOpenApi{} + err = json.Unmarshal(key, &newapp) + if err != nil { + log.Printf("[ERROR] Failed openapi unmarshal during auto-download: %s", app.Success, len(app.OpenAPI), err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + + err = json.Unmarshal(key, &newapp) + if err != nil { + log.Printf("[ERROR] Failed openapi unmarshal during auto-download: %s", app.Success, len(app.OpenAPI), err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + + buildSwaggerApp(resp, []byte(newapp.Body), user, true) + return + } +} + func activateWorkflowAppDocker(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { @@ -846,9 +934,9 @@ func activateWorkflowAppDocker(resp http.ResponseWriter, request *http.Request) apps, err := shuffle.FindWorkflowAppByName(ctx, appName) //log.Printf("[INFO] Found %d apps for %s", len(apps), appName) if err != nil || len(apps) == 0 { - log.Printf("[WARNING] Error getting app %s (app config): %s", appName, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + log.Printf("[WARNING] Error getting app %s (app config). Starting remote download.: %s", appName, err) + + handleRemoteDownloadApp(resp, ctx, user, fileId) return } @@ -869,10 +957,12 @@ func activateWorkflowAppDocker(resp http.ResponseWriter, request *http.Request) app = &selectedApp } else { - log.Printf("[WARNING] Error getting app with ID %s (app config): %s", fileId, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + log.Printf("[WARNING] Error getting app with ID %s (app config): %s. Starting remote download(2)", fileId, err) + handleRemoteDownloadApp(resp, ctx, user, fileId) return + //resp.WriteHeader(401) + //resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + //return } } diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 8c4bf04d..822b91e0 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -1,35 +1,100 @@ module main -go 1.16 +go 1.19 //replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared -//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi -//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch - require ( - cloud.google.com/go/datastore v1.6.0 - cloud.google.com/go/iam v0.1.1 // indirect - cloud.google.com/go/pubsub v1.17.1 - cloud.google.com/go/storage v1.18.2 + cloud.google.com/go/datastore v1.10.0 + cloud.google.com/go/pubsub v1.28.0 + cloud.google.com/go/storage v1.28.1 github.com/basgys/goxml2json v1.1.0 github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 - github.com/docker/docker v20.10.12+incompatible - github.com/frikky/kin-openapi v0.41.0 - github.com/fsouza/go-dockerclient v1.7.7 + github.com/docker/docker v20.10.21+incompatible + github.com/frikky/kin-openapi v0.42.0 + github.com/fsouza/go-dockerclient v1.9.0 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-billy/v5 v5.3.1 - github.com/go-git/go-git/v5 v5.4.2 + github.com/go-git/go-git/v5 v5.5.0 github.com/gorilla/mux v1.8.0 github.com/h2non/filetype v1.1.3 - github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.2.42 - go4.org v0.0.0-20201209231011-d4a079459e60 // indirect - golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce - google.golang.org/api v0.65.0 + github.com/shuffle/shuffle-shared v0.3.35 + golang.org/x/crypto v0.3.0 + google.golang.org/api v0.103.0 google.golang.org/appengine v1.6.7 - google.golang.org/grpc v1.43.0 + google.golang.org/grpc v1.51.0 gopkg.in/src-d/go-git.v4 v4.13.1 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + cloud.google.com/go v0.105.0 // indirect + cloud.google.com/go/compute v1.13.0 // indirect + cloud.google.com/go/compute/metadata v0.2.1 // indirect + cloud.google.com/go/iam v0.7.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Microsoft/hcsshim v0.9.3 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/adrg/strutil v0.2.3 // indirect + github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect + github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 // indirect + github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect + github.com/cloudflare/circl v1.1.0 // indirect + github.com/containerd/cgroups v1.0.3 // indirect + github.com/containerd/containerd v1.6.6 // indirect + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/frikky/go-elasticsearch/v8 v8.13.1 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/swag v0.19.5 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/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/uuid v1.3.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/mailru/easyjson v0.7.0 // indirect + github.com/moby/sys/mount v0.3.3 // indirect + github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/opencontainers/runc v1.1.2 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pjbgf/sha1cd v0.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/skeema/knownhosts v1.1.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + github.com/src-d/gcfg v1.4.0 // indirect + github.com/xanzy/ssh-agent v0.3.2 // indirect + go.opencensus.io v0.24.0 // indirect + go4.org v0.0.0-20201209231011-d4a079459e60 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.2.0 // indirect + golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.2.0 // indirect + golang.org/x/text v0.4.0 // indirect + golang.org/x/tools v0.1.12 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 9447c1cb..4c9b21b5 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -691,13 +691,25 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) err = shuffle.SetUser(ctx, newUser, true) if err != nil { - log.Printf("Error adding User %s: %s", username, err) + log.Printf("[ERROR] Problem adding User %s: %s", username, err) return err } neworg, err := shuffle.GetOrg(ctx, org.Id) if err == nil { //neworg.Users = append(neworg.Users, *newUser) + for tutorialIndex, tutorial := range neworg.Tutorials { + if tutorial.Name == "Invite teammates" { + neworg.Tutorials[tutorialIndex].Description = fmt.Sprintf("%d users are in your org. Org name and Image change next.", len(neworg.Users)) + if len(neworg.Users) > 0 { + neworg.Tutorials[tutorialIndex].Done = true + neworg.Tutorials[tutorialIndex].Link = "/admin" + } + + break + } + } + err = shuffle.SetOrg(ctx, *neworg, neworg.Id) if err != nil { log.Printf("Failed updating org with user %s", newUser.Username) @@ -729,7 +741,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { if err != nil { if (countErr == nil && count > 0) || countErr != nil { resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`)) + resp.Write([]byte(`{"success": false, "reason": "Users already exist. Please go to /login to log into your admin user."}`)) return } } @@ -843,6 +855,8 @@ func handleCookie(request *http.Request) bool { return true } +// Returns whether the user is logged in or not etc. +// Also has more data about the user and org func handleInfo(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { @@ -981,7 +995,8 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { } org, err := shuffle.GetOrg(ctx, userInfo.ActiveOrg.Id) - if err == nil { + //if err == nil { + if len(org.Id) > 0 { userInfo.ActiveOrg = shuffle.OrgMini{ Id: org.Id, Name: org.Name, @@ -990,6 +1005,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { Image: org.Image, } } + //} userInfo.ActiveOrg.Users = []shuffle.UserMini{} userOrgs := []shuffle.OrgMini{} @@ -1000,7 +1016,8 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { } org, err := shuffle.GetOrg(ctx, item) - if err == nil { + _ = err + if len(org.Id) > 0 { userOrgs = append(userOrgs, shuffle.OrgMini{ Id: org.Id, Name: org.Name, @@ -1035,7 +1052,35 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { chatDisabled = true } - tutorialsFinished := []string{} + orgPriorities := org.Priorities + if len(org.Priorities) < 5 { + log.Printf("[WARNING] Should find and add priorities as length is less than 5 for org %s", userInfo.ActiveOrg.Id) + newPriorities, err := shuffle.GetPriorities(ctx, userInfo, org) + if err != nil { + log.Printf("[WARNING] Failed getting new priorities for org %s: %s", org.Id, err) + //orgPriorities = []shuffle.Priority{} + } else { + orgPriorities = newPriorities + } + } + + tutorialsFinished := []shuffle.Tutorial{} + for _, tutorial := range userInfo.PersonalInfo.Tutorials { + tutorialsFinished = append(tutorialsFinished, shuffle.Tutorial{ + Name: tutorial, + }) + } + + if len(org.SecurityFramework.SIEM.Name) > 0 || len(org.SecurityFramework.Network.Name) > 0 || len(org.SecurityFramework.EDR.Name) > 0 || len(org.SecurityFramework.Cases.Name) > 0 || len(org.SecurityFramework.IAM.Name) > 0 || len(org.SecurityFramework.Assets.Name) > 0 || len(org.SecurityFramework.Intel.Name) > 0 || len(org.SecurityFramework.Communication.Name) > 0 { + tutorialsFinished = append(tutorialsFinished, shuffle.Tutorial{ + Name: "find_integrations", + }) + } + + for _, tutorial := range org.Tutorials { + tutorialsFinished = append(tutorialsFinished, tutorial) + } + returnValue := shuffle.HandleInfo{ Success: true, Username: userInfo.Username, @@ -1051,8 +1096,10 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { }, }, EthInfo: userInfo.EthInfo, - Tutorials: tutorialsFinished, ChatDisabled: chatDisabled, + Tutorials: tutorialsFinished, + + Priorities: orgPriorities, } returnData, err := json.Marshal(returnValue) @@ -1231,7 +1278,6 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { // Should run calculations if len(org.SSOConfig.OpenIdAuthorization) > 0 { - log.Printf("[DEBUG] Found OpenID url (PKCE!!). Extra redirect check: %s", request.URL.String()) baseSSOUrl = org.SSOConfig.OpenIdAuthorization codeChallenge := uuid.NewV4().String() @@ -1251,6 +1297,10 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { redirectUrl = url.QueryEscape(fmt.Sprintf("%s/api/v1/login_openid", os.Getenv("BASE_URL"))) } + if len(os.Getenv("SSO_REDIRECT_URL")) > 0 { + redirectUrl = url.QueryEscape(fmt.Sprintf("%s/api/v1/login_openid", os.Getenv("SSO_REDIRECT_URL"))) + } + state := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("org=%s&challenge=%s&redirect=%s", org.Id, codeChallenge, redirectUrl))) // has to happen after initial value is stored @@ -1260,12 +1310,25 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { //log.Printf("[DEBUG] Got challenge value %s (POST state)", codeChallenge) - baseSSOUrl += fmt.Sprintf("?client_id=%s&response_type=code&scope=openid&redirect_uri=%s&state=%s&code_challenge_method=S256&code_challenge=%s", org.SSOConfig.OpenIdClientId, redirectUrl, state, codeChallenge) + if len(org.SSOConfig.OpenIdClientSecret) > 0 { + + //baseSSOUrl += fmt.Sprintf("?client_id=%s&response_type=code&scope=openid&redirect_uri=%s&state=%s&client_secret=%s", org.SSOConfig.OpenIdClientId, redirectUrl, state, org.SSOConfig.OpenIdClientSecret) + state := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("org=%s&redirect=%s&challenge=%s", org.Id, redirectUrl, org.SSOConfig.OpenIdClientSecret))) + log.Printf("URL: %s", redirectUrl) + + baseSSOUrl += fmt.Sprintf("?client_id=%s&response_type=id_token&scope=openid&redirect_uri=%s&state=%s&response_mode=form_post&nonce=%s", org.SSOConfig.OpenIdClientId, redirectUrl, state, state) + //baseSSOUrl += fmt.Sprintf("&client_secret=%s", org.SSOConfig.OpenIdClientSecret) + log.Printf("[DEBUG] Found OpenID url (client secret). Extra redirect check: %s - %s", request.URL.String(), baseSSOUrl) + } else { + log.Printf("[DEBUG] Found OpenID url (PKCE!!). Extra redirect check: %s", request.URL.String()) + baseSSOUrl += fmt.Sprintf("?client_id=%s&response_type=code&scope=openid&redirect_uri=%s&state=%s&code_challenge_method=S256&code_challenge=%s", org.SSOConfig.OpenIdClientId, redirectUrl, state, codeChallenge) + } + break } if len(org.SSOConfig.SSOEntrypoint) > 0 { - log.Printf("[DEBUG] Found SAML SSO url") + log.Printf("[DEBUG] Found SAML SSO url: %s", org.SSOConfig.SSOEntrypoint) baseSSOUrl = org.SSOConfig.SSOEntrypoint break } @@ -1333,8 +1396,23 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { return } - // FIXME - have timeout here + tutorialsFinished := []shuffle.Tutorial{} + for _, tutorial := range Userdata.PersonalInfo.Tutorials { + tutorialsFinished = append(tutorialsFinished, shuffle.Tutorial{ + Name: tutorial, + }) + } + returnValue := shuffle.HandleInfo{ + Success: true, + Tutorials: tutorialsFinished, + } + loginData := `{"success": true}` + newData, err := json.Marshal(returnValue) + if err == nil { + loginData = string(newData) + } + if len(Userdata.Session) != 0 { log.Println("[INFO] User session already exists - resetting it") expiration := time.Now().Add(3600 * time.Second) @@ -1345,7 +1423,17 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { Expires: expiration, }) + returnValue.Cookies = append(returnValue.Cookies, shuffle.SessionCookie{ + Key: "session_token", + Value: Userdata.Session, + Expiration: expiration.Unix(), + }) + loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, Userdata.Session, expiration.Unix()) + newData, err := json.Marshal(returnValue) + if err == nil { + loginData = string(newData) + } //log.Printf("SESSION LENGTH MORE THAN 0 IN LOGIN: %s", Userdata.Session) err = shuffle.SetSession(ctx, Userdata, Userdata.Session) @@ -1382,7 +1470,17 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { return } + returnValue.Cookies = append(returnValue.Cookies, shuffle.SessionCookie{ + Key: "session_token", + Value: sessionToken, + Expiration: expiration.Unix(), + }) + loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, sessionToken, expiration.Unix()) + newData, err := json.Marshal(returnValue) + if err == nil { + loginData = string(newData) + } } log.Printf("[INFO] %s SUCCESSFULLY LOGGED IN with session %s", data.Username, Userdata.Session) @@ -2244,7 +2342,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { //start, startok := request.URL.Query()["start"] // OrgId: activeOrgs[0].Id, - workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest) + workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest, hook.OrgId) if err == nil { /* err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1, workflowExecution.ExecutionOrg) @@ -3055,18 +3153,19 @@ func handleSwaggerValidation(body []byte) (shuffle.ParsedOpenApi, error) { return parsed, err } -func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { +func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, skipEdit bool) { type Test struct { - Editing bool `datastore:"editing"` - Id string `datastore:"id"` - Image string `datastore:"image"` + Editing bool `json:"editing" datastore:"editing"` + Id string `json:"id" datastore:"id"` + Image string `json:"image" datastore:"image"` + Body string `json:"body" datastore:"body"` } var test Test err := json.Unmarshal(body, &test) if err != nil { - log.Printf("[WARNING] Failed unmarshalling test: %s", err) - resp.WriteHeader(401) + log.Printf("[ERROR] Failed unmarshalling in swagger build: %s", err) + resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } @@ -3076,13 +3175,13 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { hasher.Write(body) newmd5 := hex.EncodeToString(hasher.Sum(nil)) - if test.Editing && len(user.Id) > 0 { + if test.Editing && len(user.Id) > 0 && skipEdit != true { // Quick verification test ctx := context.Background() app, err := shuffle.GetApp(ctx, test.Id, user, false) if err != nil { - log.Printf("[WARNING] Error getting app when editing: %s", app.Name) - resp.WriteHeader(401) + log.Printf("[ERROR] Error getting app when editing: %s", app.Name) + resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } @@ -3090,7 +3189,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { // FIXME: Check whether it's in use. if user.Id != app.Owner && user.Role != "admin" { log.Printf("[WARNING] Wrong user (%s) for app %s when verifying swagger", user.Username, app.Name) - resp.WriteHeader(401) + resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } @@ -3114,12 +3213,13 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { } if swagger.Info == nil { - log.Printf("[ERORR] Info is nil?: %#v", swagger) + log.Printf("[ERORR] Info is nil in swagger?") resp.WriteHeader(500) resp.Write([]byte(`{"success": false, "reason": "Info not parsed"}`)) return } + swagger.Info.Title = shuffle.FixFunctionName(swagger.Info.Title, swagger.Info.Title, false) if strings.Contains(swagger.Info.Title, " ") { swagger.Info.Title = strings.Replace(swagger.Info.Title, " ", "_", -1) } @@ -3223,7 +3323,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { //log.Println(stitched) // 3. Zip and stream it directly in the directory - _, err = shuffle.StreamZipdata(ctx, identifier, stitched, "requests\nurllib3", "") + _, err = shuffle.StreamZipdata(ctx, identifier, stitched, shuffle.GetAppRequirements(), "") if err != nil { log.Printf("[ERROR] Zipfile error: %s", err) resp.WriteHeader(500) @@ -3374,7 +3474,7 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) { return } - buildSwaggerApp(resp, body, user) + buildSwaggerApp(resp, body, user, false) } // Creates osfs from folderpath with a basepath as directory base @@ -3445,7 +3545,6 @@ func handleAppHotload(ctx context.Context, location string, forceUpdate bool) er return err } - //log.Printf("Reading app folder: %#v", dir) _, _, err = IterateAppGithubFolders(ctx, fs, dir, "", "", forceUpdate) if err != nil { log.Printf("[WARNING] Githubfolders error: %s", err) @@ -3518,7 +3617,7 @@ func handleCloudExecutionOnprem(workflowId, startNode, executionSource, executio Body: ioutil.NopCloser(bytes.NewReader(b)), } - _, _, err = handleExecution(workflowId, shuffle.Workflow{}, newRequest) + _, _, err = handleExecution(workflowId, shuffle.Workflow{}, newRequest, workflow.OrgId) return err } @@ -3639,7 +3738,7 @@ func handleCloudJob(job shuffle.CloudSyncJob) error { return err } - _, _, err = handleExecution(job.PrimaryItemId, shuffle.Workflow{}, newRequest) + _, _, err = handleExecution(job.PrimaryItemId, shuffle.Workflow{}, newRequest, job.OrgId) if err != nil { log.Printf("Failed continuing workflow from cloud user_input: %s", err) return err @@ -3942,7 +4041,12 @@ func runInitEs(ctx context.Context) { Body: ioutil.NopCloser(strings.NewReader(schedule.WrappedArgument)), } - _, _, err := handleExecution(schedule.WorkflowId, shuffle.Workflow{}, request) + orgId := "" + if len(activeOrgs) > 0 { + orgId = activeOrgs[0].Id + } + + _, _, err := handleExecution(schedule.WorkflowId, shuffle.Workflow{}, request, orgId) if err != nil { log.Printf("[WARNING] Failed to execute %s: %s", schedule.WorkflowId, err) } @@ -4728,7 +4832,6 @@ func runInit(ctx context.Context) { continue } - log.Printf("ENV: %s", item.Environment) if item.Environment == "cloud" { log.Printf("Skipping cloud schedule") continue @@ -4802,7 +4905,12 @@ func runInit(ctx context.Context) { Body: ioutil.NopCloser(strings.NewReader(schedule.WrappedArgument)), } - _, _, err := handleExecution(schedule.WorkflowId, shuffle.Workflow{}, request) + orgId := "" + if len(activeOrgs) > 0 { + orgId = activeOrgs[0].Id + } + + _, _, err := handleExecution(schedule.WorkflowId, shuffle.Workflow{}, request, orgId) if err != nil { log.Printf("[WARNING] Failed to execute %s: %s", schedule.WorkflowId, err) } @@ -5963,7 +6071,9 @@ func initHandlers() { r.HandleFunc("/api/v1/workflows/collections/{key}", shuffle.HandleGetCollection).Methods("GET", "OPTIONS") // Related to use-cases that are not directly workflows. + r.HandleFunc("/api/v1/workflows/usecases/{key}", shuffle.HandleGetUsecase).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/workflows/usecases", shuffle.LoadUsecases).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/usecases", shuffle.UpdateUsecases).Methods("POST", "OPTIONS") // Legacy app things r.HandleFunc("/api/v1/workflows/apps/validate", validateAppInput).Methods("POST", "OPTIONS") @@ -6041,13 +6151,14 @@ func initHandlers() { r.HandleFunc("/api/v1/orgs/{orgId}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/apps/{key}/execute", executeSingleAction).Methods("POST", "OPTIONS") // Docker orborus specific - downloads an image r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/migrate_database", migrateDatabase).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("GET", "POST", "OPTIONS") - r.HandleFunc("/api/v1/login_openid", shuffle.HandleOpenId).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/login_openid", shuffle.HandleOpenId).Methods("GET", "POST", "OPTIONS") // Important for email, IDS etc. Create this by: // PS: For cloud, this has to use cloud storage. @@ -6056,7 +6167,8 @@ func initHandlers() { r.HandleFunc("/api/v1/files/namespaces/{namespace}", shuffle.HandleGetFileNamespace).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}/content", shuffle.HandleGetFileContent).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/create", shuffle.HandleCreateFile).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/files/{fileId}/upload", shuffle.HandleUploadFile).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/files/{fileId}/upload", shuffle.HandleUploadFile).Methods("POST", "OPTIONS", "PATCH") + r.HandleFunc("/api/v1/files/{fileId}/edit", shuffle.HandleEditFile).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleGetFileMeta).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS") @@ -6070,6 +6182,10 @@ func initHandlers() { r.HandleFunc("/api/v1/users/notifications/clear", shuffle.HandleClearNotifications).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/users/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/dashboards/{key}/widgets", shuffle.HandleNewWidget).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/dashboards/{key}/widgets/{widget_id}", shuffle.HandleGetWidget).Methods("GET", "OPTIONS") + http.Handle("/", r) } diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index ffdf9b4f..1a8cba08 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -98,7 +98,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)), } - _, _, err := handleExecution(workflowId, shuffle.Workflow{ExecutingOrg: shuffle.OrgMini{Id: orgId}}, request) + _, _, err := handleExecution(workflowId, shuffle.Workflow{ExecutingOrg: shuffle.OrgMini{Id: orgId}}, request, orgId) if err != nil { log.Printf("Failed to execute %s: %s", workflowId, err) } @@ -395,6 +395,40 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { } } + for _, action := range workflowExecution.Workflow.Actions { + found := false + for _, result := range workflowExecution.Results { + if result.Action.ID == action.ID { + found = true + break + } + } + + if found { + continue + } + + //log.Printf("[DEBUG] Maybe not handled yet: %s", action.ID) + cacheId := fmt.Sprintf("%s_%s_result", workflowExecution.ExecutionId, action.ID) + cache, err := shuffle.GetCache(ctx, cacheId) + if err != nil { + //log.Printf("[WARNING] Couldn't find in fix exec %s (2): %s", cacheId, err) + continue + } + + actionResult := shuffle.ActionResult{} + cacheData := []byte(cache.([]uint8)) + + // Just ensuring the data is good + err = json.Unmarshal(cacheData, &actionResult) + if err != nil { + continue + } else { + log.Printf("[DEBUG] APPENDING %s result to send to app or something\n\n\n\n", action.ID) + workflowExecution.Results = append(workflowExecution.Results, actionResult) + } + } + newjson, err := json.Marshal(workflowExecution) if err != nil { resp.WriteHeader(401) @@ -559,7 +593,8 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl } //log.Printf("BASE LENGTH: %d", len(workflowExecution.Results)) - workflowExecution, dbSave, err := shuffle.ParsedExecutionResult(ctx, *workflowExecution, actionResult, false) + + workflowExecution, dbSave, err := shuffle.ParsedExecutionResult(ctx, *workflowExecution, actionResult, false, 0) if err != nil { b, suberr := json.Marshal(actionResult) if suberr != nil { @@ -745,7 +780,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { if item.TriggerType == "SCHEDULE" && item.Status != "uninitialized" { err = deleteSchedule(ctx, item.ID) if err != nil { - log.Printf("Failed to delete schedule: %s - is it started?", err) + log.Printf("[DEBUG] Failed to delete schedule: %s - is it started?", err) } } else if item.TriggerType == "WEBHOOK" { //err = removeWebhookFunction(ctx, item.ID) @@ -755,7 +790,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { } else if item.TriggerType == "EMAIL" { err = shuffle.HandleOutlookSubRemoval(ctx, user, workflow.ID, item.ID) if err != nil { - log.Printf("Failed to delete OUTLOOK email sub (checking gmail after): %s", err) + log.Printf("[DEBUG] Failed to delete OUTLOOK email sub (checking gmail after): %s", err) } err = shuffle.HandleGmailSubRemoval(ctx, user, workflow.ID, item.ID) @@ -763,14 +798,8 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { log.Printf("Failed to delete gmail email sub: %s", err) } } - - //err = increaseStatisticsField(ctx, "total_workflow_triggers", workflow.ID, -1, workflow.OrgId) - //if err != nil { - // log.Printf("Failed to increase total workflows: %s", err) - //} } - // FIXME - maybe delete workflow executions err = shuffle.DeleteKey(ctx, "workflow", fileId) if err != nil { log.Printf("[DEBUG]] Failed deleting key %s", fileId) @@ -780,11 +809,6 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { } log.Printf("[INFO] Should have deleted workflow %s (%s)", workflow.Name, fileId) - //memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) - //memcache.Delete(ctx, memcacheName) - //memcacheName = fmt.Sprintf("%s_workflows", user.Username) - //memcache.Delete(ctx, memcacheName) - //cacheKey := fmt.Sprintf("%s_workflows", user.Id) cacheKey := fmt.Sprintf("%s_workflows", user.Id) shuffle.DeleteCache(ctx, cacheKey) log.Printf("[DEBUG] Cleared workflow cache for %s (%s)", user.Username, user.Id) @@ -830,7 +854,7 @@ func getWorkflowLocal(fileId string, request *http.Request) ([]byte, error) { return body, nil } -func handleExecution(id string, workflow shuffle.Workflow, request *http.Request) (shuffle.WorkflowExecution, string, error) { +func handleExecution(id string, workflow shuffle.Workflow, request *http.Request, orgId string) (shuffle.WorkflowExecution, string, error) { //go func() { // log.Printf("\n\nPRE TIME: %s\n\n", time.Now().Format("2006-01-02 15:04:05")) // _ = <-time.After(time.Second * 60) @@ -849,8 +873,12 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request } if len(workflow.ExecutingOrg.Id) == 0 { - log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID) - return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined") + if len(orgId) > 0 { + workflow.ExecutingOrg.Id = orgId + } else { + log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID) + return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined") + } } if len(workflow.Actions) == 0 { @@ -1048,6 +1076,10 @@ func cloudExecuteAction(execution shuffle.WorkflowExecution) error { return nil } +// 1. Check CORS +// 2. Check authentication +// 3. Check authorization +// 4. Run the actual function func executeWorkflow(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { @@ -1104,8 +1136,8 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { executionAuthValid, newOrgId = shuffle.RunExecuteAccessValidation(request, workflow) if !executionAuthValid { - log.Printf("[INFO] Api authentication failed in execute workflow: %s", userErr) - resp.WriteHeader(401) + log.Printf("[INFO] Api authorization failed in execute workflow: %s", userErr) + resp.WriteHeader(403) resp.Write([]byte(`{"success": false}`)) return } else { @@ -1122,7 +1154,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { log.Printf("[AUDIT] Letting user %s execute %s because they're admin of the same org", user.Username, workflow.ID) } else { log.Printf("[AUDIT] Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID) - resp.WriteHeader(401) + resp.WriteHeader(403) resp.Write([]byte(`{"success": false}`)) return } @@ -1133,7 +1165,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { user.ActiveOrg.Users = []shuffle.UserMini{} workflow.ExecutingOrg = user.ActiveOrg - workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request) + workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request, user.ActiveOrg.Id) if err == nil { resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization))) @@ -1384,15 +1416,14 @@ func stopScheduleGCP(resp http.ResponseWriter, request *http.Request) { } func deleteSchedule(ctx context.Context, id string) error { - log.Printf("Should stop schedule %s!", id) + log.Printf("[DEBUG] Should stop schedule %s!", id) err := shuffle.DeleteKey(ctx, "schedules", id) if err != nil { - log.Printf("Failed to delete schedule: %s", err) + log.Printf("[ERROR] Failed to delete schedule: %s", err) return err } else { if value, exists := scheduledJobs[id]; exists { - log.Printf("STOPPING THIS SCHEDULE: %s", id) - // Looks like this does the trick? Hurr + // Stops the schedule properly value.Lock() } else { // FIXME - allow it to kind of stop anyway? @@ -1495,14 +1526,19 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { // Finds the startnode for the specific schedule startNode := "" - for _, branch := range workflow.Branches { - if branch.SourceID == schedule.Id { - startNode = branch.DestinationID - } - } + if schedule.Start != "" { + startNode = schedule.Start + } else { - if startNode == "" { - startNode = workflow.Start + for _, branch := range workflow.Branches { + if branch.SourceID == schedule.Id { + startNode = branch.DestinationID + } + } + + if startNode == "" { + startNode = workflow.Start + } } //log.Printf("Startnode: %s", startNode) @@ -2047,8 +2083,9 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000, 0) appCounter := 0 if err != nil { - log.Printf("Failed to get existing generated apps") + log.Printf("[WARNING] Failed to get existing generated apps for OpenAPI verification: %s", err) } + for _, file := range dir { if len(onlyname) > 0 && file.Name() != onlyname { continue @@ -2606,7 +2643,7 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) { return } - log.Printf("[INFO] Execution: %s should execute onprem with execution environment \"%s\". Workflow: %s", workflowExecution.ExecutionId, environment, workflowExecution.Workflow.ID) + log.Printf("[INFO] Execution (single action): %s should execute onprem with execution environment \"%s\". Workflow: %s", workflowExecution.ExecutionId, environment, workflowExecution.Workflow.ID) executionRequest := shuffle.ExecutionRequest{ ExecutionId: workflowExecution.ExecutionId, diff --git a/backend/tests/files.sh b/backend/tests/files.sh index 71776148..c92a0ebc 100755 --- a/backend/tests/files.sh +++ b/backend/tests/files.sh @@ -3,7 +3,7 @@ #curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -d '{"filename": "file.txt", "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", "workflow_id": "global"}' # #echo -curl http://localhost:5001/api/v1/apps/upload -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -F 'shuffle_file=@files.sh' +#curl http://localhost:5001/api/v1/apps/upload -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -F 'shuffle_file=@files.sh' # #curl http://localhost:5001/api/v1/files/1915981b-b897-4db1-8a2e-44bc34cead3b/content -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" #curl http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687 -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" @@ -16,7 +16,7 @@ curl http://localhost:5001/api/v1/apps/upload -H "Authorization: Bearer db0373c6 #r.HandleFunc("/api/v1/files/{fileId}", handleDeleteFile).Methods("DELETE", "OPTIONS") -#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" -d '{"filename": "rule2.yar", "org_id": "b4e88fe9-352b-47b4-b280-960181670acf", "workflow_id": "global", "namespace": "yara"}' -#curl http://localhost:5001/api/v1/files/5cb941ad-fa1c-4444-a685-92024b1fa31c/upload -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" -F 'shuffle_file=@upload.sh' +#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer 09627dcb-7e2a-4843-819b-417d268ff840" -d '{"filename": "rule2.yar", "org_id": "11f67b76-6051-4425-b0d6-be23daac6d12", "workflow_id": "global", "namespace": "yara"}' +curl http://localhost:5002/api/v1/files/file_366ee8d2-1af6-4270-8639-213af30b4a29/upload -H "Authorization: Bearer 09627dcb-7e2a-4843-819b-417d268ff840" -F 'shuffle_file=@upload.sh' #curl http://localhost:5001/api/v1/files/namespaces/yara -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" --output rules.zip diff --git a/backend/tests/hooks.sh b/backend/tests/hooks.sh index 738111af..42e0ede0 100644 --- a/backend/tests/hooks.sh +++ b/backend/tests/hooks.sh @@ -15,9 +15,9 @@ #curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_982995716e67c3a549092d3a3a7921cd" -H "Content-Type:application/json" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data '{"name":"Keyboard Cat"}' -v ## GET HOOK -#curl http://localhost:5001/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" +#curl http://localhost:5001/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer " -#curl https://shuffler.io/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" +#curl https://shuffler.io/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer " #curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_3ceff795-ce9a-43a2-a2f5-d4401a6e772d" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut' diff --git a/backend/tests/upload.sh b/backend/tests/upload.sh index 792d6005..c14c13ae 100644 --- a/backend/tests/upload.sh +++ b/backend/tests/upload.sh @@ -1 +1,4 @@ -# +# hello +this is line 2 +and 3 +Is it a python problem? diff --git a/docker-compose.yml b/docker-compose.yml index 3ad3ec48..4451238d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: frontend: - image: ghcr.io/frikky/shuffle-frontend:latest + image: ghcr.io/shuffle/shuffle-frontend:latest container_name: shuffle-frontend hostname: shuffle-frontend ports: @@ -15,7 +15,7 @@ services: depends_on: - backend backend: - image: ghcr.io/frikky/shuffle-backend:latest + image: ghcr.io/shuffle/shuffle-backend:latest container_name: shuffle-backend hostname: ${BACKEND_HOSTNAME} # Here for debugging: @@ -29,11 +29,12 @@ services: - ${SHUFFLE_FILE_LOCATION}:/shuffle-files:z env_file: .env environment: + #- DOCKER_HOST=tcp://docker-socket-proxy:2375 - SHUFFLE_APP_HOTLOAD_FOLDER=/shuffle-apps - SHUFFLE_FILE_LOCATION=/shuffle-files restart: unless-stopped orborus: - image: ghcr.io/frikky/shuffle-orborus:latest + image: ghcr.io/shuffle/shuffle-orborus:latest container_name: shuffle-orborus hostname: shuffle-orborus networks: @@ -41,6 +42,7 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock environment: + #- DOCKER_HOST=tcp://docker-socket-proxy:2375 - SHUFFLE_WORKER_VERSION=latest - ENVIRONMENT_NAME=${ENVIRONMENT_NAME} - BASE_URL=http://${OUTER_HOSTNAME}:5001 @@ -52,25 +54,22 @@ services: - HTTPS_PROXY=${HTTPS_PROXY} - SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY} - SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY} - - SHUFFLE_SWARM_NETWORK_NAME=shuffle_swarm_executions - - SHUFFLE_SCALE_REPLICAS=1 - - SHUFFLE_SWARM_CONFIG=runn restart: unless-stopped security_opt: - seccomp:unconfined opensearch: - image: opensearchproject/opensearch:1.2.4 + image: opensearchproject/opensearch:2.4.0 hostname: shuffle-opensearch container_name: shuffle-opensearch environment: - bootstrap.memory_lock=true - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM + - cluster.initial_master_nodes=shuffle-opensearch - cluster.routing.allocation.disk.threshold_enabled=false - cluster.name=shuffle-cluster - node.name=shuffle-opensearch - - discovery.seed_hosts=shuffle-opensearch - - cluster.initial_master_nodes=shuffle-opensearch - node.store.allow_mmap=false + - discovery.seed_hosts=shuffle-opensearch ulimits: memlock: soft: -1 @@ -87,6 +86,8 @@ services: restart: unless-stopped #docker-socket-proxy: # image: tecnativa/docker-socket-proxy + # container_name: shuffle-frontend + # hostname: docker-socket-proxy # privileged: true # environment: # - SERVICES=1 @@ -105,6 +106,7 @@ services: # - POST=1 # - AUTH=1 # - SECRETS=1 + # - SWARM=1 # volumes: # - /var/run/docker.sock:/var/run/docker.sock # networks: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 4f1c47bf..b2d4f2ce 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -9,7 +9,7 @@ ENV PATH /usr/src/app/node_modules/.bin:$PATH COPY package.json /usr/src/app/package.json RUN yarn config set "strict-ssl" false -g -RUN yarn install +RUN yarn install --network-timeout 1000000 # copy only required files to not trigger rebuilding every time COPY ./certs /usr/src/app/certs/ diff --git a/frontend/README.md b/frontend/README.md index 2257fc4e..fe883a45 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,4 +1,4 @@ -# Certificate: +## Localhost Certificate info: Creating a localhost certificate: @@ -7,3 +7,14 @@ openssl genrsa -out privkey.pem 2048 openssl req -new -key privkey.pem -out certreq.csr openssl x509 -req -days 3650 -in certreq.csr -signkey privkey.pem -out fullchain.pem ``` + +## Using your own certificate +If you have your own .crt and .key file, you can do it like this: +``` +openssl x509 -in mycert.crt -out fullchain.cert.pem -outform PEM +``` + +The KEY file has to be named privkey.pem +``` +mv cert.key privkey.pem +``` diff --git a/frontend/package.json b/frontend/package.json index 55e61c59..d23724c9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "shuffler", "homepage": "https://shuffler.io", - "version": "1.0.0", + "version": "1.1.0", "private": true, "dependencies": { "@babel/core": "^7.15.8", @@ -9,7 +9,6 @@ "@emotion/react": "^11.7.0", "@emotion/styled": "^11.6.0", "@material-ui/core": "^4.5.2", - "@material-ui/data-grid": "^4.0.0-alpha.22", "@material-ui/icons": "^4.5.1", "@material-ui/lab": "^4.0.0-alpha.58", "@material-ui/styles": "^4.5.2", @@ -17,8 +16,10 @@ "@metamask/detect-provider": "^1.2.0", "@mui/icons-material": "^5.2.1", "@mui/material": "^5.2.3", + "@mui/x-data-grid": "^5.17.11", "@uiw/react-codemirror": "^3.2.1", "@use-it/interval": "^1.0.0", + "algoliasearch": "^4.13.1", "babel-eslint": "^10.1.0", "class-transformer": "^0.4.0", "create-react-app": "^4.0.3", @@ -46,6 +47,7 @@ "react": "^16.14.0", "react-alert": "^5.5.0", "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", @@ -58,6 +60,7 @@ "react-dropzone": "^10.1.10", "react-ga": "^2.7.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", @@ -69,6 +72,7 @@ "react-shepherd": "^3.3.6", "reactstrap": "^7.1.0", "reaviz": "^12.1.0", + "search-insights": "^2.2.1", "shellwords": "^0.1.1", "simplebar": "^4.2.3", "styled-components": "^4.4.0", diff --git a/frontend/public/images/Arrow.png b/frontend/public/images/Arrow.png new file mode 100644 index 00000000..56c7d85c Binary files /dev/null and b/frontend/public/images/Arrow.png differ diff --git a/frontend/public/images/finalize.gif b/frontend/public/images/finalize.gif new file mode 100644 index 00000000..f2656fa6 Binary files /dev/null and b/frontend/public/images/finalize.gif differ diff --git a/frontend/public/images/logo-algolia-nebula-blue-full.svg b/frontend/public/images/logo-algolia-nebula-blue-full.svg new file mode 100644 index 00000000..886c422e --- /dev/null +++ b/frontend/public/images/logo-algolia-nebula-blue-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/images/social/discord.png b/frontend/public/images/social/discord.png new file mode 100644 index 00000000..c3a1b58d Binary files /dev/null and b/frontend/public/images/social/discord.png differ diff --git a/frontend/public/images/social/shuffle_logo_round.png b/frontend/public/images/social/shuffle_logo_round.png new file mode 100644 index 00000000..61c7f660 Binary files /dev/null and b/frontend/public/images/social/shuffle_logo_round.png differ diff --git a/frontend/public/images/welcome_cog.png b/frontend/public/images/welcome_cog.png new file mode 100644 index 00000000..c3eef260 Binary files /dev/null and b/frontend/public/images/welcome_cog.png differ diff --git a/frontend/run.sh b/frontend/run.sh index d659fd64..0ba2beea 100755 --- a/frontend/run.sh +++ b/frontend/run.sh @@ -6,6 +6,7 @@ docker rm shuffle-frontend echo "Running build for website" #sudo npm run build docker build . -t ghcr.io/frikky/shuffle-frontend:nightly +docker tag ghcr.io/frikky/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:nightly echo "Starting server" # Rerun build locally for it to update :) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e1b800a4..49d3566c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -15,13 +15,16 @@ import theme from "./theme"; import Apps from "./views/Apps"; import AppCreator from "./views/AppCreator"; +import Welcome from "./views/Welcome.jsx"; 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 SetAuthentication from "./views/SetAuthentication"; import SetAuthenticationSSO from "./views/SetAuthenticationSSO"; +import Search from "./views/Search.jsx"; import LandingPageNew from "./views/LandingpageNew"; import LoginPage from "./views/LoginPage"; @@ -40,6 +43,7 @@ import { isMobile } from "react-device-detect"; import detectEthereumProvider from "@metamask/detect-provider"; import Drift from "react-driftjs"; +import DashboardPage from "./views/TempDashboard.jsx"; // Production - backend proxy forwarding in nginx var globalUrl = window.location.origin; @@ -50,11 +54,12 @@ if (window.location.port === "3000") { //globalUrl = "http://localhost:5002" } -if (globalUrl.includes("githubpreview.dev")) { +// Development on Github Codespaces +if (globalUrl.includes("app.github.dev")) { //globalUrl = globalUrl.replace("3000", "5001") - globalUrl = "https://frikky-shuffle-5gvr4xx62w64-5001.githubpreview.dev" + globalUrl = "https://frikky-shuffle-5gvr4xx62w64-5001.preview.app.github.dev" } -console.log("global: ", globalUrl) +//console.log("global: ", globalUrl) const App = (message, props) => { @@ -64,11 +69,7 @@ const App = (message, props) => { const [isLoggedIn, setIsLoggedIn] = useState(false) const [dataset, setDataset] = useState(false) const [isLoaded, setIsLoaded] = useState(false) - const [curpath, setCurpath] = useState( - typeof window === "undefined" || window.location === undefined - ? "" - : window.location.pathname - ) + const [curpath, setCurpath] = useState(typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname) useEffect(() => { @@ -130,7 +131,7 @@ const App = (message, props) => { .then((responseJson) => { var userInfo = {}; if (responseJson.success === true) { - console.log(responseJson); + //console.log("USER: ", responseJson); userInfo = responseJson; setIsLoggedIn(true); @@ -303,6 +304,7 @@ const App = (message, props) => { > {!isLoaded ? null : @@ -310,7 +312,7 @@ const App = (message, props) => { { }, ]} /> - }
{ /> } /> + } /> { /> } /> + + } + /> + + } + /> + + } + /> { - const {globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, size, inputUsecase, isLoggedIn, } = props; +const AppFramework = (props) => { + const { globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, setFrameworkData, size, inputUsecase, isLoggedIn, color, discoveryWrapper, setDiscoveryWrapper, userdata, apps, inputUsecases, setInputUsecases } = props; const [cy, setCy] = React.useState() const [edgesStarted, setEdgesStarted] = React.useState(false) const [graphDone, setGraphDone] = React.useState(false) const [cyDone, setCyDone] = React.useState(false) const [discoveryData, setDiscoveryData] = React.useState({}) const [selectionOpen, setSelectionOpen] = React.useState(true) + const [frameworkSuggestions, setFrameworkSuggestions] = React.useState([]) const [newSelectedApp, setNewSelectedApp] = React.useState({}) const [defaultSearch, setDefaultSearch] = React.useState("") const [animationStarted, setAnimationStarted] = React.useState(false) + const [paperTitle, setPaperTitle] = React.useState("") + const [changedApp, setChangedApp] = React.useState("") + + + const [usecaseType, setUsecaseType] = React.useState(0) + const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing") + const scale = size === undefined ? 1 : size > 5 ? 3 : size const alert = useAlert() + const showRecommendations = (changed, frameworkData) => { + console.log("Inside recommendation loader") + setChangedApp(changed) + + // Alternative changed + // This is for secondary values like email = comms + var alternativeChanged = changed + if (changed == "COMMS") { + alternativeChanged = "email" + } + + // FIX: + // 0. Get workflows loaded in from usecasesearch + // 1. Search through workflow templates for matching app types + // 2. Validate if template is already in use~ (workflows with same tools) + // 3. Generate the workflow(s) - PS: Fix new workflow templates + // 4. Moving on! + + // How can we load templates? UsecaseSearch? + var showusecases = [] + //const foundusecase = usecaseTypes.find(data => data.name.toLowerCase() === defaultSearch.toLowerCase()) + for (var key in usecaseTypes) { + for (var subkey in usecaseTypes[key].value) { + const usecase = usecaseTypes[key].value[subkey] + + if (usecase.active === false) { + continue + } + + var potential = false + var matches = [] + for (var itemtype in usecase.items) { + var apptype = usecase.items[itemtype].app_type.toLowerCase() + if (apptype.toLowerCase() === "email" || apptype.toLowerCase() === "comms" || apptype === "communication") { + apptype = "Comms" + } + //console.log("OLD: ", changed, "USECASE: ", apptype) + + //console.log("APptype, changed, framework: ", apptype.toLowerCase(), alternativeChanged.toLowerCase(), changed.toLowerCase(), frameworkData) + if (changed.toLowerCase() === apptype.toLowerCase() || changed.toLowerCase().includes(apptype.toLowerCase()) || alternativeChanged.toLowerCase() === apptype.toLowerCase() || alternativeChanged.toLowerCase().includes(apptype.toLowerCase())) { + potential = true + console.log("Potential: !", apptype) + + if (frameworkData[apptype] !== undefined && frameworkData[apptype].name !== undefined && frameworkData[apptype].name !== null && frameworkData[apptype].name.length > 0) { + usecase.items[itemtype].app = frameworkData[apptype] + } + + matches.push(usecase.items[itemtype]) + } else { + // Check if the type is done in frameworkData + if (frameworkData[apptype] !== undefined) { + //console.log("NOT UNDEFINED: ", frameworkData[apptype]) + if (frameworkData[apptype].name !== undefined && frameworkData[apptype].name !== null && frameworkData[apptype].name.length > 0) { + //console.log("FOUND: ", frameworkData[apptype]) + usecase.items[itemtype].app = frameworkData[apptype] + + //console.log("Real app!") + matches.push(usecase.items[itemtype]) + } + + //if (frameworkData[apptype] !== undefined) { + } else { + console.log("UNDEFINED APP (bad name?): ", apptype) + } + } + } + + // Adds to list if it's all matching and unhandled + if (potential) { + // Check finished usecases. + if (inputUsecases !== undefined && setInputUsecases !== undefined && usecase.usecase_references !== undefined && usecase.usecase_references.length > 0) { + var foundUsecase = false + for (var usecaseKey in inputUsecases) { + const usecaseCategory = inputUsecases[usecaseKey] + for (var subUsecaseKey in usecaseCategory.list) { + const loopUsecase = usecaseCategory.list[subUsecaseKey] + if (loopUsecase.matches === undefined || loopUsecase.matches === null || loopUsecase.matches.length === 0) { + //console.log("No matches - continuing") + continue + } + + if (usecase.usecase_references.includes(loopUsecase.name)) { + foundUsecase = true + break + } + } + + if (foundUsecase) { + break + } + } + + if (!foundUsecase) { + console.log("Usecase NOT found!") + } else { + console.log("FOUND usecase existing in ", usecase.usecase_references) + continue + } + } else { + console.log("No usecase to try to match it to (usecase.usecase_references in UsecaseSearch)") + } + + console.log("Usecase: ", usecase) + if (matches.length === usecase.items.length) { + usecase.color = "#c51152" + usecase.type = usecaseTypes[key].name + showusecases.push(usecase) + } + } + } + } + + // FIXME: Check if a usecase has already been handled + console.log("") + console.log("GOT USECASES: ", showusecases) + + // FIXME: Just showing one usecase at a time for now + if (showusecases.length > 0) { + setFrameworkSuggestions(showusecases.slice(0,1)) + } + } + + useEffect(() => { + console.log("DISCWRAP CHANG: ", discoveryWrapper) + if (discoveryWrapper === undefined || discoveryWrapper.id === "SHUFFLE" || discoveryWrapper.id === undefined || cy === undefined) { + setDiscoveryData({}) + + if (cy !== undefined) { + cy.nodes().unselect() + } + + return + } + + + // Find the node and click it? + //setTimeout(() => { + const nodes = cy.nodes().jsons() + for (var key in nodes) { + const node = nodes[key] + var newSearchName = discoveryWrapper.id.valueOf() + if (newSearchName === "EMAIL") { + newSearchName = "COMMS" + } + + if (newSearchName === "ERADICATION" || newSearchName === "ENDPOINT") { + newSearchName = "EDR & AV" + } + + if (node.data.id === newSearchName) { + const tmpnode = cy.getElementById(node.data.id) + if (tmpnode !== undefined) { + tmpnode.select() + } + + setDefaultSearch(discoveryWrapper.id) + setPaperTitle(discoveryWrapper.id) + } + } + //}, 50,) + //setDiscoveryData(discoveryWrapper) + }, [discoveryWrapper]) + const setUsecaseItem = (inputUsecase) => { var parsedUsecase = inputUsecase const edges = cy.edges().jsons() @@ -576,7 +752,41 @@ const Framework = (props) => { }) } + const activateApp = (appid) => { + fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Failed to activate") + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson.success === false) { + alert.error("Failed to activate the app") + } else { + //alert.success("App activated for your organization! Refresh the page to use the app.") + } + }) + .catch(error => { + //alert.error(error.toString()) + console.log("Activate app error: ", error.toString()) + }); + } + const setFrameworkItem = (data) => { + console.log("Setting framework item: ", data, isCloud) + if (!isCloud) { + activateApp(data.id) + } + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { method: "POST", headers: { @@ -613,11 +823,22 @@ const Framework = (props) => { } useEffect(() => { - if (discoveryData.id === undefined) { + console.log("New selected app: ", newSelectedApp, discoveryData) + if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) { return } - if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) { + //if (paperTitle.length > 0) { + // console.log("No papertitle (parent button)") + + // cy.elements().unselect() + // return + //} + + if (discoveryData.id === undefined) { + console.log("No discoverydata (parent button)") + + cy.elements().unselect() return } @@ -625,8 +846,11 @@ const Framework = (props) => { "type": discoveryData.id, "name": newSelectedApp.name, "id": newSelectedApp.objectID, + "large_image": newSelectedApp.image_url, + "description": newSelectedApp.description === undefined ? "" : newSelectedApp.description, } + const foundelement = cy.getElementById(discoveryData.id) if (foundelement !== undefined && foundelement !== null) { foundelement.data("large_image", newSelectedApp.image_url) @@ -637,7 +861,35 @@ const Framework = (props) => { foundelement.data("height", `${85*scale}px`) } + if (setFrameworkData !== undefined) { + // Find discoveryData.id + var keys = [] + for (const [key, value] of Object.entries(frameworkData)) { + if (key.toLowerCase() === discoveryData.id.toLowerCase()) { + keys.push(key) + } + } + + if (keys.length === 0) { + console.log("Failed to find: ", discoveryData.id, " IN ", frameworkData) + } else { + for (var key in keys) { + frameworkData[keys[key]] = submitValue + } + + console.log("Frameworkdata: ", frameworkData) + setFrameworkData(frameworkData) + + if (discoveryData.large_image !== undefined && discoveryData.large_image !== null && discoveryData.large_image.includes("storage.googleapis.com")) { + showRecommendations(discoveryData.id, frameworkData) + } else { + console.log("Skipping recommendations during unselect") + } + } + } + setFrameworkItem(submitValue) + cy.elements().unselect() }, [newSelectedApp]) @@ -647,52 +899,105 @@ const Framework = (props) => { window.location.host === "shuffler.io"; const imgSize = 50; - var parsedFrameworkData = frameworkData + var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData + // Awful mapping to make sure all access is always there if (frameworkData !== undefined) { if (frameworkData.cases !== undefined) { - frameworkData.Cases = frameworkData.cases - } - if (frameworkData.siem !== undefined) { - frameworkData.SIEM = frameworkData.siem - } - if (frameworkData.assets !== undefined) { - frameworkData.Assets = frameworkData.assets - } - if (frameworkData.intel !== undefined) { - frameworkData.Intel = frameworkData.intel - } - if (frameworkData.communication !== undefined) { - frameworkData.Comms = frameworkData.communication - } - if (frameworkData.network !== undefined) { - frameworkData.Network = frameworkData.network - } - if (frameworkData.iam !== undefined) { - frameworkData.IAM = frameworkData.iam - } - if (frameworkData.edr !== undefined) { - frameworkData["EDR & AV"] = frameworkData.edr + if (frameworkData.cases.large_image === undefined && frameworkData.cases.large_image === null || frameworkData.cases.large_image === "") { + frameworkData.cases = {} + } + + parsedFrameworkData.Cases = frameworkData.cases + } else { + parsedFrameworkData.Cases = {} } - parsedFrameworkData = frameworkData - } else { - console.log("No frameworkdata: ") - parsedFrameworkData = { - "Cases": {}, - "SIEM": {}, - "Assets": {}, - "IAM": {}, - "Intel": {}, - "Comms": {}, - "Network": {}, - "EDR & AV": {}, + if (frameworkData.siem !== undefined) { + if (frameworkData.siem.large_image === undefined && frameworkData.siem.large_image === null || frameworkData.siem.large_image === "") { + frameworkData.siem = {} + } + + parsedFrameworkData.SIEM = frameworkData.siem + } else { + parsedFrameworkData.SIEM = {} } + + if (frameworkData.assets !== undefined) { + if (frameworkData.assets.large_image === undefined && frameworkData.assets.large_image === null || frameworkData.assets.large_image === "") { + frameworkData.assets = {} + } + + parsedFrameworkData.Assets = frameworkData.assets + } else { + parsedFrameworkData.Assets = {} + } + + if (frameworkData.intel !== undefined) { + if (frameworkData.intel.large_image === undefined && frameworkData.intel.large_image === null || frameworkData.intel.large_image === "") { + frameworkData.intel = {} + } + + parsedFrameworkData.Intel = frameworkData.intel + } else { + parsedFrameworkData.Intel= {} + } + + if (frameworkData.communication !== undefined) { + if (frameworkData.communication.large_image === undefined && frameworkData.communication.large_image === null || frameworkData.communication.large_image === "") { + frameworkData.communication = {} + } + + parsedFrameworkData.Comms = frameworkData.communication + } else { + parsedFrameworkData.Comms = {} + } + + if (frameworkData.network !== undefined) { + if (frameworkData.network.large_image === undefined && frameworkData.network.large_image === null || frameworkData.network.large_image === "") { + frameworkData.network = {} + } + + parsedFrameworkData.Network = frameworkData.network + } else { + parsedFrameworkData.Network = {} + } + + if (frameworkData.iam !== undefined) { + if (frameworkData.iam.large_image === undefined && frameworkData.iam.large_image === null || frameworkData.iam.large_image === "") { + frameworkData.iam = {} + } + + parsedFrameworkData.IAM = frameworkData.iam + } else { + parsedFrameworkData.IAM = {} + } + + if (frameworkData.edr !== undefined) { + if (frameworkData.edr.large_image === undefined && frameworkData.edr.large_image === null || frameworkData.edr.large_image === "") { + frameworkData.edr = {} + } + + parsedFrameworkData["EDR & AV"] = frameworkData.edr + } else { + parsedFrameworkData["EDR & AV"] = {} + } + + } else { + //console.log("No frameworkdata for org! Setting default") + parsedFrameworkData["Cases"] = {} + parsedFrameworkData["SIEM"] = {} + parsedFrameworkData["Assets"] = {} + parsedFrameworkData["IAM"] = {} + parsedFrameworkData["Intel"] = {} + parsedFrameworkData["Comms"] = {} + parsedFrameworkData["Network"] = {} + parsedFrameworkData["EDR & AV"] = {} } + //console.log("Framework - update? ", parsedFrameworkData) + // 0 = automated, 1 = manual - const [usecaseType, setUsecaseType] = React.useState(0) - const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing") const elements = [] const surfaceColor = "#27292D" @@ -865,14 +1170,61 @@ const Framework = (props) => { ) } + const onNodeUnselect = (event) => { + var data = event.target.data(); + console.log("UNSELECT: ", data) + + var parsedStyle = { + "border-width": "10px", + "border-opacity": ".7", + "border-color": "#7fe57f", + } + + // Some error here? + if (event.target !== undefined && event.target !== null) { + event.target.animate( + { + style: parsedStyle, + }, + { + duration: animationDuration, + } + ) + + setTimeout(() => { + event.target.animate( + { + style: { + "border-width": "3px", + }, + }, + { + duration: animationDuration, + } + ) + }, 2500) + } + + //setDiscoveryData({}) + setDiscoveryWrapper({}) + setSelectionOpen(false) + setDefaultSearch("") + setPaperTitle("") + + //setDiscoveryData({}) + } const onNodeSelect = (event) => { - const data = event.target.data(); + var data = event.target.data(); console.log("Node: ", data) if (data.id === "SHUFFLE") { event.target.unselect() return } + + if (data.label === "EDR & AV") { + data.label = "ERADICATION" + } setDiscoveryData(data) setSelectionOpen(true) @@ -908,6 +1260,9 @@ const Framework = (props) => { cy.on("select", "node", (e) => { onNodeSelect(e) }) + cy.on("unselect", "node", (e) => { + onNodeUnselect(e) + }) cy.on("mouseover", "node", (e) => {onNodeHover(e)}) cy.on("mouseout", "node", (e) => onNodeHoverOut(e)); @@ -924,10 +1279,7 @@ const Framework = (props) => { const shiftmodifier = 3*scale //const svgSize = `${40*scale}px` const svgSize = `${40}px` - console.log("Size: ", svgSize) - - console.log("Framework: ", parsedFrameworkData) const fontSize = `${12*scale}px` const defaultSize = `${85*scale}px` const iconSize = `${45*scale}px` @@ -1121,10 +1473,10 @@ const Framework = (props) => { description: parsedFrameworkData.Network.description === undefined ? "" : parsedFrameworkData.Network.description, app_id: parsedFrameworkData.Network.id === undefined ? "" : parsedFrameworkData.Network.id, text_margin_y: parsedFrameworkData.Network.large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData.Network.large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData.Network.large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize, + margin_x: parsedFrameworkData.Network.large_image === undefined ? `${32*scale}px` : "0px", + margin_y: parsedFrameworkData.Network.large_image === undefined ? `${19*scale}px` : `0px`, + width: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize, + height: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize, large_image: parsedFrameworkData.Network.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8, , `) : parsedFrameworkData.Network.large_image, @@ -1265,14 +1617,11 @@ const Framework = (props) => { changeUsecase(selectedUsecase, usecaseType) if (inputUsecase !== undefined && inputUsecase !== null) { - console.log("Got usecase: ", inputUsecase) - for (var key in inputUsecase.process) { if (inputUsecase.process[key].source === "" || inputUsecase.process[key].target === "") { continue } - console.log("Edge: ", inputUsecase.process[key]) inputUsecase.process[key].label = parseInt(key)+1 inputUsecase.process[key].id = uuidv4(); @@ -1377,8 +1726,11 @@ const Framework = (props) => { //} } + const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color + console.log("BGCOLOR: ", bgColor) + return ( - + {data.name} @@ -1447,8 +1799,23 @@ const Framework = (props) => { //autounselectify={true} var usecasediff = -100 - return ( -
+ const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color + + return ( +
+
+ + +
+ {showOptions === false ? null :
{Object.keys(usecases).map((data, index) => { @@ -1480,7 +1847,15 @@ const Framework = (props) => { { Object.getOwnPropertyNames(discoveryData).length > 0 ? - + + {paperTitle.length > 0 ? + + + {paperTitle} + + + + : null} { e.preventDefault(); setDiscoveryData({}) setDefaultSearch("") + setPaperTitle("") }} > - {/* {/*Causes errors in Cytoscape. Removing for now.} { "label": discoveryData.label, "name": "" }) + setNewSelectedApp({ - "image_url": "", - "name": "", + "animate": false, + "app_id": "", + "boxheight": "66.3px", + "boxwidth": "66.3px", + "description": "", + "errors": [], + "font_size": "9.36px", + "height": "66.3px", "id": "", + "isValid": true, + "is_valid": true, + "label": "SIEM", + "large_image": "asd", + "margin_x": "0px", + "margin_y": "0px", + "name": "", + "text_margin_y": "46.800000000000004px", + "width": "66.3px", "objectID": "remove", }) + setSelectionOpen(true) setDefaultSearch("") @@ -1544,7 +1936,6 @@ const Framework = (props) => { - */}
{discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
@@ -1561,18 +1952,18 @@ const Framework = (props) => { newSelectedApp.name !== undefined && newSelectedApp.name !== null && newSelectedApp.name.length > 0 ? newSelectedApp.name : - `No ${discoveryData.label} app chosen` + `Find your ${discoveryData.label} app!` }
{discoveryData !== undefined && discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? - + {discoveryData.description} {/*isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ? - { { }
- {selectionOpen ? - isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ? - - : -
- Coming in 1.0.0. Register for Shuffle cloud to try an early version now. -
- : null} -
+ : null} +
: null } @@ -1649,4 +2037,4 @@ const Framework = (props) => { ) } -export default Framework; +export default AppFramework; diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx new file mode 100644 index 00000000..562146c0 --- /dev/null +++ b/frontend/src/components/AppGrid.jsx @@ -0,0 +1,378 @@ +import React, {useEffect, useState} from 'react'; + +import ReactGA from 'react-ga'; +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 AppGrid = props => { + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata } = 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 : 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 = params["q"] + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery) + refine(foundQuery) + } + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='off' + type="search" + color="primary" + defaultValue={currentRefinement} + placeholder="Find Apps..." + id="shuffle_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + var workflowDelay = -50 + const Hits = ({ hits, insights }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + //console.log(hits) + //var curhits = hits + //if (hits.length > 0 && defaultApps.length === 0) { + // setDefaultApps(hits) + //} + + //const [defaultApps, setDefaultApps] = React.useState([]) + //console.log(hits) + //if (hits.length > 0 && hits.length !== innerHits.length) { + // setInnerHits(hits) + //} + + console.log("In appgrid") + + return ( + + {hits.map((data, index) => { + + workflowDelay += 50 + + const paperStyle = { + backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : theme.palette.inputColor, + color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)", + border: `1px solid ${innerColor}`, + padding: 15, + cursor: "pointer", + position: "relative", + minHeight: 116, + } + + if (counted === 12/xs*rowHandler) { + return null + } + + counted += 1 + var parsedname = "" + for (var key = 0; key < data.name.length; key++) { + var character = data.name.charAt(key) + if (character === character.toUpperCase()) { + //console.log(data.name[key], data.name[key+1]) + if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) { + } else { + parsedname += " " + } + } + + parsedname += character + } + + parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ") + const appUrl = isCloud ? `/apps/${data.objectID}?queryID=${data.__queryID}` : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}` + return ( + + + + { + setMouseHoverIndex(index) + /* + ReactGA.event({ + category: "app_grid_view", + action: `search_bar_click`, + label: "", + }) + */ + }} onMouseOut={() => { + setMouseHoverIndex(-1) + }} onClick={() => { + if (isCloud) { + ReactGA.event({ + category: "app_grid_view", + action: `app_${parsedname}_${data.id}_click`, + label: "", + }) + } + + //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") + console.log(searchClient) + aa('init', { + appId: searchClient.appId, + apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] + }) + + const timestamp = new Date().getTime() + aa('sendEvents', [ + { + eventType: 'click', + eventName: 'Product Clicked', + index: 'appsearch', + objectIDs: [data.objectID], + timestamp: timestamp, + queryID: data.__queryID, + positions: [data.__position], + userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, + } + ]) + + }}> + + {data.name} + +
+ {index === mouseHoverIndex || showName === true ? + parsedname + : + null + } + {data.generated ? + + {data.invalid ? + + : + + } + + : + + + + } + + + + + ) + })} + + ) + } + + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(Hits) + //const CustomHits = connectHitInsights(aa)(Hits) + const selectButtonStyle = { + minWidth: 150, + maxWidth: 150, + minHeight: 50, + } + + return ( +
+ {/* +
+ +
+ */} +
+ +
+ +
+ + +
+ {showSuggestion === true ? +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + {formMessage} +
+ : null + } + + + + Search by + + + Algolia logo + + +
+
+ ) +} + +export default AppGrid; diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx new file mode 100644 index 00000000..0573883d --- /dev/null +++ b/frontend/src/components/Appsearch.jsx @@ -0,0 +1,245 @@ +import React, { useState, useEffect } from 'react'; + +import ReactGA from 'react-ga'; +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 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 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, } = 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 ? 12 : 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 [selectedApp, setSelectedApp] = 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 integration 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) + }); + } + + // value={currentRefinement} + const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { + useEffect(() => { + //console.log("FIRST LOAD ONLY? RUN REFINEMENT: !", currentRefinement) + if (defaultSearch !== undefined && defaultSearch !== null) { + refine(defaultSearch) + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='on' + type="search" + color="primary" + defaultValue={defaultSearch} + placeholder={`Find ${defaultSearch} Apps...`} + id="shuffle_workflow_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + //value={currentRefinement} + } + + const Hits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + return ( + + {hits.map((data, index) => { + 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: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e", + textAlign: "left", + padding: 10, + cursor: "pointer", + position: "relative", + overflow: "hidden", + width: "100%", + minHeight: 37, + maxHeight: 52, + } + + if (counted === 12/xs*rowHandler) { + return null + } + + counted += 1 + var parsedname = data.name.valueOf() + //for (var key = 0; key < data.name.length; key++) { + // var character = data.name.charAt(key) + // if (character === character.toUpperCase()) { + // //console.log(data.name[key], data.name[key+1]) + // if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) { + // } else { + // parsedname += " " + // } + // } + + // parsedname += character + //} + + parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ") + + return ( + { + setMouseHoverIndex(index) + /* + ReactGA.event({ + category: "app_grid_view", + action: `search_bar_click`, + label: "", + }) + */ + }} onMouseOut={() => { + setMouseHoverIndex(-1) + }} onClick={() => { + if (setNewSelectedApp !== undefined) { + setNewSelectedApp(data) + } + + if (isCloud) { + ReactGA.event({ + category: "app_search", + action: `app_${parsedname}_${data.id}_personalize_click`, + label: "", + }) + } + + const queryID = "" + + if (queryID !== undefined && queryID !== null) { + try { + aa('init', { + appId: searchClient.appId, + apiKey: searchClient.transporter.headers["x-algolia-api-key"] + }) + + const timestamp = new Date().getTime() + aa('sendEvents', [ + { + eventType: 'conversion', + eventName: 'App Framework Activation', + index: 'appsearch', + objectIDs: [data.objectID], + timestamp: timestamp, + queryID: queryID, + userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, + } + ]) + } catch (e) { + console.log("Failed algolia search update: ", e) + } + } + }}> +
+ {data.name} + + {parsedname} + +
+
+ ) + })} +
+ ) + } + + const InputHits = ConfiguredHits === undefined ? Hits : ConfiguredHits + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(InputHits) + + return ( +
+ + {/* showSearch === false ? null : +
+ +
+ */} +
+ +
+ +
+
+ ) +} + +export default Appsearch; diff --git a/frontend/src/components/AppsearchPopout.jsx b/frontend/src/components/AppsearchPopout.jsx new file mode 100644 index 00000000..7e15dc67 --- /dev/null +++ b/frontend/src/components/AppsearchPopout.jsx @@ -0,0 +1,189 @@ +import React, { useState, useEffect } from 'react'; + +import theme from '../theme'; +import AppSearch from './Appsearch.jsx'; + +import { + Paper, + Typography, + Divider, + IconButton, + Badge, + CircularProgress, + Tooltip, + Button, +} from "@material-ui/core"; + +import { + Close as CloseIcon, + Delete as DeleteIcon, +} from "@material-ui/icons"; + +const AppSearchPopout = (props) => { + const { + cy, + paperTitle, + setPaperTitle, + newSelectedApp, + setNewSelectedApp, + selectionOpen, + setSelectionOpen, + discoveryData, + setDiscoveryData, + userdata, + } = props; + + const [defaultSearch, setDefaultSearch] = React.useState(paperTitle !== undefined ? paperTitle : "") + + if (selectionOpen !== true) { + return null + } + + return ( + + {paperTitle !== undefined && paperTitle.length > 0 ? + + + {paperTitle} + + + + : null} + + { + //cy.elements().unselectify(); + if (cy !== undefined) { + cy.elements().unselect() + } + + e.preventDefault(); + setSelectionOpen(false) + }} + > + + + + {/* {/*Causes errors in Cytoscape. Removing for now.} + + { + e.preventDefault(); + setDiscoveryData({ + "id": discoveryData.id, + "label": discoveryData.label, + "name": "" + }) + setNewSelectedApp({ + "image_url": "", + "name": "", + "id": "", + "objectID": "remove", + }) + setSelectionOpen(true) + setDefaultSearch("") + + const foundelement = cy.getElementById(discoveryData.id) + if (foundelement !== undefined && foundelement !== null) { + console.log("element: ", foundelement) + foundelement.data("large_image", discoveryData.large_image) + foundelement.data("text_margin_y", "14px") + foundelement.data("margin_x", "32px") + foundelement.data("margin_y", "19x") + foundelement.data("width", "45px") + foundelement.data("height", "45px") + } + + setTimeout(() => { + setDiscoveryData({}) + setNewSelectedApp({}) + }, 1000) + }} + > + + + + */} +
+ {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? +
+ + {discoveryData.id} 0 ? newSelectedApp.image_url : discoveryData.large_image} style={{height: 40, width: 40, margin: "auto",}}/> +
+ : + {discoveryData.id} + } + + {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? + discoveryData.name + : + newSelectedApp.name !== undefined && newSelectedApp.name !== null && newSelectedApp.name.length > 0 ? + newSelectedApp.name + : + `No ${discoveryData.label} app chosen` + } + +
+
+ {discoveryData !== undefined && discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ? + + + {discoveryData.description} + + {/*isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ? + {< + newSelectedApp={newSelectedApp} + setNewSelectedApp={setNewSelectedApp} + defaultSearch={defaultSearch} + />} + : + null + */} + + : + selectionOpen + ? + + + Click an app below to select it + + + : + + } +
+
+ {selectionOpen ? + + : null} +
+
+ ) +} + +export default AppSearchPopout; diff --git a/frontend/src/components/AuthenticationItem.jsx b/frontend/src/components/AuthenticationItem.jsx new file mode 100644 index 00000000..45879519 --- /dev/null +++ b/frontend/src/components/AuthenticationItem.jsx @@ -0,0 +1,278 @@ +import React, { useState, useEffect } from "react"; + +import theme from '../theme'; +import { useAlert } from "react-alert"; +import { + Tooltip, + IconButton, + ListItem, + ListItemText, + FormGroup, + FormControl, + InputLabel, + FormLabel, + FormControlLabel, + Select, + MenuItem, + Grid, + Paper, + Typography, + TextField, + Zoom, +} from "@material-ui/core"; + +import { + Edit as EditIcon, + Delete as DeleteIcon, + SelectAll as SelectAllIcon, +} from "@material-ui/icons"; + +const AuthenticationItem = (props) => { + const { data, index, globalUrl, getAppAuthentication } = props + + const [selectedAuthentication, setSelectedAuthentication] = React.useState({}) + const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false); + const [authenticationFields, setAuthenticationFields] = React.useState([]); + + const alert = useAlert(); + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + //console.log("Auth data: ", data) + if (data.type === "oauth2") { + data.fields = [ + { + key: "url", + value: "Secret. Replaced during app execution!", + }, + { + key: "client_id", + value: "Secret. Replaced during app execution!", + }, + { + key: "client_secret", + value: "Secret. Replaced during app execution!", + }, + { + key: "scope", + value: "Secret. Replaced during app execution!", + }, + ]; + } + + const deleteAuthentication = (data) => { + alert.info("Deleting auth " + data.label); + + // Just use this one? + const url = globalUrl + "/api/v1/apps/authentication/" + data.id; + console.log("URL: ", url); + fetch(url, { + method: "DELETE", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => + response.json().then((responseJson) => { + console.log("RESP: ", responseJson); + if (responseJson["success"] === false) { + alert.error("Failed deleting auth"); + } else { + // Need to wait because query in ES is too fast + setTimeout(() => { + getAppAuthentication(); + }, 1000); + //alert.success("Successfully deleted authentication!") + } + }) + ) + .catch((error) => { + console.log("Error in userdata: ", error); + }); + } + + const editAuthenticationConfig = (id) => { + const data = { + id: id, + action: "assign_everywhere", + }; + const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config"; + + 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) { + alert.error("Failed overwriting appauth in workflows"); + } else { + alert.success("Successfully updated auth everywhere!"); + //setSelectedUserModalOpen(false); + setTimeout(() => { + getAppAuthentication(); + }, 1000); + } + }) + ) + .catch((error) => { + alert.error("Err: " + error.toString()); + }); + }; + + const updateAppAuthentication = (field) => { + setSelectedAuthenticationModalOpen(true); + setSelectedAuthentication(field); + //{selectedAuthentication.fields.map((data, index) => { + var newfields = []; + for (var key in field.fields) { + newfields.push({ + key: field.fields[key].key, + value: "", + }); + } + setAuthenticationFields(newfields); + } + + return ( + + + style={{ minWidth: 75, maxWidth: 75 }} + /> + + + {/* + + */} + + {/* + + */} + { + return data.key; + }) + .join(", ") + } + style={{ + minWidth: 125, + maxWidth: 125, + overflow: "hidden", + }} + /> + + + { + updateAppAuthentication(data); + }} + > + + + {data.defined ? ( + + { + editAuthenticationConfig(data.id); + }} + > + + + + ) : ( + + {}} + > + + + + )} + { + deleteAuthentication(data); + }} + > + + + + + ) + } + +export default AuthenticationItem diff --git a/frontend/src/components/AuthenticationNormal.jsx b/frontend/src/components/AuthenticationNormal.jsx new file mode 100644 index 00000000..aae3a128 --- /dev/null +++ b/frontend/src/components/AuthenticationNormal.jsx @@ -0,0 +1,366 @@ +import React, { useState, useEffect } from "react"; +import theme from '../theme'; +import { v4 as uuidv4 } from "uuid"; + + +import { + Button, + Divider, + Select, + MenuItem, + TextField, + DialogActions, + DialogTitle, + DialogContent, + Typography, +} from "@material-ui/core"; + +import { + LockOpen as LockOpenIcon, +} from "@material-ui/icons"; + +const AuthenticationData = (props) => { + const { + globalUrl, + saveWorkflow, + selectedApp, + workflow, + selectedAction, + authenticationType, + getAppAuthentication, + appAuthentication, + setSelectedAction, + setAuthenticationModalOpen, + isCloud, + } = props; + + const setNewAppAuth = (appAuthData) => { + console.log("DAta: ", appAuthData); + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(appAuthData), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for setting app auth :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to set app auth: " + responseJson.reason); + } else { + if (getAppAuthentication !== undefined) { + getAppAuthentication() + } + + if (setAuthenticationModalOpen !== undefined) { + setAuthenticationModalOpen(false) + } + + // Needs a refresh with the new authentication.. + //alert.success("Successfully saved new app auth") + } + }) + .catch((error) => { + //alert.error(error.toString()); + console.log("New auth error: ", error.toString()); + }); + } + + const [authenticationOption, setAuthenticationOptions] = React.useState({ + app: JSON.parse(JSON.stringify(selectedApp)), + fields: {}, + label: "", + usage: [ + { + workflow_id: workflow.id, + }, + ], + id: uuidv4(), + active: true, + }); + + if ( + selectedApp.authentication === undefined || + selectedApp.authentication.parameters === null || + selectedApp.authentication.parameters === undefined || + selectedApp.authentication.parameters.length === 0 + ) { + return ( + + + {selectedApp.name} does not require authentication + + + ); + } + + authenticationOption.app.actions = []; + + for (var key in selectedApp.authentication.parameters) { + if ( + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] === undefined + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] = ""; + } + } + + const handleSubmitCheck = () => { + console.log("NEW AUTH: ", authenticationOption); + 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 (var key in selectedApp.authentication.parameters) { + if ( + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ].length === 0 + ) { + if ( + selectedApp.authentication.parameters[key].value !== undefined && + selectedApp.authentication.parameters[key].value !== null && + selectedApp.authentication.parameters[key].value.length > 0 + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] = selectedApp.authentication.parameters[key].value; + } else { + if ( + selectedApp.authentication.parameters[key].schema.type === "bool" + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] = "false"; + } else { + alert.info( + "Field " + + selectedApp.authentication.parameters[key].name + + " can't be empty" + ); + return; + } + } + } + } + + console.log("Action: ", selectedAction); + 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 = []; + for (const key in newAuthOption.fields) { + const value = newAuthOption.fields[key]; + newFields.push({ + key: key, + value: value, + }); + } + + console.log("FIELDS: ", newFields); + newAuthOption.fields = newFields; + setNewAppAuth(newAuthOption); + + //if (configureWorkflowModalOpen) { + // setSelectedAction({}); + //} + + //setUpdate(authenticationOption.id); + }; + + if ( + authenticationOption.label === null || + authenticationOption.label === undefined + ) { + authenticationOption.label = selectedApp.name + " authentication"; + } + + return ( +
+ +
+ Authentication for {selectedApp.name} +
+
+ + + What is app authentication? + +
+ These are required fields for authenticating with {selectedApp.name} +
+ Name - what is this used for? + { + authenticationOption.label = event.target.value; + }} + /> + +
+ {selectedApp.authentication.parameters.map((data, index) => { + return ( +
+ + {data.name} + + {data.schema !== undefined && + data.schema !== null && + data.schema.type === "bool" ? ( + + ) : ( + { + authenticationOption.fields[data.name] = + event.target.value; + }} + /> + )} +
+ ); + })} + + + + + +
+ ); +}; + +export default AuthenticationData diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index fbd7eb79..0e5cc988 100644 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -1,4 +1,5 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; +import { useInterval } from "react-powerhooks"; import { InputAdornment, @@ -13,9 +14,15 @@ import { List, ListItem, ListItemText, + Fade, } from "@material-ui/core"; -import { FavoriteBorder as FavoriteBorderIcon } from "@material-ui/icons"; +import { + FavoriteBorder as FavoriteBorderIcon, + Error as ErrorIcon, + CheckCircleRounded as CheckCircleRoundedIcon, +} from "@mui/icons-material"; import { FixName } from "../views/Apps.jsx"; +import aa from 'search-insights' // Handles workflow updates on first open to highlight the issues of the workflow // Variables @@ -26,6 +33,7 @@ import { FixName } from "../views/Apps.jsx"; // Specifically used for UNSAVED workflows only? const ConfigureWorkflow = (props) => { const { + userdata, globalUrl, theme, workflow, @@ -43,15 +51,34 @@ const ConfigureWorkflow = (props) => { isCloud, setAuthenticationType, alert, + showTriggers, + workflowExecutions, + getWorkflowExecution, } = props; const [requiredActions, setRequiredActions] = React.useState([]); const [requiredVariables, setRequiredVariables] = React.useState([]); const [requiredTriggers, setRequiredTriggers] = React.useState([]); const [previousAuth, setPreviousAuth] = React.useState(appAuthentication); - const [firstLoad, setFirstLoad] = React.useState(""); const [itemChanged, setItemChanged] = React.useState(false); - var finished = false; + const [firstLoad, setFirstLoad] = React.useState(""); + const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false); + const [checkStarted, setCheckStarted] = React.useState(false); + + const { start, stop } = useInterval({ + duration: 3000, + startImmediate: false, + callback: () => { + if (getWorkflowExecution !== undefined && workflowExecutions !== undefined) { + const paramkey = workflow.id + getWorkflowExecution(paramkey) + } else { + console.log("Executions or getWorkflowExecutions not defined") + } + }, + }); + + // Where is this from? if (workflow === undefined || workflow === null) { return null; } @@ -94,18 +121,13 @@ const ConfigureWorkflow = (props) => { }; if (firstLoad.length === 0 || firstLoad !== workflow.id) { - if (finished) { - setConfigureWorkflowModalOpen(false); - return null; - } - if (apps === undefined || apps === null || apps.length === 0) { console.log("No apps loaded: ", apps); setConfigureWorkflowModalOpen(false); return null; } - setFirstLoad(workflow.id); + setFirstLoad(workflow.id) const newactions = []; for (var key in workflow.actions) { const action = workflow.actions[key]; @@ -121,6 +143,8 @@ const ConfigureWorkflow = (props) => { action: action, update_version: action.app_version, app: {}, + steps: [], + show_steps: false, }; const app = apps.find( @@ -129,17 +153,21 @@ const ConfigureWorkflow = (props) => { (app.app_version === action.app_version || (app.loop_versions !== null && app.loop_versions.includes(action.app_version))) - ); + ) + + //newaction.steps = wazuhSteps if (app === undefined || app === null) { - //console.log("App not found: ", action.app_name); - const subapp = apps.find(app => app.name === action.app_name) if (subapp !== undefined && subapp !== null) { newaction.update_version = "1.1.0" } - newaction.must_activate = true; + newaction.steps.push({ + "title": "Activate app", + "type": "activate", + "required": true, + }) } else { if ( action.authentication_id === "" && @@ -160,6 +188,12 @@ const ConfigureWorkflow = (props) => { } } + newaction.steps.push({ + "title": "Authenticate app", + "type": "authenticate", + "required": true, + }) + if (!filled) { newaction.must_authenticate = true; newaction.action_ids.push(action.id); @@ -247,6 +281,38 @@ const ConfigureWorkflow = (props) => { var trigger = workflow.triggers[key]; trigger.index = key; + if (trigger.trigger_type === "WEBHOOK") { + console.log("Found webhook: ", trigger) + if (trigger.app_association !== undefined && trigger.app_association.name !== null && trigger.app_association.name !== "") { + console.log("Actions: ", newactions) + const findapp = trigger.app_association.name.toLowerCase() + const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp) + + // Adding webhook to start of it + if (foundindex >= 0) { + const tmpsteps = newactions[foundindex].steps + newactions[foundindex].steps = [ + { + "title": "Configure Webhook", + "type": "webhook", + "required": true, + } + ] + + for (var subkey in tmpsteps) { + newactions[foundindex].steps.push(tmpsteps[subkey]) + } + + newactions[foundindex].show_steps = true + + console.log("CHANGED ACTION: ", newactions[foundindex]) + //console.log("Index: ", newactions[foundindex]) + + continue + } + } + } + if (trigger.status === "running") { continue; } @@ -272,17 +338,19 @@ const ConfigureWorkflow = (props) => { setRequiredTriggers(requiredTriggers); setRequiredVariables(requiredVariables); setRequiredActions(newactions); - } + } if (appAuthentication.length !== previousAuth.length) { - var newactions = []; + var newactions = [] for (var actionkey in requiredActions) { var newaction = requiredActions[actionkey]; const app = newaction.app; for (var key in appAuthentication) { const auth = appAuthentication[key]; - if (auth.app.name === app.name && auth.active) { + + // Does this account for all the different ones of the same? + if (auth.app.name === app.name && auth.active === true) { newaction.auth_done = true; break; } @@ -298,7 +366,7 @@ const ConfigureWorkflow = (props) => { } const TriggerSection = (props) => { - const { trigger } = props; + const { trigger } = props return ( @@ -460,6 +528,26 @@ const ConfigureWorkflow = (props) => { }; const activateApp = (app_id, app_name, app_version) => { + + if (aa !== undefined) { + aa('init', { + appId: "JNSS5CFDZZ", + apiKey: "db08e40265e2941b9a7d8f644b6e5240", + }) + + const timestamp = new Date().getTime() + aa('sendEvents', [ + { + eventType: 'conversion', + eventName: 'Public App Activated', + index: 'appsearch', + objectIDs: [app_id], + timestamp: timestamp, + userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, + } + ]) + } + fetch( `${globalUrl}/api/v1/apps/${app_id}/activate?app_name=${app_name}&app_version=${app_version}`, { @@ -502,6 +590,7 @@ const ConfigureWorkflow = (props) => { return ( + {/* { secondary={action.app_version} style={{}} /> - {action.must_authenticate ? ( - action.auth_done ? ( - - ) : selectedAction.app_name === action.app_name ? ( - - ) : ( - - ) - ) : null} - {action.must_activate ? ( + {action.app_name} + + {action.auth_done ? "Authenticated" : `Authenticate ${action.app_name.replaceAll("_", " ")}`} + + + : null} + {action.update_version !== action.app_version ? - ) : null} - {action.update_version !== action.app_version ? ( - - ) : null} + : + action.must_activate ? + + : + null + } ); - }; + } + + // Based on the color here. Default: #f86a3e + //backgroundColor: selectedUsecaseCategory === usecase.name ? usecase.color : theme.palette.surfaceColor, + const BoxHighlight = (props) => { + const {data, appname, appinfo, index, activeStep, setActiveStep, finished, } = props + + const [hovered, setHovered] = useState(false) + const [isOpen, setIsOpen] = useState(false) + const [isLoading, setIsLoading] = useState(false) + + // This kind of just works for new workflows.. + // What if we try many times? + + var webhook = { + "name": "Testhook", + "description": `A Webhook Trigger has been started and is ready to receive events from ${appname}. Click to copy the URL to send events to.`, + "url": "", + } + + useEffect(() => { + if (data.type === "webhook" && !finished) { + if (!checkStarted) { + setCheckStarted(true) + start() + } + } + }, []) + + // Load webhook docs from the app itself (Wazuh) + // Add a "sample" for what the event is supposed to look like + // Have a listener for when ACTUALLY is received + // INJECT the URL into the documentation when loading it in + // How can we load it in? Should we just use the app name & get docs -> parse? + if (data.type === "webhook" && workflow.triggers !== undefined && workflow.triggers !== null) { + //console.log("Find webhook in the workflow!") + for (var key in workflow.triggers) { + if (workflow.triggers[key].trigger_type !== "WEBHOOK") { + continue + } + + for (var subkey in workflow.triggers[key].parameters) { + const param = workflow.triggers[key].parameters[subkey] + if (param.name === "url") { + webhook.url = param.value + + if (isLoading === false) { + setIsLoading(true) + } + break + } + } + } + } else if (data.type == "authenticate") { + //console.log("Handle app authentication in the workflow!") + } + + return ( +
{ + setIsOpen(!isOpen) + setActiveStep(index) + }} + onMouseOver={() => { + setHovered(true); + }} + onMouseOut={() => { + setHovered(false); + }} + > + +
+ {data.title} + {finished ? + + : + + } +
+ + {activeStep === index ? +
+ {data.type === "webhook" ? +
{ + event.preventDefault() + console.log("Clicked Webhook") + + var copyText = document.getElementById("copy_element_shuffle") + if (copyText !== undefined && copyText !== null) { + console.log("NAVIGATOR: ", navigator); + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + alert.error("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(webhook.url); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + alert.success("Copied Webhook URL"); + } + }}> + {webhook.description} + {/*{webhook.url}*/} + {isLoading && finished === false ? +
+ +
+ : + null + } +
+ : + + } +
+ : null} + +
+ ) + } + + const AppWrapper = (props) => { + const {data, parentindex} = props + const [clicked, setClicked] = useState(true) + const [hovered, setHovered] = useState(false) + const [activeStep, setActiveStep] = useState(0) + const [firstRun, setFirstRun] = useState(true) + const [finishCount, setFinishCount] = useState(0) + + return ( +
+
{ + //setClicked(!clicked) + }} + onMouseOver={() => { + setHovered(true); + }} + onMouseOut={() => { + setHovered(false); + }} + > + + {data.label} + + + Configure {data.app_name.replaceAll("_", " ")} + +
+ {clicked === true ? + data.steps.map((step, index) => { + var finished = false + if (step.type === "activate") { + if (data.activation_done === true) { + finished = true + + if (index === activeStep && firstRun === true) { + setActiveStep(activeStep+1) + } + + if (firstRun) { + setFinishCount(finishCount+1) + } + } + } + + if (step.type === "authenticate") { + console.log("AUTH STEP: ", step) + if (data.must_authenticate === true ) { + finished = false + } else { + if (data.activation_done === true && data.auth_done === true) { + finished = true + + if (firstRun) { + setFinishCount(finishCount+1) + } + + if (index === activeStep && firstRun === true) { + setActiveStep(activeStep+1) + } + } + } + } + + if (step.type === "webhook") { + for (var key in workflowExecutions) { + const exec = workflowExecutions[key] + if (exec.execution_argument !== undefined && exec.execution_argument !== null && exec.execution_argument.length > 0 && exec.execution_source === "webhook") { + //console.log("Done: ", exec) + + finished = true + if (index === activeStep && firstRun === true) { + setActiveStep(activeStep+1) + + } + + if (firstRun) { + setFinishCount(finishCount+1) + } + + // Finished + source = webhook + + stop() + //if (isLoading === true) { + // setIsLoading(false) + //} + + + break + } + } + } + + if (firstRun === true && index === data.steps.length-1) { + setFirstRun(false) + } + + return ( + + ) + }) + : null} +
+ ) + } + + const topColor = "#f86a3e, #fc3922" return (
- {workflow.name} - - The following configuration makes the workflow ready immediately. - - {requiredActions.length > 0 ? ( - - - Actions - - - {requiredActions.map((data, index) => { - return ; - })} - - - ) : null} +
+
+
+ {workflow.name} + + The following configuration makes the workflow ready immediately. + + {requiredActions.length > 0 ? ( + + + Required Actions + - {requiredVariables.length > 0 ? ( - - - Variables - - - {requiredVariables.map((data, index) => { - return ; - })} - - - ) : null} + + {requiredActions.map((data, index) => { + return ( +
+ {data.steps !== undefined && data.steps !== null && data.show_steps === true ? + + : + + } +
+ ) + })} +
+
+ ) : null} - {requiredTriggers.length > 0 ? ( - - - Triggers - - - {requiredTriggers.map((data, index) => { - return ; - })} - - - ) : null} -
- - {/* - - */} - - -
+ {requiredVariables.length > 0 ? ( + + + Variables + + + {requiredVariables.map((data, index) => { + return ; + })} + + + ) : null} + + {requiredTriggers.length > 0 && showTriggers !== false ? ( + + + Triggers + + + {requiredTriggers.map((data, index) => { + return ; + })} + + + ) : null} + +
+ {showFinalizeAnimation ? + finalize workflow animation { + console.log("Img loaded.") + setTimeout(() => { + console.log("Img closing.") + setConfigureWorkflowModalOpen(false); + }, 1250) + + }}/> + : + + {/* + + */} + + + } +
+
); }; diff --git a/frontend/src/components/Countries.jsx b/frontend/src/components/Countries.jsx new file mode 100644 index 00000000..2cb9c853 --- /dev/null +++ b/frontend/src/components/Countries.jsx @@ -0,0 +1,434 @@ +const countries = [ + { code: 'GB', label: 'United Kingdom', phone: '44' }, + { + code: 'US', + label: 'United States', + phone: '1', + suggested: true, + }, + { code: 'IN', label: 'India', phone: '91' }, + { code: 'AD', label: 'Andorra', phone: '376' }, + { + code: 'AE', + label: 'United Arab Emirates', + phone: '971', + }, + { code: 'AF', label: 'Afghanistan', phone: '93' }, + { + code: 'AG', + label: 'Antigua and Barbuda', + phone: '1-268', + }, + { code: 'AI', label: 'Anguilla', phone: '1-264' }, + { code: 'AL', label: 'Albania', phone: '355' }, + { code: 'AM', label: 'Armenia', phone: '374' }, + { code: 'AO', label: 'Angola', phone: '244' }, + { code: 'AQ', label: 'Antarctica', phone: '672' }, + { code: 'AR', label: 'Argentina', phone: '54' }, + { code: 'AS', label: 'American Samoa', phone: '1-684' }, + { code: 'AT', label: 'Austria', phone: '43' }, + { + code: 'AU', + label: 'Australia', + phone: '61', + suggested: true, + }, + { code: 'AW', label: 'Aruba', phone: '297' }, + { code: 'AX', label: 'Alland Islands', phone: '358' }, + { code: 'AZ', label: 'Azerbaijan', phone: '994' }, + { + code: 'BA', + label: 'Bosnia and Herzegovina', + phone: '387', + }, + { code: 'BB', label: 'Barbados', phone: '1-246' }, + { code: 'BD', label: 'Bangladesh', phone: '880' }, + { code: 'BE', label: 'Belgium', phone: '32' }, + { code: 'BF', label: 'Burkina Faso', phone: '226' }, + { code: 'BG', label: 'Bulgaria', phone: '359' }, + { code: 'BH', label: 'Bahrain', phone: '973' }, + { code: 'BI', label: 'Burundi', phone: '257' }, + { code: 'BJ', label: 'Benin', phone: '229' }, + { code: 'BL', label: 'Saint Barthelemy', phone: '590' }, + { code: 'BM', label: 'Bermuda', phone: '1-441' }, + { code: 'BN', label: 'Brunei Darussalam', phone: '673' }, + { code: 'BO', label: 'Bolivia', phone: '591' }, + { code: 'BR', label: 'Brazil', phone: '55' }, + { code: 'BS', label: 'Bahamas', phone: '1-242' }, + { code: 'BT', label: 'Bhutan', phone: '975' }, + { code: 'BV', label: 'Bouvet Island', phone: '47' }, + { code: 'BW', label: 'Botswana', phone: '267' }, + { code: 'BY', label: 'Belarus', phone: '375' }, + { code: 'BZ', label: 'Belize', phone: '501' }, + { + code: 'CA', + label: 'Canada', + phone: '1', + suggested: true, + }, + { + code: 'CC', + label: 'Cocos (Keeling) Islands', + phone: '61', + }, + { + code: 'CD', + label: 'Congo, Democratic Republic of the', + phone: '243', + }, + { + code: 'CF', + label: 'Central African Republic', + phone: '236', + }, + { + code: 'CG', + label: 'Congo, Republic of the', + phone: '242', + }, + { code: 'CH', label: 'Switzerland', phone: '41' }, + { code: 'CI', label: "Cote d'Ivoire", phone: '225' }, + { code: 'CK', label: 'Cook Islands', phone: '682' }, + { code: 'CL', label: 'Chile', phone: '56' }, + { code: 'CM', label: 'Cameroon', phone: '237' }, + { code: 'CN', label: 'China', phone: '86' }, + { code: 'CO', label: 'Colombia', phone: '57' }, + { code: 'CR', label: 'Costa Rica', phone: '506' }, + { code: 'CU', label: 'Cuba', phone: '53' }, + { code: 'CV', label: 'Cape Verde', phone: '238' }, + { code: 'CW', label: 'Curacao', phone: '599' }, + { code: 'CX', label: 'Christmas Island', phone: '61' }, + { code: 'CY', label: 'Cyprus', phone: '357' }, + { code: 'CZ', label: 'Czech Republic', phone: '420' }, + { + code: 'DE', + label: 'Germany', + phone: '49', + suggested: true, + }, + { code: 'DJ', label: 'Djibouti', phone: '253' }, + { code: 'DK', label: 'Denmark', phone: '45' }, + { code: 'DM', label: 'Dominica', phone: '1-767' }, + { + code: 'DO', + label: 'Dominican Republic', + phone: '1-809', + }, + { code: 'DZ', label: 'Algeria', phone: '213' }, + { code: 'EC', label: 'Ecuador', phone: '593' }, + { code: 'EE', label: 'Estonia', phone: '372' }, + { code: 'EG', label: 'Egypt', phone: '20' }, + { code: 'EH', label: 'Western Sahara', phone: '212' }, + { code: 'ER', label: 'Eritrea', phone: '291' }, + { code: 'ES', label: 'Spain', phone: '34' }, + { code: 'ET', label: 'Ethiopia', phone: '251' }, + { code: 'FI', label: 'Finland', phone: '358' }, + { code: 'FJ', label: 'Fiji', phone: '679' }, + { + code: 'FK', + label: 'Falkland Islands (Malvinas)', + phone: '500', + }, + { + code: 'FM', + label: 'Micronesia, Federated States of', + phone: '691', + }, + { code: 'FO', label: 'Faroe Islands', phone: '298' }, + { + code: 'FR', + label: 'France', + phone: '33', + suggested: true, + }, + { code: 'GA', label: 'Gabon', phone: '241' }, + { code: 'GB', label: 'United Kingdom', phone: '44' }, + { code: 'GD', label: 'Grenada', phone: '1-473' }, + { code: 'GE', label: 'Georgia', phone: '995' }, + { code: 'GF', label: 'French Guiana', phone: '594' }, + { code: 'GG', label: 'Guernsey', phone: '44' }, + { code: 'GH', label: 'Ghana', phone: '233' }, + { code: 'GI', label: 'Gibraltar', phone: '350' }, + { code: 'GL', label: 'Greenland', phone: '299' }, + { code: 'GM', label: 'Gambia', phone: '220' }, + { code: 'GN', label: 'Guinea', phone: '224' }, + { code: 'GP', label: 'Guadeloupe', phone: '590' }, + { code: 'GQ', label: 'Equatorial Guinea', phone: '240' }, + { code: 'GR', label: 'Greece', phone: '30' }, + { + code: 'GS', + label: 'South Georgia and the South Sandwich Islands', + phone: '500', + }, + { code: 'GT', label: 'Guatemala', phone: '502' }, + { code: 'GU', label: 'Guam', phone: '1-671' }, + { code: 'GW', label: 'Guinea-Bissau', phone: '245' }, + { code: 'GY', label: 'Guyana', phone: '592' }, + { code: 'HK', label: 'Hong Kong', phone: '852' }, + { + code: 'HM', + label: 'Heard Island and McDonald Islands', + phone: '672', + }, + { code: 'HN', label: 'Honduras', phone: '504' }, + { code: 'HR', label: 'Croatia', phone: '385' }, + { code: 'HT', label: 'Haiti', phone: '509' }, + { code: 'HU', label: 'Hungary', phone: '36' }, + { code: 'ID', label: 'Indonesia', phone: '62' }, + { code: 'IE', label: 'Ireland', phone: '353' }, + { code: 'IL', label: 'Israel', phone: '972' }, + { code: 'IM', label: 'Isle of Man', phone: '44' }, + { code: 'IN', label: 'India', phone: '91' }, + { + code: 'IO', + label: 'British Indian Ocean Territory', + phone: '246', + }, + { code: 'IQ', label: 'Iraq', phone: '964' }, + { + code: 'IR', + label: 'Iran, Islamic Republic of', + phone: '98', + }, + { code: 'IS', label: 'Iceland', phone: '354' }, + { code: 'IT', label: 'Italy', phone: '39' }, + { code: 'JE', label: 'Jersey', phone: '44' }, + { code: 'JM', label: 'Jamaica', phone: '1-876' }, + { code: 'JO', label: 'Jordan', phone: '962' }, + { + code: 'JP', + label: 'Japan', + phone: '81', + suggested: true, + }, + { code: 'KE', label: 'Kenya', phone: '254' }, + { code: 'KG', label: 'Kyrgyzstan', phone: '996' }, + { code: 'KH', label: 'Cambodia', phone: '855' }, + { code: 'KI', label: 'Kiribati', phone: '686' }, + { code: 'KM', label: 'Comoros', phone: '269' }, + { + code: 'KN', + label: 'Saint Kitts and Nevis', + phone: '1-869', + }, + { + code: 'KP', + label: "Korea, Democratic People's Republic of", + phone: '850', + }, + { code: 'KR', label: 'Korea, Republic of', phone: '82' }, + { code: 'KW', label: 'Kuwait', phone: '965' }, + { code: 'KY', label: 'Cayman Islands', phone: '1-345' }, + { code: 'KZ', label: 'Kazakhstan', phone: '7' }, + { + code: 'LA', + label: "Lao People's Democratic Republic", + phone: '856', + }, + { code: 'LB', label: 'Lebanon', phone: '961' }, + { code: 'LC', label: 'Saint Lucia', phone: '1-758' }, + { code: 'LI', label: 'Liechtenstein', phone: '423' }, + { code: 'LK', label: 'Sri Lanka', phone: '94' }, + { code: 'LR', label: 'Liberia', phone: '231' }, + { code: 'LS', label: 'Lesotho', phone: '266' }, + { code: 'LT', label: 'Lithuania', phone: '370' }, + { code: 'LU', label: 'Luxembourg', phone: '352' }, + { code: 'LV', label: 'Latvia', phone: '371' }, + { code: 'LY', label: 'Libya', phone: '218' }, + { code: 'MA', label: 'Morocco', phone: '212' }, + { code: 'MC', label: 'Monaco', phone: '377' }, + { + code: 'MD', + label: 'Moldova, Republic of', + phone: '373', + }, + { code: 'ME', label: 'Montenegro', phone: '382' }, + { + code: 'MF', + label: 'Saint Martin (French part)', + phone: '590', + }, + { code: 'MG', label: 'Madagascar', phone: '261' }, + { code: 'MH', label: 'Marshall Islands', phone: '692' }, + { + code: 'MK', + label: 'Macedonia, the Former Yugoslav Republic of', + phone: '389', + }, + { code: 'ML', label: 'Mali', phone: '223' }, + { code: 'MM', label: 'Myanmar', phone: '95' }, + { code: 'MN', label: 'Mongolia', phone: '976' }, + { code: 'MO', label: 'Macao', phone: '853' }, + { + code: 'MP', + label: 'Northern Mariana Islands', + phone: '1-670', + }, + { code: 'MQ', label: 'Martinique', phone: '596' }, + { code: 'MR', label: 'Mauritania', phone: '222' }, + { code: 'MS', label: 'Montserrat', phone: '1-664' }, + { code: 'MT', label: 'Malta', phone: '356' }, + { code: 'MU', label: 'Mauritius', phone: '230' }, + { code: 'MV', label: 'Maldives', phone: '960' }, + { code: 'MW', label: 'Malawi', phone: '265' }, + { code: 'MX', label: 'Mexico', phone: '52' }, + { code: 'MY', label: 'Malaysia', phone: '60' }, + { code: 'MZ', label: 'Mozambique', phone: '258' }, + { code: 'NA', label: 'Namibia', phone: '264' }, + { code: 'NC', label: 'New Caledonia', phone: '687' }, + { code: 'NE', label: 'Niger', phone: '227' }, + { code: 'NF', label: 'Norfolk Island', phone: '672' }, + { code: 'NG', label: 'Nigeria', phone: '234' }, + { code: 'NI', label: 'Nicaragua', phone: '505' }, + { code: 'NL', label: 'Netherlands', phone: '31' }, + { code: 'NO', label: 'Norway', phone: '47' }, + { code: 'NP', label: 'Nepal', phone: '977' }, + { code: 'NR', label: 'Nauru', phone: '674' }, + { code: 'NU', label: 'Niue', phone: '683' }, + { code: 'NZ', label: 'New Zealand', phone: '64' }, + { code: 'OM', label: 'Oman', phone: '968' }, + { code: 'PA', label: 'Panama', phone: '507' }, + { code: 'PE', label: 'Peru', phone: '51' }, + { code: 'PF', label: 'French Polynesia', phone: '689' }, + { code: 'PG', label: 'Papua New Guinea', phone: '675' }, + { code: 'PH', label: 'Philippines', phone: '63' }, + { code: 'PK', label: 'Pakistan', phone: '92' }, + { code: 'PL', label: 'Poland', phone: '48' }, + { + code: 'PM', + label: 'Saint Pierre and Miquelon', + phone: '508', + }, + { code: 'PN', label: 'Pitcairn', phone: '870' }, + { code: 'PR', label: 'Puerto Rico', phone: '1' }, + { + code: 'PS', + label: 'Palestine, State of', + phone: '970', + }, + { code: 'PT', label: 'Portugal', phone: '351' }, + { code: 'PW', label: 'Palau', phone: '680' }, + { code: 'PY', label: 'Paraguay', phone: '595' }, + { code: 'QA', label: 'Qatar', phone: '974' }, + { code: 'RE', label: 'Reunion', phone: '262' }, + { code: 'RO', label: 'Romania', phone: '40' }, + { code: 'RS', label: 'Serbia', phone: '381' }, + { code: 'RU', label: 'Russian Federation', phone: '7' }, + { code: 'RW', label: 'Rwanda', phone: '250' }, + { code: 'SA', label: 'Saudi Arabia', phone: '966' }, + { code: 'SB', label: 'Solomon Islands', phone: '677' }, + { code: 'SC', label: 'Seychelles', phone: '248' }, + { code: 'SD', label: 'Sudan', phone: '249' }, + { code: 'SE', label: 'Sweden', phone: '46' }, + { code: 'SG', label: 'Singapore', phone: '65' }, + { code: 'SH', label: 'Saint Helena', phone: '290' }, + { code: 'SI', label: 'Slovenia', phone: '386' }, + { + code: 'SJ', + label: 'Svalbard and Jan Mayen', + phone: '47', + }, + { code: 'SK', label: 'Slovakia', phone: '421' }, + { code: 'SL', label: 'Sierra Leone', phone: '232' }, + { code: 'SM', label: 'San Marino', phone: '378' }, + { code: 'SN', label: 'Senegal', phone: '221' }, + { code: 'SO', label: 'Somalia', phone: '252' }, + { code: 'SR', label: 'Suriname', phone: '597' }, + { code: 'SS', label: 'South Sudan', phone: '211' }, + { + code: 'ST', + label: 'Sao Tome and Principe', + phone: '239', + }, + { code: 'SV', label: 'El Salvador', phone: '503' }, + { + code: 'SX', + label: 'Sint Maarten (Dutch part)', + phone: '1-721', + }, + { + code: 'SY', + label: 'Syrian Arab Republic', + phone: '963', + }, + { code: 'SZ', label: 'Swaziland', phone: '268' }, + { + code: 'TC', + label: 'Turks and Caicos Islands', + phone: '1-649', + }, + { code: 'TD', label: 'Chad', phone: '235' }, + { + code: 'TF', + label: 'French Southern Territories', + phone: '262', + }, + { code: 'TG', label: 'Togo', phone: '228' }, + { code: 'TH', label: 'Thailand', phone: '66' }, + { code: 'TJ', label: 'Tajikistan', phone: '992' }, + { code: 'TK', label: 'Tokelau', phone: '690' }, + { code: 'TL', label: 'Timor-Leste', phone: '670' }, + { code: 'TM', label: 'Turkmenistan', phone: '993' }, + { code: 'TN', label: 'Tunisia', phone: '216' }, + { code: 'TO', label: 'Tonga', phone: '676' }, + { code: 'TR', label: 'Turkey', phone: '90' }, + { + code: 'TT', + label: 'Trinidad and Tobago', + phone: '1-868', + }, + { code: 'TV', label: 'Tuvalu', phone: '688' }, + { + code: 'TW', + label: 'Taiwan, Province of China', + phone: '886', + }, + { + code: 'TZ', + label: 'United Republic of Tanzania', + phone: '255', + }, + { code: 'UA', label: 'Ukraine', phone: '380' }, + { code: 'UG', label: 'Uganda', phone: '256' }, + { + code: 'US', + label: 'United States', + phone: '1', + suggested: true, + }, + { code: 'UY', label: 'Uruguay', phone: '598' }, + { code: 'UZ', label: 'Uzbekistan', phone: '998' }, + { + code: 'VA', + label: 'Holy See (Vatican City State)', + phone: '379', + }, + { + code: 'VC', + label: 'Saint Vincent and the Grenadines', + phone: '1-784', + }, + { code: 'VE', label: 'Venezuela', phone: '58' }, + { + code: 'VG', + label: 'British Virgin Islands', + phone: '1-284', + }, + { + code: 'VI', + label: 'US Virgin Islands', + phone: '1-340', + }, + { code: 'VN', label: 'Vietnam', phone: '84' }, + { code: 'VU', label: 'Vanuatu', phone: '678' }, + { code: 'WF', label: 'Wallis and Futuna', phone: '681' }, + { code: 'WS', label: 'Samoa', phone: '685' }, + { code: 'XK', label: 'Kosovo', phone: '383' }, + { code: 'YE', label: 'Yemen', phone: '967' }, + { code: 'YT', label: 'Mayotte', phone: '262' }, + { code: 'ZA', label: 'South Africa', phone: '27' }, + { code: 'ZM', label: 'Zambia', phone: '260' }, + { code: 'ZW', label: 'Zimbabwe', phone: '263' }, +]; + +export default countries diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx new file mode 100644 index 00000000..66d852c5 --- /dev/null +++ b/frontend/src/components/CreatorGrid.jsx @@ -0,0 +1,306 @@ +import React, { useEffect, useState } from 'react'; + +import ReactGA from 'react-ga'; +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 } from 'react-instantsearch-dom'; +import { + Grid, + Paper, + TextField, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip, + Card, + Box, + CardContent, + IconButton, + Zoom, + CardMedia, + CardActionArea, +} from '@material-ui/core'; + +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(""); + 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 isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + const innerColor = "rgba(255,255,255,0.65)" + const borderRadius = 3 + window.title = "Shuffle | Workflows | Discover your use-case" + + 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} ) => { + 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) + } + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='off' + type="search" + color="primary" + value={currentRefinement} + placeholder="Find Creators..." + id="shuffle_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + const paperAppContainer = { + display: "flex", + flexWrap: "wrap", + alignContent: "space-between", + marginTop: 5, + } + + const Hits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + return ( + + {hits.map((data, index) => { + if (counted === 12/xs*rowHandler) { + return null + } + + counted += 1 + const creatorUrl = !isCloud ? `https://shuffler.io/creators/${data.username}` : `/creators/${data.username}` + + return ( + + + + + + +
+ {"Creator + + @{data.username} + + + {data.verified === true ? + + + + : + null + } + +
+ + {data.apps === undefined || data.apps === null ? 0 : data.apps} apps {data.workflows === null || data.workflows === undefined ? 0 : data.workflows} workflows + + {data.specialized_apps !== undefined && data.specialized_apps !== null && data.specialized_apps.length > 0 ? + + {data.specialized_apps.map((app, index) => { + // Putting all this in secondary of ListItemText looked weird. + return ( +
{ + console.log("Click") + //navigate("/apps/"+app.id) + }} + > + + + +
+ ) + })} +
+ : + null} +
+
+
+
+
+
+ ) + })} +
+ ) + } + + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(Hits) + + return ( +
+ + +
+ +
+ +
+ {showSuggestion === true ? +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + {formMessage} +
+ : null + } +
+ ) +} + +export default CreatorGrid; diff --git a/frontend/src/components/DocsGrid.jsx b/frontend/src/components/DocsGrid.jsx new file mode 100644 index 00000000..706c861a --- /dev/null +++ b/frontend/src/components/DocsGrid.jsx @@ -0,0 +1,356 @@ +import React, {useEffect, useState} from 'react'; + +import ReactGA from 'react-ga'; +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 aa from 'search-insights' + +import algoliasearch from 'algoliasearch/lite'; +import { InstantSearch, Configure, connectSearchBox, connectHits } from 'react-instantsearch-dom'; +import { + Zoom, + Grid, + Paper, + TextField, + Avatar, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip, + List, + ListItem, + ListItemAvatar, + ListItemText, +} from '@material-ui/core'; + +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(""); + 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 = params["q"] + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery) + refine(foundQuery) + } + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='off' + type="search" + color="primary" + defaultValue={currentRefinement} + placeholder="Search our Documentation..." + id="shuffle_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + var workflowDelay = -50 + const Hits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + //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) + //} + + var counted = 0 + return ( + + {hits.map((data, index) => { + workflowDelay += 50 + + const innerlistitemStyle = { + width: "100%", + 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%", + } + + if (counted >= 12/xs*rowHandler) { + return null + } + + counted += 1 + + var name = data.name === undefined ? + data.filename.charAt(0).toUpperCase() + data.filename.slice(1).replaceAll("_", " ") + " - " + data.title : + (data.name.charAt(0).toUpperCase()+data.name.slice(1)).replaceAll("_", " ") + + if (name.length > 96) { + name = name.slice(0, 96)+"..." + } + + //const secondaryText = data.data !== undefined ? data.data.slice(0, 100)+"..." : "" + const secondaryText = data.data !== undefined ? data.data.slice(0, 100)+"..." : "" + const baseImage = + const avatar = data.image_url === undefined ? + baseImage + : + + + var parsedUrl = data.urlpath !== undefined ? data.urlpath : "" + parsedUrl += `?queryID=${data.__queryID}` + + return ( + + { + 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 Appgrid', + index: 'documentation', + objectIDs: [data.objectID], + timestamp: timestamp, + queryID: data.__queryID, + positions: [data.__position], + userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, + } + ]) + + console.log("CLICK") + }}> + { + setMouseHoverIndex(index) + }}> + + {avatar} + + + {/* + + + + + + */} + + + + ) + })} + + ) + } + + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(Hits) + const selectButtonStyle = { + minWidth: 150, + maxWidth: 150, + minHeight: 50, + } + + return ( +
+ {/* +
+ +
+ */} +
+ +
+ +
+ + +
+ {showSuggestion === true ? +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + {formMessage} +
+ : null + } + + + + Search by + + + Algolia logo + + +
+
+ ) +} + +export default DocsGrid; diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx new file mode 100644 index 00000000..57852c5d --- /dev/null +++ b/frontend/src/components/EditWorkflow.jsx @@ -0,0 +1,490 @@ +import React, { useEffect, useContext } from "react"; +import theme from '../theme'; +import { isMobile } from "react-device-detect" +import ChipInput from "material-ui-chip-input"; +import UsecaseSearch from "../components/UsecaseSearch.jsx" + +import { + Badge, + Avatar, + Grid, + InputLabel, + Select, + ListSubheader, + Paper, + Tooltip, + Divider, + Button, + TextField, + IconButton, + Menu, + MenuItem, + FormControlLabel, + Chip, + Switch, + Typography, + Zoom, + CircularProgress, + Dialog, + DialogTitle, + DialogActions, + DialogContent, + OutlinedInput, + Checkbox, + ListItemText, + Radio, + RadioGroup, + FormControl, + FormLabel, + +} from "@material-ui/core"; + +import { + ExpandLess as ExpandLessIcon, + ExpandMore as ExpandMoreIcon, + Publish as PublishIcon, +} from "@material-ui/icons"; + +const EditWorkflow = (props) => { + const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, } = props + + 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 [newWorkflowTags, setNewWorkflowTags] = React.useState(workflow.tags !== undefined && workflow.tags !== null ? JSON.parse(JSON.stringify(workflow.tags)) : []) + 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 : "") + + + // Gets the generated workflow + const getGeneratedWorkflow = (workflow_id) => { + fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 when getting workflow"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.id === workflow_id) { + console.log("GOT WORKFLOW: ", responseJson) + if (name === "") { + innerWorkflow.name = responseJson.name + setName(responseJson.name) + } + + if (description === "") { + innerWorkflow.description = responseJson.description + setDescription(description) + } + + if (newWorkflowTags === []) { + innerWorkflow.tags = responseJson.tags + setNewWorkflowTags(responseJson.tags) + } + + if (selectedUsecases === []) { + selectedUsecases = responseJson.usecase_ids + } + + innerWorkflow.id = responseJson.id + innerWorkflow.blogpost = responseJson.blogpost + innerWorkflow.actions = responseJson.actions + innerWorkflow.triggers = responseJson.triggers + innerWorkflow.branches = responseJson.branches + innerWorkflow.comments = responseJson.comments + innerWorkflow.workflow_variables = responseJson.workflow_variables + innerWorkflow.execution_variables = responseJson.execution_variables + + + setInnerWorkflow(innerWorkflow) + setUpdate(Math.random()) + } + }) + .catch((error) => { + //alert.error(error.toString()); + console.log("Get workflow error: ", error.toString()); + }) + } + + if (foundWorkflowId.length > 0) { + getGeneratedWorkflow(foundWorkflowId) + + setFoundWorkflowId("") + } else { + } + + if (modalOpen !== true) { + return null + } + + const newWorkflow = isEditing === true ? false : true + + var upload = ""; + var total_count = 0 + + return ( + { + setModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, + maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, + minHeight: 400, + }, + }} + > + +
+
+ + {newWorkflow ? "New" : "Editing"} workflow + + + Workflows can be built from scratch, or from templates. Usecases can help you discover next steps, and you can search for them directly. Learn more + + {showUpload === true ? +
+ + + +
+ : null} +
+ {newWorkflow === true ? +
+ + Use a Template + + + Start your workflow from our templating system. This uses publied workflows from our Creators to generate full Usecases or parts of your Workflow. + +
+ : null} +
+
+ + +
+ { + setName(event.target.value) + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + placeholder="Name" + required + margin="dense" + defaultValue={innerWorkflow.name} + label="Name" + autoFocus + fullWidth + /> + { + setDescription(event.target.value) + }} + InputProps={{ + style: { + color: "white", + }, + }} + maxRows={4} + color="primary" + defaultValue={innerWorkflow.description} + placeholder="Description" + multiline + label="Description" + margin="dense" + fullWidth + /> +
+ { + newWorkflowTags.push(chip); + setNewWorkflowTags(newWorkflowTags); + }} + onDelete={(chip, index) => { + newWorkflowTags.splice(index, 1); + setNewWorkflowTags(newWorkflowTags); + }} + /> + {usecases !== null && usecases !== undefined && usecases.length > 0 ? + + Usecases + + + : null} +
+ + {showMoreClicked === true ? + + + + Status + { + console.log("Data: ", e.target.value) + + innerWorkflow.workflow_type = e.target.value + setInnerWorkflow(innerWorkflow) + }} + > + } label="Test" /> + } label="Production" /> + + + +
+ + + Type + { + console.log("Data: ", e.target.value) + + innerWorkflow.workflow_type = e.target.value + setInnerWorkflow(innerWorkflow) + }} + > + } label="Trigger" /> + } label="Subflow" /> + } label="Standalone" /> + + + + + + { + innerWorkflow.blogpost = event.target.value + setInnerWorkflow(innerWorkflow) + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={innerWorkflow.blogpost} + placeholder="A blogpost or other reference for how this work workflow was built, and what it's for." + rows="1" + label="blogpost" + margin="dense" + fullWidth + /> + { + innerWorkflow.video = event.target.value + setInnerWorkflow(innerWorkflow) + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={innerWorkflow.video} + placeholder="A youtube or loom link to the video" + rows="1" + label="Video" + margin="dense" + fullWidth + /> + { + innerWorkflow.default_return_value = event.target.value + setInnerWorkflow(innerWorkflow) + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={innerWorkflow.default_return_value} + placeholder="Default return value (used for Subflows if the subflow fails)" + rows="3" + multiline + label="Default return value" + margin="dense" + fullWidth + /> + + : null} + + { + setShowMoreClicked(!showMoreClicked); + }} + > + {showMoreClicked ? : } + + +
+ {newWorkflow === true ? +
+ +
+ : null} + + + + + + +
+ ) +} + +export default EditWorkflow; diff --git a/frontend/src/components/ExtraApps.jsx b/frontend/src/components/ExtraApps.jsx new file mode 100644 index 00000000..c854a7ba --- /dev/null +++ b/frontend/src/components/ExtraApps.jsx @@ -0,0 +1,27 @@ +// Move this to the backend to be loaded in? +const extraApps = [{ + "name": "Cases", + "description": "Allows use of other Case Management apps without knowing how to use them.", + "app_version": "1.0.0", + "app_name": "Cases", + "type": "ACTION", + "large_image": encodeURI('data:image/svg+xml;utf-8,'), + "template": true, + "actions": [{ + "name": "Create Alert", + "description": "Create a ticket", + "parameters": [{ + "name": "id", + }, + { + "name": "name", + }, + { + "name": "description", + "multiline": true, + }, + ], + }], +}] + +export default extraApps diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 7a7ae609..6aee8626 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -39,6 +39,7 @@ import { } 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"; @@ -179,6 +180,8 @@ const Header = (props) => { org_id: orgId, }; + localStorage.setItem("getting_started_sidebar", "open"); + fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { mode: "cors", method: "POST", @@ -506,7 +509,7 @@ const Header = (props) => { //
const loginTextBrowser = !isLoggedIn ? (
- +
{ -
+ {!isLoaded ? null : + userdata.chat_disabled === true ? null : +
+ +
+ } +
{
) : (
-
+
- +
{
- +
{ */} - +
{ */}
+ {!isLoaded ? null : + userdata.chat_disabled === true ? null : +
+ +
+ }
{avatarMenu} {notificationMenu} @@ -710,7 +725,6 @@ const Header = (props) => { userdata.orgs.splice(foundIndex+1, 1) } else { console.log("ORG NOT FOUND IN LIST: ", childorg) - } // This is stupid :) diff --git a/frontend/src/components/LandingpageUsecases.jsx b/frontend/src/components/LandingpageUsecases.jsx index 2f4e2478..49a4f332 100644 --- a/frontend/src/components/LandingpageUsecases.jsx +++ b/frontend/src/components/LandingpageUsecases.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import {isMobile} from "react-device-detect"; -import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx"; +import AppFramework, { usecases } from "../components/AppFramework.jsx"; import {Link} from 'react-router-dom'; import ReactGA from 'react-ga'; @@ -169,7 +169,7 @@ const LandingpageUsecases = (props) => {
{isMobile ? null :
- +
} {isMobile ? null : diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 77fc31f5..fcb03f23 100644 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -1,5 +1,6 @@ import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; import { useTheme } from "@material-ui/core/styles"; +import theme from '../theme'; import { v4 as uuidv4 } from "uuid"; import { @@ -36,7 +37,10 @@ import { Switch, Fade, } from "@material-ui/core"; -import { LockOpen as LockOpenIcon } from "@material-ui/icons"; +import { + LockOpen as LockOpenIcon, + SupervisorAccount as SupervisorAccountIcon, +} from "@mui/icons-material"; const ITEM_HEIGHT = 55; const ITEM_PADDING_TOP = 8; @@ -53,6 +57,20 @@ const MenuProps = { getContentAnchorEl: null, }; +const registeredApps = [ + "gmail", + "slack", + "webex", + "zoho_desk", + "outlook_graph", + "outlook_office365", + "microsoft_teams", + "microsoft_teams_user_access", + "todoist", + "microsoft_sentinel", + "microsoft_365_defender", +] + const AuthenticationOauth2 = (props) => { const { saveWorkflow, @@ -65,8 +83,9 @@ const AuthenticationOauth2 = (props) => { setSelectedAction, setNewAppAuth, setAuthenticationModalOpen, + isCloud, + autoAuth, } = props; - const theme = useTheme(); //const [update, setUpdate] = React.useState("|") const [defaultConfigSet, setDefaultConfigSet] = React.useState( @@ -76,7 +95,8 @@ const AuthenticationOauth2 = (props) => { authenticationType.client_secret !== undefined && authenticationType.client_secret !== null && authenticationType.client_secret.length > 0 - ); + ); + const [clientId, setClientId] = React.useState( defaultConfigSet ? authenticationType.client_id : "" ); @@ -85,11 +105,13 @@ const AuthenticationOauth2 = (props) => { ); const [oauthUrl, setOauthUrl] = React.useState(""); const [buttonClicked, setButtonClicked] = React.useState(false); - const [selectedScopes, setSelectedScopes] = React.useState([]); + const [offlineAccess, setOfflineAccess] = React.useState(true); const allscopes = authenticationType.scope !== undefined ? authenticationType.scope : []; + console.log("ALLSCOPES: ", allscopes) + const [selectedScopes, setSelectedScopes] = React.useState(allscopes.length === 1 ? [allscopes[0]] : []) const [manuallyConfigure, setManuallyConfigure] = React.useState( defaultConfigSet ? false : true ); @@ -106,11 +128,103 @@ const AuthenticationOauth2 = (props) => { active: true, }); + useEffect(() => { + console.log("Should automatically click the auto-auth button?") + if (autoAuth === true && selectedApp !== undefined) { + startOauth2Request() + } + }, []) + if (selectedApp.authentication === undefined) { return null; } - const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => { + const startOauth2Request = (admin_consent) => { + console.log("APP: ", selectedApp) + if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") { + handleOauth2Request( + + "efe4c3fe-84a1-4821-a84f-23a6cfe8e72d", + "", + "https://graph.microsoft.com", + ["Mail.ReadWrite"], + admin_consent, + ); + } else if (selectedApp.name.toLowerCase() == "gmail") { + handleOauth2Request( + "253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com", + "", + "https://gmail.googleapis.com", + ["https://www.googleapis.com/auth/gmail.modify", + "https://www.googleapis.com/auth/gmail.send", + "https://www.googleapis.com/auth/gmail.insert", + "https://www.googleapis.com/auth/gmail.compose"], + admin_consent, + ) + } else if (selectedApp.name.toLowerCase() == "zoho_desk") { + handleOauth2Request( + "1000.ZR5MHUW6B0L6W1VUENFGIATFS0TOJT", + "", + "https://desk.zoho.com", + ["Desk.tickets.READ", + "Desk.tickets.UPDATE", + "Desk.tickets.DELETE", + "Desk.tickets.CREATE"], + admin_consent, + ) + } else if (selectedApp.name.toLowerCase() == "slack") { + handleOauth2Request( + "151779186901.2448678750935", + "", + "https://slack.com", + ["admin", "chat:write", "im:read", "im:write", "search:read", "usergroups:read", "usergroups:write"], + admin_consent, + ) + } else if (selectedApp.name.toLowerCase() == "webex") { + handleOauth2Request( + "Cab184f3d7271f540443c79b5b79845e3387abbbdb3db4233a87ea3a5432fb3d5", + "", + "https://webexapis.com", + ["spark:all"], + admin_consent, + ) + } else if (selectedApp.name.toLowerCase().includes("microsoft_teams")) { + handleOauth2Request( + "31cb4c84-658e-43d5-ae84-22c9142e967a", + "", + "https://graph.microsoft.com", + ["ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "Chat.Create", "Chat.ReadWrite", "Chat.Read"], + admin_consent, + ) + } else if (selectedApp.name.toLowerCase().includes("todoist")) { + handleOauth2Request( + "35fa3a384040470db0c8527e90a3c2eb", + "", + "https://api.todoist.com", + ["task:add"], + admin_consent, + ) + } else if (selectedApp.name.toLowerCase().includes("microsoft_sentinel")) { + handleOauth2Request( + "4c16e8c4-3d34-4aa1-ac94-262ea170b7f7", + "", + "https://management.azure.com", + ["https://management.azure.com/user_impersonation"], + admin_consent, + ) + } else if (selectedApp.name.toLowerCase().includes("microsoft_365_defender")) { + handleOauth2Request( + "4c16e8c4-3d34-4aa1-ac94-262ea170b7f7", + "", + "https://graph.microsoft.com", + ["SecurityEvents.ReadWrite.All"], + admin_consent, + ) + } + } + + + const handleOauth2Request = (client_id, client_secret, oauth_url, scopes, admin_consent) => { setButtonClicked(true); console.log("SCOPES: ", scopes); @@ -151,12 +265,21 @@ const AuthenticationOauth2 = (props) => { state += `%26refresh_uri%3d${authentication_url}`; } - const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; + // No prompt forcing + var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`; + if (admin_consent === true) { + console.log("Running Oauth2 WITH admin consent") + //url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`; + url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`; + } + // Force new consent + //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; + + // Admin consent //const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent` - //console.log("Full URI: ", url) - //console.log("Redirect Uri: ", redirectUri) - // &resource=https%3A%2F%2Fgraph.microsoft.com& + + // &resource=https%3A%2F%2Fgraph.microsoft.com& // FIXME: Awful, but works for prototyping // How can we get a callback properly realtime? @@ -168,12 +291,20 @@ const AuthenticationOauth2 = (props) => { var open = true; const timer = setInterval(() => { if (newwin.closed) { + console.log("Closing?") + + if (setAuthenticationModalOpen !== undefined) { + setAuthenticationModalOpen(false) + } + setButtonClicked(false); clearInterval(timer); //alert('"Secure Payment" window closed!'); getAppAuthentication(true, true); - } + } else { + console.log("Not closed") + } }, 1000); //do { // setTimeout(() => { @@ -337,6 +468,95 @@ const AuthenticationOauth2 = (props) => {
+ + {isCloud && registeredApps.includes(selectedApp.name.toLowerCase()) ? + + + + {buttonClicked ? + null + : + + + + } + + + OR + + + : null} {/* { {buttonClicked ? ( ) : ( - "Oauth2 request" + "Manually Authenticate" )} + + {defaultConfigSet ? ( ... or diff --git a/frontend/src/components/OrgHeader.jsx b/frontend/src/components/OrgHeader.jsx index a370af93..881c303b 100644 --- a/frontend/src/components/OrgHeader.jsx +++ b/frontend/src/components/OrgHeader.jsx @@ -97,6 +97,15 @@ const OrgHeader = (props) => { ? "" : selectedOrganization.defaults.notification_workflow ); + + const [documentationReference, setDocumentationReference] = React.useState( + selectedOrganization.defaults === undefined + ? "" + : selectedOrganization.defaults.documentation_reference === undefined || + selectedOrganization.defaults.documentation_reference.length === 0 + ? "" + : selectedOrganization.defaults.documentation_reference + ); const [openidClientId, setOpenidClientId] = React.useState( selectedOrganization.sso_config === undefined ? "" @@ -105,6 +114,14 @@ const OrgHeader = (props) => { ? "" : selectedOrganization.sso_config.client_id ); + const [openidClientSecret, setOpenidClientSecret] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.client_secret === undefined || + selectedOrganization.sso_config.client_secret.length === 0 + ? "" + : selectedOrganization.sso_config.client_secret + ); const [openidAuthorization, setOpenidAuthorization] = React.useState( selectedOrganization.sso_config === undefined ? "" @@ -237,11 +254,13 @@ const OrgHeader = (props) => { workflow_download_repo: workflowDownloadUrl, workflow_download_branch: workflowDownloadBranch, notification_workflow: notificationWorkflow, + documentation_reference: documentationReference, }, { sso_entrypoint: ssoEntrypoint, sso_certificate: ssoCertificate, client_id: openidClientId, + client_secret: openidClientSecret, openid_authorization: openidAuthorization, openid_token: openidToken, } @@ -439,8 +458,279 @@ const OrgHeader = (props) => { }} /> + + + + Org Documentation reference + { + setDocumentationReference(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + - {isCloud ? null : ( + {isCloud ? null : + + OpenID connect + + + + Client ID + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The OpenID client ID from the identity provider" + value={openidClientId} + onChange={(e) => { + setOpenidClientId(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Client Secret (optional) + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE" + value={openidClientSecret} + onChange={(e) => { + setOpenidClientSecret(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + + + Authorization URL + { + setOpenidAuthorization(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Token URL + { + setOpenidToken(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + } + {/*isCloud ? null : */} + + SAML SSO (v1.1) + + + + SSO Entrypoint (IdP) + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The entrypoint URL from your provider" + value={ssoEntrypoint} + onChange={(e) => { + setSsoEntrypoint(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + SSO Certificate (X509) + { + setSsoCertificate(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + {isCloud ? + + IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso + + : null} + + {isCloud ? null : ( App Download URL @@ -576,200 +866,10 @@ const OrgHeader = (props) => { )} - {isCloud ? null : - - OpenID connect - - - - Client ID - 0 - } - id="outlined-with-placeholder" - margin="normal" - variant="outlined" - placeholder="The OpenID client ID from the identity provider" - value={openidClientId} - onChange={(e) => { - setOpenidClientId(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - Authorization URL - { - setOpenidAuthorization(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - Token URL - { - setOpenidToken(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - } - {isCloud ? null : - - SAML SSO (v1.1) - - - - SSO Entrypoint (IdP) - 0 - } - id="outlined-with-placeholder" - margin="normal" - variant="outlined" - placeholder="The entrypoint URL from your provider" - value={ssoEntrypoint} - onChange={(e) => { - setSsoEntrypoint(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - SSO Certificate (X509) - { - setSsoCertificate(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - } + +
+ {orgSaveButton} +
{/* {expanded ? diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index ed1481ed..455c0e57 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -8,7 +8,6 @@ import { useTheme } from "@material-ui/core/styles"; import NestedMenuItem from "material-ui-nested-menu-item"; import { useAlert } from "react-alert"; import theme from '../theme'; -//import NestedMenuItem from "./NestedMenu.jsx"; import { ButtonGroup, @@ -83,14 +82,15 @@ import { ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon, AutoFixHigh as AutoFixHighIcon, + SquareFoot as SquareFootIcon, } from '@mui/icons-material'; //} from "@material-ui/icons"; import Autocomplete from "@material-ui/lab/Autocomplete"; -import CodeMirror from "@uiw/react-codemirror"; -import "codemirror/keymap/sublime"; -import "codemirror/theme/gruvbox-dark.css"; +//import CodeMirror from "@uiw/react-codemirror"; +//import "codemirror/keymap/sublime"; +//import "codemirror/theme/gruvbox-dark.css"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx"; const useStyles = makeStyles({ @@ -118,17 +118,12 @@ const useStyles = makeStyles({ }, }, }); -//const useStyles = makeStyles((theme) => -// createStyles({ -// notchedOutline: { -// borderColor: "#f85a3e !important" -// }, -//) - + const openApiFieldDesc = "Generated by OpenAPI body example"; const ParsedAction = (props) => { const { workflow, + files, setWorkflow, setAction, setSelectedAction, @@ -167,6 +162,9 @@ const ParsedAction = (props) => { isCloud, lastSaved, setLastSaved, + setShowVideo, + //expansionModalOpen, + //setExpansionModalOpen, } = props; //const theme = useTheme(); @@ -181,6 +179,13 @@ const ParsedAction = (props) => { const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); + + useEffect(() => { + if (setLastSaved !== undefined) { + setLastSaved(false) + } + }, [expansionModalOpen]) + useEffect(() => { if (selectedAction.parameters !== null && selectedAction.parameters !== undefined) { const paramcheck = selectedAction.parameters.find(param => param.name === "body") @@ -439,7 +444,8 @@ const ParsedAction = (props) => { highlight: "shuffle_cache", autocomplete: "shuffle_cache", example: "", - }); + }) + if ( workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && @@ -480,6 +486,7 @@ const ParsedAction = (props) => { // Loops parent nodes' old results to fix autocomplete if (getParents !== undefined) { var parents = getParents(selectedAction); + if (parents.length > 1) { for (var key in parents) { const item = parents[key]; @@ -514,8 +521,12 @@ const ParsedAction = (props) => { const valid = validateJson(foundResult) if (valid.valid) { - exampledata = valid.result; - break; + if (valid.result.success === false) { + //console.log("Skipping success false autocomplete") + } else { + exampledata = valid.result; + break; + } } else { exampledata = foundResult; } @@ -535,6 +546,7 @@ const ParsedAction = (props) => { autocomplete: itemlabelComplete, example: exampledata, }; + actionlist.push(actionvalue); } } @@ -549,7 +561,20 @@ const ParsedAction = (props) => { const calculateHelpertext = (input_data) => { var helperText = "" var looperText = "" - const found = input_data.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) + //const found = input_data.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) + var found = input_data.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) + + if (found !== null && found !== undefined) { + var new_occurences = [] + for (var key in found) { + if (found[key][0] !== "\\") { + new_occurences.push(found[key]) + } + } + + console.log("New found: ", new_occurences) + found = new_occurences.valueOf() + } if (found !== null) { try { @@ -559,7 +584,7 @@ const ParsedAction = (props) => { if ((variableSplit.length-1) > 1) { //console.log("Larger than 1: ", variableSplit) if (looperText.length === 0) { - looperText += "PS: Double looping (.#) may cause problems." + looperText += "PS: Double looping (.#.#) may cause problems." } } @@ -607,10 +632,12 @@ const ParsedAction = (props) => { ); if (paramcheck !== undefined) { // Escapes all double quotes - const toReplace = event.target.value - .trim() - .replaceAll('\\"', '"') - .replaceAll('"', '\\"'); + var toReplace = event.target.value.trim() + + + if (!toReplace.startsWith("{") && !toReplace.startsWith("[")) { + toReplace = toReplace.replaceAll('\\"', '"').replaceAll('"', '\\"') + } console.log("REPLACE WITH: ", toReplace); if ( @@ -761,10 +788,16 @@ const ParsedAction = (props) => { } //console.log("CHANGING ACTION COUNT !") + selectedActionParameters[count].autocompleted = false + selectedAction.parameters[count].autocompleted = false selectedActionParameters[count].value = event.target.value; selectedAction.parameters[count].value = event.target.value; var forceUpdate = false + if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { + console.log("APIKEY - this shouldn't show up!") + } + if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && data.name === "input_list") { //console.log("FILTER LIST!: ", event, count, data) const parsedvalue = event.target.value @@ -929,6 +962,8 @@ const ParsedAction = (props) => { } } + selectedActionParameters[count].autocompleted = false + selectedAction.parameters[count].autocompleted = false selectedActionParameters[count].value = data selectedAction.parameters[count].value = data setSelectedAction(selectedAction) @@ -1042,6 +1077,8 @@ const ParsedAction = (props) => { */ } + //console.log("APP: ", selectedApp) + // FIXME: Issue #40 - selectedActionParameters not reset if ( Object.getOwnPropertyNames(selectedAction).length > 0 && @@ -1068,6 +1105,114 @@ const ParsedAction = (props) => { Parameters + {selectedAction.template === true && selectedAction.matching_actions !== undefined && selectedAction.matching_actions !== null && selectedAction.matching_actions.length > 0 ? +
+ + Select an app you want to use + + { + console.log("LABEL: ", option) + if ( + option === undefined || + option === null || + option.app_name === undefined || + option.app_name === null + ) { + return null; + } + + const newname = ( + option.app_name.charAt(0).toUpperCase() + option.app_name.substring(1) + ).replaceAll("_", " "); + return newname; + }} + options={selectedAction.matching_actions} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + console.log("SELECT: ", event, newValue) + // Workaround with event lol + //if (newValue !== undefined && newValue !== null) { + // setNewSelectedAction({ target: { value: newValue.name } }); + //} + }} + renderOption={(data) => { + var newActionname = data.app_name; + if ( + data.label !== undefined && + data.label !== null && + data.label.length > 0 + ) { + newActionname = data.label; + } + + const iconInfo = GetIconInfo({ name: data.app_name }); + const useIcon = iconInfo.originalIcon; + + newActionname = ( + newActionname.charAt(0).toUpperCase() + + newActionname.substring(1) + ).replaceAll("_", " "); + + return ( +
+ + {useIcon} + + {newActionname} +
+ ); + }} + renderInput={(params) => { + if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { + const prefixes = ["Post", "Put", "Patch"] + for (var key in prefixes) { + if (params.inputProps.value.startsWith(prefixes[key])) { + params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) + if (params.inputProps.value.length > 1) { + params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) + } + break + } + } + } + + return ( + + ); + }} + /> +
+ : null} {selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenDescription === false ? (
{ ); } + // Added autofill to make this ALOT simpler + if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { + if (selectedActionParameters[count].length === 0) { + selectedAction.parameters[count].value = "TMP: Will be replaced during execution if cloud" + setSelectedAction(selectedAction) + } + + return null + } + var staticcolor = "inherit"; var actioncolor = "inherit"; var varcolor = "inherit"; @@ -1160,12 +1315,41 @@ const ParsedAction = (props) => { if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length === 0) { data.value = data.example; } + + // In case of data.example + if (data.value === undefined || data.value === null) { + data.value = "" + } + + if (data.value.length === 0) { + if (data.name.toLowerCase() === "headers") { + console.log("Should show headers field instead with + and -!") + + // Check if file ID exists + // + const fileFound = selectedActionParameters.find(param => param.name === "file_id") + if (fileFound === undefined || fileFound === null) { + data.value = data.example + } else { + // Purposely unset it if set by default when using files + data.value = "" + } + } + } + + /* + if (data.name !== "queries" && data.name !== "key" && data.name !== "value" ) { + data.value = data.example + } + } + */ } if (data.name.startsWith("${") && data.name.endsWith("}")) { const paramcheck = selectedAction.parameters.find( (param) => param.name === "body" ); + if (paramcheck !== undefined && paramcheck !== null) { if ( paramcheck["value_replace"] !== undefined && @@ -1211,6 +1395,7 @@ const ParsedAction = (props) => { var hideBodyButton = ""; const hideBodyButtonValue = (
{ if (currentItem.description === openApiFieldDesc) { currentItem.field_active = !hideBody; - console.log("Changing", currentItem); + //console.log("Changing", currentItem); } } }} @@ -1274,11 +1459,11 @@ const ParsedAction = (props) => { hideBodyButton = hideBodyButtonValue; if (found === null || !hideBody) { - console.log("Should hide body? ", found) - // if (found === null) { setActivateHidingBodyButton(true); - } + } else { + console.log("In found: ", found, hideBody) + } } else { //console.log("SHOW BUTTON"); @@ -1309,7 +1494,7 @@ const ParsedAction = (props) => { description: openApiFieldDesc, example: "", id: "", - multiline: false, + multiline: true, name: tmpitem, options: null, required: false, @@ -1347,6 +1532,7 @@ const ParsedAction = (props) => { setcodedata={setcodedata} expansionModalOpen={expansionModalOpen} setExpansionModalOpen={setExpansionModalOpen} + globalUrl={globalUrl} /> ) @@ -1357,6 +1543,28 @@ const ParsedAction = (props) => { if (data !== undefined && data !== null && data.value !== undefined && data.value !== null && data.value.length > 0) { baseHelperText = calculateHelpertext(data.value) } + + + var tmpitem = data.name.valueOf(); + if (data.name.startsWith("${") && data.name.endsWith("}")) { + tmpitem = tmpitem.slice(2, data.name.length - 1); + } + + if (tmpitem === "from_shuffle") { + tmpitem = "from" + } + + tmpitem = ( + tmpitem.charAt(0).toUpperCase() + tmpitem.substring(1) + ).replaceAll("_", " "); + + if (tmpitem === "Username basic") { + tmpitem = "Username" + } else if (tmpitem === "Password basic") { + tmpitem = "Password" + } + + multiline = data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline var datafield = ( { }} /> - + { @@ -1414,11 +1622,18 @@ const ParsedAction = (props) => { ), }} - multiline={multiline} + multiline={data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline} helperText={returnHelperText(data.name, data.value)} onClick={() => { + console.log("Clicked field: ", clickedFieldId, data.name) + if (data.name === "file_id") { + console.log("show file video?") + if (setShowVideo !== undefined) { + setShowVideo("https://www.youtube.com/embed/DPYowyTbsSk") + } + } + //(data.name.toLowerCase().includes("api") || /* - console.log("Clicked field: ", clickedFieldId); setExpansionModalOpen(false); if ( setScrollConfig !== undefined && @@ -1432,7 +1647,7 @@ const ParsedAction = (props) => { } */ - console.log("Clicked field: ", clickedFieldId) + //console.log("Clicked field: ", clickedFieldId) if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) { scrollConfig.selected = clickedFieldId setScrollConfig(scrollConfig) @@ -1440,7 +1655,7 @@ const ParsedAction = (props) => { } }} id={clickedFieldId} - rows={rows} + rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" defaultValue={data.value} //value={data.value} @@ -1474,13 +1689,8 @@ const ParsedAction = (props) => { > {openApiHelperText} - ) : data.name.startsWith("${") && data.name.endsWith("}") ? ( - - OpenAPI helperfield - - ) : null + ) : data.name.startsWith("${") && data.name.endsWith("}") ? + null : null } onBlur={(event) => { baseHelperText = calculateHelpertext(event.target.value) @@ -1490,6 +1700,198 @@ const ParsedAction = (props) => { }} /> ); + + // Finds headers from a string to be used for autocompletion + const findHeaders = (inputdata) => { + var splitdata = inputdata.split("\n") + + var foundnewline = false + var allValues = [] + for (var key in splitdata) { + const line = splitdata[key] + if (line === "") { + foundnewline = true + continue + } + + var splitvalue = "" + if (line.includes(":")) { + splitvalue = ":" + } + + if (line.includes("=")) { + splitvalue = "=" + } + + if (splitvalue.length === 0){ + allValues.push({ + key: line, + value: "", + }) + continue + } + + var splitKeys = line.split(splitvalue) + if (splitKeys.length > 1) { + allValues.push({ + key: splitKeys[0].trim(), + value: splitKeys[1].trim(), + }) + } else { + console.log("No keys for ", line) + } + } + + // Just add one + if (foundnewline) { + allValues.push({ + key: "", + value: "", + }) + } + + return allValues + } + + if (data.name.toLowerCase() === "headers") { + //var tmpheaders = findHeaders(data.value) + var tmpheaders = findHeaders(selectedActionParameters[count].value) + const tmpdatafield = +
+ {tmpheaders.map((inputdata, index) => { + const oldkey = inputdata.key + const oldval = inputdata.value + + return ( + +
+ { + console.log("Change from oldkey to new: ", oldkey, e.target.value) + + // Find the right line to replace! + //const newval = selectedActionParameters[count].value.replace(oldval, e.target.value, 1) + const tmpsplit = selectedActionParameters[count].value.split("\n") + var valsplit = [] + var add_empty = false + for (var key in tmpsplit) { + if (tmpsplit[key] === "") { + add_empty = true + continue + } + + valsplit.push(tmpsplit[key]) + } + + if (add_empty) { + valsplit.push("") + } + console.log("Split: ", valsplit) + + var newarr = [] + for (var key in valsplit) { + var line = valsplit[key] + + if (key == index) { + if (oldkey === "") { + if (line.includes("=") || line.includes(":")) { + newarr.push(e.target.value + line) + } else { + newarr.push(e.target.value + ": " + line) + } + } else { + newarr.push(line.replace(oldkey, e.target.value, 1)) + } + + } else { + newarr.push(line) + } + } + + var newval = newarr.join("\n") + console.log("Fixed: ", newval) + + selectedActionParameters[count].value = newval + selectedAction.parameters[count].value = newval + setSelectedAction(selectedAction) + setSelectedActionParameters(selectedActionParameters) + }} + /> + { + console.log("Change from oldval to new: ", oldval, e.target.value) + + // Find the right line to replace! + //const newval = selectedActionParameters[count].value.replace(oldval, e.target.value, 1) + var tmpsplit = selectedActionParameters[count].value.split("\n") + var valsplit = [] + var add_empty = false + for (var key in tmpsplit) { + if (tmpsplit[key] === "") { + add_empty = true + continue + } + + valsplit.push(tmpsplit[key]) + } + + if (add_empty) { + valsplit.push("") + } + console.log("Split: ", valsplit) + + var newarr = [] + for (var key in valsplit) { + var line = valsplit[key] + + if (key == index) { + if (oldval === "") { + if (line.includes("=") || line.includes(":")) { + newarr.push(line + e.target.value) + } else { + newarr.push(line + ": " + e.target.value) + } + } else { + newarr.push(line.replace(oldval, e.target.value, 1)) + } + + } else { + newarr.push(line) + } + } + + var newval = newarr.join("\n") + console.log("Fixed: ", newval) + + selectedActionParameters[count].value = newval + selectedAction.parameters[count].value = newval + setSelectedAction(selectedAction) + setSelectedActionParameters(selectedActionParameters) + }} + /> +
+
+ ) + })} + +
+ } //console.log("FIELD VALUE: ", data.value) //const regexp = new RegExp("\W+\.", "g") @@ -1509,6 +1911,14 @@ const ParsedAction = (props) => { // Basic helpertext + if (data.name.toLowerCase() === "file_category") { + //selectedActionParameters[count].options.length > 0 + console.log("FileS: ", files) + if (files.namespaces !== undefined && files.namespaces !== null && files.namespaces.length > 0) { + data.options = files.namespaces + } + } + //const keywords = ["len", "lower", "upper", "trim", "split", "length", "number", "parse", "join"] if ( selectedActionParameters[count].schema !== undefined && @@ -1573,16 +1983,22 @@ const ParsedAction = (props) => { } */ } else if ( - selectedActionParameters[count].options !== undefined && + (data.options !== undefined && + data.options !== null && + data.options.length > 0) + || + (selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && - selectedActionParameters[count].options.length > 0 + selectedActionParameters[count].options.length > 0) ) { + const parsedoptions = data.options !== undefined && data.options !== null && data.options.length > 0 ? data.options : selectedActionParameters[count].options + if (selectedActionParameters[count].value === "") { // && selectedActionParameters[count].required) { // Rofl, dirty workaround :) const e = { target: { - value: selectedActionParameters[count].options[0], + value: parsedoptions[0], }, }; @@ -1613,7 +2029,7 @@ const ParsedAction = (props) => { borderRadius: theme.palette.borderRadius, }} > - {selectedActionParameters[count].options.map( + {parsedoptions.map( (data, index) => { const split_data = data.split("||"); var viewed_data = data; @@ -1645,6 +2061,7 @@ const ParsedAction = (props) => { return null; } + // Shows nested list of nodes > their JSON lists const ActionlistWrapper = (props) => { const handleMenuClose = () => { @@ -1671,14 +2088,12 @@ const ParsedAction = (props) => { return; } - console.log("AUTOCOMPLETE1: ", values); - var toComplete = selectedActionParameters[count].value.trim() .endsWith("$") ? values[0].autocomplete : "$" + values[0].autocomplete; + toComplete = toComplete.toLowerCase().replaceAll(" ", "_"); - console.log("AUTOCOMPLETE: ", toComplete); for (var key in values) { if (key == 0 || values[key].autocomplete.length === 0) { continue; @@ -1842,114 +2257,147 @@ const ParsedAction = (props) => { } const coverColor = "#82ccc3" + //menuPosition.left -= 50 + //menuPosition.top -= 250 + //console.log("POS: ", menuPosition1) + var menuPosition1 = menuPosition + if (menuPosition1 === null) { + menuPosition1 = { + "left": 0, + "top": 0, + } + } else if (menuPosition1.top === null || menuPosition1.top === undefined) { + menuPosition1.top = 0 + } else if (menuPosition1.left === null || menuPosition1.left === undefined) { + menuPosition1.left = 0 + } + + //console.log("POS1: ", menuPosition1) + return parsedPaths.length > 0 ? ( +
{icon} {innerdata.name}
} parentMenuOpen={!!menuPosition} style={{ - backgroundColor: theme.palette.inputColor, color: "white", minWidth: 250, maxWidth: 250, - maxHeight: 650, - scrollX: "", + maxHeight: 50, + overflow: "hidden", }} - //PaperProps={{ - // style: { - // maxHeight: 400, - // width: 250, - // } - //}} onClick={() => { console.log("CLICKED: ", innerdata); console.log(innerdata.example) handleItemClick([innerdata]); }} > - { - //console.log("HOVER: ", pathdata); - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - - {innerdata.name} - - - {parsedPaths.map((pathdata, index) => { - // FIXME: Should be recursive in here - // - const icon = - pathdata.type === "value" ? ( - - ) : pathdata.type === "list" ? ( - - ) : ( - - ); - // + + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + + {innerdata.name} + + + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + // + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ); + // - console.log("Path: ", pathdata) - const indentation_count = (pathdata.name.match(/\./g) || []).length+1 - const baseIndent =
- //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 - const boxPadding = 0 - const namesplit = pathdata.name.split(".") - const newname = namesplit[namesplit.length-1] - console.log(newname) - return ( - { - //console.log("HOVER: ", pathdata); - }} - onClick={() => { - handleItemClick([innerdata, pathdata]); - }} - > - -
- {Array(indentation_count).fill().map((subdata, subindex) => { - return ( - baseIndent - ) - })} - {icon} {newname} -
-
-
- ); - })} + const indentation_count = (pathdata.name.match(/\./g) || []).length+1 + const baseIndent =
+ //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 + const boxPadding = 0 + const namesplit = pathdata.name.split(".") + const newname = namesplit[namesplit.length-1] + return ( + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
+ {Array(indentation_count).fill().map((subdata, subindex) => { + return ( + baseIndent + ) + })} + {icon} {newname} + {pathdata.type === "list" ? { + e.preventDefault() + e.stopPropagation() + + console.log("INNER: ", innerdata, pathdata) + + // Removing .list from autocomplete + var newname = pathdata.name + if (newname.length > 5) { + newname = newname.slice(0, newname.length-5) + } + selectedActionParameters[count].value += `{{ $${innerdata.name}.${newname} | size }}` + selectedAction.parameters[count].value = selectedActionParameters[count].value; + setSelectedAction(selectedAction); + setUpdate(Math.random()); + setShowDropdown(false); + setMenuPosition(null); + + // innerdata.name + // pathdata.name + //handleItemClick([innerdata, newpathdata]) + //console.log("CLICK LENGTH!") + }} /> : null} +
+
+
+ ); + })} + ) : ( { color: "white", minWidth: 250, maxWidth: 250, - marginRight: 250, + marginRight: 0, }} value={innerdata} onMouseOver={() => handleMouseover()} @@ -1985,22 +2433,6 @@ const ParsedAction = (props) => { ); }; - - var tmpitem = data.name.valueOf(); - if (data.name.startsWith("${") && data.name.endsWith("}")) { - tmpitem = tmpitem.slice(2, data.name.length - 1); - } - - tmpitem = ( - tmpitem.charAt(0).toUpperCase() + tmpitem.substring(1) - ).replaceAll("_", " "); - - if (tmpitem === "Username basic") { - tmpitem = "Username" - } else if (tmpitem === "Password basic") { - tmpitem = "Password" - } - const description = data.description === undefined ? "" : data.description; @@ -2028,6 +2460,11 @@ const ParsedAction = (props) => { { /*
*/ } + + //console.log(data.configuration) + + const buttonTitle = `Authenticate ${selectedApp.name.replaceAll("_", " ")}` + const hasAutocomplete = data.autocompleted === true return (
{hideBodyButton} @@ -2036,7 +2473,7 @@ const ParsedAction = (props) => { > {data.configuration === true ? ( { ) : null} + {hasAutocomplete === true ? + + + + : + null} +
{ //} //console.log("env: ", selectedActionEnvironment) - const baselabel = selectedAction.label; + var baselabel = selectedAction.label; return (
@@ -2262,7 +2713,7 @@ const ParsedAction = (props) => { selectedAction.app_name.substring(1) ).replaceAll("_", " ")}

-
+
{ var foundResult = workflowExecutions[key].results.find( (result) => result.action.id === selectedAction.id - ); + ) + if (foundResult === undefined || foundResult === null) { continue; } - setSelectedResult(foundResult); + const oldstartnode = cy.getElementById(selectedAction.id); + console.log("FOUND NODe: ", oldstartnode) + if (oldstartnode !== undefined && oldstartnode !== null) { + const foundname = oldstartnode.data("label") + if (foundname !== undefined && foundname !== null) { + foundResult.action.label = foundname + } + } + setSelectedResult(foundResult); if (setCodeModalOpen !== undefined) { setCodeModalOpen(true); } + break; } } @@ -2306,7 +2767,7 @@ const ParsedAction = (props) => { title="See previous results for this action" placement="top" > - + { paddingRight: 0, }} onClick={() => { - setAuthenticationModalOpen(true); + setAuthenticationModalOpen(true) }} > - + { onClick={() => {}} > @@ -2350,10 +2811,11 @@ const ParsedAction = (props) => { title="What are actions?" placement="top" > - + + {/* { title={selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "Click to enable magic parsing" : "Click to disable magic parsing"} placement="top" > - + + + + */} + { + }} + > + + + + @@ -2466,7 +2950,7 @@ const ParsedAction = (props) => { />
- Name + Name { onChange={selectedNameChange} onBlur={(e) => { const name = e.target.value; - console.log("CHANGED FROM2: ", baselabel); - console.log("CHANGED TO: ", name); - for (var key in workflow.actions) { - for (var subkey in workflow.actions[key].parameters) { - const param = workflow.actions[key].parameters[subkey]; - if (param.value.includes(baselabel)) { - //if (param.value.toLowerCase().includes(baselabel)) { - console.log("FOUND: ", param); + const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") + const newname = "$"+name.toLowerCase().replaceAll(" ", "_") - workflow.actions[key].parameters[subkey].value.replaceAll( - baselabel, - e.target.value - ); + // Change in actions, triggers & conditions + // Highlight the changes somehow with a glow? + // + // Should make it a function lol + if (workflow.branches !== undefined && workflow.branches !== null) { + for (var key in workflow.branches) { + for (var subkey in workflow.branches[key].conditions) { + const condition = workflow.branches[key].conditions[subkey] + const sourceparam = condition.source + const destinationparam = condition.destination + + // Should have a smarter way of discovering node names + // Finding index(es) and replacing at the location + if (sourceparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) + const extralength = newname.length-parsedBaseLabel.length + sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].source.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + + if (destinationparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) + const extralength = newname.length-parsedBaseLabel.length + destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } } } } - console.log("DID REPLACE ACTUALLY WORK?? - Something is buggy."); + for (var key in workflow.actions) { + if (workflow.actions[key].id === selectedAction.id) { + continue + } + + for (var subkey in workflow.actions[key].parameters) { + const param = workflow.actions[key].parameters[subkey]; + if (!param.value.includes("$")) { + continue + } + + // Should have a smarter way of discovering node names + // Do regex? + // Finding index(es) and replacing at the location + // + + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (param.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = param.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.actions[key].parameters[subkey].value) + const extralength = newname.length-parsedBaseLabel.length + param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) + + console.log("New: ", workflow.actions[key].parameters[subkey].value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + + console.log("DID NAME REPLACE ACTUALLY WORK? - may be missing it in certain triggers"); setWorkflow(workflow); + setUpdate(Math.random()); + baselabel = name }} />
@@ -2509,7 +3160,7 @@ const ParsedAction = (props) => { placement="top" > - Delay + Delay { selectedAction.authentication !== null && selectedAction.authentication.length > 0 ? (
- Authentication + Authentication
{ color: "white", }, }} + filterOptions={(options, { inputValue }) => { + //console.log("Option contains?: ", inputValue, options) + const lowercaseValue = inputValue.toLowerCase() + options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) + + return options + }} getOptionLabel={(option) => { if ( option === undefined || @@ -2841,6 +3498,7 @@ const ParsedAction = (props) => { const newname = ( option.name.charAt(0).toUpperCase() + option.name.substring(1) ).replaceAll("_", " "); + return newname; }} options={sortByKey(selectedApp.actions, "label")} @@ -2853,7 +3511,11 @@ const ParsedAction = (props) => { onChange={(event, newValue) => { // Workaround with event lol if (newValue !== undefined && newValue !== null) { - setNewSelectedAction({ target: { value: newValue.name } }); + setNewSelectedAction({ + target: { + value: newValue.name + } + }); } }} renderOption={(data) => { @@ -2884,23 +3546,82 @@ const ParsedAction = (props) => { newActionname.substring(1) ).replaceAll("_", " "); + var method = "" + var extraDescription = "" + if (data.name.includes("get_")) { + method = "GET" + } else if (data.name.includes("post_")) { + method = "POST" + } else if (data.name.includes("put_")) { + method = "PUT" + } else if (data.name.includes("patch_")) { + method = "PATCH" + } else if (data.name.includes("delete_")) { + method = "DELETE" + } else if (data.name.includes("options_")) { + method = "OPTIONS" + } else if (data.name.includes("connect_")) { + method = "CONNECT" + } + + // FIXME: Should it require a base URL? + if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { + var extraUrl = "" + const descSplit = data.description.split("\n") + for (var line in descSplit) { + if (descSplit[line].includes("http") && descSplit[line].includes("://")) { + const urlsplit = descSplit[line].split("/") + try { + extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") + } catch (e) { + console.log("Failed - running with -1") + extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") + } + + + //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) + break + } + } + + if (extraUrl.length > 0) { + if (extraUrl.includes(" ")) { + extraUrl = extraUrl.split(" ")[0] + } + + if (extraUrl.includes("#")) { + extraUrl = extraUrl.split("#")[0] + } + extraDescription = `${method} ${extraUrl}` + } else { + console.log("No url found. Check again :)") + } + } + return ( -
- - {useIcon} - - {newActionname} +
+
+ + {useIcon} + + {newActionname} +
+ {extraDescription.length > 0 ? + + {extraDescription} + + : null}
); @@ -2920,15 +3641,17 @@ const ParsedAction = (props) => { } return ( - + ); }} /> diff --git a/frontend/src/components/ScrollToTop.jsx b/frontend/src/components/ScrollToTop.jsx index f166cf1f..2e19a282 100644 --- a/frontend/src/components/ScrollToTop.jsx +++ b/frontend/src/components/ScrollToTop.jsx @@ -2,23 +2,26 @@ import { useEffect } from "react"; //import { withRouter } from "react-router-dom"; import { useLocation } from "react-router-dom"; -function ScrollToTop({ getUserNotifications, setCurpath, history }) { +// ensures scrolling happens in the right way on different pages and when changing +function ScrollToTop({ getUserNotifications, curpath, setCurpath, history }) { let location = useLocation(); useEffect(() => { - //const unlisten = history.listen(() => { - window.scroll({ - top: 0, - left: 0, - behavior: "smooth", - }); + // Custom handler for certain scroll mechanics + // + console.log("OLD: ", curpath, "NeW: ", window.location.pathname) + if (curpath === window.location.pathname && curpath === "/usecases") { + } else { - setCurpath(window.location.pathname); - getUserNotifications(); - //}); - //return () => { - // unlisten(); - //}; + window.scroll({ + top: 0, + left: 0, + behavior: "smooth", + }); + + setCurpath(window.location.pathname); + getUserNotifications(); + } }, [location]); return null; diff --git a/frontend/src/components/Searchfield.js b/frontend/src/components/Searchfield.js new file mode 100644 index 00000000..a15ff5f3 --- /dev/null +++ b/frontend/src/components/Searchfield.js @@ -0,0 +1,648 @@ +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: ( + { + event.preventDefault() + }}> + { + setSearchOpen(false) + }} /> + + ), + */ + + return ( +
{isMobile ? null :
- +
} {isMobile ? null : diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx index 5afb4bd3..5ba568f2 100644 --- a/frontend/src/components/ShuffleCodeEditor.jsx +++ b/frontend/src/components/ShuffleCodeEditor.jsx @@ -1,5 +1,6 @@ import React, {useState, useEffect, useLayoutEffect} from 'react'; import { + CircularProgress, IconButton, Dialog, Modal, @@ -7,19 +8,33 @@ import { DialogTitle, DialogContent, Typography, - Paper + Paper, + Menu, + MenuItem, + Button, } from '@material-ui/core'; import Checkbox from '@mui/material/Checkbox'; import { orange } from '@mui/material/colors'; import { isMobile } from "react-device-detect" +import { GetParsedPaths, FindJsonPath } from "../views/Apps.jsx"; +import NestedMenuItem from "material-ui-nested-menu-item"; import { FullscreenExit as FullscreenExitIcon, -} from "@material-ui/icons"; + Extension as ExtensionIcon, + Apps as AppsIcon, + FavoriteBorder as FavoriteBorderIcon, + Schedule as ScheduleIcon, + FormatListNumbered as FormatListNumberedIcon, + SquareFoot as SquareFootIcon, + Circle as CircleIcon, + Add as AddIcon, + PlayArrow as PlayArrowIcon, +} from '@mui/icons-material'; import { - AutoFixHigh as AutoFixHighIcon, CompressOutlined, + AutoFixHigh as AutoFixHighIcon, CompressOutlined, QrCodeScannerOutlined, } from '@mui/icons-material'; import { useTheme } from '@material-ui/core/styles'; @@ -33,12 +48,32 @@ import 'codemirror/addon/selection/mark-selection.js' import 'codemirror/theme/gruvbox-dark.css'; import 'codemirror/theme/duotone-light.css'; import { padding, textAlign } from '@mui/system'; +import data from '../frameworkStyle.jsx'; +import { useNavigate, Link, useParams } from "react-router-dom"; + +const liquidFilters = [ + {"name": "Size", "value": "size", "example": ""}, + {"name": "Date", "value": `date: "%Y%m%d"`, "example": `{{ "now" | date: "%s" }}`}, + {"name": "Escape String", "value": `{{ \"\"\"'string with weird'" quotes\"\"\" | escape_string }}`, "example": ``}, + {"name": "Flatten", "value": `flatten`, "example": `{{ [1, [1, 2], [2, 3, 4]] | flatten }}`}, +] + +const mathFilters = [ + {"name": "Plus", "value": "plus: 1", "example": `{{ "1" | plus: 1 }}`}, + {"name": "Minus", "value": "minus: 1", "example": `{{ "1" | minus: 1 }}`}, +] + +const pythonFilters = [ + {"name": "Hello World", "value": `{% python %}\nprint("hello world")\n{% endpython %}`, "example": ``}, + {"name": "Handle JSON", "value": `{% python %}\nimport json\njsondata = json.loads(r"""$nodename""")\n{% endpython %}`, "example": ``}, +] const CodeEditor = (props) => { - const { fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata } = props + const { globalUrl, fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata, isFileEditor, runUpdateText } = props + const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); // const {codelang, setcodelang} = props - const theme = useTheme(); + const theme = useTheme(); const [validation, setValidation] = React.useState(false); const [expOutput, setExpOutput] = React.useState(" "); const [linewrap, setlinewrap] = React.useState(true); @@ -51,62 +86,63 @@ const CodeEditor = (props) => { const [variableOccurences, setVariableOccurences] = React.useState([]); const [currentLocation, setCurrentLocation] = React.useState([]); const [currentVariable, setCurrentVariable] = React.useState(""); - // useEffect(() => { - // console.log(currentLocation) - // }, [currentLocation]) + const [anchorEl, setAnchorEl] = React.useState(null); + const [anchorEl2, setAnchorEl2] = React.useState(null); + const [anchorEl3, setAnchorEl3] = React.useState(null); + const [mainVariables, setMainVariables] = React.useState([]); + const [availableVariables, setAvailableVariables] = React.useState([]); - // const [allVariable, setAllVariable] = React.useState([]); - var allVariable = [] - var mainVariables = [] - - // console.log(actionlist.length) - for(var i=0; i { + setShowAutocomplete(false); + + setMenuPosition(null); } - // {actionlist.map((data, index) => { - // console.log(data) - // console.log(actionlist.length) - // console.log(data.autocomplete) - // allVariable.push('$'+data.autocomplete.substring(0, 25)) - // return ( - //
- // - //
- // ) - // })} + let navigate = useNavigate(); + // console.log("is it file editor? - ", isFileEditor); + + useEffect(() => { + var allVariables = [] + var tmpVariables = [] + + if (actionlist === undefined || actionlist === null) { + return + } + + for(var i=0; i < actionlist.length; i++){ + allVariables.push('$'+actionlist[i].autocomplete.toLowerCase()) + tmpVariables.push('$'+actionlist[i].autocomplete.toLowerCase()) + + var parsedPaths = [] + if (typeof actionlist[i].example === "object") { + parsedPaths = GetParsedPaths(actionlist[i].example, ""); + } + + for (var key in parsedPaths) { + const fullpath = "$"+actionlist[i].autocomplete.toLowerCase()+parsedPaths[key].autocomplete + if (!allVariables.includes(fullpath)) { + allVariables.push(fullpath) + } + } + } + + setAvailableVariables(allVariables) + setMainVariables(tmpVariables) + }, []) const autoFormat = (input) => { if (validation !== true) { @@ -124,7 +160,7 @@ const CodeEditor = (props) => { } } - function findIndex(line, loc) { + const findIndex = (line, loc) => { // var temp_arr = [] // for(var i=0; i { var variable_occurences = code_line.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) try{ - for(var occ = 0; occ { // temp_arr.push(temp_arr[temp_arr.length-1]+1) // variable_ranges.push(temp_arr) var temp_arr = [dollar_occurences[occ]] - for(var occ_len = 0; occ_len { if(loc === variable_ranges[occ][occ1]){ popup = true setCurrentLocation([line, dollar_occurences[occ]]) - console.log("Current Location : "+dollar_occurences[occ]) + try{ setCurrentVariable(variable_occurences[occ]) - console.log("Current Variable : "+variable_occurences[occ]) + } catch (e) { // setCurrentVariable("") // console.log("Current Variable : Nothing") } + occ = Infinity break } @@ -197,7 +237,35 @@ const CodeEditor = (props) => { // console.log(dollar_occurences) } - function highlight_variables(value){ + const fixVariable = (inputvariable) => { + if (inputvariable === undefined || inputvariable === null) { + return inputvariable + } + + if (!inputvariable.includes(".")) { + return inputvariable + } + + const itemsplit = inputvariable.split(".") + var newitem = [] + var removedIndexes = 0 + for (var key in itemsplit) { + var tmpitem = itemsplit[key] + if (tmpitem.startsWith("#")) { + removedIndexes += tmpitem.length-1 + tmpitem = "#" + } + + newitem.push(tmpitem) + } + + //console.log("Fixed item: ", newitem, "removed length: ", removedIndexes) + + return newitem.join(".") + //return inputvariable + } + + const highlight_variables = (value) => { // value.markText({line:0, ch:2}, {line:0, ch:8}, {"css": "background-color: #f85a3e; border-radius: 4px; color: white"}) // value.markText({line:0, ch:13}, {line:0, ch:15}, {"css": "background-color: #f85a3e; border-radius: 4px; color: white"}) // value.markText({line:0, ch:19}, {line:0, ch:26}, {"css": "background-color: #f85a3e; border-radius: 4px; color: white"}) @@ -217,12 +285,26 @@ const CodeEditor = (props) => { // console.log(code_variables_loc) var code_lines = localcodedata.split('\n') - for (var i = 0; i { // console.log(actionlist[j].autocomplete); // } + // Finds occurences of dollar signs var dollar_occurence = [] - for(var ch=0; ch action.autocomplete.toLowerCase() === variable_occurence[occ].slice(1,).toLowerCase()) - var correctVariable = allVariable.includes(variable_occurence[occ].toLowerCase()) - // console.log(actionlist) + const fixedVariable = fixVariable(variable_occurence[occ]) + var correctVariable = availableVariables.includes(fixedVariable) if(!correctVariable) { value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: rgb(248, 106, 62, 0.9); padding-top: 2px; padding-bottom: 2px; color: white"}) } @@ -266,7 +356,9 @@ const CodeEditor = (props) => { } // console.log(correctVariables) } - } catch (e) {} + } catch (e) { + console.log("Error in color highlighting: ", e) + } } } @@ -286,7 +378,7 @@ const CodeEditor = (props) => { // return "" // } - function replaceVariables(swapVariable){ + const replaceVariables = (swapVariable) => { // var updatedCode = localcodedata.slice(0,index) + "$" + str + localcodedata.slice(index+currentVariable.length+1,) // setlocalcodedata(updatedCode) // setEditorPopupOpen(false) @@ -308,26 +400,110 @@ const CodeEditor = (props) => { setlocalcodedata(updatedCode) } - function expectedOutput(input) { + const expectedOutput = (input) => { + //const found = input.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) const found = input.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) - //console.log(found) + //if (found === null || found === undefined) { + // console.log("No output found!") + // return + //} - try{ - // When the found array is empty. + console.log("FOUND: ", found) + + // Whelp this is inefficient af. Single loop pls + // When the found array is empty. + try { for (var i = 0; i < found.length; i++) { - // console.log(found[i]); + try { + //found[i] = found[i].toLowerCase() + const fixedVariable = fixVariable(found[i]) + //var correctVariable = availableVariables.includes(fixedVariable) - for (var j = 0; j < actionlist.length; j++) { - if(found[i].slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){ - input = input.replace(found[i], JSON.stringify(actionlist[j].example)); - // console.log(input) - // console.log(actionlist[j].example) + // + var valuefound = false + for (var j = 0; j < actionlist.length; j++) { + if(fixedVariable.slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){ + valuefound = true + + console.log("Valuefound: ", fixedVariable, actionlist[j].example) + + try { + if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) { + input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example)); + } else { + input = input.replace(fixedVariable, actionlist[j].example) + } + } catch (e) { + input = input.replace(fixedVariable, actionlist[j].example) + } + } else { + } } - // console.log(actionlist[j].autocomplete); + + if (!valuefound && availableVariables.includes(fixedVariable)) { + var shouldbreak = false + for (var k=0; k < actionlist.length; k++){ + var parsedPaths = [] + if (typeof actionlist[k].example === "object") { + parsedPaths = GetParsedPaths(actionlist[k].example, ""); + } + + for (var key in parsedPaths) { + const fullpath = "$"+actionlist[k].autocomplete.toLowerCase()+parsedPaths[key].autocomplete + if (fullpath === fixedVariable) { + //if (actionlist[k].example === undefined) { + // actionlist[k].example = "TMP" + //} + + var new_input = "" + try { + new_input = FindJsonPath(fullpath, actionlist[k].example) + } catch (e) { + console.log("ERR IN INPUT: ", e) + } + + //console.log("Got output for: ", fullpath, new_input, actionlist[k].example, typeof new_input) + + if (typeof new_input === "object") { + new_input = JSON.stringify(new_input) + } else { + if (typeof new_input === "string") { + new_input = new_input + } else { + console.log("NO TYPE? ", typeof new_input) + try { + new_input = new_input.toString() + } catch (e) { + new_input = "" + } + } + } + + //console.log("FOUND2: ", fixedVariable, actionlist[j].example) + input = input.replace(fixedVariable, new_input) + + //} catch (e) { + // input = input.replace(found[i], actionlist[k].example) + //} + + shouldbreak = true + break + } + } + + if (shouldbreak) { + break + } + } + } + } catch (e) { + console.log("Replace error: ", e) } } - } catch (e) {} + } catch (e) { + console.log("Outer replace error: ", e) + } const tmpValidation = validateJson(input.valueOf()) //setValidation(true) @@ -340,19 +516,126 @@ const CodeEditor = (props) => { } } + const handleItemClick = (values) => { + if ( + values === undefined || + values === null || + values.length === 0 + ) { + return; + } + + var toComplete = localcodedata.trim().endsWith("$") ? values[0].autocomplete : "$" + values[0].autocomplete; + + toComplete = toComplete.toLowerCase().replaceAll(" ", "_"); + for (var key in values) { + if (key == 0 || values[key].autocomplete.length === 0) { + continue; + } + + toComplete += values[key].autocomplete; + } + + setlocalcodedata(localcodedata+toComplete) + setMenuPosition(null) + } + + const handleClick = (item) => { + if (item === undefined || item.value === undefined || item.value === null) { + return + } + + if (!item.value.includes("{%") && !item.value.includes("{{")) { + setlocalcodedata(localcodedata+" | "+item.value+" }}") + } else { + setlocalcodedata(localcodedata+item.value) + } + + setAnchorEl(null) + setAnchorEl2(null) + setAnchorEl3(null) + } + + const executeSingleAction = (inputdata) => { + //if (serverside === true) { + // return + //} + + if (validation === true) { + inputdata = JSON.stringify(inputdata) + } + + const appid = "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":"3e2bdf9d5069fe3f4746c29d68785a6a","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":{}} + + setExecutionResult({ + "valid": false, + "result": baseResult, + }) + + setExecuting(true) + + fetch(globalUrl+"/api/v1/apps/"+appid+"/execute", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(actiondata), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!") + } + + return response.json() + }) + .then((responseJson) => { + //console.log("RESPONSE: ", responseJson) + 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) + + const validate = validateJson(responseJson.result) + setExecutionResult(validate) + } else if (responseJson.success === false && responseJson.reason !== undefined && responseJson.reason !== null) { + alert.error(responseJson.reason) + setExecutionResult({"valid": false, "result": responseJson.reason}) + } else if (responseJson.success === true) { + setExecutionResult({"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."}) + } else { + setExecutionResult({"valid": false, "result": "Couldn't finish execution (2). Please fill all the required fields, and validate the execution."}) + } + + setExecuting(false) + }) + .catch(error => { + //alert.error("Execution error: "+error.toString()) + console.log("error: ", error) + setExecuting(false) + }) + } + return ( { console.log("In closer") - changeActionParameterCodeMirror({target: {value: ""}}, fieldCount, localcodedata) + + if (changeActionParameterCodeMirror !== undefined) { + changeActionParameterCodeMirror({target: {value: ""}}, fieldCount, localcodedata) + } else { + console.log("No action called changeActionParameterCodeMirror in code editor") + } //setExpansionModalOpen(false) }} PaperComponent={PaperComponent} - aria-labelledby="draggable-dialog-title" PaperProps={{ style: { backgroundColor: theme.palette.surfaceColor, @@ -364,6 +647,26 @@ const CodeEditor = (props) => { }, }} > + { isFileEditor ? +
+
+ + File Editor + +
+
+ :
{ { + + }} + > + + + + + + + {
-
- +
} + + + { isFileEditor ? null : +
+ + { + setAnchorEl(null); + }} + MenuListProps={{ + 'aria-labelledby': 'basic-button', + }} + > + {liquidFilters.map((item, index) => { + return ( + { + handleClick(item) + }}>{item.name} + ) + })} + + + { + setAnchorEl2(null); + }} + MenuListProps={{ + 'aria-labelledby': 'basic-button', + }} + > + {mathFilters.map((item, index) => { + return ( + { + handleClick(item) + }}>{item.name} + ) + })} + + + { + setAnchorEl3(null); + }} + MenuListProps={{ + 'aria-labelledby': 'basic-button', + }} + > + {pythonFilters.map((item, index) => { + return ( + { + handleClick(item) + }}>{item.name} + ) + })} + + + { + handleMenuClose(); + }} + open={!!menuPosition} + style={{ + color: "white", + marginTop: 2, + maxHeight: 650, + }} + > + {actionlist.map((innerdata) => { + const icon = + innerdata.type === "action" ? ( + + ) : innerdata.type === "workflow_variable" || + innerdata.type === "execution_variable" ? ( + + ) : ( + + ); + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById( + "execution_argument_input_field" + ); + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e"; + } else { + exec_text_field.style.border = ""; + } + } + }; + + const handleActionHover = (inside, actionId) => { + }; + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true); + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id); + } + }; + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false); + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id); + } + }; + + var parsedPaths = []; + if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } + + const coverColor = "#82ccc3" + //menuPosition.left -= 50 + //menuPosition.top -= 250 + //console.log("POS: ", menuPosition1) + var menuPosition1 = menuPosition + if (menuPosition1 === null) { + menuPosition1 = { + "left": 0, + "top": 0, + } + } else if (menuPosition1.top === null || menuPosition1.top === undefined) { + menuPosition1.top = 0 + } else if (menuPosition1.left === null || menuPosition1.left === undefined) { + menuPosition1.left = 0 + } + + //console.log("POS1: ", menuPosition1) + + return parsedPaths.length > 0 ? ( + + {icon} {innerdata.name} +
+ } + parentMenuOpen={!!menuPosition} + style={{ + color: "white", + minWidth: 250, + maxWidth: 250, + maxHeight: 50, + overflow: "hidden", + }} + onClick={() => { + console.log("CLICKED: ", innerdata); + console.log(innerdata.example) + handleItemClick([innerdata]); + }} + > + + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + + {innerdata.name} + + + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + // + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ); + // + + const indentation_count = (pathdata.name.match(/\./g) || []).length+1 + //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 + const boxPadding = 0 + const namesplit = pathdata.name.split(".") + const newname = namesplit[namesplit.length-1] + return ( + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
+ {Array(indentation_count).fill().map((subdata, subindex) => { + return ( +
+ ) + })} + {icon} {newname} + {pathdata.type === "list" ? { + e.preventDefault() + e.stopPropagation() + + console.log("INNER: ", innerdata, pathdata) + + // Removing .list from autocomplete + var newname = pathdata.name + if (newname.length > 5) { + newname = newname.slice(0, newname.length-5) + } + + //selectedActionParameters[count].value += `{{ $${innerdata.name}.${newname} | size }}` + //selectedAction.parameters[count].value = selectedActionParameters[count].value; + //setSelectedAction(selectedAction); + //setShowDropdown(false); + setMenuPosition(null); + + // innerdata.name + // pathdata.name + //handleItemClick([innerdata, newpathdata]) + //console.log("CLICK LENGTH!") + }} /> : null} +
+ + + ); + })} + + + ) : ( + handleMouseover()} + onMouseOut={() => { + handleMouseOut(); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + +
+ {icon} {innerdata.name} +
+
+
+ ); + })} + + +
} { @@ -421,26 +1113,11 @@ const CodeEditor = (props) => { onChange={(value) => { setlocalcodedata(value.getValue()) expectedOutput(value.getValue()) - // console.log(allVariable) - // console.log(value.getValue().split('\n')[value.getCursor().line]) - // console.log(value.getCursor()) - // console.log(value) - // console.log(value.getValue().indexOf('$')) - // console.log(value.display.input.prevInput) + if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){ setEditorPopupOpen(true) - // console.log(findIndex(value.getValue())) - // console.log(findlocation(findIndex(value.getValue()))) - // setCurrentLocation(findlocation(findIndex(value.getValue()))) - // console.log(currentLocation) - // console.log(findVariables(findlocation(findIndex(value.getValue())), value.getValue())) - // setCurrentVariable(findVariables(findlocation(findIndex(value.getValue())), value.getValue())) - // console.log(actionlist) } - // setVariableOccurences(findIndex(value.getValue())) - // setCurrentVariable(findVariables(value.getValue())) - // console.log(currentLocation) - // console.log(currentVariable) + // console.log(findIndex(value.getValue())) // highlight_variables(value) }} @@ -448,13 +1125,13 @@ const CodeEditor = (props) => { styleSelectedText: true, theme: codeTheme, keyMap: 'sublime', - mode: 'javascript', + mode: 'python', lineWrapping: linewrap, // mode: {codelang}, }} /> - {editorPopupOpen ? + {/*editorPopupOpen ? { }} > {data.substring(0, 25)} - {/* {Object.keys(data.example).forEach(key => key)} */}
) })} - : null} + : null*/}
{/* @@ -567,121 +1242,158 @@ const CodeEditor = (props) => { */}
- -
- {isMobile ? null : - + {isMobile ? null : + + + Expected Output + + { + executeSingleAction(expOutput) + }}> + + {executing ? : } + + + + + } + {isMobile ? null : + validation === true ? + { + //handleReactJsonClipboard(copy); + }} + displayDataTypes={false} + onSelect={(select) => { + //HandleJsonCopy(validate.result, select, "exec"); + }} + name={"JSON autocompletion"} + /> + : +

+ {expOutput} +

+ } + {executionResult.valid === true ? + { + //handleReactJsonClipboard(copy); + }} + displayDataTypes={false} + onSelect={(select) => { + //HandleJsonCopy(validate.result, select, "exec"); + }} + name={"Test result"} + /> + : + + {executionResult.result.length > 0 ? + + Test output: {executionResult.result} + + : null} + + } +
+ ) + } +
+
+ Cancel + + - + height: 35, + flex: 1, + marginLeft: 10, + marginTop: 20, + cursor: "pointer" + }} + onClick={(event) => { + // console.log(codedata) + // console.log(fieldCount) + if (isFileEditor === true){ + runUpdateText(localcodedata); + setcodedata(localcodedata); + setExpansionModalOpen(false) + } + else { + changeActionParameterCodeMirror(event, fieldCount, localcodedata) + setExpansionModalOpen(false) + setcodedata(localcodedata)} + }} + > + Done +
) } diff --git a/frontend/src/components/SuggestedWorkflows.jsx b/frontend/src/components/SuggestedWorkflows.jsx new file mode 100644 index 00000000..94898915 --- /dev/null +++ b/frontend/src/components/SuggestedWorkflows.jsx @@ -0,0 +1,230 @@ +import React, { useState, useEffect } from 'react'; +import ReactGA from 'react-ga'; +import theme from '../theme'; +import PaperComponent from "../components/PaperComponent.jsx" +import UsecaseSearch, { usecaseTypes } from "../components/UsecaseSearch.jsx" + +import { + Paper, + Typography, + Divider, + IconButton, + Badge, + CircularProgress, + Tooltip, + Dialog, +} from "@material-ui/core"; + +import { + Close as CloseIcon, + Delete as DeleteIcon, + AutoFixHigh as AutoFixHighIcon, + Done as DoneIcon, +} from "@mui/icons-material"; + +const SuggestedWorkflows = (props) => { + const { globalUrl, userdata, usecaseSuggestions, frameworkData, setUsecaseSuggestions, inputSearch, apps, } = props + + const [usecaseSearch, setUsecaseSearch] = React.useState("") + const [usecaseSearchType, setUsecaseSearchType] = React.useState("") + const [finishedUsecases, setFinishedUsecases] = React.useState([]) + const [previousUsecase, setPreviousUsecase] = React.useState("") + const [closeWindow, setCloseWindow] = React.useState(false) + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + + useEffect(() => { + if (closeWindow === true) { + console.log("WINDOW CLOSED") + finishedUsecases.push(usecaseSearch) + setFinishedUsecases(finishedUsecases) + + setCloseWindow(false) + } + }, [closeWindow]) + + if (usecaseSuggestions === undefined || usecaseSuggestions.length === 0) { + return null + } + + if (inputSearch !== previousUsecase) { + setPreviousUsecase(inputSearch) + setFinishedUsecases([]) + } + + if (finishedUsecases.length === usecaseSuggestions.length) { + console.log("Closing finished usecases 2") + return null + } + + + //useEffect(() => { + // //if (defaultSearch === + // //setFinishedUsecases(finishedUsecases) + // console.log("Finished default usecase?", usecaseSearch) + //}, [usecaseSearch]) + + const foundZindex = usecaseSearch.length > 0 && usecaseSearchType.length > 0 ? -1 : 12500 + + const IndividualUsecase = (props) => { + const { usecase, index } = props + const [hovering, setHovering] = React.useState(false) + + const usecasename = usecase.name + const bordercolor = usecase.color !== undefined ? usecase.color : "rgba(255,255,255,0.3)" + + + const srcimage = usecase.items[0].app + var dstimage = usecase.items[1].app + if (usecase.items.length > 2) { + dstimage = usecase.items[2].app + } + + const finished = finishedUsecases.includes(usecasename) + const selectedIcon = finished ? : + + if (finished) { + return null + } + + // Simple visual of the usecase + return ( + +
{ + setHovering(true) + }} onMouseOut={() => { + setHovering(false) + }} onClick={() => { + if (isCloud) { + ReactGA.event({ + category: "welcome", + action: "click_suggested_workflow", + label: usecasename, + }) + } + + console.log("Try usecase ", usecasename) + setUsecaseSearchType(usecase.type) + setUsecaseSearch(usecasename) + + }}> +
+ + {usecasename} + +
+ {srcimage.large_image} + {dstimage.large_image} +
+ +
+
+ {selectedIcon} +
+
+
+ ) + } + + // + return ( + + 0 && usecaseSearchType.length > 0} + onClose={() => { + finishedUsecases.push(usecaseSearch) + setFinishedUsecases(finishedUsecases) + + + + setUsecaseSearch("") + setUsecaseSearchType("") + }} + PaperProps={{ + style: { + pointerEvents: "auto", + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: 450, + padding: 50, + overflow: "hidden", + zIndex: 10012, + border: theme.palette.defaultBorder, + }, + }} + > + { + finishedUsecases.push(usecaseSearch) + setFinishedUsecases(finishedUsecases) + + setUsecaseSearch("") + setUsecaseSearchType("") + }} + > + + + + +
+ + Suggested Workflows ({finishedUsecases.length}/{usecaseSuggestions.length}) + + { + if (setUsecaseSuggestions !== undefined) { + setUsecaseSuggestions([]) + } + }} + > + + + {usecaseSuggestions.map((usecase, index) => { + + return ( + + ) + + })} +
+
+ ) +} + +export default SuggestedWorkflows; diff --git a/frontend/src/components/UsecaseSearch.jsx b/frontend/src/components/UsecaseSearch.jsx new file mode 100644 index 00000000..e9edb0f4 --- /dev/null +++ b/frontend/src/components/UsecaseSearch.jsx @@ -0,0 +1,1592 @@ +import React, { useState, useEffect } from "react"; +import theme from '../theme'; +import { useNavigate, Link } from "react-router-dom"; +import { useAlert } from "react-alert"; +import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; +import PaperComponent from "../components/PaperComponent.jsx" +import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; +import AuthenticationNormal from "../components/AuthenticationNormal.jsx"; +import AppsearchPopout from "../components/AppsearchPopout.jsx"; + +import { + ExpandLess as ExpandLessIcon, + ExpandMore as ExpandMoreIcon, + AddCircleOutline as AddCircleOutlineIcon, + Delete as DeleteIcon, + Description as DescriptionIcon, + Close as CloseIcon, +} from '@mui/icons-material'; + +import { + Dialog, + IconButton, + Typography, + Button, + CircularProgress, + Tooltip, + Divider, +} from "@material-ui/core"; + +const defaultValue = {"id": "", "name": "", + "source": {"text": "No trigger selected", "error": ""}, + "destination": {"text": "No subflow selected", "error": ""}, + "middle": [] +} + +export const usecaseTypes = [{ + "name": "enrichment", + "value": [{ + "name": "EDR Ticket Enrichment", + "usecase_references": ["EDR to ticket"], + "active": true, + "items": [{ + "name": "When an EDR alert is found", + "app_type": "edr", + "type": "trigger", + }, { + "name": "Create a ticket", + "app_type": "cases", + "type": "subflow", + }], + }, + { + "name": "SIEM alert Enrichment", + "usecase_references": ["SIEM to ticket"], + "active": true, + "items": [{ + "name": "When a SIEM alert is found", + "app_type": "siem", + "type": "trigger", + }, + { + "name": "Create a ticket", + "app_type": "cases", + "type": "subflow", + }], + }, + { + "name": "Email Enrichment", + "usecase_references": ["Email management"], + "active": true, + "items": [{ + "name": "When I get an email", + "app_type": "email", + "type": "trigger", + }, + { + "name": "Create a ticket", + "app_type": "cases", + "type": "subflow", + }], + }] +}, +{ + "name": "phishing", + "value": [ + { + "name": "Email analysis", + "usecase_references": ["Email management"], + "active": true, + "items": [{ + "name": "When I get an email", + "app_type": "email", + "type": "trigger", + },{ + "name": "Create a ticket", + "app_type": "cases", + "type": "subflow", + }], + }] + }, + { + "name": "detection", + "value": [ + { + "name": "Sigma rule detection", + "active": false, + "items": [{ + "name": "When a sigma rule triggers", + "app_type": "siem", + "type": "trigger", + }, + { + "name": "Create a ticket", + "app_type": "cases", + "type": "subflow", + }], + }] + }, + { + "name": "response", + "value": [ + { + "name": "EDR host isolation", + "active": false, + "items": [{ + "name": "When malicious endpoint activity is detected", + "app_type": "edr", + "type": "trigger", + }, + { + "name": "Isolate the host", + "app_type": "edr", + "type": "subflow", + }], + }] + } +] + +export const triggerlist = [ + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "No trigger selected", + "image": "", + "type": "", + "action_type": "", + "error": "", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "When I get an email", + "image": "", + "type": "email", + "action_type": "receive", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "When a ticket is created", + "image": "", + "type": "cases", + "action_type": "case_opened", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "When an EDR alert is found", + "image": "", + "type": "edr", + "action_type": "case_opened", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "When a SIEM alert is found", + "image": "", + "type": "siem", + "action_type": "case_opened", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "When a sigma rule triggers", + "image": "", + "type": "siem", + "action_type": "case_opened", + "disabled": true, + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "When malicious endpoint activity is detected", + "image": "", + "type": "edr", + "action_type": "case_opened", + "disabled": true, + } +] + +export const midflows = [ + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "Enrich", + "image": "", + "type": "intel", + "action_type": "enrich", + }, + { + + "app_id": "", + "app_name": "", + "app_version": "", + "text": "Analyze", + "image": "", + "type": "intel", + "action_type": "analyze", + "disabled": true, + } +] + +export const subflows = [ + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "No subflow selected", + "image": "", + "type": "", + "action_type": "", + "error": "", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "Create a ticket", + "image": "", + "type": "cases", + "action_type": "case_create", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "Update a ticket", + "image": "", + "type": "cases", + "action_type": "case_update", + "disabled": true, + //"source_type": "case_opened", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "Answer the sender", + "image": "", + "type": "email", + "action_type": "send", + "disabled": true, + //"source_type": "case_opened", + }, + { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "Isolate the host", + "image": "", + "type": "edr", + "action_type": "respond", + "disabled": true, + //"source_type": "case_opened", + }, +] + +const UsecaseSearch = (props) => { + const { defaultSearch, appFramework, globalUrl, showTitle, canExpand, apps, setFoundWorkflowId, getFramework, userdata, usecaseSearch, setUsecaseSearch, autotry, setCloseWindow, } = props + + + const [allusecases, setAllUsecases] = React.useState([ + JSON.parse(JSON.stringify(defaultValue)) + ]) + const [searchType, setSearchType] = React.useState("") + const [usecaseIndex, setUsecaseIndex] = React.useState(0) + const [_, setUpdate] = useState(""); // Used for rendring, don't remove + const [isUploading, setIsUploading] = React.useState(false) + const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = React.useState(false); + const [configureWorkflowAuth, setConfigureWorkflowAuth] = React.useState([]); + const [workflow, setWorkflow] = React.useState({}); + const [appAuthentication, setAppAuthentication] = React.useState([]); + const [authenticationType, setAuthenticationType] = React.useState(""); + const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false); + const [selectedApp, setSelectedApp] = React.useState({}); + const [selectedAction, setSelectedAction] = React.useState({}); + const [firstRequest, setFirstRequest] = React.useState(true); + + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + 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") + + if (setCloseWindow !== undefined) { + setCloseWindow(true) + } + setUsecaseSearch("") + } + }, [configureWorkflowModalOpen]) + + const rerunWorkflowCheck = (defaultSearch, usecaseSearch) => { + const foundusecase = usecaseTypes.find(data => data.name.toLowerCase() === defaultSearch.toLowerCase()) + + //console.log("FOUND: ", usecaseTypes, defaultSearch.toLowerCase(), foundusecase) + + if (foundusecase !== undefined && foundusecase !== null) { + // Just choose the first one. + if (foundusecase.value !== undefined && foundusecase.value !== null && foundusecase.value.length > 0) { + var selectedusecase = foundusecase.value[0] + if (usecaseSearch !== undefined && usecaseSearch !== null) { + const foundSubcase = foundusecase.value.find(usecase => usecase.name.toLowerCase() === usecaseSearch.toLowerCase()) + if (foundSubcase !== undefined && foundSubcase !== null) { + selectedusecase = foundSubcase + } + } + + var newitem = JSON.parse(JSON.stringify(defaultValue)) + + for (var key in selectedusecase.items) { + // Check in different types + const itemname = selectedusecase.items[key].name.toLowerCase() + const trigger = triggerlist.find(data => data.text.toLowerCase() === itemname) + if (trigger !== undefined && trigger !== null) { + newitem.name = selectedusecase.name + newitem.source = trigger + + continue + } + + const midflow = midflows.find(data => data.text.toLowerCase() === itemname) + if (midflow !== undefined && midflow !== null) { + newitem.name = selectedusecase.name + newitem.middle.push(midflow) + + continue + } + + const subflow = subflows.find(data => data.text.toLowerCase() === itemname) + if (subflow !== undefined && subflow !== null) { + newitem.name = selectedusecase.name + newitem.destination = subflow + + continue + } + } + + setAllUsecases([newitem]) + setUpdate(Math.random()) + } + } else { + console.log("NOT FOUND FOR: ", defaultSearch) + setAllUsecases([JSON.parse(JSON.stringify(defaultValue))]) + setUpdate(Math.random()) + } + } + + if (defaultSearch !== undefined && defaultSearch !== null && defaultSearch !== searchType) { + console.log("Setting searchtype to", defaultSearch) + + + setSearchType(defaultSearch) + setUsecaseIndex(0) + rerunWorkflowCheck(defaultSearch, usecaseSearch) + } + + //if (defaultSearch !== undefined && defaultSearch.length > 0 && allusecases[usecaseIndex].name === "") { + // rerunWorkflowCheck() + //} + + /* + defaultSearch === "Enrichment" ? + [{ + "id": "", + "source": { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "When I get an email", + "image": "", + "type": "email", + "action_type": "receive", + }, + "middle": [{ + "app_id": "", + "app_name": "", + "app_version": "", + "text": "Enrich the data", + "image": "", + "type": "intel", + "action_type": "case_create", + }], + "destination": { + "app_id": "", + "app_name": "", + "app_version": "", + "text": "Create a ticket", + "image": "", + "type": "cases", + "action_type": "case_create", + }, + }] + : [] + ) + */ + + /* + if (defaultSearch === undefined || defaultSearch === null || defaultSearch.length === 0) { + return ( +
+ + Choose a Usecase + +
+ ) + } + */ + + // Image = the source EMAIL system used + var usecases = JSON.parse(JSON.stringify(allusecases)) + + const imagestyle = { + height: 50, + width: 50, + borderRadius: 25, + border: "1px solid rgba(255,255,255,0.3)", + cursor: "pointer", + } + + const getType = (inputtype) => { + if (inputtype === undefined) { + return inputtype + + } + + if (inputtype.toLowerCase() === "email" || inputtype.toLowerCase() === "comms") { + inputtype = "communication" + } + + return inputtype + } + + // FIXME: Add a way for it to automatically discover + // relevant workflows from the app at this point + // or maybe it should be added directly to the app frameworks' + // data + + const getAppAuthentication = (updateAction) => { + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for app auth :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success) { + setAppAuthentication(responseJson.data); + } + }) + .catch((error) => { + console.log("App auth loading error: "+error.toString()); + }) + } + + //saveWorkflow={undefined} {/*saveWorkflow*/} + //setNewAppAuth={undefined} {/*setNewAppAuth*/} + const authenticationModal = authenticationModalOpen ? ( + { + //if (configureWorkflowModalOpen) { + // setSelectedAction({}); + //} + }} + PaperProps={{ + style: { + pointerEvents: "auto", + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: 1100, + minHeight: 700, + maxHeight: 700, + padding: 15, + overflow: "hidden", + zIndex: 10012, + border: theme.palette.defaultBorder, + }, + }} + > +
+ {selectedApp.reference_info === undefined || + selectedApp.reference_info === null || + selectedApp.reference_info.github_url === undefined || + selectedApp.reference_info.github_url === null || + selectedApp.reference_info.github_url.length === 0 ? ( + + {`Documentation + + ) : ( + + {`Documentation + + )} +
+ { + setAuthenticationModalOpen(false); + if (configureWorkflowModalOpen) { + setSelectedAction({}); + } + }} + > + + +
+
+ {authenticationType.type === "oauth2" ? ( + + ) : ( + + )} +
+
+ {selectedApp.documentation === undefined || + selectedApp.documentation === null || + selectedApp.documentation.length === 0 ? ( + + + {selectedApp.description} + + + + There is currently no extended documentation available for this + app. + + + Want help help making or using this app?{" "} + + Join the community on Discord! + + + + + Want to help change this app directly? + + {selectedApp.reference_info === undefined || + selectedApp.reference_info === null || + selectedApp.reference_info.github_url === undefined || + selectedApp.reference_info.github_url === null || + selectedApp.reference_info.github_url.length === 0 ? ( + + + + Check it out on Github! + + + + ) : ( + + + + Check it out on Github! + + + + )} + + ) : + null + } +
+
+
+ ) : null + + const deleteWorkflow = (workflow_id) => { + fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + console.log("Deleted workflow") + }) + .catch((error) => { + //alert.error(error.toString()); + console.log("Delete workflow error: ", error.toString()); + }) + } + + const getWorkflow = (workflow_id) => { + fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + setWorkflow(responseJson) + + if (window !== undefined && !window.location.href.includes("/workflows")) { + setConfigureWorkflowModalOpen(true) + } + }) + .catch((error) => { + //alert.error(error.toString()); + console.log("Get workflows error: ", error.toString()); + }) + } + + // Stolen from /views/Workflows + // Due to states, not easy to just import as component~ + const setNewWorkflow = ( + name, + description, + tags, + defaultReturnValue, + editingWorkflow, + redirect, + currentUsecases, + inputblogpost, + inputstatus, + ) => { + var method = "POST"; + var extraData = ""; + var workflowdata = {}; + + if (editingWorkflow.id !== undefined) { + console.log("Building original workflow"); + method = "PUT"; + extraData = "/" + editingWorkflow.id + "?skip_save=true"; + workflowdata = editingWorkflow; + + console.log("REMOVING OWNER"); + workflowdata["owner"] = ""; + // FIXME: Loop triggers and turn them off? + } + + workflowdata["name"] = name; + workflowdata["description"] = description; + if (tags !== undefined) { + workflowdata["tags"] = tags; + } + workflowdata["blogpost"] = inputblogpost + workflowdata["status"] = inputstatus + + if (defaultReturnValue !== undefined) { + workflowdata["default_return_value"] = defaultReturnValue; + } + + if (currentUsecases !== undefined && currentUsecases !== null) { + workflowdata["usecase_ids"] = currentUsecases + //workflows[0].category = ["detect"] + //workflows[0].usecase_ids = ["Correlate tickets"] + } + + const new_url = `${globalUrl}/api/v1/workflows${extraData}` + return fetch(new_url, { + method: method, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(workflowdata), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + return; + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + alert.error("Error setting workflow: ", responseJson.reason) + } else { + alert.error("Error setting workflow.") + } + + return + } + + return responseJson; + }) + .catch((error) => { + alert.error(error.toString()); + }); + } + + const mergeWorkflowUsecases = (usecasedata) => { + //const url = `${globalUrl}/api/v1/workflows/merge`; + const url = `https://shuffler.io/api/v1/workflows/merge`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(usecasedata), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + setIsUploading(false) + var changed = false + + if (responseJson.success === false) { + if (responseJson.reason !== null && responseJson.reason !== undefined) { + //alert.error(responseJson.reason) + } + + if (responseJson.source === "") { + const appname = + usecasedata.source.error = usecasedata.source.app_name === undefined ? "Select a Trigger workflow first" : `${usecasedata.source.app_name} has no public trigger workflow yet. Click this to try another app` + changed = true + } else { + usecasedata.source.error = "" + changed = true + } + + if (responseJson.destination === "") { + usecasedata.destination.error = usecasedata.destination.app_name === undefined ? "Select a Subflow first" : `${usecasedata.destination.app_name} has no public subflow yet. Click this to try another app` + changed = true + } else { + usecasedata.destination.error = "" + changed = true + } + + if (responseJson.middle !== undefined) { + for (var key in responseJson.middle) { + for (var subkey in usecasedata.middle) { + if (responseJson.middle[key] === usecasedata.middle[key].text) { + console.log("Found: ") + if (usecasedata.middle[subkey].app_name === undefined || usecasedata.middle[subkey].app_name === "") { + usecasedata.middle[subkey].error = `${usecasedata.middle[subkey].type} app must be selected first. Click this to change` + } else { + usecasedata.middle[subkey].error = `${usecasedata.middle[subkey].app_name} has no public subflow yet. Click this to change` + } + + changed = true + break + } + } + } + } + } else { + // Gets a full workflow that has to be handled from cloud directly + // + if (!isCloud) { + console.log("Not cloud!") + if (setFoundWorkflowId !== undefined) { + setFoundWorkflowId(responseJson.id) + } + setWorkflow(responseJson) + + setNewWorkflow( + responseJson.name, + responseJson.description, + responseJson.tags, + responseJson.default_return_value, + {}, + false, + [], + "", + responseJson.status, + ) + .then((response) => { + if (response !== undefined) { + // SET THE FULL THING + responseJson.id = response.id; + responseJson.first_save = false; + responseJson.previously_saved = false; + responseJson.is_valid = false; + + // Actually create it + setNewWorkflow( + responseJson.name, + responseJson.description, + responseJson.tags, + responseJson.default_return_value, + responseJson, + false, + [], + "", + responseJson.status, + ).then((response) => { + if (response !== undefined) { + alert.success("Successfully generated " + responseJson.name); + } + }); + } + }) + .catch((error) => { + alert.error("Generate error: " + error.toString()); + }) + + + } else if (isCloud) { + if (responseJson.workflow_id !== null && responseJson.workflow_id !== undefined) { + if (responseJson.added_auth !== undefined && responseJson.added_auth !== null && responseJson.added_auth.length > 0) { + console.log("SHOULD HANDLE AUTH: ", responseJson.added_auth) + setConfigureWorkflowAuth(responseJson.added_auth) + + if (setFoundWorkflowId !== undefined) { + console.log("Set found workflow id: ", responseJson.workflow_id) + setFoundWorkflowId(responseJson.workflow_id) + } + + getWorkflow(responseJson.workflow_id) + getAppAuthentication() + } else { + if (setFoundWorkflowId !== undefined) { + console.log("Set found workflow id: ", responseJson.workflow_id) + setFoundWorkflowId(responseJson.workflow_id) + } + + setWorkflow({"id": responseJson.workflow_id}) + } + } + + if (responseJson.auth_required === true) { + console.log("SET AUTH AS NEXT STEP!") + } + + + } + } + + if (changed === true) { + setAllUsecases([usecasedata]) + } + }) + ) + .catch((error) => { + setIsUploading(false) + console.log("Merge err: ", error.toString()) + //alert.error("Err: " + error.toString()); + }); + } + + //console.log("Filled in: ", appFramework, usecases) + if (appFramework !== undefined && usecases !== undefined) { + + for (var key in usecases) { + // source + const usecase = usecases[key] + if (usecase.source.type === undefined || usecase.source.image === "" || usecase.source.image === undefined) { + usecases[key].source.image = theme.palette.defaultImage + } + + if (usecase.destination.type === undefined || usecase.destination.image === "" || usecase.destination.image === undefined) { + usecases[key].destination.image = theme.palette.defaultImage + } + + const srctype = getType(usecase.source.type) + const dsttype = getType(usecase.destination.type) + + const srcinfo = appFramework[srctype] + if (srcinfo !== undefined && srcinfo.large_image !== undefined && srcinfo.large_image !== "" && (usecases[key].source.app_name === undefined || usecases[key].source.app_name === "")) { + usecases[key].source.image = srcinfo.large_image + usecases[key].source.app_id = srcinfo.id + usecases[key].source.app_name = srcinfo.name + } + + const destinfo = appFramework[dsttype] + if (destinfo !== undefined && destinfo.large_image !== undefined && destinfo.large_image !== "" && (usecases[key].destination.app_name === undefined || usecases[key].destination.app_name === "")) { + usecases[key].destination.image = destinfo.large_image + usecases[key].destination.app_id = destinfo.id + usecases[key].destination.app_name = destinfo.name + } + + if (usecase.middle !== undefined && usecase.middle !== null && usecase.middle.length > 0) { + for (var subkey in usecase.middle) { + const midcase = usecase.middle[subkey] + + const midtype = getType(midcase.type) + const midinfo = appFramework[midtype] + if (midinfo !== undefined && midinfo.large_image !== undefined && midinfo.large_image !== "" && (usecases[key].middle[subkey].app_name === undefined || usecases[key].middle[subkey].app_name === "")) { + usecases[key].middle[subkey].image = midinfo.large_image + usecases[key].middle[subkey].app_id = midinfo.id + usecases[key].middle[subkey].app_name = midinfo.name + } else { + if (usecases[key].middle[subkey].image === undefined || usecases[key].middle[subkey].image === "") { + usecases[key].middle[subkey].image = theme.palette.defaultImage + } + } + } + } + } + + //if (firstRequest) { + // setAllUsecases(usecases) + // setFirstRequest(false) + //} + } + + const configureWorkflowModal = + configureWorkflowModalOpen ? ( + + { + setConfigureWorkflowModalOpen(false); + }} + > + + + + {/* + referenceUrl={referenceUrl} + submitSchedule={submitSchedule} + appAuthentication={appAuthentication} + selectedAction={selectedAction} + saveWorkflow={saveWorkflow} + newWebhook={newWebhook} + */} + + ) : null + + + const createWorkflowFromTemplate = (data) => { + if (workflow.id !== undefined && workflow.id !== null && workflow.id.length !== 0) { + console.log("Should delete old one: ", workflow.id) + deleteWorkflow(workflow.id) + } + + // Should be searched? + setIsUploading(true) + + var changed = false + if (data.source.app_name === "") { + data.source.error = `'${data.source.type}' app must be selected first` + changed = true + } + + if (data.destination.app_name === "") { + data.destination.error = `'${data.destination.type}' app must be selected first` + changed = true + } + + if (data.middle !== undefined && data.middle !== null && data.middle.length > 0) { + for (var key in data.middle) { + const middleItem = data.middle[key] + if (middleItem.app_name === "") { + data.middle[key].error = `'${middleItem.type}' app must be selected first` + //changed = true + } + } + } + + if (changed) { + alert.error("Errors were found. Click them to sort sort them out or go to the next usecase.") + + setUpdate(Math.random()) + setIsUploading(false) + return + } + + // Auto finding these during deploy + //data.source.workflow_id = "e506060f-0c58-4f95-a0b8-f671103d78e5" + //data.destination.workflow_id = "ffe8122d-0787-425a-aa20-c2587ee75a83" + //if (data.middle !== undefined && data.middle !== null && data.middle.length > 0) { + // data.middle[0].workflow_id = "1077d9ee-b571-4410-a7e6-261f32f346c5" + //} + + mergeWorkflowUsecases(data) + } + + if (autotry === true && firstRequest === true) { + console.log("Should autotry the usecase! Make sure data is in order first. Usecase: ", usecases) + + if (usecases !== undefined && usecases !== null && usecases.length > 0 && usecases[0].name !== "") { + setFirstRequest(false) + createWorkflowFromTemplate(usecases[0]) + } + //{usecases.map((data, index) => { + } + + + const ShowBox = (props) => { + const { data, type, index, miditem, subindex } = props + + const [expanded, setexpanded] = React.useState(false) + const [newSelectedApp, setNewSelectedApp] = React.useState({}) + const [paperTitle, setPaperTitle] = React.useState(data.type) + const [selectionOpen, setSelectionOpen] = React.useState(false) + const [discoveryData, setDiscoveryData] = React.useState({ + "id": "", + "label": "", + "name": "", + "large_image": "", + }) + + //console.log("Data: ", data) + const activateApp = (appid) => { + fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Failed to activate") + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson.success === false) { + alert.error("Failed to activate the app") + } else { + //alert.success("App activated for your organization! Refresh the page to use the app.") + } + }) + .catch(error => { + //alert.error(error.toString()) + console.log("Activate app error: ", error.toString()) + }); + } + + const setFrameworkItem = (data) => { + // Making sure the app is being auto-built and added + 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!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + alert.error("Failed updating default app: " + responseJson.reason) + } else { + alert.error("Failed to update framework for your org.") + + } + } else { + if (getFramework !== undefined) { + getFramework() + } + } + + //setFrameworkLoaded(true) + //setFrameworkData(responseJson) + }) + .catch((error) => { + alert.error(error.toString()); + //setFrameworkLoaded(true) + }) + } + + useEffect(() => { + if (newSelectedApp.objectID === undefined) { + return + } + + var discoveredtype = "" + if (type === "middle") { + console.log("Updating middle index", subindex) + + allusecases[index][type][subindex]["app_id"] = newSelectedApp.objectID + allusecases[index][type][subindex]["app_name"] = newSelectedApp.name + allusecases[index][type][subindex]["app_version"] = newSelectedApp.app_version + allusecases[index][type][subindex]["image"] = newSelectedApp.image_url + allusecases[index][type][subindex]["error"] = "" + + discoveredtype = allusecases[index][type][subindex]["type"] + } else { + allusecases[index][type]["app_id"] = newSelectedApp.objectID + allusecases[index][type]["app_name"] = newSelectedApp.name + allusecases[index][type]["app_version"] = newSelectedApp.app_version + allusecases[index][type]["image"] = newSelectedApp.image_url + allusecases[index][type]["error"] = "" + + discoveredtype = allusecases[index][type]["type"] + } + + setSelectionOpen(false) + setUpdate(Math.random()) + setAllUsecases(allusecases) + + const submitValue = { + "type": discoveredtype, + "name": newSelectedApp.name, + "id": newSelectedApp.objectID, + "large_image": newSelectedApp.image_url, + "description": newSelectedApp.description, + } + + setFrameworkItem(submitValue) + }, [newSelectedApp]) + + if (data.text === undefined || data.text === null || data.text.length === 0) { + return null + } + + const changeAppType = () => { + setSelectionOpen(true) + } + + const looplist = type === "source" ? triggerlist : type === "destination" ? subflows : midflows + const hasError = data.error !== undefined && data.error !== null && data.error.length > 0 + const borderColor = hasError ? theme.palette.primary.main : "rgba(255,255,255,0.3)" + + return ( +
+ + {selectionOpen === true ? + + : null} + +
+
+ + {data.app_name} { + + if (data.type === undefined || data.type === null || data.type === "") { + setexpanded(true) + console.log("No type. Skipping window open.") + return + } + + changeAppType() + }}/> + +
+ + {data.text} + + {hasError ? + { + if (data.type === undefined || data.type === null || data.type === "") { + setexpanded(true) + console.log("No type. Skipping window open.") + return + } + + changeAppType() + }}> + {data.error} + + : + null} +
+
+
+ + + { + }} + > + + + + + + + + + {type === "middle" ? + { + allusecases[index][type] = [] + + setAllUsecases(allusecases) + setUpdate(Math.random()) + }} + > + + + : null} + + + { + setexpanded(!expanded) + }} + > + {expanded ? : } + + + +
+
+ + {expanded ? + looplist.map((subdata, curindex) => { + if (appFramework === undefined) { + subdata.image = theme.palette.defaultImage + } else { + const srctype = getType(subdata.type) + const srcinfo = appFramework[srctype] + + if (srcinfo !== undefined && srcinfo.large_image !== undefined && srcinfo.large_image !== "" && (subdata.app_name === undefined || subdata.app_name === "")) { + subdata.image = srcinfo.large_image + subdata.app_id = srcinfo.id + subdata.app_name = srcinfo.name + } else { + subdata.image = theme.palette.defaultImage + } + } + + return ( +
{ + if (subdata.disabled === true) { + //alert.info("Usecase not available yet.") + return + } + + if (type === "middle") { + allusecases[index][type][subindex] = subdata + } else { + allusecases[index][type] = subdata + } + + setAllUsecases(allusecases) + setUpdate(Math.random()) + setexpanded(false) + }}> + {subdata.app_name} { + }}/> + + {subdata.text} + +
+ ) + }) + : + null + } +
+ ) + } + + //console.log("ALLUSECASES: ", allusecases) + + return ( +
+ {configureWorkflowModal} + {authenticationModal} + {showTitle !== false && defaultSearch !== undefined ? + + {defaultSearch}: {allusecases[usecaseIndex].name} + + : null} + {usecases.map((data, index) => { + return ( +
+ + + {data.middle !== undefined && data.middle !== null && data.middle.length > 0 ? + data.middle.map((innerdata, innerindex) => { + return ( +
+
+ +
+ ) + }) + : null} + +
+ {defaultSearch !== undefined && data.middle.length === 0 ? + + { + console.log("Click add middle!") + + allusecases[index].middle.push(midflows[0]) + setAllUsecases(allusecases) + setUpdate(Math.random()) + }} + > + + +
+ + : null} + + + {showTitle !== false && workflow.id === undefined ? + + : null} + {workflow.id === undefined || defaultSearch === undefined ? + null + : + + + + } +
+ ) + })} +
+ ) +} + +export default UsecaseSearch diff --git a/frontend/src/components/WelcomeForm2.jsx b/frontend/src/components/WelcomeForm2.jsx new file mode 100644 index 00000000..a3ccf86d --- /dev/null +++ b/frontend/src/components/WelcomeForm2.jsx @@ -0,0 +1,830 @@ +import React, { useState, useEffect } from "react"; +import ReactGA from 'react-ga'; +import Button from "@material-ui/core/Button"; +import Checkbox from '@mui/material/Checkbox'; + +import AliceCarousel from 'react-alice-carousel'; +import 'react-alice-carousel/lib/alice-carousel.css'; + +import SearchIcon from '@mui/icons-material/Search'; +import EmailIcon from '@mui/icons-material/Email'; +import NewReleasesIcon from '@mui/icons-material/NewReleases'; +import ExtensionIcon from '@mui/icons-material/Extension'; +import LightbulbIcon from '@mui/icons-material/Lightbulb'; +import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; +import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; + +import theme from '../theme'; +import { + Fade, + IconButton, + FormGroup, + FormControl, + InputLabel, + FormLabel, + FormControlLabel, + Select, + MenuItem, + Grid, + Paper, + Typography, + TextField, + Zoom, + List, + ListItem, + ListItemText, + Divider, + Tooltip, + Chip, +} from "@material-ui/core"; +import { useAlert } from "react-alert"; + +import { useNavigate, Link } from "react-router-dom"; +import WorkflowSearch from '../components/Workflowsearch.jsx'; +import AuthenticationItem from '../components/AuthenticationItem.jsx'; +import WorkflowPaper from "../components/WorkflowPaper.jsx" +import UsecaseSearch from "../components/UsecaseSearch.jsx" + + +const responsive = { + 0: { items: 1 }, +}; + +const WelcomeForm = (props) => { + const { userdata, globalUrl, discoveryWrapper, setDiscoveryWrapper, appFramework, getFramework, activeStep, setActiveStep, steps, skipped, setSkipped, getApps, apps, handleSetSearch, usecaseButtons, defaultSearch, setDefaultSearch, selectionOpen, setSelectionOpen, } = props + + const usecaseItems = [ +
+ +
+ , +
+ +
+ , +
+ +
+ , +
+ +
+ ] + + const [discoveryData, setDiscoveryData] = React.useState({}) + const [name, setName] = React.useState("") + const [orgName, setOrgName] = React.useState("") + const [role, setRole] = React.useState("") + const [orgType, setOrgType] = React.useState("") + const [finishedApps, setFinishedApps] = React.useState([]) + const [authentication, setAuthentication] = React.useState([]); + const [newSelectedApp, setNewSelectedApp] = React.useState({}) + const [thumbIndex, setThumbIndex] = useState(0); + const [thumbAnimation, setThumbAnimation] = useState(false); + const [clickdiff, setclickdiff] = useState(0); + + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + + const alert = useAlert(); + let navigate = useNavigate(); + + const onNodeSelect = (label) => { + if (setDiscoveryWrapper !== undefined) { + setDiscoveryWrapper( + {"id": label} + ) + } + + setSelectionOpen(true) + setDefaultSearch(label) + } + + useEffect(() => { + if (userdata.id === undefined) { + return + } + + if (userdata.name !== undefined && userdata.name !== null && userdata.name.length > 0) { + setName(userdata.name) + } + + if (userdata.active_org !== undefined && userdata.active_org.name !== undefined && userdata.active_org.name !== null && userdata.active_org.name.length > 0) { + setOrgName(userdata.active_org.name) + } + }, [userdata]) + + useEffect(() => { + if (discoveryWrapper === undefined || discoveryWrapper.id === undefined) { + setDefaultSearch("") + var newfinishedApps = finishedApps + newfinishedApps.push(defaultSearch) + setFinishedApps(finishedApps) + } + }, [discoveryWrapper]) + + useEffect(() => { + if ( + window.location.search !== undefined && + window.location.search !== null + ) { + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundTab = params["tab"]; + if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) { + if (foundTab === 3 || foundTab === "3") { + //console.log("Set search!") + } + } else { + //navigate(`/welcome?tab=1`) + } + } + }, []) + + const isStepOptional = step => { + return step === 1 + } + + const sendUserUpdate = (name, role, userId) => { + const data = { + "tutorial": "welcome", + "firstname": name, + "company_role": role, + "user_id": userId, + } + + const url = `${globalUrl}/api/v1/users/updateuser` + fetch(url, { + mode: "cors", + method: "PUT", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + console.log("Update user success") + //alert.error("Failed updating org: ", responseJson.reason); + } else { + console.log("Update success!") + //alert.success("Successfully edited org!"); + } + }) + ) + .catch((error) => { + console.log("Update err: ", error.toString()) + //alert.error("Err: " + error.toString()); + }); + } + + const sendOrgUpdate = (orgname, company_type, orgId, priority) => { + var data = { + org_id: orgId, + }; + + if (orgname.length > 0) { + data.name = orgname + } + + if (company_type.length > 0) { + data.company_type = company_type + } + + if (priority.length > 0) { + data.priority = priority + } + + const url = globalUrl + `/api/v1/orgs/${orgId}`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + console.log("Update of org failed") + //alert.error("Failed updating org: ", responseJson.reason); + } else { + //alert.success("Successfully edited org!"); + } + }) + ) + .catch((error) => { + console.log("Update err: ", error.toString()) + //alert.error("Err: " + error.toString()); + }); + } + + var workflowDelay = -50 + const NewHits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + const paperAppContainer = { + display: "flex", + flexWrap: "wrap", + alignContent: "space-between", + marginTop: 5, + } + + return ( + + {hits.map((data, index) => { + workflowDelay += 50 + + if (index > 3) { + return null + } + + return ( + + + + + + ) + })} + + ) + } + + const isStepSkipped = step => { + return skipped.has(step) + } + + const handleNext = () => { + setDefaultSearch("") + + if (activeStep === 0) { + console.log("Should send basic information about org (fetch)") + setclickdiff(240) + navigate(`/welcome?tab=2`) + + if (isCloud) { + ReactGA.event({ + category: "welcome", + action: "click_page_one_next", + label: "", + }) + } + + if (userdata.active_org !== undefined && userdata.active_org.id !== undefined && userdata.active_org.id !== null && userdata.active_org.id.length > 0) { + sendOrgUpdate(orgName, orgType, userdata.active_org.id, "") + } + + if (userdata.id !== undefined && userdata.id !== null && userdata.id.length > 0) { + sendUserUpdate(name, role, userdata.id) + } + + } else if (activeStep === 1) { + console.log("Should send secondary info about apps and other things") + setDiscoveryWrapper({}) + + navigate(`/welcome?tab=3`) + //handleSetSearch("Enrichment", "2. Enrich") + handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase) + getApps() + + // Make sure it's up to date + if (getFramework !== undefined) { + getFramework() + } + } else if (activeStep === 2) { + console.log("Should send third page with workflows activated and the like") + } + + + let newSkipped = skipped; + if (isStepSkipped(activeStep)) { + newSkipped = new Set(newSkipped.values()); + newSkipped.delete(activeStep); + } + + setActiveStep(prevActiveStep => prevActiveStep + 1); + setSkipped(newSkipped); + } + + const handleBack = () => { + setActiveStep(prevActiveStep => prevActiveStep - 1); + + if (activeStep === 2) { + setDiscoveryWrapper({}) + + if (getFramework !== undefined) { + getFramework() + } + navigate("/welcome?tab=2") + } else if (activeStep === 1) { + navigate("/welcome?tab=1") + } + }; + + const handleSkip = () => { + setclickdiff(240) + if (!isStepOptional(activeStep)) { + throw new Error("You can't skip a step that isn't optional."); + } + setActiveStep(prevActiveStep => prevActiveStep + 1); + setSkipped(prevSkipped => { + const newSkipped = new Set(prevSkipped.values()); + newSkipped.add(activeStep); + return newSkipped; + }); + }; + + const handleReset = () => { + setActiveStep(0); + }; + + useEffect(() => { + console.log("Selected app changed (effect)") + }, [newSelectedApp]) + + //const buttonWidth = 145 + const buttonWidth = 450 + const buttonMargin = 10 + const sizing = 475 + const buttonStyle = { + flex: 1, + width: "100%", + padding: 25, + margin: buttonMargin, + fontSize: 18, + } + + const slideNext = () => { + if (!thumbAnimation && thumbIndex < usecaseItems.length - 1) { + //handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase) + setThumbIndex(thumbIndex + 1); + } else if (!thumbAnimation && thumbIndex === usecaseItems.length - 1) { + setThumbIndex(0) + } + }; + + const slidePrev = () => { + if (!thumbAnimation && thumbIndex > 0) { + setThumbIndex(thumbIndex - 1); + } else if (!thumbAnimation && thumbIndex === 0) { + setThumbIndex(usecaseItems.length-1) + } + }; + + const newButtonStyle = { + padding: 22, + flex: 1, + margin: buttonMargin, + minWidth: buttonWidth, + maxWidth: buttonWidth, + } + + const getStepContent = (step) => { + switch (step) { + case 0: + return ( + + + {/*isCloud ? null : + + This data will be used within the product and NOT be shared unless cloud synchronization is configured. + + */} + + In order to understand how we best can help you find relevant Usecases, please provide the information below. This is optional, but highly encouraged. + + + { + setName(e.target.value) + }} + /> + + + { + setOrgName(e.target.value) + }} + /> + + + + Your Role + + + + + + Company Type + + + + + + ) + case 1: + return ( + +
+ + Clicks the buttons below to find your apps, then we will help you find relevant workflows. Can't find your app? { + if (window.drift !== undefined) { + window.drift.api.startInteraction({ interactionId: 340043 }) + } else { + console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) + } + }}>Contact our App Developers! + + {/*The app framework helps us access and authenticate the most important APIs for you. */} + + {/* + + + What is your development experience? + + + + */} + + {/*Find your integrations!*/} +
+ +
+
+ + +
+
+ + +
+ {/* + What do you want to automate first ? + + { onNodeSelect("Email") }} />} + label="Email" + labelPlacement="Email" + /> + { onNodeSelect("SIEM") }} />} + label="SIEM" + labelPlacement="SIEM" + /> + { onNodeSelect("EDR") }} />} + label="EDR" + labelPlacement="EDR" + /> + + */} +
+ {/* + + + What tools do you use? + + + + */} +
+
+ ) + case 2: + return ( + +
+ + These are some of our Workflow templates, used to start new Workflows. Use the right and left buttons to find new Usecases, and click the orange button to build it. + + {/**/} + {/* +
+ {usecaseButtons.map((usecase, index) => { + + return ( + { + console.log("Clicked: ", usecase.name) + if (defaultSearch === usecase.name) { + //setSelectedUsecaseCategory("") + } else { + handleSetSearch(usecase.name, usecase.usecase) + } + //addFilter(usecase.name.slice(3,usecase.name.length)) + }} + variant="outlined" + color="primary" + /> + ) + })} +
+ */} +
+ {/* + + */} + +
+ + { + slidePrev() + }} + > + + + +
+ +
+ + { + slideNext() + }} + > + + + +
+
+
+
+ ) + default: + return "unknown step" + } + } + + return ( +
+ {/*selectionOpen ? + + : null*/} +
+ {activeStep === steps.length ? ( +
+ You Will be Redirected to getting Start Page Wait for 5-sec. + + + +
+ ) : ( +
+ {getStepContent(activeStep)} +
+ {activeStep === 2 || activeStep === 1 ? +
+ + +
+ : +
+ + {/*isStepOptional(activeStep) && ( + + )*/} + + {activeStep === 0 ? + + : null} +
+ } +
+ )} +
+
+ ); +} + +export default WelcomeForm diff --git a/frontend/src/components/WorkflowGrid.jsx b/frontend/src/components/WorkflowGrid.jsx new file mode 100644 index 00000000..c086790d --- /dev/null +++ b/frontend/src/components/WorkflowGrid.jsx @@ -0,0 +1,370 @@ +import React, { useEffect, useState } from 'react'; + +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 } from 'react-instantsearch-dom'; +import { + Grid, + Paper, + TextField, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip, + Zoom, + Chip, +} from '@material-ui/core'; + +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 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 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 | Workflows | Discover your use-case" + + 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 handleKeysetting = (categorydata, workflows) => { + console.log("Workflows: ", workflows) + //workflows[0].category = ["detect"] + //workflows[0].usecase_ids = ["Correlate tickets"] + + if (workflows !== undefined && workflows !== null) { + const newcategories = [] + for (var key in categorydata) { + var category = categorydata[key] + category.matches = [] + + for (var subcategorykey in category.list) { + var subcategory = category.list[subcategorykey] + subcategory.matches = [] + + for (var workflowkey in workflows) { + const workflow = workflows[workflowkey] + + if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) { + for (var usecasekey in workflow.usecase_ids) { + if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { + console.log("Got match: ", workflow.usecase_ids[usecasekey]) + + category.matches.push({ + "workflow": workflow.id, + "category": subcategory.name, + }) + subcategory.matches.push(workflow.id) + break + } + } + } + + if (subcategory.matches.length > 0) { + break + } + } + } + + newcategories.push(category) + } + + console.log("Categories: ", newcategories) + setUsecases(newcategories) + } else { + for (var key in categorydata) { + categorydata[key].matches = [] + } + setUsecases(categorydata) + } + } + + const fetchUsecases = (workflows) => { + fetch(globalUrl + "/api/v1/workflows/usecases", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for usecases"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + console.log("Usecases: ", responseJson) + //handleKeysetting(responseJson, workflows) + } + }) + .catch((error) => { + //alert.error("ERROR: " + error.toString()); + console.log("ERROR: " + error.toString()); + }); + }; + + useEffect(() => { + fetchUsecases() + }, []) + + // 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) { + console.log("Got query: ", foundQuery) + refine(foundQuery) + } + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='off' + type="search" + color="primary" + value={currentRefinement} + placeholder="Find Workflows..." + id="shuffle_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + const paperAppContainer = { + display: "flex", + flexWrap: "wrap", + alignContent: "space-between", + marginTop: 5, + } + + var workflowDelay = -50 + const Hits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + return ( + + {hits.map((data, index) => { + workflowDelay += 50 + + if (counted === 12/xs*rowHandler) { + return null + } + + counted += 1 + + return ( + + + {alternativeView === true ? + + : + + } + + + ) + })} + + ) + } + + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(Hits) + + return ( +
+ + +
+ +
+ {usecases !== null && usecases !== undefined && usecases.length > 0 ? +
+ {usecases.map((usecase, index) => { + console.log(usecase) + return ( + { + console.log("Clicked!") + //addFilter(usecase.name.slice(3,usecase.name.length)) + }} + variant="outlined" + color="primary" + /> + ) + })} +
+ : null} + +
+ {showSuggestion === true ? +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + {formMessage} +
+ : null + } + + + + Search by + + + Algolia logo + + +
+ ) +} + +export default AppGrid; diff --git a/frontend/src/components/WorkflowGridNew.jsx b/frontend/src/components/WorkflowGridNew.jsx new file mode 100644 index 00000000..f0a06e87 --- /dev/null +++ b/frontend/src/components/WorkflowGridNew.jsx @@ -0,0 +1,291 @@ +import React, { useState, useEffect, useLayoutEffect } from "react"; +import theme from '../theme'; + +import { + Chip, + Typography, + Paper, + Avatar, + Grid, + Tooltip, +} from "@material-ui/core"; + +import { + AvatarGroup, +} from "@mui/material" + +import { + Restore as RestoreIcon, + Edit as EditIcon, + BubbleChart as BubbleChartIcon, + MoreVert as MoreVertIcon, +} from '@material-ui/icons'; + +import { useNavigate, Link, useParams } from "react-router-dom"; + +const workflowActionStyle = { + display: "flex", + width: 160, + height: 44, + justifyContent: "space-between", +} + +const paperAppStyle = { + minHeight: 130, + maxHeight: 130, + overflow: "hidden", + width: "100%", + color: "white", + backgroundColor: theme.palette.surfaceColor, + padding: "12px 12px 0px 15px", + borderRadius: 5, + display: "flex", + boxSizing: "border-box", + position: "relative", +} + +const chipStyle = { + backgroundColor: "#3d3f43", + marginRight: 5, + paddingLeft: 5, + paddingRight: 5, + height: 28, + cursor: "pointer", + borderColor: "#3d3f43", + color: "white", +} + +const WorkflowPaper = (props) => { + const { data } = props; + let navigate = useNavigate(); + + const [open, setOpen] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + const appGroup = data.action_references === undefined || data.action_references === null ? [] : data.action_references + + //console.log("Workflow: ", data) + var boxColor = "#86c142"; + + var parsedName = data.name; + if ( + parsedName !== undefined && + parsedName !== null && + parsedName.length > 20 + ) { + parsedName = parsedName.slice(0, 21) + ".."; + } + + + const imageStyle = { + width: 24, + height: 24, + marginRight: 10, + border: "1px solid rgba(255,255,255,0.3)", + } + var image = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.image !== undefined && data.creator_info.image !== null && data.creator_info.image.length > 0 ? : + const creatorname = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.username !== undefined && data.creator_info.username !== null && data.creator_info.username.length > 0 ? data.creator_info.username : "" + var orgName = ""; + var orgId = ""; + if ((data.objectID === undefined || data.objectID === null) && data.id !== undefined && data.id !== null) { + data.objectID = data.id + } + + //console.log("IMG: ", data) + var parsedUrl = `/workflows/${data.objectID}` + if (data.__queryID !== undefined && data.__queryID !== null) { + parsedUrl += `?queryID=${data.__queryID}` + } + + return ( +
+ +
+ + + +
{ + if (data.creator_info !== undefined) { + navigate("/creators/"+data.creator_info.username) + } + }} + > + {image} +
+
+ + + + {parsedName} + + + +
+ + {appGroup.length > 0 ? +
+ + {appGroup.map((app, index) => { + return ( +
{ + navigate("/apps/"+app.id) + }} + > + + + +
+ ) + })} +
+
+ : + + + + + {data.actions === undefined || data.actions === null ? 1 : data.actions.length} + + + + } + + + + + {data.triggers === undefined || data.triggers === null ? 1 : data.triggers.length} + + + + + { + }} + > + + + + + {0} + + + +
+ + {data.tags !== undefined && data.tags !== null + ? data.tags.map((tag, index) => { + if (index >= 3) { + return null; + } + + return ( + + ); + }) + : null} + +
+ +
+ ) + } + +export default WorkflowPaper diff --git a/frontend/src/components/WorkflowPaper.jsx b/frontend/src/components/WorkflowPaper.jsx index 5d3b68cb..558de437 100644 --- a/frontend/src/components/WorkflowPaper.jsx +++ b/frontend/src/components/WorkflowPaper.jsx @@ -63,6 +63,10 @@ const WorkflowPaper = (props) => { const [anchorEl, setAnchorEl] = React.useState(null); const appGroup = data.action_references === undefined || data.action_references === null ? [] : data.action_references + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + //console.log("Workflow: ", data) var boxColor = "#86c142"; @@ -91,6 +95,14 @@ const WorkflowPaper = (props) => { } //console.log("IMG: ", data) + var parsedUrl = `/workflows/${data.objectID}` + if (data.__queryID !== undefined && data.__queryID !== null) { + parsedUrl += `?queryID=${data.__queryID}` + } + + if (!isCloud) { + parsedUrl = `https://shuffler.io${parsedUrl}` + } return (
@@ -133,12 +145,14 @@ const WorkflowPaper = (props) => { flex: 10, }} > - {parsedName} - + diff --git a/frontend/src/components/WorkflowPaperNew.jsx b/frontend/src/components/WorkflowPaperNew.jsx new file mode 100644 index 00000000..17fb67e5 --- /dev/null +++ b/frontend/src/components/WorkflowPaperNew.jsx @@ -0,0 +1,332 @@ +import React, { useState, useEffect, useLayoutEffect } from "react"; +import theme from '../theme'; + +import { + Chip, + Typography, + Paper, + Avatar, + Grid, + Tooltip, + Button, +} 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 + + const activateWorkflow = (workflow) => { + console.log("Should activate: ", workflow) + + } + + //console.log("Workflow: ", data) + var boxColor = "#86c142"; + + var parsedName = data.name; + if ( + parsedName !== undefined && + parsedName !== null && + parsedName.length > 35 + ) { + parsedName = parsedName.slice(0, 36) + ".."; + } + + const imageStyle = { + width: 28, + height: 28, + marginRight: 10, + border: "1px solid rgba(255,255,255,0.3)", + } + + var image = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.image !== undefined && data.creator_info.image !== null && data.creator_info.image.length > 0 ? : + const creatorname = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.username !== undefined && data.creator_info.username !== null && data.creator_info.username.length > 0 ? data.creator_info.username : "Shuffle" + 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}` + } + + const paperImgStyle = { + height: 150, + width: "100%", + backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", + color: "white", + position: "relative", + borderRadius: "10px 10px 0% 0%", + } + + const bgImage1 = "https://avatars.githubusercontent.com/u/5719530?v=4" + const bgImage2 = "https://avatars.githubusercontent.com/u/5719530?v=4" + const itemSize = 70 + + return ( +
+
+
+ Image alt +
+
+ Image alt +
+
+ +
+ + + +
{ + if (data.creator_info !== undefined) { + navigate("/creators/"+data.creator_info.username) + } + }} + > + {image} +
+
+ + + + {parsedName} + + + +
+ + {/* + {appGroup.length > 0 ? +
+ + {appGroup.map((app, index) => { + return ( +
{ + navigate("/apps/"+app.id) + }} + > + + + +
+ ) + })} +
+
+ : + + + + + {data.actions === undefined || data.actions === null ? 1 : data.actions.length} + + + + } + */} + {/* + + + + + {data.triggers === undefined || data.triggers === null ? 1 : data.triggers.length} + + + + + { + }} + > + + + + + {0} + + + + */} +
+ {/* + + {data.tags !== undefined && data.tags !== null + ? data.tags.map((tag, index) => { + if (index >= 3) { + return null; + } + + return ( + + ); + }) + : null} + + */} + + +
+ +
+ ) + } + +export default WorkflowPaper diff --git a/frontend/src/components/Workflowsearch.jsx b/frontend/src/components/Workflowsearch.jsx index e69de29b..73539521 100644 --- a/frontend/src/components/Workflowsearch.jsx +++ b/frontend/src/components/Workflowsearch.jsx @@ -0,0 +1,216 @@ +import React, { useState, useEffect } from 'react'; + +import ReactGA from 'react-ga'; +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 algoliasearch from 'algoliasearch'; +import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; +import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core'; + +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const WorkflowSearch = props => { + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, selectAble, } = 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(""); + const [message, setMessage] = React.useState(""); + const [formMessage, setFormMessage] = React.useState(""); + const [selectedApp, setSelectedApp] = 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 integration 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) + }); + } + + // value={currentRefinement} + const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { + useEffect(() => { + //console.log("FIRST LOAD ONLY? RUN REFINEMENT: !", currentRefinement) + if (defaultSearch !== undefined && defaultSearch !== null) { + refine(defaultSearch) + } + }, []) + + return ( +
+ + + + ), + }} + autoComplete='on' + type="search" + color="primary" + defaultValue={defaultSearch} + placeholder={`Find ${defaultSearch} Workflows...`} + id="shuffle_workflow_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + //value={currentRefinement} + } + + if (selectAble === true) { + console.log("Make it possible to select a Workflow!!") + } + + const Hits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + return ( + + {hits.map((data, index) => { + 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: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e", + textAlign: "left", + padding: 10, + cursor: "pointer", + position: "relative", + overflow: "hidden", + width: "100%", + } + + if (counted === 12/xs*rowHandler) { + return null + } + + counted += 1 + var parsedname = "" + for (var key = 0; key < data.name.length; key++) { + var character = data.name.charAt(key) + if (character === character.toUpperCase()) { + //console.log(data.name[key], data.name[key+1]) + if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) { + } else { + parsedname += " " + } + } + + parsedname += character + } + + parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ") + + return ( + { + setMouseHoverIndex(index) + /* + ReactGA.event({ + category: "app_grid_view", + action: `search_bar_click`, + label: "", + }) + */ + }} onMouseOut={() => { + setMouseHoverIndex(-1) + }} onClick={() => { + setNewSelectedApp(data) + //if (data.objectID !== data.objectID) { + //} + + //ReactGA.event({ + // category: "app_search", + // action: `app_${parsedname}_${data.id}_click`, + // label: "", + //}) + }}> +
+ {/*{data.name}*/} + + {parsedname} + +
+
+ ) + })} +
+ ) + } + + const InputHits = ConfiguredHits === undefined ? Hits : ConfiguredHits + const CustomSearchBox = connectSearchBox(SearchBox) + const CustomHits = connectHits(InputHits) + + return ( +
+ + {/* showSearch === false ? null : +
+ +
+ */} +
+ +
+ +
+
+ ) +} + +export default WorkflowSearch; diff --git a/frontend/src/defaultCytoscapeStyle.js b/frontend/src/defaultCytoscapeStyle.js index ba7891ea..35c4b7f8 100644 --- a/frontend/src/defaultCytoscapeStyle.js +++ b/frontend/src/defaultCytoscapeStyle.js @@ -57,6 +57,7 @@ const data = [ padding: "0px", margin: "0px", "background-color": "data(backgroundcolor)", + "background-image": "data(backgroundimage)", "border-color": "#ffffff", "text-margin-x": "0px", "z-index": 4999, @@ -163,8 +164,8 @@ const data = [ selector: "node[?isSuggestion]", css: { shape: "ellipse", - width: "30px", - height: "30px", + width: "50px", + height: "50px", "z-index": "5002", "font-size": "0px", border: "1px solid rgba(255,255,255,0.9)", @@ -173,6 +174,15 @@ const data = [ label: "data(label)", }, }, + { + selector: "node[?canConnect]", + css: { + "border-color": "#f86a3e", + "border-width": "10px", + "z-index": "5002", + "background-color": "#f86a3e", + }, + }, { selector: "node[?isDescriptor]", css: { diff --git a/frontend/src/theme.js b/frontend/src/theme.js index 73d23933..ad6ca44e 100644 --- a/frontend/src/theme.js +++ b/frontend/src/theme.js @@ -15,6 +15,7 @@ const theme = createMuiTheme({ type: "dark", surfaceColor: "#27292d", inputColor: "#383B40", + platformColor: "#1F2023", borderRadius: 5, defaultBorder: "1px solid rgba(255,255,255,0.3)", jsonTheme: "brewer", diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 11315745..b6284821 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -3,13 +3,20 @@ import React, { useState, useEffect } from "react"; import { makeStyles } from "@material-ui/styles"; import { useTheme } from "@material-ui/core/styles"; import { useNavigate, Link } from "react-router-dom"; +import countries from "../components/Countries.jsx"; +import CodeEditor from "../components/ShuffleCodeEditor.jsx"; +import getLocalCodeData from "../components/ShuffleCodeEditor.jsx"; + +import AddIcon from "@mui/icons-material/Add"; +import ClearIcon from '@mui/icons-material/Clear'; +//import ToggleButton from '@mui/material/ToggleButton'; import { FormControl, InputLabel, Paper, - OutlinedInput, - Checkbox, + OutlinedInput, + Checkbox, Card, Tooltip, FormControlLabel, @@ -36,8 +43,11 @@ import { DialogActions, DialogContent, CircularProgress, + Box, } from "@material-ui/core"; +import { Autocomplete } from "@mui/material"; + import { Edit as EditIcon, FileCopy as FileCopyIcon, @@ -65,6 +75,7 @@ import { useAlert } from "react-alert"; import Dropzone from "../components/Dropzone"; import HandlePayment from "./HandlePayment"; import OrgHeader from "../components/OrgHeader.jsx"; +import { display, style } from "@mui/system"; const useStyles = makeStyles({ notchedOutline: { @@ -81,18 +92,43 @@ const MenuProps = { width: 500, }, }, - getContentAnchorEl: () => null, -} + getContentAnchorEl: () => null, +}; + + +const FileCategoryInput = (props) => { + const isSet = props.isSet; + console.log("inside filecategoryinput"); + console.log("isset value" , isSet); + if (isSet){ + return ( + + )} + } const Admin = (props) => { - const { globalUrl, userdata, serverside} = props; + const { globalUrl, userdata, serverside } = props; var upload = ""; var to_be_copied = ""; const theme = useTheme(); const classes = useStyles(); - let navigate = useNavigate(); + let navigate = useNavigate(); const [firstRequest, setFirstRequest] = React.useState(true); const [orgRequest, setOrgRequest] = React.useState(true); @@ -105,7 +141,9 @@ const Admin = (props) => { const [loading, setLoading] = React.useState(false); const [selectedOrganization, setSelectedOrganization] = React.useState({}); - //console.log("Selected: ", selectedOrganization) + const [selectedDealModalOpen, setSelectedDealModalOpen] = + React.useState(false); + //console.log("Selected: ", selectedOrganization) const [organizationFeatures, setOrganizationFeatures] = React.useState({}); const [loginInfo, setLoginInfo] = React.useState(""); const [curTab, setCurTab] = React.useState(0); @@ -140,6 +178,19 @@ const Admin = (props) => { const [secret2FA, setSecret2FA] = React.useState(""); const [show2faSetup, setShow2faSetup] = useState(false); + const [dealName, setDealName] = React.useState(""); + const [dealAddress, setDealAddress] = React.useState(""); + const [dealType, setDealType] = React.useState("MSSP"); + const [dealCountry, setDealCountry] = React.useState("United States"); + const [dealCurrency, setDealCurrency] = React.useState("USD"); + const [dealStatus, setDealStatus] = React.useState("initiated"); + const [dealValue, setDealValue] = React.useState(""); + const [dealDiscount, setDealDiscount] = React.useState(""); + const [dealerror, setDealerror] = React.useState(""); + const [dealList, setDealList] = React.useState([]); + + const [fileContent, setFileContent] = React.useState(""); + useEffect(() => { if (isDropzone) { //redirectOpenApi(); @@ -151,6 +202,46 @@ const Admin = (props) => { window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + const [openEditor, setOpenEditor] = React.useState(false); + const [renderTextBox, setRenderTextBox] = React.useState(false); + const [openFileId, setOpenFileId] = React.useState(false); + const allowedFileTypes = ["txt", "py", "yaml","yml","json"] + + const runUpdateText = (text) =>{ + fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body:text, + credentials: "include", + }).then((response) => { + if (response.status !== 200) { + console.log("Can't update file"); + } + return response.json(); + }) + //console.log(text); + } + + const handleKeyDown = (event) => { + if (event.key === 'Enter') { + + console.log('do validate') + console.log("new namespace name->",event.target.value); + fileNamespaces.push(event.target.value); + setSelectedNamespace(event.target.value); + setRenderTextBox(false); + } + if (event.key === 'Escape'){ // not working for some reasons + console.log('escape pressed') + setRenderTextBox(false); + } + + } + + const get2faCode = (userId) => { fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, { method: "GET", @@ -258,7 +349,6 @@ const Admin = (props) => { }) .then((response) => response.json().then((responseJson) => { - console.log("RESP: ", responseJson); if (responseJson["success"] === false) { alert.error("Failed deleting auth"); } else { @@ -565,11 +655,13 @@ const Admin = (props) => { alert.error("Failed creating suborg. Please try again"); } } else { - alert.success("Successfully created suborg. Reloading in 3 seconds!"); + alert.success( + "Successfully created suborg. Reloading in 3 seconds!" + ); setSelectedUserModalOpen(false); setTimeout(() => { - window.location.reload() + window.location.reload(); }, 2500); } @@ -648,6 +740,47 @@ const Admin = (props) => { }); }; + const handleGetDeals = (orgId) => { + console.log("Get deals!"); + + if (orgId.length === 0) { + alert.error( + "Organization ID not defined (get deals). Please contact us on https://shuffler.io if this persists logout." + ); + return; + } + + const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Bad status code in get deals: ", response.status); + } + + return response.json(); + }) + .then((responseJson) => { + console.log("Got deals: ", responseJson); + if (responseJson.success === false) { + alert.error("Failed loading deals. Contact support if this persists"); + } else { + setDealList(responseJson); + } + }) + .catch((error) => { + console.log("Error getting org deals: ", error); + alert.error( + "Failed getting deals for your org. Contact support if this persists." + ); + }); + }; + const handleGetOrg = (orgId) => { if (orgId.length === 0) { alert.error( @@ -674,7 +807,7 @@ const Admin = (props) => { }) .then((responseJson) => { if (responseJson["success"] === false) { - alert.error("Failed getting org: ", responseJson.readon); + alert.error("Failed getting your org: ", responseJson.readon); } else { if ( responseJson.sync_features === undefined || @@ -682,7 +815,16 @@ const Admin = (props) => { ) { responseJson.sync_features = {}; } - setSelectedOrganization(responseJson); + + if ( + isCloud && + responseJson.partner_info !== undefined && + responseJson.partner_info.reseller === true + ) { + handleGetDeals(orgId); + } + + setSelectedOrganization(responseJson) var lists = { active: { triggers: [], @@ -713,7 +855,7 @@ const Admin = (props) => { }; const inviteUser = (data) => { - console.log("INPUT: ", data); + //console.log("INPUT: ", data); setLoginInfo(""); // Just use this one? @@ -870,18 +1012,21 @@ const Admin = (props) => { const abortEnvironmentWorkflows = (environment) => { //console.log("Aborting all workflows started >10 minutes ago, not finished"); - fetch(`${globalUrl}/api/v1/environments/${environment.id}/stop?deleteall=true`, { - method: "GET", - credentials: "include", - }) + fetch( + `${globalUrl}/api/v1/environments/${environment.id}/stop?deleteall=true`, + { + method: "GET", + credentials: "include", + } + ) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); - alert.error("Failed aborting dangling workflows") + alert.error("Failed aborting dangling workflows"); return; } else { - alert.info("Aborted all dangling workflows") - } + alert.info("Aborted all dangling workflows"); + } return response.json(); }) @@ -1012,7 +1157,7 @@ const Admin = (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.") + alert.error("File was created, but failed to upload."); return; } @@ -1023,7 +1168,7 @@ const Admin = (props) => { //setFiles(responseJson) }) .catch((error) => { - alert.error(error.toString()) + alert.error(error.toString()); }); }; @@ -1034,9 +1179,14 @@ const Admin = (props) => { workflow_id: "global", }; - if (selectedNamespace !== undefined && selectedNamespace !== null && selectedNamespace.length > 0 && selectedNamespace !== "default") { - data.namespace = selectedNamespace - } + if ( + selectedNamespace !== undefined && + selectedNamespace !== null && + selectedNamespace.length > 0 && + selectedNamespace !== "default" + ) { + data.namespace = selectedNamespace; + } fetch(globalUrl + "/api/v1/files/create", { method: "POST", @@ -1064,7 +1214,7 @@ const Admin = (props) => { } }) .catch((error) => { - alert.error("Failed to upload file ", filename) + alert.error("Failed to upload file ", filename); console.log(error.toString()); }); }; @@ -1123,25 +1273,26 @@ const Admin = (props) => { return response.json(); }) .then((responseJson) => { - if (responseJson.success) { - alert.info("Successfully deleted file "+file.name) - - } else if (responseJson.reason !== undefined && responseJson.reason !== null) { - alert.error("Failed to delete file: " + responseJson.reason) + if (responseJson.success) { + alert.info("Successfully deleted file " + file.name); + } else if ( + responseJson.reason !== undefined && + responseJson.reason !== null + ) { + alert.error("Failed to delete file: " + responseJson.reason); + } + setTimeout(() => { + getFiles(); + }, 1500); - } - setTimeout(() => { - getFiles(); - }, 1500); - - console.log(responseJson) + console.log(responseJson); }) .catch((error) => { alert.error(error.toString()); }); }; - const downloadFile = (file) => { + const readFileData = (file) => { fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { method: "GET", headers: { @@ -1152,16 +1303,56 @@ const Admin = (props) => { }) .then((response) => { if (response.status !== 200) { - console.log("Status not 200 for apps :O!"); + console.log("Status not 200 for file :O!"); return ""; } - return response.text(); }) .then((respdata) => { + // console.log("respdata ->", respdata); + // console.log("respdata type ->", typeof(respdata)); + if (respdata.length === 0) { - alert.error("Failed getting file. Is it deleted?"); - return; + alert.error("Failed getting file. Is it deleted?"); + return; + } + return respdata + }) + .then((responseData) => { + + setFileContent(responseData); + //console.log("filecontent state ",fileContent); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + var localData = ""; + + // useEffect(() => { + // console.log('confirm', fileContent); + // }, [fileContent]) + + const downloadFile = (file) => { + fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { + method: "GET", + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return ""; + } + + console.log("Resp: ", response) + + return response.blob() + }) + .then((respdata) => { + if (respdata.length === 0) { + alert.error("Failed getting file. Is it deleted?"); + return; } var blob = new Blob([respdata], { @@ -1372,9 +1563,9 @@ const Admin = (props) => { 6: "suborgs", }; const setConfig = (event, inputValue) => { - const newValue = parseInt(inputValue) + const newValue = parseInt(inputValue); - setCurTab(newValue) + setCurTab(newValue); if (newValue === 1) { document.title = "Shuffle - admin - users"; getUsers(); @@ -1397,18 +1588,13 @@ const Admin = (props) => { document.title = "Shuffle - admin"; } - console.log("NEWVALUE: ", newValue) - if (newValue === 6) { console.log("Should get apps for categories."); } - console.log("PROPS: ", props) - - navigate(`/admin?tab=${views[newValue]}`) - + navigate(`/admin?tab=${views[newValue]}`); setModalUser({}); - }; + } if (firstRequest) { setFirstRequest(false); @@ -1417,24 +1603,28 @@ const Admin = (props) => { getUsers(); } else { getSettings(); - } + } - 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 && + 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 ( selectedOrganization.id === undefined && @@ -1487,9 +1677,9 @@ const Admin = (props) => { } else { alert.success("Set the user field " + field + " to " + value); - if (field !== "suborgs") { - setSelectedUserModalOpen(false); - } + if (field !== "suborgs") { + setSelectedUserModalOpen(false); + } } }) .catch((error) => { @@ -1635,60 +1825,310 @@ const Admin = (props) => { ) : null; - const handleOrgEditChange = (event) => { - if (userdata.id === selectedUser.id) { - alert.info("Can't remove orgs from yourself") - return - } + const handleOrgEditChange = (event) => { + if (userdata.id === selectedUser.id) { + alert.info("Can't remove orgs from yourself"); + return; + } - console.log("event: ", event.target.value) - setMatchingOrganizations(event.target.value) - // Workaround for empty orgs - if (event.target.value.length === 0) { - event.target.value.push("REMOVE") - } + console.log("event: ", event.target.value); + setMatchingOrganizations(event.target.value); + // Workaround for empty orgs + if (event.target.value.length === 0) { + event.target.value.push("REMOVE"); + } - setUser(selectedUser.id, "suborgs", event.target.value) - //setUser(selectedUser.id, "suborgs", matchingOrganizations) - } + setUser(selectedUser.id, "suborgs", event.target.value); + //setUser(selectedUser.id, "suborgs", matchingOrganizations) + }; - const userOrgEdit = selectedUser.id !== undefined && selectedUser.orgs !== undefined && selectedUser.orgs !== null && selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ? - - Accessible Sub-Organizations ({selectedUser.orgs? selectedUser.orgs.length-1 : 0}) - - - : null + const userOrgEdit = + selectedUser.id !== undefined && + selectedUser.orgs !== undefined && + selectedUser.orgs !== null && + selectedOrganization.child_orgs !== undefined && + selectedOrganization.child_orgs !== null && + selectedOrganization.child_orgs.length > 0 ? ( + + + Accessible Sub-Organizations ( + {selectedUser.orgs ? selectedUser.orgs.length - 1 : 0}) + + + + ) : null; + + const products = [ + { code: "", label: "MSSP", phone: "" }, + { code: "", label: "Enterprise", phone: "" }, + { code: "", label: "Consultancy", phone: "" }, + { code: "", label: "Support", phone: "" }, + ]; + + const addDealModal = ( + { + setSelectedDealModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + + Register new deal + + +
+ { + setDealName(e.target.value); + }} + /> + { + setDealAddress(e.target.value); + }} + /> +
+
+ { + setDealValue(e.target.value); + }} + /> + option.label} + onChange={(event, newValue) => { + setDealCountry(newValue.label); + }} + renderOption={(props, option) => ( + img": { mr: 2, flexShrink: 0 } }} + {...props} + > + + {option.label} ({option.code}) +{option.phone} + + )} + renderInput={(params) => ( + + )} + /> + { + setDealType(newValue); + }} + getOptionLabel={(option) => option.label} + renderOption={(props, option) => ( + img": { mr: 2, flexShrink: 0 } }} + {...props} + > + {option.label} + + )} + renderInput={(params) => ( + + )} + /> +
+ {dealerror.length > 0 ? ( + + error registering: {dealerror} + + ) : null} +
+ + +
+
+
+ ); const editUserModal = ( { setSelectedUserModalOpen(false); - setImage2FA(""); - setSecret2FA(""); + setImage2FA(""); + setSecret2FA(""); }} PaperProps={{ style: { @@ -1786,7 +2226,7 @@ const Admin = (props) => {
)} - {userOrgEdit} + {userOrgEdit} { color="primary" disabled={selectedUser.username === userdata.username} onClick={() => { - deleteUser(selectedUser) - setSelectedUserModalOpen(false); - }} + deleteUser(selectedUser); + setSelectedUserModalOpen(false); + }} > {selectedUser.active ? "Delete from org" : "Delete from org"} @@ -1824,14 +2264,15 @@ const Admin = (props) => { run2FASetup(userdata); }} disabled={ - (selectedUser.role === "admin" && selectedUser.username !== userdata.username) + selectedUser.role === "admin" && + selectedUser.username !== userdata.username } variant="outlined" color="primary" > - { selectedUser.mfa_info !== undefined && - selectedUser.mfa_info !== null && - selectedUser.mfa_info.active === true + {selectedUser.mfa_info !== undefined && + selectedUser.mfa_info !== null && + selectedUser.mfa_info.active === true ? "Disable 2FA" : "Enable 2FA"} @@ -1939,7 +2380,7 @@ const Admin = (props) => { ) : ( - ); + ) return ( { margin: 4, backgroundColor: theme.palette.inputColor, color: "white", - minHeight: expanded ? 200 : "inherit", - maxHeight: expanded ? 200 : "inherit", + minHeight: expanded ? 250 : "inherit", + maxHeight: expanded ? 250 : "inherit", }} > @@ -1972,19 +2413,19 @@ const Admin = (props) => { {expanded ? (
- Usage:{" "} + Usage:  {props.data.limit === 0 ? ( - "Infinite" + "Unlimited" ) : ( - {props.data.usage} / {props.data.limit} + {props.data.usage} / {props.data.limit === "" ? "Unlimited" : props.data.limit} )} - + {/* Data sharing: {props.data.data_collection} - - Description: {secondary} + */} + Description: {secondary}
) : null} @@ -2115,6 +2556,62 @@ const Admin = (props) => { ); + const submitDeal = (dealName, dealAddress, dealCountry, dealValue) => { + if (dealerror.length > 0) { + setDealerror(""); + } + + const orgId = selectedOrganization.id; + const data = { + reseller_org: orgId, + name: dealName, + address: dealAddress, + country: dealCountry, + value: dealValue, + }; + + const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + setSelectedDealModalOpen(false); + alert.success( + "Added new deal! We will be in touch shortly with an update." + ); + + setDealName(""); + setDealAddress(""); + setDealValue(""); + setDealCountry("United States"); + setDealType("MSSP"); + } else { + setDealerror(responseJson.reason); + } + }) + .catch(function (error) { + //console.log("Error: ", error); + setDealerror(error.toString()); + alert.error("Failed adding deal reg: ", error); + }); + }; + const cancelSubscriptions = (subscription_id) => { console.log(selectedOrganization); const orgId = selectedOrganization.id; @@ -2188,6 +2685,22 @@ const Admin = (props) => {
) : (
+ {/* + + { + console.log("Should go to icon") + }} + > + + + + */} { + {selectedOrganization.defaults !== undefined && selectedOrganization.defaults.documentation_reference !== undefined && selectedOrganization.defaults.documentation_reference !== null && selectedOrganization.defaults.documentation_reference.includes("http") ? + + + + + + + + : null} {selectedOrganization.name.length > 0 ? ( { cloud sync @@ -2405,7 +2934,7 @@ const Admin = (props) => { - Cloud sync features + Cloud sync features (monthly usage) {selectedOrganization.sync_features === undefined || @@ -2415,11 +2944,16 @@ const Admin = (props) => { key, index ) { - if (key === "schedule") { + // unnecessary parts + if (key === "schedule" || key === "apps" || key === "updates") { return null; } const item = selectedOrganization.sync_features[key]; + if (item === null) { + return null + } + const newkey = key.replaceAll("_", " "); const griditem = { primary: newkey, @@ -2430,7 +2964,8 @@ const Admin = (props) => { ? "Not defined yet" : item.description, limit: item.limit, - usage: 0, + usage: item.usage === undefined || + item.usage === null ? 0 : item.usage, data_collection: "None", active: item.active, icon: , @@ -2451,6 +2986,190 @@ const Admin = (props) => { }} /> {isCloud && + selectedOrganization.partner_info !== undefined && + selectedOrganization.partner_info.reseller === true ? ( +
+ + Reseller dashboard + + + + + + + + + + + + + + + + + + {dealList.length === 0 ? ( + + No deals registered yet. Click "Add deal" to register one + + ) : ( + dealList.map((deal, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + return ( + + + + + + + + + + + + + ); + }) + )} + + + +
+ ) : null} + {isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ? ( @@ -2709,9 +3428,6 @@ const Admin = (props) => { ); - - - const usersView = curTab === 1 ? (
@@ -2721,6 +3437,7 @@ const Admin = (props) => { Add, edit, block or change passwords.{" "} @@ -2785,12 +3502,14 @@ const Admin = (props) => { primary="MFA" style={{ minWidth: 100, maxWidth: 100 }} /> - {selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ? - - : null} + {selectedOrganization.child_orgs !== undefined && + selectedOrganization.child_orgs !== null && + selectedOrganization.child_orgs.length > 0 ? ( + + ) : null} { > Org User - - Org Reader + Org Reader } @@ -2943,40 +3662,63 @@ const Admin = (props) => { } style={{ minWidth: 100, maxWidth: 100 }} /> - {selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ? - - : null} - + {selectedOrganization.child_orgs !== undefined && + selectedOrganization.child_orgs !== null && + selectedOrganization.child_orgs.length > 0 ? ( + + ) : null} + { setSelectedUserModalOpen(true); setSelectedUser(data); - // Find matching orgs between current org and current user's access to those orgs - if (userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 && selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0) { - console.log("In here?") - var active = [] - for (var key in userdata.orgs) { - console.log("ORG: ", userdata.orgs[key]) - const found = selectedOrganization.child_orgs.find(item => item.id === userdata.orgs[key].id) - if (found !== null && found !== undefined) { + // Find matching orgs between current org and current user's access to those orgs + if ( + userdata.orgs !== undefined && + userdata.orgs !== null && + userdata.orgs.length > 0 && + selectedOrganization.child_orgs !== undefined && + selectedOrganization.child_orgs !== null && + selectedOrganization.child_orgs.length > 0 + ) { + console.log("In here?"); + var active = []; + for (var key in userdata.orgs) { + console.log("ORG: ", userdata.orgs[key]); + const found = + selectedOrganization.child_orgs.find( + (item) => item.id === userdata.orgs[key].id + ); + if (found !== null && found !== undefined) { + if ( + data.orgs === undefined || + data.orgs === null + ) { + continue; + } - if (data.orgs === undefined || data.orgs === null) { - continue - } + const subfound = data.orgs.find( + (item) => item === found.id + ); + if ( + subfound !== null && + subfound !== undefined + ) { + active.push(subfound); + } + } + } - const subfound = data.orgs.find(item => item === found.id) - if (subfound !== null && subfound !== undefined) { - active.push(subfound) - } - } - } - - setMatchingOrganizations(active) - } + setMatchingOrganizations(active); + } }} > @@ -3005,8 +3747,8 @@ const Admin = (props) => { get2faCode(data.id); } else { // Should remove? - setImage2FA(""); - setSecret2FA(""); + setImage2FA(""); + setSecret2FA(""); } setShow2faSetup(!show2faSetup); @@ -3054,6 +3796,7 @@ const Admin = (props) => { uploadFiles(files); }; + const filesView = curTab === 3 ? ( { Files from Workflows.{" "} @@ -3087,6 +3831,8 @@ const Admin = (props) => { > Upload files + {/* */} { {fileNamespaces !== undefined && fileNamespaces !== null && fileNamespaces.length > 1 ? ( - + File Category ) : null} +
+ {renderTextBox ? + + + + + : + + + } + {renderTextBox && { + handleKeyDown(event); + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + placeholder="File category name" + required + margin="dense" + defaultValue={""} + autoFocus + />}
+ + { backgroundColor: theme.palette.inputColor, }} /> + { bgColor = "#1f2023"; } + const isDisabledButton = isCloud || file.filesize < 100000 && file.status === ("active") && allowedFileTypes.includes(file.filename.split(".")[1]) === true + return ( - + { }} /> - - + primary= + + + { + setOpenEditor(true) + setOpenFileId(file.id) + readFileData(file) + }} + > + + + + + + + { + downloadFile(file); + }} + > + + + + + + + { + deleteFile(file); + }} + > + + + + + { - downloadFile(file); + const elementName = "copy_element_shuffle"; + var copyText = + document.getElementById(elementName); + if ( + copyText !== null && + copyText !== undefined + ) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + alert.error( + "Can only copy over HTTPS (port 3443)" + ); + return; + } + + navigator.clipboard.writeText(file.id); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + alert.info(file.id + " copied to clipboard"); + } }} > - + - - - - - { - deleteFile(file); - }} - > - - - - - - { - const elementName = "copy_element_shuffle"; - var copyText = document.getElementById(elementName); - if (copyText !== null && copyText !== undefined) { - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - alert.error( - "Can only copy over HTTPS (port 3443)" - ); - return; - } - - navigator.clipboard.writeText(file.id); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999 - ); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - - alert.info(file.id + " copied to clipboard"); - } - }} - > - - - - + + style={{ - minWidth: 150, - maxWidth: 150, - overflow: "hidden", + minWidth: 250, + maxWidth: 250, + // overflow: "hidden", }} /> @@ -3596,8 +4445,8 @@ const Admin = (props) => {

App Authentication

- Control the authentication options for individual apps.{" "} - PS: Actions performed here can be destructive! + Control the authentication options for individual apps. PS: Actions + performed here can be destructive!  
{ primary="Workflows" style={{ minWidth: 100, maxWidth: 100, overflow: "hidden" }} /> - {/* + {/* { bgColor = "#1f2023"; } - //console.log("Auth data: ", data) - if (data.type === "oauth2") { - data.fields = [ - { - "key": "url", - "value": "Secret. Replaced during app execution!", - }, - { - "key": "client_id", - "value": "Secret. Replaced during app execution!", - }, - { - "key": "client_secret", - "value": "Secret. Replaced during app execution!", - }, - { - "key": "scope", - "value": "Secret. Replaced during app execution!", - }] - } + //console.log("Auth data: ", data) + if (data.type === "oauth2") { + data.fields = [ + { + key: "url", + value: "Secret. Replaced during app execution!", + }, + { + key: "client_id", + value: "Secret. Replaced during app execution!", + }, + { + key: "client_secret", + value: "Secret. Replaced during app execution!", + }, + { + key: "scope", + value: "Secret. Replaced during app execution!", + }, + ]; + } return ( @@ -3688,7 +4538,10 @@ const Admin = (props) => { primary= style={{ minWidth: 75, maxWidth: 75 }} /> @@ -3704,7 +4557,7 @@ const Admin = (props) => { primary={data.app.name} style={{ minWidth: 175, maxWidth: 175, marginLeft: 10 }} /> - {/* + {/* { style={{ minWidth: 100, maxWidth: 100, - textAlign: "center", + textAlign: "center", overflow: "hidden", }} /> - {/* + {/* { overflow: "hidden", }} /> - + { @@ -3852,7 +4705,7 @@ const Admin = (props) => { setShowArchived(!showArchived); }} />{" "} - Show disabled + Show disabled { primary="Orborus running" style={{ minWidth: 200, maxWidth: 200 }} /> + { ? environment.running_ip === undefined || environment.running_ip === null || environment.running_ip.length === 0 - ? "Not running" + ? +
+ Not running +
: environment.running_ip : "N/A" } @@ -3934,9 +4794,58 @@ const Admin = (props) => { overflow: "hidden", }} /> + + + { + if (environment.Type === "cloud") { + alert.info("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.") + return + } + + const elementName = "copy_element_shuffle"; + const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth + const commandData = `docker run --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${globalUrl}" -d ghcr.io/shuffle/shuffle-orborus:latest` + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + alert.error("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(commandData); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + alert.info("Orborus command copied to clipboard"); + } + }} + > + + + + } + /> + { {environment.default ? null : ( - +
@@ -4025,7 +4944,7 @@ const Admin = (props) => { style={{}} variant="contained" color="primary" - disabled={userdata.admin !== "true"} + disabled={userdata.admin !== "true"} onClick={() => { setModalOpen(true); }} @@ -4197,7 +5116,7 @@ const Admin = (props) => { // primary={environment.Registered ? "true" : "false"} const iconStyle = { marginRight: 10 }; - const data = ( + const data = (
{ @@ -4220,7 +5139,7 @@ const Admin = (props) => { /> Users @@ -4233,34 +5152,34 @@ const Admin = (props) => { /> Files /> Schedules /> - - - Environments - - /> - - Organizations - - /> + + + Environments + + /> + + Organizations + + /> {/*window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null*/} {/*window.location.protocol === "http:" && window.location.port === "3000" ? Categories/> : null*/} @@ -4291,6 +5210,7 @@ const Admin = (props) => { {modalView} {cloudSyncModal} {editUserModal} + {addDealModal} {editAuthenticationModal} {data} { const { globalUrl, isLoggedIn, isLoaded, userdata } = defaultprops; const referenceUrl = globalUrl + "/api/v1/hooks/"; @@ -240,7 +264,6 @@ const AngularWorkflow = (defaultprops) => { const yellow = "#FECC00"; //const theme = useTheme(); - const [bodyWidth, bodyHeight] = useWindowSize(); var to_be_copied = ""; const [firstrequest, setFirstrequest] = React.useState(true); @@ -261,6 +284,12 @@ const AngularWorkflow = (defaultprops) => { const [leftViewOpen, setLeftViewOpen] = React.useState(isMobile ? false : true); const [leftBarSize, setLeftBarSize] = React.useState(isMobile ? 0 : 350); const [creatorProfile, setCreatorProfile] = React.useState({}); + const [usecases, setUsecases] = React.useState([]); + const [files, setFiles] = React.useState({ + "namespaces": [ + "default", + ] + }); const [appGroup, setAppGroup] = React.useState([]); const [triggerGroup, setTriggerGroup] = React.useState([]); const [executionText, setExecutionText] = React.useState(""); @@ -334,13 +363,13 @@ const AngularWorkflow = (defaultprops) => { const [environments, setEnvironments] = React.useState([]); const [established, setEstablished] = React.useState(false); + const [setupSent, setSetupSent] = React.useState(false); const [graphSetup, setGraphSetup] = React.useState(false); const [selectedApp, setSelectedApp] = React.useState({}); const [selectedAction, setSelectedAction] = React.useState({}); - const [selectedActionEnvironment, setSelectedActionEnvironment] = - React.useState({}); + const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); const [executionRequest, setExecutionRequest] = React.useState({}); @@ -349,6 +378,9 @@ const AngularWorkflow = (defaultprops) => { const [executionModalView, setExecutionModalView] = React.useState(0); const [executionData, setExecutionData] = React.useState({}); const [appsLoaded, setAppsLoaded] = React.useState(false); + const [showVideo, setShowVideo] = React.useState(""); + const [editWorkflowModalOpen, setEditWorkflowModalOpen] = React.useState(false); + const [userediting, setUserediting] = React.useState(false) const [lastSaved, setLastSaved] = React.useState(true); @@ -373,6 +405,9 @@ const AngularWorkflow = (defaultprops) => { const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"]; const unloadText = "Are you sure you want to leave without saving (CTRL+S)?"; const classes = useStyles(); + + const [bodyWidth, bodyHeight] = useWindowSize() + //console.log("Mobile: ", isMobile, bodyWidth, bodyHeight) const cytoscapeWidth = isMobile ? bodyWidth - leftBarSize : bodyWidth - leftBarSize - 25 @@ -387,6 +422,54 @@ const AngularWorkflow = (defaultprops) => { }, }); + const getAppDocs = (appname, location, version) => { + fetch(`${globalUrl}/api/v1/docs/${appname}?location=${location}&version=${version}`, { + headers: { + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + //alert.success("Successfully GOT app "+appId) + } else { + //alert.error("Failed getting app"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) { + if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) { + selectedApp.documentation = responseJson.reason + setSelectedApp(selectedApp) + setUpdate(Math.random()) + } + } + } + + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + useEffect(() => { + if (authenticationModalOpen === true && selectedAction.app_name !== undefined) { + console.log(`Should get app docs for: ${selectedAction.app_name}`) + //console.log(selectedAction) + //console.log("APP: ", selectedApp) + + if (selectedAction.documentation === undefined || selectedAction.documentation === null || selectedAction.documentation.length === 0) { + // SelectedApp.documentation = Markdown? If so, it works + // + const apptype = selectedApp.generated === false ? "python" : "openapi" + getAppDocs(selectedAction.app_name, apptype, selectedAction.app_version) + } + } + }, [authenticationModalOpen]) + const getAvailableWorkflows = (trigger_index) => { fetch(globalUrl + "/api/v1/workflows", { method: "GET", @@ -450,7 +533,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Workflow error: ", error.toString()) }); }; @@ -538,7 +622,7 @@ const AngularWorkflow = (defaultprops) => { setUserSettings(responseJson); }) .catch((error) => { - console.log(error); + console.log("Apikey error: ", error); }); }; @@ -573,12 +657,11 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error); + console.log("Settings error: ", error); }); }; const setNewAppAuth = (appAuthData) => { - console.log("DAta: ", appAuthData); fetch(globalUrl + "/api/v1/apps/authentication", { method: "PUT", headers: { @@ -607,7 +690,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("New auth error: ", error.toString()); }); }; @@ -691,7 +775,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Get execution error: ", error.toString()); }); }; @@ -718,7 +803,7 @@ const AngularWorkflow = (defaultprops) => { handleUpdateResults(responseJson, executionRequest); }) .catch((error) => { - console.log("Error: ", error); + console.log("Execution result Error: ", error); stop(); }); }; @@ -750,7 +835,8 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Abort error: ", error.toString()); }); }; @@ -778,7 +864,7 @@ const AngularWorkflow = (defaultprops) => { ) { setExecutionData(responseJson); } else { - console.log("NOT updating state."); + //console.log("NOT updating state."); } } } @@ -962,6 +1048,7 @@ const AngularWorkflow = (defaultprops) => { const sendStreamRequest = (body) => { console.log("Stream not activated yet.") return + // Session may be important here huh body.user_id = userdata.id @@ -986,10 +1073,9 @@ const AngularWorkflow = (defaultprops) => { console.log("RESP: ", responseJson) }) .catch((error) => { - console.log("Stream error: ", error.toString()) + console.log("Stream send error: ", error.toString()) //alert.error(error.toString()); }) - } const saveWorkflow = (curworkflow, executionArgument, startNode) => { @@ -1111,6 +1197,9 @@ const AngularWorkflow = (defaultprops) => { } curworkflowTrigger.position = cyelements[key].position(); + if (curworkflowTrigger.canConnect === false) { + continue + } newTriggers.push(curworkflowTrigger); } else if (type === "COMMENT") { @@ -1161,6 +1250,10 @@ const AngularWorkflow = (defaultprops) => { } } + if (userediting === true) { + useworkflow.user_editing = true + } + useworkflow.actions = newActions; useworkflow.triggers = newTriggers; useworkflow.branches = newBranches; @@ -1215,9 +1308,16 @@ const AngularWorkflow = (defaultprops) => { if (responseJson.reason !== undefined && responseJson.reason !== null) { alert.error("Failed to save: " + responseJson.reason); } else { - alert.error("Failed to save. Please contact your admin if this is unexpected.") + alert.error("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.") } } else { + + sendStreamRequest({ + "item": "workflow", + "type": "save", + "id": workflow.id, + }) + if ( responseJson.new_id !== undefined && responseJson.new_id !== null @@ -1247,7 +1347,7 @@ const AngularWorkflow = (defaultprops) => { } for (var key in workflow.errors) { - alert.info(workflow.errors[key]); + //alert.info(workflow.errors[key]); } setWorkflow(workflow); @@ -1261,7 +1361,8 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { setSavingState(0); - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Save workflow error: ", error.toString()); }); return success; @@ -1313,9 +1414,9 @@ const AngularWorkflow = (defaultprops) => { return; } - setVisited([]); - setExecutionRequest({}); - stop(); + setVisited([]) + setExecutionRequest({}) + stop() var curelements = cy.elements(); for (var i = 0; i < curelements.length; i++) { @@ -1336,6 +1437,7 @@ const AngularWorkflow = (defaultprops) => { } ) .then((response) => { + setExecutionRequestStarted(false) if (response.status !== 200) { console.log("Status not 200 for WORKFLOW EXECUTION :O!"); } @@ -1386,7 +1488,9 @@ const AngularWorkflow = (defaultprops) => { start(); }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + setExecutionRequestStarted(false) + console.log("Execute workflow err: ", error.toString()); }); }) }; @@ -1424,8 +1528,6 @@ const AngularWorkflow = (defaultprops) => { } if (cy !== undefined) { - console.log("NEW AUTH = reset cy's onnodeselect"); - // Remove the old listener for select, run with new one cy.removeListener("select"); cy.on("select", "node", (e) => onNodeSelect(e, newauth)); @@ -1482,13 +1584,17 @@ const AngularWorkflow = (defaultprops) => { } if (appUpdates === true) { + console.log("Closing auth modal: Success") + setAuthenticationModalOpen(false); setSelectedAction(selectedAction); setWorkflow(workflow); saveWorkflow(workflow); alert.info("Added and updated authentication!"); } else { - alert.error("Failed to find new authentication - did it work?"); + console.log("Closing auth modal? FAIL") + + alert.error("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); } } else { alert.info("No authentication to update"); @@ -1500,7 +1606,8 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { setAuthLoaded(true); - alert.error("Auth loading error: " + error.toString()); + //alert.error("Auth loading error: " + error.toString()); + console.log("AppAuth error: " + error.toString()); }); }; @@ -1577,12 +1684,13 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { setAppsLoaded(true) - alert.error("App loading error: "+error.toString()); + //alert.error("App loading error: "+error.toString()); + console.log("App loading error: "+error.toString()); }); }; // Searhc by username, userId, workflow, appId should all work - const getUserProfile = (username) => { + const getUserProfile = (username, rerun) => { fetch(`${globalUrl}/api/v1/users/creators/${username}`, { method: "GET", headers: { @@ -1603,18 +1711,61 @@ const AngularWorkflow = (defaultprops) => { if (responseJson.success !== false) { console.log("Found creator: ", responseJson) setCreatorProfile(responseJson) + } else { + console.log("Couldn't find the creator profile (rerun?): ", responseJson, rerun) + // If the current user is any of the Shuffle Creators + // AND the workflow doesn't have an owner: allow editing. + // else: Allow suggestions? + //console.log("User: ", userdata) + //if (rerun !== true) { + // getUserProfile(userdata.id, true) + //} } }) .catch((error) => { - console.log(error); + console.log("Get userprofile error: ", error); }) } - const getWorkflow = (workflow_id, sourcenode) => { - console.log( - //`Getting workflow ${workflow_id} with append value ${sourcenode}` - ); + const getFiles = () => { + fetch(globalUrl + "/api/v1/files", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return; + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.files !== undefined && responseJson.files !== null) { + setFiles(responseJson); + } else { + setFiles({"namespaces": [ + "default" + ]}); + } + + if ( + responseJson.namespaces !== undefined && + responseJson.namespaces !== null + ) { + //setFileNamespaces(responseJson.namespaces); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const getWorkflow = (workflow_id, sourcenode) => { fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { method: "GET", headers: { @@ -1650,12 +1801,13 @@ const AngularWorkflow = (defaultprops) => { } if (responseJson.public) { - alert.info("This workflow is public. Save the workflow to use it in your organization."); + //alert.info("This workflow is public. Save the workflow to use it in your organization."); + setAuthLoaded(true) console.log("RESP: ", responseJson) if (Object.getOwnPropertyNames(creatorProfile).length === 0) { //getUserProfile("frikky") - getUserProfile(responseJson.id) + getUserProfile(responseJson.id, false) } //{appGroup.map((data, index) => { @@ -1682,8 +1834,14 @@ const AngularWorkflow = (defaultprops) => { } setTriggerGroup(appsFound) - } - + } else { + getAppAuthentication(); + getEnvironments(); + getWorkflowExecution(props.match.params.key, ""); + getAvailableWorkflows(-1); + getSettings(); + getFiles() + } // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows. if (sourcenode.id !== undefined) { @@ -1709,6 +1867,9 @@ const AngularWorkflow = (defaultprops) => { node.data = action; + node.data.canConnect = false + node.data.is_valid = true + node.data.isValid = true node.data._id = action["id"]; node.data.type = "ACTION"; node.data.source_workflow = responseJson.id; @@ -1716,6 +1877,11 @@ const AngularWorkflow = (defaultprops) => { nodefound = true; } + if (responseJson.public) { + node.data.is_valid = true + node.is_valid = true + } + var example = ""; if ( action.example !== undefined && @@ -1729,6 +1895,27 @@ const AngularWorkflow = (defaultprops) => { return node; }); + var triggers = responseJson.triggers.map((trigger) => { + const node = {}; + + console.log("Only add workflow: ", trigger.app_name) + if (trigger.app_name !== "Shuffle Workflow" && trigger.app_name !== "User Input") { + return null + } + + node.position = trigger.position; + node.data = trigger; + + node.data.canConnect = false + node.data.id = trigger["id"]; + node.data._id = trigger["id"]; + node.data.type = "TRIGGER"; + + return node; + }); + + triggers = triggers.filter((trigger) => trigger !== null); + const insertedNodes = [].concat(actions, triggers); var edges = responseJson.branches.map((branch, index) => { const edge = {}; var conditions = responseJson.branches[index].conditions; @@ -1743,20 +1930,21 @@ const AngularWorkflow = (defaultprops) => { label = conditions.length + " conditions"; } - const sourceFound = actions.findIndex( + const sourceFound = insertedNodes.findIndex( (action) => action.data.id === branch.source_id ); if (sourceFound < 0) { return null; } - const destinationFound = actions.findIndex( + const destinationFound = insertedNodes.findIndex( (action) => action.data.id === branch.destination_id ); if (destinationFound < 0) { return null; } + edge.data = { id: branch.id, _id: branch.id, @@ -1769,12 +1957,17 @@ const AngularWorkflow = (defaultprops) => { source_workflow: responseJson.id, }; + if (responseJson.public) { + edge.data.is_valid = true + edge.is_valid = true + } + return edge; }); edges = edges.filter((edge) => edge !== null); cy.removeListener("add"); - cy.add(actions); + cy.add(insertedNodes) cy.add(edges); if (nodefound === true) { @@ -1817,7 +2010,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Get workflows error: ", error.toString()); }); }; @@ -1832,20 +2026,21 @@ const AngularWorkflow = (defaultprops) => { // Wait for new node to possibly be selected //setTimeout(() => { const typeIds = cy.elements('node:selected').jsons(); - console.log("Found: ", typeIds) for (var idkey in typeIds) { const item = typeIds[idkey] - console.log("items: ", item) if (item.data.isButton === true) { - console.log("Reselect old node & return - or just return?") + //console.log("Reselect old node & return - or just return?") if (item.data.buttonType === "delete" && item.data.attachedTo === nodedata.id) { - console.log("delete of same node!") + //console.log("delete of same node!") } return } } + // Unselecting all + //cy.elements().unselect() + //if (nodedata.app_name === undefined && nodedata.source === undefined) { // return; //} @@ -1916,6 +2111,7 @@ const AngularWorkflow = (defaultprops) => { setSelectedTriggerIndex(-1) setTriggerFolders([]) setSubworkflow({}) + setLocalFirstrequest(true) // Can be used for right side view setRightSideBarOpen(false); @@ -1926,6 +2122,12 @@ const AngularWorkflow = (defaultprops) => { }); //console.timeEnd("UNSELECT"); }) + + sendStreamRequest({ + "item": "node", + "type": "unselect", + "userid": userdata.id, + }) //}, 150) }; @@ -1993,9 +2195,14 @@ const AngularWorkflow = (defaultprops) => { return; } + if (nodedata.parameters === undefined) { + return + } + const workflow_id = nodedata.parameters.find( (param) => param.name === "workflow" ); + if (workflow.id === workflow_id.valu) { return; } @@ -2165,6 +2372,7 @@ const AngularWorkflow = (defaultprops) => { console.log("Node already exists - don't add descriptor node"); } } + originalLocation = { x: 0, y: 0, @@ -2174,7 +2382,10 @@ const AngularWorkflow = (defaultprops) => { "item": "node", "type": "move", "id": nodedata.id, - "location": {"x": event.target.position("x"), "y": event.target.position("y")} + "location": { + "x": event.target.position("x"), + "y": event.target.position("y"), + } }) }; @@ -2206,6 +2417,16 @@ const AngularWorkflow = (defaultprops) => { //console.log("No appid? ", nodedata) } + if (nodedata.buttonType === "edgehandler") { + console.log("Enable edgehandler!") + console.log("Find parent: ", nodedata.attachedTo) + const parentNode = cy.getElementById(nodedata.attachedTo); + if (parentNode !== null && parentNode !== undefined) { + console.log("Start parentnode tracking!") + //cy.edgehandles().start(parentNode) + } + } + if (nodedata.id === selectedAction.id) { return; } @@ -2344,6 +2565,7 @@ const AngularWorkflow = (defaultprops) => { //event.target.unselect(); setRightSideBarOpen(true); return + } else if (data.buttonType === "copy") { console.log("COPY!"); @@ -2461,7 +2683,7 @@ const AngularWorkflow = (defaultprops) => { //var curaction = JSON.parse(JSON.stringify(data)) // FIXME: Trust it to just work? //event.target.data() - var curaction = workflow.actions.find((a) => a.id === data.id); + var curaction = workflow.actions.find((a) => a.id === data.id) if (!curaction || curaction === undefined) { console.log("NOT FOUND DATA: ", event.target.data()) if (data.id !== undefined && data.app_name !== undefined) { @@ -2484,15 +2706,50 @@ const AngularWorkflow = (defaultprops) => { newapps = filteredApps } - const curapp = newapps.find( - (a) => - a.name === curaction.app_name && - (a.app_version === curaction.app_version || - (a.loop_versions !== null && - a.loop_versions.includes(curaction.app_version))) - ); + // Check ID first, then names etc + // That way it always selects the right IF it exists + var curapp = newapps.find((a) => + a.id === curaction.app_id + ) + + if (curapp === undefined || curapp === null) { + console.log("Couldn't find ID - checking with name & version") + + curapp = newapps.find((a) => + a.name === curaction.app_name && + (a.app_version === curaction.app_version || + (a.loop_versions !== null && + a.loop_versions.includes(curaction.app_version))) + ) + } + + if (curaction.template === true) { + //newapps. + const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase() + console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname) + + curaction.matching_actions = [] + for (var key in newapps) { + for (var subkey in newapps[key].actions) { + const tmpaction = newapps[key].actions[subkey] + if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) { + console.log("MATCH!: ", newapps[key]) + curaction.matching_actions.push({ + "app_name": newapps[key].name, + "app_version": newapps[key].app_version, + "app_id": newapps[key].id, + "action": tmpaction, + "large_image": newapps[key].large_image, + "app_index": key, + "action_index": subkey, + }) + } + } + } + } + if (!curapp || curapp === undefined) { - console.log("APPS: ", newapps) + console.log("APPS - couldn't find it: ", newapps) //alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`); const tmpapp = { @@ -2506,23 +2763,25 @@ const AngularWorkflow = (defaultprops) => { setSelectedApp(tmpapp); setSelectedAction(curaction); } else { + //if (curapp.id !== curaction.id) { + // curaction.app_id = curapp.id + // //.valueOf() + //} + curaction.app_id = curapp.id + setAuthenticationType( - curapp.authentication.type === "oauth2" && - curapp.authentication.redirect_uri !== undefined && - curapp.authentication.redirect_uri !== null - ? { - type: "oauth2", - redirect_uri: curapp.authentication.redirect_uri, - refresh_uri: curapp.authentication.refresh_uri, - token_uri: curapp.authentication.token_uri, - scope: curapp.authentication.scope, - client_id: curapp.authentication.client_id, - client_secret: curapp.authentication.client_secret, - } - : { - type: "", - } - ); + curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null ? { + type: "oauth2", + redirect_uri: curapp.authentication.redirect_uri, + refresh_uri: curapp.authentication.refresh_uri, + token_uri: curapp.authentication.token_uri, + scope: curapp.authentication.scope, + client_id: curapp.authentication.client_id, + client_secret: curapp.authentication.client_secret, + } : { + type: "", + } + ) const requiresAuth = curapp.authentication.required; //&& ((curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null)) setRequiresAuthentication(requiresAuth); @@ -2539,15 +2798,15 @@ const AngularWorkflow = (defaultprops) => { findAuthId = curaction.authentication_id; } - var tmpAuth = JSON.parse(JSON.stringify(newAppAuth)); + const tmpAuth = JSON.parse(JSON.stringify(newAppAuth)); + //var tmpAuth = newAppAuth for (var key in tmpAuth) { var item = tmpAuth[key]; const newfields = {}; for (var filterkey in item.fields) { - newfields[item.fields[filterkey].key] = - item.fields[filterkey].value; + newfields[item.fields[filterkey].key] = item.fields[filterkey].value; } item.fields = newfields; @@ -2585,13 +2844,24 @@ const AngularWorkflow = (defaultprops) => { curaction.parameters[key].options.length > 0 && curaction.parameters[key].value === "" ) { - curaction.parameters[key].value = - curaction.parameters[key].options[0]; + curaction.parameters[key].value = curaction.parameters[key].options[0]; } } - } + } else { + console.log("Should check APP if it has the same params as ACTION") + for (var key in curapp.actions) { + const tmpaction = curapp.actions[key] + if (tmpaction.name === curaction.name) { + console.log("Found action - needs change?", tmpaction) + if (tmpaction.parameters !== undefined && tmpaction.parameters !== null && tmpaction.parameters.length > 0) { + curaction.parameters = JSON.parse(JSON.stringify(tmpaction.parameters)) + } + break + } + } + } - console.log("ACTION: ", curaction) + console.log("ACTION CLICK: ", curaction) setSelectedApp(curapp); setSelectedAction(curaction); @@ -2601,17 +2871,17 @@ const AngularWorkflow = (defaultprops) => { cy.on("free", "node", (e) => onNodeDragStop(e, curaction)); } - console.log("Object: ", environments) if (environments !== undefined && environments !== null && (typeof environments === "array" || typeof environments === "object")) { var parsedenv = environments - if (typeof environments === "object") { - parsedenv = [environments] - } + //if (typeof environments === "object") { + // parsedenv = [environments] + //} - var env = parsedenv.find((a) => a.Name === curaction.environment); - if (!env || env === undefined) { - env = parsedenv[defaultEnvironmentIndex]; - } + const envs = parsedenv.find((a) => a.Name === curaction.environment); + var env = environments[defaultEnvironmentIndex] + if (envs !== undefined && envs !== null) { + env = envs + } setSelectedActionEnvironment(env); } @@ -2673,10 +2943,24 @@ const AngularWorkflow = (defaultprops) => { left: 0, selected: "", }); + + console.log("DOne in the node update") + + sendStreamRequest({ + "item": "node", + "type": "select", + "id": data.id, + "userid": userdata.id, + "location": { + "x": event.target.position("x"), + "y": event.target.position("y"), + } + }) + }) } - const activateApp = (appid) => { + const activateApp = (appid, refresh) => { fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", { method: 'GET', headers: { @@ -2696,11 +2980,16 @@ const AngularWorkflow = (defaultprops) => { if (responseJson.success === false) { alert.error("Failed to activate the app") } else { - alert.success("App activated for your organization!") + alert.success("App activated for your organization! Refresh the page to use the app.") + + if (refresh === true) { + getApps() + } } }) .catch(error => { - alert.error(error.toString()) + //alert.error(error.toString()) + console.log("Activate app error: ", error.toString()) }); } @@ -2789,8 +3078,8 @@ const AngularWorkflow = (defaultprops) => { return ""; } - console.log("NOT REPLACING ON PURPOSE!!") - return "" + //console.log("NOT REPLACING ON PURPOSE!!") + //return "" // Basically just a stupid if-else :) const synonyms = { @@ -2808,6 +3097,8 @@ const AngularWorkflow = (defaultprops) => { "uid", "uuid", "team id", + "message id", + "message_id", ], title: ["title", "name", "message"], description: ["description", "explanation", "story", "details"], @@ -2824,10 +3115,11 @@ const AngularWorkflow = (defaultprops) => { "value", "item", ], + tags: ["tags", "taxonomies"], }; // 1. Find the right synonym - // 2. + // 2. Replace with an autocomplete if it exists var selectedsynonyms = [paramname]; for (const [key, value] of Object.entries(synonyms)) { if (key === paramname || value.includes(paramname)) { @@ -2866,6 +3158,7 @@ const AngularWorkflow = (defaultprops) => { if (toreturn.length > 0) { break; } + } else { var selectedkey = ""; if (isNaN(key)) { @@ -2883,8 +3176,8 @@ const AngularWorkflow = (defaultprops) => { } } else { if (selectedsynonyms.includes(key.toLowerCase())) { - toreturn = `${basekey}.${key}`; - break; + toreturn = `${basekey}.${key}` + break } } } @@ -2906,15 +3199,21 @@ const AngularWorkflow = (defaultprops) => { const param = dstdata.parameters[paramkey]; // Skip authentication params if (param.configuration) { - continue; + continue } + if (param.options !== undefined && param.options !== null && param.options.length > 0) { + continue + } + const paramname = param.name.toLowerCase().trim().replaceAll("_", " "); const foundresult = GetParamMatch(paramname, exampledata, ""); if (foundresult.length > 0) { + console.log("FOUND ReS for field: ", dstdata.parameters[paramkey].name, foundresult) if (dstdata.parameters[paramkey].value.length === 0) { dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`; + dstdata.parameters[paramkey].autocompleted = true } } } @@ -2936,9 +3235,13 @@ const AngularWorkflow = (defaultprops) => { const param = dstdata.parameters[paramkey]; // Skip authentication params if (param.configuration) { - continue; + continue } + if (param.options !== undefined && param.options !== null && param.options.length > 0) { + continue + } + const paramname = param.name .toLowerCase() .trim() @@ -2947,13 +3250,10 @@ const AngularWorkflow = (defaultprops) => { const foundresult = GetParamMatch(paramname, exampledata, ""); if (foundresult.length > 0) { if (dstdata.parameters[paramkey].value.length === 0) { - dstdata.parameters[ - paramkey - ].value = `$${parentlabel}${foundresult}`; + dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`; + dstdata.parameters[paramkey].autocompleted = true } else { - dstdata.parameters[ - paramkey - ].value = `$${parentlabel}${foundresult}`; + //dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`; } } } @@ -2969,18 +3269,60 @@ const AngularWorkflow = (defaultprops) => { setLastSaved(false); const edge = event.target.data(); + console.log("edge added: ", edge) + if (edge.source === undefined && edge.target === undefined) { + return + } + + if (edge.readded === true) { + console.log("Readded edge - stopping") + + event.target.data("readded", false) + return + } + const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { } else { + console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) + if (sourcenode.data("type") === "TRIGGER") { + if (sourcenode.data("app_name") !== "Shuffle Workflow" && sourcenode.data("app_name") !== "User Input") { + setTimeout(() => { + const alledges = cy.edges().jsons() + console.log("edges: ", alledges, edge) + var targetedge = alledges.findIndex( + (data) => data.data.source === edge.source && data.data.id !== edge.id + ) + + console.log("Node: ", targetedge) + if (targetedge !== -1) { + event.target.remove() + + //console.log("Found branch already!") + alert.info("Triggers can have exactly one target node") + return + + + // name: "Shuffle Workflow", + // name: "User Input", + } else { + console.log("Node doesn't already have one") + } + }, 50) + } + } + const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position()) const currentedge = cy.getElementById(edge.id) if (currentedge !== undefined && currentedge !== null) { currentedge.style('control-point-distance', edgeCurve.distance) currentedge.style('control-point-weight', edgeCurve.weight) } + } + var targetnode = workflow.triggers.findIndex( (data) => data.id === edge.target ); @@ -2997,9 +3339,8 @@ const AngularWorkflow = (defaultprops) => { } const eventTarget = event.target.target() - console.log("BUTTON! Find parent from: ", eventTarget) + console.log("BUTTON ADDED! Find parent from: ", eventTarget) if (eventTarget.data("isButton") === true) { - console.log("ACTUALLY A BUTTON!") const parentNode = cy.getElementById(eventTarget.data("attachedTo")) event.target.remove() console.log("Setting it to parentnode: ", parentNode.data()) @@ -3116,7 +3457,7 @@ const AngularWorkflow = (defaultprops) => { newdst !== null ) { const dstdata = RunAutocompleter(newdst.data()); - console.log("DST: ", dstdata); + console.log("AUTO DST: ", dstdata); } var newbranch = { @@ -3148,10 +3489,14 @@ const AngularWorkflow = (defaultprops) => { const node = event.target; const nodedata = event.target.data(); + if (Object.keys(nodedata).length === 1) { + console.log("Check if another node actually exists before adding") + } + if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined) ) { //if (nodedata.app_id === undefined) { - console.log("Returning because node is not valid: ", nodedata) + //console.log("Returning because node is not valid: ", nodedata) return; } @@ -3320,12 +3665,48 @@ const AngularWorkflow = (defaultprops) => { const onEdgeRemoved = (event) => { setLastSaved(false); - const edge = event.target; if (edge.data("decorator") === true) { return; } + // Check if the source is trigger and can start + console.log("Removed: ", edge.data()) + const allNodes = cy.nodes().jsons() + for (var key in allNodes) { + const curnode = allNodes[key] + if (curnode.data.type !== "TRIGGER") { + continue + } + + if (curnode.data.id === edge.data("source")) { + console.log("Found matching trigger source: ", curnode) + if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") { + // If it's started, READD the edge + if (curnode.data.status === "running") { + console.log("Edge is running - readd it: ", edge.data()) + + // Just making sure it's not running infinitely + var newdata = edge.data() + newdata.readded = true + + try { + cy.add({ + group: "edges", + data: newdata, + }) + + alert.error("You must STOP the trigger before deleting its branches") + } catch (e) { + console.log("Failed re-adding edge: ", e) + } + } + + //status: "uninitialized", + } + } + } + workflow.branches = workflow.branches.filter( (a) => a.id !== edge.data().id ); @@ -3334,10 +3715,10 @@ const AngularWorkflow = (defaultprops) => { // trigger as source check const indexcheck = workflow.triggers.findIndex( - (data) => edge.data()["source"] === data.id + (data) => edge.data("source") === data.id ); if (indexcheck !== -1) { - console.log("Shouldnt remove edge from trigger"); + console.log("Shouldnt remove edge from trigger? "); } if (edge.data().source !== undefined) { @@ -3613,7 +3994,7 @@ const AngularWorkflow = (defaultprops) => { var found = false; var showEnvCnt = 0; for (var key in responseJson) { - if (responseJson[key].default) { + if (responseJson[key].default && !found) { setDefaultEnvironmentIndex(key); found = true; } @@ -3638,7 +4019,6 @@ const AngularWorkflow = (defaultprops) => { // FIXME: Don't allow multiple in cloud yet. Cloud -> Onprem isn't stable. if (isCloud) { - console.log("Envs: ", responseJson) if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { setEnvironments(responseJson); } else { @@ -3649,7 +4029,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Get environments error: ", error.toString()); }); }; @@ -3678,6 +4059,7 @@ const AngularWorkflow = (defaultprops) => { ) { cy.getElementById(currentNode.data.id).remove(); } + } } @@ -3692,7 +4074,6 @@ const AngularWorkflow = (defaultprops) => { for (var idkey in typeIds) { const item = typeIds[idkey] if (item.data.id === nodedata.id) { - console.log("items: ", item.data.id, nodedata.id) return } } @@ -3819,7 +4200,7 @@ const AngularWorkflow = (defaultprops) => { if (parentNode.data("isButton") || parentNode.data("buttonId")) return; const px = parentNode.position("x") + 300; - const py = parentNode.position("y") + 0; + const py = parentNode.position("y") + 100; const circleId = (newNodeId = uuidv4()); parentNode.data("circleId", circleId); @@ -3847,8 +4228,51 @@ const AngularWorkflow = (defaultprops) => { position: { x: px, y: py }, locked: true, }); + + //suggestions[0].id = uuidv4() + //cy.add({ + // group: "nodes", + // data: suggestions[0], + // position: { x: parentNode.position("x") + 300, y: parentNode.position("y") - 100}, + // locked: true, + //}); } + const addDeleteButton2 = (event) => { + var parentNode = cy.$("#" + event.target.data("id")); + if (parentNode.data("isButton") || parentNode.data("buttonId")) return; + + const px = parentNode.position("x") + 100; + const py = parentNode.position("y") + 35; + const circleId = (newNodeId = uuidv4()); + + parentNode.data("circleId", circleId); + + const iconInfo = { + icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z", + iconColor: buttonColor, + iconBackgroundColor: buttonBackgroundColor, + }; + const svg_pin = ``; + const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); + + cy.add({ + group: "nodes", + data: { + weight: 30, + id: circleId, + name: "This is autocomplete", + buttonType: "delete", + attachedTo: event.target.data("id"), + icon: svgpin_Url, + iconBackground: iconInfo.iconBackgroundColor, + is_valid: true, + }, + position: { x: px, y: py }, + locked: true, + }); + }; + const addDeleteButton = (event) => { var parentNode = cy.$("#" + event.target.data("id")); if (parentNode.data("isButton") || parentNode.data("buttonId")) return; @@ -3908,12 +4332,19 @@ const AngularWorkflow = (defaultprops) => { for (var key in allNodes) { const currentNode = allNodes[key]; if ( - currentNode.data.isButton && + (currentNode.data.isButton || currentNode.data.isSuggestion) && currentNode.data.attachedTo !== nodedata.id ) { cy.getElementById(currentNode.data.id).remove(); } + /*if ( + currentNode.data.isSuggestion && + currentNode.data.attachedTo !== nodedata.id + ) { + cy.getElementById(currentNode.data.id).remove(); + }*/ + if ( currentNode.data.isButton && currentNode.data.attachedTo === nodedata.id @@ -3934,7 +4365,10 @@ const AngularWorkflow = (defaultprops) => { addStartnodeButton(event); } - //addSuggestionButtons(event) + // autocomplete + // right click + // suggestions + //addSuggestionButtons(event); } } @@ -3957,6 +4391,28 @@ const AngularWorkflow = (defaultprops) => { if (nodedata.type !== "COMMENT") { parsedStyle.color = "white"; + + //if (!event.target.data("isButton") && !event.target.data("buttonId")) { + // const px = event.target.position("x") - 0; + // const py = event.target.position("y") - 50; + // const circleId = (newNodeId = uuidv4()); + + // console.log("Got px, py: ", px, py) + // + // cy.add({ + // group: "nodes", + // data: { + // weight: 30, + // id: circleId, + // isButton: true, + // attachedTo: event.target.data("id"), + // buttonType: "edgehandler", + // is_valid: true, + // }, + // position: { x: px, y: py }, + // locked: true, + // }) + //} } if (event.target !== undefined && event.target !== null) { @@ -3992,6 +4448,11 @@ const AngularWorkflow = (defaultprops) => { return; } + const cytoscapeElement = document.getElementById("cytoscape_view") + if (cytoscapeElement !== undefined && cytoscapeElement !== null) { + cytoscapeElement.style.cursor = "default" + } + //event.target.removeStyle(); }; @@ -4006,51 +4467,81 @@ const AngularWorkflow = (defaultprops) => { return; } - const sourcecolor = cy - .getElementById(event.target.data("source")) - .style("border-color"); - const targetcolor = cy - .getElementById(event.target.data("target")) - .style("border-color"); + const cytoscapeElement = document.getElementById("cytoscape_view") + if (cytoscapeElement !== undefined && cytoscapeElement !== null) { + cytoscapeElement.style.cursor = "pointer" + } - //console.log(sourcecolor, targetcolor) - if ( - sourcecolor !== null && - sourcecolor !== undefined && - targetcolor !== null && - targetcolor !== undefined && - !sourcecolor.includes("rgb") && - !targetcolor.includes("rgb") - ) { - console.log(sourcecolor) - console.log(targetcolor) + //const sourcecolor = cy + // .getElementById(event.target.data("source")) + // .style("border-color"); + //const targetcolor = cy + // .getElementById(event.target.data("target")) + // .style("border-color"); - if (event.target !== null && event.target.value !== null) { - event.target.animate({ - style: { - "target-arrow-color": targetcolor, - "line-fill": "linear-gradient", - "line-gradient-stop-colors": [sourcecolor, targetcolor], - "line-gradient-stop-positions": [0, 1], - }, - duration: animationDuration, - }) - } else { - event.target.animate({ - style: { - "target-arrow-color": targetcolor, - "line-fill": "linear-gradient", - "line-gradient-stop-colors": ["#41dcab", "#41dcab"], - "line-gradient-stop-positions": [0, 1], - }, - duration: animationDuration, - }) + ////console.log(sourcecolor, targetcolor) + //if ( + // sourcecolor !== null && + // sourcecolor !== undefined && + // targetcolor !== null && + // targetcolor !== undefined && + // !sourcecolor.includes("rgb") && + // !targetcolor.includes("rgb") + //) { + // console.log(sourcecolor) + // console.log(targetcolor) - } - } + // if (event.target !== null && event.target.value !== null) { + // event.target.animate({ + // style: { + // "target-arrow-color": targetcolor, + // "line-fill": "linear-gradient", + // "line-gradient-stop-colors": [sourcecolor, targetcolor], + // "line-gradient-stop-positions": [0, 1], + // }, + // duration: animationDuration, + // }) + // } else { + // event.target.animate({ + // style: { + // "target-arrow-color": targetcolor, + // "line-fill": "linear-gradient", + // "line-gradient-stop-colors": ["#41dcab", "#41dcab"], + // "line-gradient-stop-positions": [0, 1], + // }, + // duration: animationDuration, + // }) + + // } + //} + + if (event.target !== undefined && event.target !== null) { + //const targetcolor = "#66a8b1" + //const parsedStyle = { + // "width": "10px", + // "font-size": "18px", + // "target-arrow-color": "#66a8b1", + // "color": "#66a8b1", + //} + + //event.target.addClass("shuffle-hover-highlight"); + + //console.log("Style1: ", event.target) + //console.log("Style: ", event.target.style()) + + //event.target.animate( + // { + // style: parsedStyle, + // }, + // { + // duration: animationDuration, + // } + //) + } } - // Thanks :) + // Calculates how a trigger should curve + // Thanks to: // https://codepen.io/guillaumethomas/pen/xxbbBKO const calculateEdgeCurve = (sourcenodePosition, destinationnodePosition) => { const xParsed = destinationnodePosition.x - sourcenodePosition.x @@ -4120,10 +4611,16 @@ const AngularWorkflow = (defaultprops) => { node.position = action.position; node.data = action; + node.data.id = action["id"]; node.data._id = action["id"]; node.data.type = "ACTION"; node.isStartNode = action["id"] === workflow.start; + if (workflow.public === true) { + node.data.is_valid = true + node.is_valid = true + } + var example = ""; if ( action.example !== undefined && @@ -4177,6 +4674,7 @@ const AngularWorkflow = (defaultprops) => { node.data = trigger; node.data._id = trigger["id"]; + node.data.id = trigger["id"]; node.data.type = "TRIGGER"; return node; @@ -4232,13 +4730,18 @@ const AngularWorkflow = (defaultprops) => { // This is an attempt at prettier edges. The numbers are weird to work with. // Bezier curves //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html - const sourcenode = actions.find(node => node.data._id === branch.source_id) - const destinationnode = actions.find(node => node.data._id === branch.destination_id) - if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) { - //node.data._id = action["id"] - //console.log("SOURCE: ", sourcenode.position) - //console.log("DESTINATIONNODE: ", destinationnode.position) + var sourcenode = actions.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id) + var destinationnode = actions.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id) + if (sourcenode === undefined) { + sourcenode = triggers.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id) + } + + if (destinationnode === undefined) { + destinationnode = triggers.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id) + } + + if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) { const edgeCurve = calculateEdgeCurve(sourcenode.position, destinationnode.position) edge.style = { 'control-point-distance': edgeCurve.distance, @@ -4370,17 +4873,53 @@ const AngularWorkflow = (defaultprops) => { */ }; + + if (isLoaded && setupSent === false) { + setSetupSent(true) + + sendStreamRequest({ + "item": "workflow", + "type": "enter", + "userid": userdata.id, + }) + } + + const fetchUsecases = () => { + fetch(globalUrl + "/api/v1/workflows/usecases", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for usecases"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + console.log("Usecases: ", usecases) + setUsecases(responseJson) + } else { + } + }) + .catch((error) => { + //alert.error("ERROR: " + error.toString()); + console.log("ERROR getting usecases: " + error.toString()); + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps //useEffect(() => { if (firstrequest) { setFirstrequest(false); getWorkflow(props.match.params.key, {}); getApps(); - getAppAuthentication(); - getEnvironments(); - getWorkflowExecution(props.match.params.key, ""); - getAvailableWorkflows(-1); - getSettings(); + fetchUsecases() const cursearch = typeof window === "undefined" || window.location === undefined @@ -4428,42 +4967,77 @@ const AngularWorkflow = (defaultprops) => { setGraphSetup(true); setupGraph(); console.log("In graph setup") - } else if ( - // 2nd load - configures cytoscape - // - !established && - cy !== undefined && - ((apps !== null && - apps !== undefined && - apps.length > 0) || workflow.public === true) && - Object.getOwnPropertyNames(workflow).length > 0 && - authLoaded - ) { + + // 2nd load - configures cytoscape + } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && authLoaded) { console.log("In POST graph setup!") + + //This part has to load LAST, as it's kind of not async. //This means we need everything else to happen first. setEstablished(true); // Validate if the node is just a node lol - cy.edgehandles({ - handleNodes: (el) => { - if (el.isNode() && - !el.data("isButton") && - !el.data("isDescriptor") && - !el.data("isSuggestion") && - el.data("type") !== "COMMENT") { - return true - } - return false - }, - preview: true, - toggleOffOnLeave: true, - loopAllowed: function (node) { - return false; - }, - }); + console.log("CY grid: ", cy.gridGuide) + + // https://www.npmjs.com/package/cytoscape-grid-guide + // + if (cy.gridGuide !== undefined) { + cy.gridGuide({ + gridSpacing: 30, + guidelinesStyle: { + strokeStyle: "#8b7d6b", // color of geometric guidelines + geometricGuidelineRange: 400, // range of geometric guidelines + range: 100, // max range of distribution guidelines + minDistRange: 10, // min range for distribution guidelines + distGuidelineOffset: 10, // shift amount of distribution guidelines + horizontalDistColor: "#ff0000", // color of horizontal distribution alignment + verticalDistColor: "#00ff00", // color of vertical distribution alignment + initPosAlignmentColor: "#0000ff", // color of alignment to initial mouse location + lineDash: [0, 0], // line style of geometric guidelines + horizontalDistLine: [0, 0], // line style of horizontal distribution guidelines + verticalDistLine: [0, 0], // line style of vertical distribution guidelines + initPosAlignmentLine: [0, 0], // line style of alignment to initial mouse position + } + }) + } else { + console.log("ERROR: Failed to render grid as it's unitialized") + } + + if (cy.edgehandles !== undefined) { + console.log("Inside edgehandles") + cy.edgehandles({ + handleNodes: (el) => { + console.log("in handlenodes") + + if (el.isNode() && + !el.data("isButton") && + !el.data("isDescriptor") && + !el.data("isSuggestion") && + el.data("type") !== "COMMENT") { + return true + } + + return false + }, + preview: false, + toggleOffOnLeave: true, + loopAllowed: function (node) { + return false; + }, + }); + + //cy.edgehandles({ + // preview: false, + //}) + + //cy.edgehandles().enable() + } else { + console.log("ERROR: Failed to initialize edgehandler") + } + // preview: true, cy.fit(null, 200); @@ -4518,11 +5092,7 @@ const AngularWorkflow = (defaultprops) => { const stopSchedule = (trigger, triggerindex) => { fetch( - globalUrl + - "/api/v1/workflows/" + - props.match.params.key + - "/schedule/" + - trigger.id, + `${globalUrl}/api/v1/workflows/${props.match.params.key}/schedule/${trigger.id}`, { method: "DELETE", headers: { @@ -4556,7 +5126,8 @@ const AngularWorkflow = (defaultprops) => { saveWorkflow(workflow); }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Stop schedule error: ", error.toString()); }); }; @@ -4566,17 +5137,29 @@ const AngularWorkflow = (defaultprops) => { return; } - alert.info("Attempting to create schedule with name " + trigger.name); + var mappedStartnode = "" + const alledges = cy.edges().jsons() + for (var key in alledges) { + const tmp = alledges[key] + console.log("TMP: ", tmp, tmp.data.source) + if (tmp.data.source === trigger.id) { + mappedStartnode = tmp.data.target + break + } + } + + alert.info("Creating schedule with name " + trigger.name); const data = { name: trigger.name, frequency: workflow.triggers[triggerindex].parameters[0].value, execution_argument: workflow.triggers[triggerindex].parameters[1].value, environment: workflow.triggers[triggerindex].environment, id: trigger.id, - }; + start: mappedStartnode, + } fetch( - globalUrl + "/api/v1/workflows/" + props.match.params.key + "/schedule", + `${globalUrl}/api/v1/workflows/${props.match.params.key}/schedule`, { method: "POST", headers: { @@ -4608,7 +5191,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Get schedule error: ", error.toString()); }); }; @@ -4963,6 +5547,7 @@ const AngularWorkflow = (defaultprops) => { var thisview = ( @@ -5056,14 +5641,14 @@ const AngularWorkflow = (defaultprops) => { is_valid: true, label: "Webhook", environment: "onprem", - description: "Simple HTTP webhook", + description: "Custom HTTP input", long_description: "Execute a workflow with an unauthicated POST request", }, { name: "Schedule", type: "TRIGGER", status: "uninitialized", - description: "Schedule execution time", + description: "Specify time", trigger_type: "SCHEDULE", errors: null, large_image: @@ -5084,7 +5669,7 @@ const AngularWorkflow = (defaultprops) => { is_valid: true, label: "Subflow", environment: "onprem", - description: "Control another workflow", + description: "Control a workflow", long_description: "Execute another workflow from this workflow", }, { @@ -5105,10 +5690,10 @@ const AngularWorkflow = (defaultprops) => { name: "Office365", type: "TRIGGER", status: "uninitialized", - description: "Starts upon O365 email", + description: "O365 email trigger", trigger_type: "EMAIL", errors: null, - is_valid: cloudSyncEnabled || isCloud ? true : false, + is_valid: isCloud ? true : false, label: "Email", environment: "cloud", large_image: @@ -5119,10 +5704,10 @@ const AngularWorkflow = (defaultprops) => { name: "Gmail", type: "TRIGGER", status: "uninitialized", - description: "Trigger based on Gmail", + description: "Gmail email trigger", trigger_type: "EMAIL", errors: null, - is_valid: cloudSyncEnabled || isCloud ? true : false, + is_valid: isCloud ? true : false, label: "Email", environment: "cloud", large_image: @@ -5147,12 +5732,12 @@ const AngularWorkflow = (defaultprops) => { {triggers.map((trigger, index) => { var imageline = trigger.large_image.length === 0 ? ( - + ) : ( ); @@ -5196,10 +5781,11 @@ const AngularWorkflow = (defaultprops) => { display: "flex", flexDirection: "column", marginLeft: "20px", + overflow: "hidden", }} > - -

+ +

{trigger.name}

@@ -5312,11 +5898,11 @@ const AngularWorkflow = (defaultprops) => { //const activateApp = (appid) => { if (newAppData.activated === false) { console.log("SHOULD ACTIVATE!") - activateApp(newAppData.app_id) + activateApp(newAppData.app_id, false) } // AUTHENTICATION - if (app.authentication.required) { + if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) { console.log("App auth is required!") // Setup auth here :) @@ -5330,23 +5916,29 @@ const AngularWorkflow = (defaultprops) => { findAuthId = newAppData.authentication_id; } - console.log("Found auth: ", findAuthId) - var tmpAuth = JSON.parse(JSON.stringify(appAuthentication)); + const tmpAuth = JSON.parse(JSON.stringify(appAuthentication)); for (var key in tmpAuth) { var item = tmpAuth[key]; const newfields = {}; for (var filterkey in item.fields) { - newfields[item.fields[filterkey].key] = - item.fields[filterkey].value; + newfields[item.fields[filterkey].key] = item.fields[filterkey].value; } item.fields = newfields; - if (item.app.name === app.name) { + if (item.app.id === app.id || item.app.name === app.name) { authenticationOptions.push(item); - if (item.id === findAuthId) { - newAppData.selectedAuthentication = item; - } + + if (item.id === findAuthId) { + newAppData.selectedAuthentication = item + newAppData.authentication_id = item.id + + } else if (findAuthId === "") { + // Will always be set to the last one if one isn't found. + // Last = timestamp too + newAppData.selectedAuthentication = item + newAppData.authentication_id = item.id + } } } @@ -5357,7 +5949,8 @@ const AngularWorkflow = (defaultprops) => { ) { for (var key in authenticationOptions) { const option = authenticationOptions[key]; - if (option.active) { + + if (option.active && newAppData.authentication_id === "") { newAppData.selectedAuthentication = option; newAppData.authentication_id = option.id; break; @@ -5441,6 +6034,7 @@ const AngularWorkflow = (defaultprops) => { var description = "" if ( + app.actions[0].parameters !== undefined && app.actions[0].parameters !== null && app.actions[0].parameters.length > 0 ) { @@ -5448,6 +6042,8 @@ const AngularWorkflow = (defaultprops) => { } if ( + app.actions[0].returns !== undefined && + app.actions[0].returns !== null && app.actions[0].returns.example !== undefined && app.actions[0].returns.example !== null && app.actions[0].returns.example.length > 0 @@ -5503,6 +6099,7 @@ const AngularWorkflow = (defaultprops) => { : "", authentication_id: "", finished: false, + template: app.template === true ? true : false, }; // FIXME: overwrite category if the ACTION chosen has a different category @@ -5527,10 +6124,12 @@ const AngularWorkflow = (defaultprops) => { }; const AppView = (props) => { - const { allApps, prioritizedApps, filteredApps } = props; + const { allApps, prioritizedApps, filteredApps, extraApps } = props; + //extraApps, const [visibleApps, setVisibleApps] = React.useState( - prioritizedApps.concat( - filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id)) + Array.prototype.concat.apply( + prioritizedApps, + filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id)), ) ); @@ -5743,15 +6342,10 @@ const AngularWorkflow = (defaultprops) => { if (value.length > 0) { var newApps = allApps.filter( (app) => - app.name - .toLowerCase() - .includes( - value.trim().toLowerCase() || - app.description - .toLowerCase() - .includes(value.trim().toLowerCase()) - ) && !(!app.activated && app.generated) - ); + app.name.toLowerCase().includes(value.trim().toLowerCase()) + || + app.description.toLowerCase().includes(value.trim().toLowerCase()) + ) // Extend search if (newApps.length === 0) { @@ -5780,6 +6374,233 @@ const AngularWorkflow = (defaultprops) => { } }; + const SearchBox = ({currentRefinement, refine, isSearchStalled, } ) => { + + useEffect(() => { + if (document !== undefined) { + const appsearchValue = document.getElementById("appsearch") + if (appsearchValue !== undefined && appsearchValue !== null) { + console.log("Value2: ", appsearchValue.value) + if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) { + refine(appsearchValue.value) + } + } + //} + } + }, []) + + return ( +
@@ -5816,11 +6637,13 @@ const AngularWorkflow = (defaultprops) => { } }} onBlur={(event) => { - console.log("BLUR: ", event.target.value); + + //navigate(`?q=${event.target.value}`) + runSearch(event.target.value); }} /> - {visibleApps.length > 0 ? ( + {visibleApps.length > extraApps.length ? (
{visibleApps.map((app, index) => { if (app.invalid) { @@ -5851,10 +6674,21 @@ const AngularWorkflow = (defaultprops) => { ) : apps.length > 0 ? (
{ + console.log("Should load in extra apps?") + }} > - Couldn't find app. Is it active? + Couldn't find the app you're looking for? Searching unactivated apps. Click one of the below apps to Activate it for your organization. + { + console.log("CLICKED") + }}> + + + + +
) : (
@@ -5879,6 +6713,7 @@ const AngularWorkflow = (defaultprops) => { const getNextActionName = (appName) => { var highest = ""; + const allitems = workflow.actions.concat(workflow.triggers); for (var key in allitems) { const item = allitems[key]; @@ -5897,6 +6732,8 @@ const AngularWorkflow = (defaultprops) => { } } + appName = appName.replaceAll(" ", "_") + if (highest) { return appName + "_" + (parseInt(highest) + 1); } else { @@ -5920,10 +6757,11 @@ const AngularWorkflow = (defaultprops) => { if (workflow.actions !== undefined && workflow.actions !== null) { const foundInfo = workflow.actions.find(ac => ac.id === selectedAction.id) - console.log("aigo: ", foundInfo) } - console.log("PRe: ", selectedAction) + // Setting an old reference just to use the same memory space elsewhere + // for selectedAction + const oldaction = JSON.parse(JSON.stringify(selectedAction)) // Does this one find the wrong one? //var newSelectedAction = JSON.parse(JSON.stringify(selectedAction)) @@ -5936,10 +6774,14 @@ const AngularWorkflow = (defaultprops) => { //console.log(newSelectedAction) // Simmple action swap autocompleter - if (selectedAction.parameters !== undefined && newSelectedAction.parameters !== undefined && selectedAction.id === newSelectedAction.id) { - console.log("OLD: ", selectedAction, "NEW: ", newSelectedAction) - for (var paramkey in selectedAction.parameters) { - const param = selectedAction.parameters[paramkey]; + if (oldaction.parameters !== undefined && newSelectedAction.parameters !== undefined && oldaction.id === newSelectedAction.id) { + var fileid_found = false + for (var paramkey in oldaction.parameters) { + const param = oldaction.parameters[paramkey]; + + if (param.name === "file_id") { + fileid_found = true + } if (param.value === null || param.value === undefined || param.value.length === 0) { continue @@ -5951,16 +6793,32 @@ const AngularWorkflow = (defaultprops) => { } if (param.name === "headers") { - console.log("Swap header?") + console.log("Swap header? For now, yes. File found: ", fileid_found) + + if (fileid_found) { + newSelectedAction.parameters[paramkey].value = "" + newSelectedAction.parameters[paramkey].autocompleted = true + + continue + } //newSelectedAction.parameters[newParamIndex].value = param.value } + if (newSelectedAction.parameters === undefined || newSelectedAction.parameters === null) { + continue + } + + // Not doing options fields const newParamIndex = newSelectedAction.parameters.findIndex(paramdata => paramdata.name === param.name) if (newParamIndex < 0) { continue } newSelectedAction.parameters[newParamIndex].value = param.value + newSelectedAction.parameters[newParamIndex].autocompleted = true + if (param.options !== undefined && param.options !== null && param.options.length > 0) { + newSelectedAction.parameters[newParamIndex].autocompleted = false + } } } @@ -5978,21 +6836,19 @@ const AngularWorkflow = (defaultprops) => { newSelectedAction.fillGradient.length > 0 ) { newSelectedAction.fillstyle = "linear-gradient"; - console.log("GRADIENT!: ", newSelectedAction); } else { newSelectedAction.iconBackground = iconInfo.iconBackgroundColor; } const foundnode = cy.getElementById(newSelectedAction.id); if (foundnode !== null && foundnode !== undefined) { - console.log("UPDATING NODE!"); foundnode.data(newSelectedAction); } } - // Takes an action as input, then runs through and updates the relevant fields - // based on previous actions' + // Takes an action as input, then runs through and updates the relevant parameters based on previous actions' results (parent nodes) + // Further checks if those fields are already set in a previously used action newSelectedAction = RunAutocompleter(newSelectedAction); if ( @@ -6019,8 +6875,6 @@ const AngularWorkflow = (defaultprops) => { //setSelectedActionEnvironment(env) - console.log("NEW ACTION: ", newSelectedAction); - setSelectedAction(newSelectedAction); if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { const foundActionIndex = workflow.actions.findIndex(actiondata => actiondata.id === newSelectedAction.id) console.log("Found action on index ", foundActionIndex) @@ -6029,6 +6883,9 @@ const AngularWorkflow = (defaultprops) => { setWorkflow(workflow) } } + + console.log("NEW ACTION: ", newSelectedAction); + setSelectedAction(newSelectedAction); setUpdate(Math.random()); // FIXME - should change icon-node (descriptor) as well @@ -6036,7 +6893,7 @@ const AngularWorkflow = (defaultprops) => { for (var key in allNodes) { const currentNode = allNodes[key]; if ( - currentNode.data.attachedTo === selectedAction.id && + currentNode.data.attachedTo === oldaction.id && currentNode.data.isDescriptor ) { const foundnode = cy.getElementById(currentNode.data.id); @@ -6075,6 +6932,14 @@ const AngularWorkflow = (defaultprops) => { event.target.value = event.target.value.replaceAll(".", ""); event.target.value = event.target.value.replaceAll(",", ""); event.target.value = event.target.value.replaceAll(" ", "_"); + event.target.value = event.target.value.replaceAll("^", "_"); + event.target.value = event.target.value.replaceAll("'", "_"); + event.target.value = event.target.value.replaceAll("\"", "_"); + event.target.value = event.target.value.replaceAll("\\", "_"); + event.target.value = event.target.value.replaceAll(":", "_"); + event.target.value = event.target.value.replaceAll(";", "_"); + event.target.value = event.target.value.replaceAll("=", "_"); + event.target.value = event.target.value.replaceAll("+", "_"); selectedAction.label = event.target.value; setSelectedAction(selectedAction); @@ -6252,6 +7117,7 @@ const AngularWorkflow = (defaultprops) => { }; const setTriggerCronWrapper = (value) => { + console.log("Cron Value: ", value) if (selectedTrigger.parameters === null) { selectedTrigger.parameters = []; } @@ -6868,6 +7734,17 @@ const AngularWorkflow = (defaultprops) => { > less than + { + conditionValue.value = "is empty"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"is empty"} + > + is empty +
@@ -6927,8 +7804,10 @@ const AngularWorkflow = (defaultprops) => { } var currentedge = cy.getElementById(selectedEdge.id); - if (currentedge !== undefined && currentedge !== null) { - currentedge.data().label = label; + if (currentedge !== undefined && currentedge !== null && label !== undefined) { + currentedge.data("label", label) + //.label = label; + //oldstartnode[0].data("isStartNode", false); } setSelectedEdge(selectedEdge); @@ -7289,7 +8168,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error.toString()); + console.log("Get gmail folder error: ", error.toString()); }); }; @@ -7317,6 +8196,7 @@ const AngularWorkflow = (defaultprops) => { responseJson.success !== false && responseJson.length > 0 ) { + console.log("Got trigger folders: ", triggerFolders) setTriggerFolders(responseJson); } @@ -7330,7 +8210,7 @@ const AngularWorkflow = (defaultprops) => { name: "outlookfolder", id: responseJson[0].id, }, - ]; + ] selectedTrigger.parameters = [ { value: responseJson[0].displayName, @@ -7343,12 +8223,12 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error.toString()); + console.log("Get outlook folders error: ", error.toString()); }); }; const getTriggerAuth = () => { - fetch(globalUrl + "/api/v1/triggers/outlook/" + selectedTrigger.id, { + fetch(globalUrl + "/api/v1/triggers/" + selectedTrigger.id, { method: "GET", headers: { "content-type": "application/json" }, credentials: "include", @@ -7361,19 +8241,24 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .then((responseJson) => { + setTriggerAuthentication(responseJson); }) .catch((error) => { - console.log(error.toString()); + //console.log(error.toString()); + console.log("Set trigger auth error: ", error.toString()); }); }; // Getting the triggers and the folders if they exist - // This is horrible hahah if (localFirstrequest) { + //console.log("Trigger: ", selectedTrigger) + //console.log("Triggername: ", selectedTrigger.name) + //if (selectedTrigger.name.toLowerCase() === "gmail") { + setGmailFolders(); + setOutlookFolders(); + getTriggerAuth(); - setOutlookFolders(); - setGmailFolders(); setLocalFirstrequest(false); } @@ -7424,7 +8309,7 @@ const AngularWorkflow = (defaultprops) => { console.log("BRANCH: ", branch); const startnode = branch.destination_id; const scopes = "https://www.googleapis.com/auth/gmail.readonly"; - const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`; + const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`; console.log("URL: ", url); var newwin = window.open(url, "", "width=800,height=600"); @@ -7432,7 +8317,7 @@ const AngularWorkflow = (defaultprops) => { // Check whether we got a callback somewhere var id = setInterval(function () { fetch( - globalUrl + "/api/v1/triggers/gmail/" + selectedTrigger.id, + globalUrl + "/api/v1/triggers/" + selectedTrigger.id, { method: "GET", headers: { "content-type": "application/json" }, @@ -7454,7 +8339,7 @@ const AngularWorkflow = (defaultprops) => { setGmailFolders(); }) .catch((error) => { - console.log(error.toString()); + console.log("Set gmail trigg error: ", error.toString()); }); }, 2500); @@ -7500,7 +8385,8 @@ const AngularWorkflow = (defaultprops) => { `https%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister` //const client_id = "fd55c175-aa30-4fa6-b303-09a29fb3f750" - const client_id = "bb4bff85-0d0b-4f5d-8a69-3cee8029b11a"; + //const client_id = "bb4bff85-0d0b-4f5d-8a69-3cee8029b11a"; + const client_id = "efe4c3fe-84a1-4821-a84f-23a6cfe8e72d"; const username = userdata.id; console.log(redirectUri); @@ -7518,7 +8404,8 @@ const AngularWorkflow = (defaultprops) => { console.log("BRANCH: ", branch); const startnode = branch.destination_id; - const url = `https://login.microsoftonline.com/common/oauth2/authorize?access_type=offline&client_id=${client_id}&redirect_uri=${redirectUri}&resource=https%3A%2F%2Fgraph.microsoft.com&response_type=code&scope=Mail.Read+User.Read+https%3A%2F%2Foutlook.office.com%2Fmail.read&prompt=login&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Doutlook%26start%3d${startnode}`; + // prompt=login + const url = `https://login.microsoftonline.com/common/oauth2/authorize?access_type=offline&client_id=${client_id}&redirect_uri=${redirectUri}&resource=https%3A%2F%2Fgraph.microsoft.com&response_type=code&scope=Mail.Read+User.Read+https%3A%2F%2Foutlook.office.com%2Fmail.read&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Doutlook%26start%3d${startnode}`; //const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}` //const scopes = "https://www.googleapis.com/auth/gmail.readonly" @@ -7531,7 +8418,7 @@ const AngularWorkflow = (defaultprops) => { // Check whether we got a callback somewhere var id = setInterval(function () { fetch( - globalUrl + "/api/v1/triggers/outlook/" + selectedTrigger.id, + globalUrl + "/api/v1/triggers/ " + selectedTrigger.id, { method: "GET", headers: { "content-type": "application/json" }, @@ -7552,7 +8439,7 @@ const AngularWorkflow = (defaultprops) => { setOutlookFolders(); }) .catch((error) => { - console.log(error.toString()); + console.log("Set outlook trigger error: ", error.toString()); }); }, 2500); @@ -7626,61 +8513,73 @@ const AngularWorkflow = (defaultprops) => { }} />
- Select {triggerAuthentication.type === "gmail" ? "labels" : "folders"} (CTRL+click) + Select {triggerAuthentication.type === "gmail" ? "labels" : "a folder"}
- } - key={selectedTrigger} - > - {triggerFolders.map((folder) => { - var folderItem = ( - - ); + {triggerFolders.length === 0 ? + + No folders found. Please authenticate and make sure the user has access folders available. If this persists,
contact us. + + : + } + key={selectedTrigger} + > + {triggerFolders.map((folder) => { + var folderItem = ( + + ); - if (folder.childFolderCount > 0) { - // Here to handle subfolders sometime later - folderItem = ( - - ); - } + if (folder.childFolderCount > 0) { + // Here to handle subfolders sometime later + folderItem = ( + + ); + } - return folderItem; - })} - + return folderItem; + })} + + } )}
@@ -7715,6 +8614,15 @@ const AngularWorkflow = (defaultprops) => {
{outlookButton} {gmailButton} + + If you have trouble using this trigger, please { + if (window.drift !== undefined) { + window.drift.api.startInteraction({ interactionId: 340043 }) + } else { + console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) + } + }}>contact us to get access +
); @@ -8599,7 +9507,7 @@ const AngularWorkflow = (defaultprops) => { ) }} - renderInput={(params) => { + renderInput={(params) => { return ( { />

+
Background-Image
+ { + selectedComment.backgroundimage = event.target.value; + console.log("Comment: ", selectedComment) + setSelectedComment(selectedComment); + }} + />
); } @@ -9116,63 +10049,171 @@ const AngularWorkflow = (defaultprops) => { placeholder={selectedTrigger.label} onChange={selectedTriggerChange} /> -
- Environment - -
+ return newname; + }} + options={sortByKey(apps, "name")} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + // Workaround with event lol + console.log(event, newValue) + if (newValue !== undefined && newValue !== null) { + var parsedvalue = JSON.parse(JSON.stringify(newValue)) + parsedvalue.actions = [] + parsedvalue.authentication = {} + selectedTrigger.app_association = parsedvalue + setUpdate(Math.random()); + } + // setNewSelectedAction({ + // target: { + // value: newValue.name + // } + // }); + //} + }} + renderOption={(app) => { + var appname = app.name.replaceAll("_", " ") + appname = appname.charAt(0).toUpperCase() + appname.substring(1) + + return ( + +
+
+ + {appname} + + + {appname} + +
+
+
+ ) + }} + renderInput={(params) => { + return ( + + ); + }} + /> + +
+ : null} + {selectedTrigger.status === "running" ? null : +
+ Environment + +
+ } { }} id="webhook_uri_field" onClick={() => { - var copyText = document.getElementById("webhook_uri_field"); - if (copyText !== undefined && copyText !== null) { - console.log("NAVIGATOR: ", navigator); - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)"); - return; - } - - navigator.clipboard.writeText(copyText.value); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999 - ); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - alert.success("Copied Webhook URL"); - } else { - console.log("Couldn't find webhook URI field: ", copyText); - } }} helperText={ workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined && @@ -9259,6 +10278,39 @@ const AngularWorkflow = (defaultprops) => { maxWidth: "95%", fontSize: "1em", }, + endAdornment: + + { + var copyText = document.getElementById("webhook_uri_field"); + if (copyText !== undefined && copyText !== null) { + console.log("NAVIGATOR: ", navigator); + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + alert.error("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(copyText.value); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + alert.success("Copied Webhook URL"); + } else { + console.log("Couldn't find webhook URI field: ", copyText); + } + }} + edge="end" + > + + + }} fullWidth disabled @@ -9472,7 +10524,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Stop mailsub error: ", error.toString()); }); }; @@ -9486,7 +10539,9 @@ const AngularWorkflow = (defaultprops) => { const splitItem = workflow.triggers[selectedTriggerIndex].parameters[0].value.split( splitter - ); + ) + + console.log("Starting mail sub: ", workflow.triggers[selectedTriggerIndex].parameters[0].value, splitItem); for (var key in splitItem) { const item = splitItem[key]; const curfolder = triggerFolders.find((a) => a.displayName === item); @@ -9548,7 +10603,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Start mailsub error: ", error.toString()); }); }; @@ -9632,7 +10688,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error.toString()); + //console.log(error.toString()); + console.log("New webhook error: ", error.toString()); }); }; @@ -9675,7 +10732,8 @@ const AngularWorkflow = (defaultprops) => { setSelectedTrigger(trigger); }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + alert.error("Delete webhook error: ", error.toString()); }); }; @@ -10220,6 +11278,14 @@ const AngularWorkflow = (defaultprops) => { setTriggerCronWrapper(e.target.value); }} /> + {/*selectedTrigger.environment === "cloud" ? + + : + null + */}
{ />
+ {/* { + */} {isMobile ? { }; const BottomCytoscapeBar = () => { - if ( - workflow.id === undefined || - workflow.id === null || - apps.length === 0 - ) { + if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) { return null; } @@ -10610,7 +11674,7 @@ const AngularWorkflow = (defaultprops) => { + @@ -10813,6 +11917,27 @@ const AngularWorkflow = (defaultprops) => { workflow.configuration.exit_on_error !== undefined ? ( ) : null} + + + + +
); @@ -10824,11 +11949,12 @@ const AngularWorkflow = (defaultprops) => { x: 300, y: 300, }; + cy.add({ group: "nodes", data: { id: newId, - label: "Your comment :)", + label: "Click to write a comment", type: "COMMENT", is_valid: true, decorator: true, @@ -10886,6 +12012,8 @@ const AngularWorkflow = (defaultprops) => { defaultReturn = { //}}>Execute websocket // - + // A list used for FRONTEND handling of whether a public workflow + // should be change-able + const allowList = ["frikky", "m1nk-code", "DavidtheGoliath"] + // console.log(allowList, userdata.public_username) + const leftView = workflow.public === true ? -
+
{ This workflow is public and { saveWorkflow() - }}>must be saved to be used in your organization. + }}>must be saved or exported before use. {Object.getOwnPropertyNames(creatorProfile).length !== 0 && creatorProfile.github_avatar !== undefined && creatorProfile.github_avatar !== null ?
@@ -11055,6 +12187,27 @@ const AngularWorkflow = (defaultprops) => {
: null } + + {workflow.blogpost !== undefined && workflow.blogpost !== null && workflow.blogpost.length > 0 ? + + : null + } + {appGroup.length > 0 ?
@@ -11071,6 +12224,7 @@ const AngularWorkflow = (defaultprops) => {
: null} + {triggerGroup.length > 0 ?
@@ -11086,6 +12240,7 @@ const AngularWorkflow = (defaultprops) => {
: null} + {/*
Mitre Att&ck:  @@ -11094,6 +12249,8 @@ const AngularWorkflow = (defaultprops) => { TBD
+ */} + {/*
@@ -11104,27 +12261,97 @@ const AngularWorkflow = (defaultprops) => {
*/} + + {workflow.video !== undefined && workflow.video !== null && workflow.video.length > 0 ? +
+ + Video + + { + workflow.video.includes("loom.com/share") && workflow.video.split("/").length > 4 ? +
+ - - : null} + + + + + +
Getting Started with Shuffle @@ -2292,8 +2289,34 @@ const GettingStarted = (props) => { console.log("Found tutorial for ", data.tutorial) tutorialFound = true } - } + if (tutorialFound === false) { + if (data.tutorial === "discover_workflows") { + if (workflows.length > 0) { + for (var key in workflows) { + const tmpworkflow = workflows[key] + if (tmpworkflow.published_id !== undefined && tmpworkflow.published_id !== null && tmpworkflow.published_id.length > 0) { + tutorialFound = true + break + } + } + } + } + + if (data.tutorial === "learn_shuffle") { + //tutorial: "discover_workflows", + if (workflows.length > 0) { + tutorialFound = true + } + } + + if (data.tutorial === "configure_organization") { + if (userdata.active_org.name !== userdata.username) { + tutorialFound = true + } + } + } + } return (
@@ -2350,6 +2373,14 @@ const GettingStarted = (props) => {
+ {/* +
+ + Need assistance? Ask our support team (it's free!). + + +
+ */}
{/*
diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index 0e6c6b51..96329944 100644 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -184,10 +184,22 @@ const LoginDialog = (props) => { ); } - setIsLoggedIn(true); + if (responseJson.tutorials === undefined || responseJson.tutorials === null || !responseJson.tutorials.includes("welcome")) { + console.log("RUN Welcome!!") + window.location.pathname = "/welcome" + return + } - //navigate("/workflows") - window.location.href = "/workflows" + const tmpView = new URLSearchParams(window.location.search).get("view") + if (tmpView !== undefined && tmpView !== null) { + //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}` + const newUrl = `/${tmpView}` + window.location.pathname = newUrl + } else { + window.location.pathname = "/workflows" + } + + setIsLoggedIn(true); } }) ) @@ -285,7 +297,7 @@ const LoginDialog = (props) => { {loginInfo === undefined || loginInfo === null || loginInfo.length === 0 ? null : ( -
Response: {loginInfo}
+
Database Response: {loginInfo}
)} diff --git a/frontend/src/views/MyView.jsx b/frontend/src/views/MyView.jsx index 3420a7d8..42c72bc3 100644 --- a/frontend/src/views/MyView.jsx +++ b/frontend/src/views/MyView.jsx @@ -51,7 +51,8 @@ import { GridToolbarContainer, GridDensitySelector, GridToolbar, -} from "@material-ui/data-grid"; +} from "@mui/x-data-grid"; + import { makeStyles } from "@material-ui/core/styles"; import ListIcon from "@material-ui/icons/List"; diff --git a/frontend/src/views/Search.jsx b/frontend/src/views/Search.jsx new file mode 100644 index 00000000..3fd27c28 --- /dev/null +++ b/frontend/src/views/Search.jsx @@ -0,0 +1,188 @@ +import React, { useState, useEffect } from "react"; + +import theme from '../theme'; +import {isMobile} from "react-device-detect"; +import AppGrid from "../components/AppGrid.jsx" +import WorkflowGrid from "../components/WorkflowGrid.jsx" +import CreatorGrid from "../components/CreatorGrid.jsx" +import DocsGrid from "../components/DocsGrid.jsx" +import { useNavigate } from "react-router-dom"; + +import { + Tabs, + Tab, +} from "@material-ui/core"; + +import { + 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 Search = (props) => { + const { globalUrl, isLoaded, serverside, userdata, hidemargins, } = props; + 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 views = { + 0: "apps", + 1: "workflows", + 2: "docs", + 3: "creators", + } + + const setConfig = (event, inputValue) => { + const newValue = parseInt(inputValue) + + setCurTab(newValue) + if (newValue === 0) { + document.title = "Shuffle - search - apps"; + } else if (newValue === 1) { + document.title = "Shuffle - search - workflows"; + } else if (newValue === 2) { + document.title = "Shuffle - search - documentation"; + } else if (newValue === 3) { + document.title = "Shuffle - search - creators"; + } else { + document.title = "Shuffle - search"; + } + + + const urlSearchParams = new URLSearchParams(window.location.search) + const params = Object.fromEntries(urlSearchParams.entries()) + const foundQuery = params["q"] + var extraQ = "" + if (foundQuery !== null && foundQuery !== undefined) { + extraQ = "&q="+foundQuery + } + + + if ((serverside === false || serverside === undefined) && window.location.pathname.includes("/search")) { + navigate(`/search?tab=${views[newValue]}`+extraQ) + } + } + + if (isLoaded === false) { + return null + } + + + // Random names for type & autoComplete. Didn't research :^) + const landingpageDataBrowser = +
+
+ + + Apps + + /> + + Workflows + + /> + + Docs + + /> + + Creators + + /> + + {curTab === 0 ? + + : + curTab === 1 ? + window.location.pathname === "/search" ? + + : + + : + curTab === 2 ? + + : + curTab === 3 ? + + : + null} +
+
+ //{/*alternativeView={true} />*/} + + const loadedCheck = isLoaded ? +
+
{landingpageDataBrowser}
+
+ : +
+
+ + // #1f2023? + return( +
+ {loadedCheck} +
+ ) +} + +export default Search; diff --git a/frontend/src/views/SetAuthentication.jsx b/frontend/src/views/SetAuthentication.jsx index e2dd8be3..58f652ed 100644 --- a/frontend/src/views/SetAuthentication.jsx +++ b/frontend/src/views/SetAuthentication.jsx @@ -1,10 +1,10 @@ -import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; +import React, { useState } from "react"; import { Typography, CircularProgress } from "@material-ui/core"; import theme from '../theme'; const SetAuthentication = (props) => { - const { globalUrl, isLoggedIn, isLoaded, userdata } = props; + const { globalUrl } = props; const [firstRequest, setFirstRequest] = useState(true); const [finished, setFinished] = useState(false); @@ -20,7 +20,7 @@ const SetAuthentication = (props) => { const params = Object.fromEntries(urlSearchParams.entries()); console.log("PARAMS: ", params) - const authenticationStore = []; + //const authenticationStore = []; var appAuthData = { label: "", app: { @@ -60,6 +60,7 @@ const SetAuthentication = (props) => { externalData.code = params.code } + var foundScope = "" if (params.state !== undefined && params.state !== null) { const paramsplit = params.state.split("&"); console.log(paramsplit); @@ -112,6 +113,7 @@ const SetAuthentication = (props) => { if (query[0] === "scope") { appAuthData.fields.push({ key: "scope", value: query[1] }); + foundScope = query[1] } if (query[0] === "client_id") { @@ -136,145 +138,157 @@ const SetAuthentication = (props) => { } } - if (externalData.handleExternal) { - console.log("RUN EXTERNAL!!: ", externalData) - - fetch(globalUrl + "/api/v1/triggers/github/register", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - body: JSON.stringify(externalData), - }) - .then((response) => { - const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; - const tmpView = new URLSearchParams(cursearch).get("state"); - if ( - tmpView !== undefined && - tmpView !== null && - tmpView.length > 0 - ) { - console.log("State to find app name from: ", tmpView) + if (foundScope !== undefined && foundScope !== null && foundScope.length > 0) { + appAuthData.label = `${foundScope}` + } + + var foundTab = params["error"]; + if (foundTab !== null && foundTab !== undefined && foundTab.length > 0) { + console.log("Found error: ", foundTab, "! Skipping Shuffle requests to validate Oauth2") + var errorDesc = params["error_description"] + if (errorDesc !== null && errorDesc !== undefined && errorDesc.length > 0) { + foundTab += "\n\n"+errorDesc + } + + setFailed(true) + setResponse(`${foundTab}`) + } else { + if (externalData.handleExternal) { + console.log("RUN EXTERNAL!!: ", externalData) + + fetch(globalUrl + "/api/v1/triggers/github/register", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(externalData), + }) + .then((response) => { + const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; + const tmpView = new URLSearchParams(cursearch).get("state"); + if ( + tmpView !== undefined && + tmpView !== null && + tmpView.length > 0 + ) { + console.log("State to find app name from: ", tmpView) + } + + if (response.status !== 200) { + console.log("Status not 200 for oauth2 authentication"); + setFailed(true); + } else { + setFinished(true); + //setTimeout(() => { + // window.close(); + //}, 2500); + } + + return response.json(); + }) + .then((responseJson) => { + //setUserSettings(responseJson) + console.log("Resp: ", responseJson); + + if (responseJson.reason !== undefined) { + setResponse(responseJson.reason); + setFinished(true); + + } else { + const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; + var tmpView = new URLSearchParams(cursearch).get("error_description"); + if ( + tmpView !== undefined && + tmpView !== null && + tmpView.length > 0 + ) { + setResponse(tmpView) + } else { + tmpView = new URLSearchParams(cursearch).get("error"); + if ( + tmpView !== undefined && + tmpView !== null && + tmpView.length > 0 + ) { + setResponse(tmpView) + } + } + } + + }) + .catch((error) => { + console.log(error); + }); + + return + } + + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(appAuthData), + }) + .then((response) => { + const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; + const tmpView = new URLSearchParams(cursearch).get("state"); + if ( + tmpView !== undefined && + tmpView !== null && + tmpView.length > 0 + ) { + console.log("State to find app name from: ", tmpView) + } + + if (response.status !== 200) { + console.log("Status not 200 for oauth2 authentication"); + setFailed(true); + } else { + setFinished(true); + setTimeout(() => { + window.close(); + }, 2500); } - if (response.status !== 200) { - console.log("Status not 200 for oauth2 authentication"); - setFailed(true); - } else { - setFinished(true); - //setTimeout(() => { - // window.close(); - //}, 2500); - } + return response.json(); + }) + .then((responseJson) => { + //setUserSettings(responseJson) + if (responseJson.reason !== undefined) { + setResponse(responseJson.reason); + setFinished(true); - return response.json(); - }) - .then((responseJson) => { - //setUserSettings(responseJson) - console.log("Resp: ", responseJson); - - if (responseJson.reason !== undefined) { - setResponse(responseJson.reason); - setFinished(true); - - } else { - const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; - var tmpView = new URLSearchParams(cursearch).get("error_description"); - if ( - tmpView !== undefined && - tmpView !== null && - tmpView.length > 0 - ) { - setResponse(tmpView) } else { - tmpView = new URLSearchParams(cursearch).get("error"); - if ( - tmpView !== undefined && - tmpView !== null && - tmpView.length > 0 - ) { + const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; + var tmpView = new URLSearchParams(cursearch).get("error_description"); + if ( + tmpView !== undefined && + tmpView !== null && + tmpView.length > 0 + ) { setResponse(tmpView) + } else { + tmpView = new URLSearchParams(cursearch).get("error"); + if ( + tmpView !== undefined && + tmpView !== null && + tmpView.length > 0 + ) { + setResponse(tmpView) + } } } - } - }) - .catch((error) => { - console.log(error); - }); - - return - } - - console.log(appAuthData); - - fetch(globalUrl + "/api/v1/apps/authentication", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - body: JSON.stringify(appAuthData), - }) - .then((response) => { - const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; - const tmpView = new URLSearchParams(cursearch).get("state"); - if ( - tmpView !== undefined && - tmpView !== null && - tmpView.length > 0 - ) { - console.log("State to find app name from: ", tmpView) - } - - if (response.status !== 200) { - console.log("Status not 200 for oauth2 authentication"); - setFailed(true); - } else { - setFinished(true); - setTimeout(() => { - window.close(); - }, 2500); - } - - return response.json(); - }) - .then((responseJson) => { - //setUserSettings(responseJson) - console.log("Resp: ", responseJson); - - if (responseJson.reason !== undefined) { - setResponse(responseJson.reason); - setFinished(true); - - } else { - const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; - var tmpView = new URLSearchParams(cursearch).get("error_description"); - if ( - tmpView !== undefined && - tmpView !== null && - tmpView.length > 0 - ) { - setResponse(tmpView) - } else { - tmpView = new URLSearchParams(cursearch).get("error"); - if ( - tmpView !== undefined && - tmpView !== null && - tmpView.length > 0 - ) { - setResponse(tmpView) - } - } - } - - }) - .catch((error) => { - console.log(error); - }); + }) + .catch((error) => { + console.log(error); + }); + } } return ( @@ -298,6 +312,9 @@ const SetAuthentication = (props) => { )}
{failed ? "Failed setup. Error: " : ""} {response} +
+
+ {failed ? "If the error persists, try to use fewer scopes. Contact our support at support@shuffler.io if you need further assistance. You may close this window." : ""}
); diff --git a/frontend/src/views/SetAuthenticationSSO.jsx b/frontend/src/views/SetAuthenticationSSO.jsx index 92afe942..371eedb8 100644 --- a/frontend/src/views/SetAuthenticationSSO.jsx +++ b/frontend/src/views/SetAuthenticationSSO.jsx @@ -1,9 +1,9 @@ -import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; +import React, { useState } from "react"; import { Typography, CircularProgress } from "@material-ui/core"; const SetAuthentication = (props) => { - const { globalUrl, isLoggedIn, isLoaded, userdata } = props; + const { globalUrl } = props; const [firstRequest, setFirstRequest] = useState(true); const [finished, setFinished] = useState(false); @@ -18,7 +18,7 @@ const SetAuthentication = (props) => { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); - const authenticationStore = []; + // const authenticationStore = []; var appAuthData = { label: "", app: { diff --git a/frontend/src/views/SettingsPage.jsx b/frontend/src/views/SettingsPage.jsx index a8e0e0e9..953c0551 100644 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; import { Grid, Typography, @@ -8,7 +9,6 @@ import { Divider, TextField, } from "@material-ui/core"; -import { Link } from "react-router-dom"; import { useAlert } from "react-alert"; import { useTheme } from "@material-ui/core/styles"; @@ -18,23 +18,21 @@ const Settings = (props) => { const { globalUrl, isLoaded, userdata, setUserData } = props; const theme = useTheme(); const alert = useAlert(); + let navigate = useNavigate(); const [username, setUsername] = useState(""); 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 [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [newPassword2, setNewPassword2] = useState(""); - const [file, setFile] = React.useState(""); - const [fileBase64, setFileBase64] = React.useState( - userdata.image === undefined || userdata.image === null - ? theme.palette.defaultImage - : userdata.image - ); + // const [file, setFile] = React.useState(""); + // const [fileBase64, setFileBase64] = React.useState( + // userdata.image === undefined || userdata.image === null + // ? theme.palette.defaultImage + // : userdata.image + // ); const [loadedValidationWorkflows, setLoadedValidationWorkflows] = React.useState([]); const [selfOwnedWorkflows, setSelfOwnedWorkflows] = React.useState([]); @@ -42,7 +40,6 @@ const Settings = (props) => { React.useState([]); // Used for error messages etc - const [formMessage] = useState(""); const [passwordFormMessage, setPasswordFormMessage] = useState(""); const [firstrequest, setFirstRequest] = useState(true); @@ -295,58 +292,58 @@ const Settings = (props) => { } }; - const registerProviders = (userdata) => { - // Register hooks here - detectEthereumProvider().then((provider) => { - if (provider) { - if (!provider.isMetaMask) { - alert.error("Only MetaMask is supported as of now."); - return; - } + // const registerProviders = (userdata) => { + // // Register hooks here + // detectEthereumProvider().then((provider) => { + // if (provider) { + // if (!provider.isMetaMask) { + // alert.error("Only MetaMask is supported as of now."); + // return; + // } - // Find the ethereum network - // Get the users' account(s) - //alert.info("Connecting to MetaMask") - //console.log("Connected: ", provider.isConnected()) + // // Find the ethereum network + // // Get the users' account(s) + // //alert.info("Connecting to MetaMask") + // //console.log("Connected: ", provider.isConnected()) - if (!provider.isConnected()) { - alert.error("Metamask is not connected."); - return; - } + // if (!provider.isConnected()) { + // alert.error("Metamask is not connected."); + // return; + // } - provider.on("message", (event) => { - alert.info("Ethereum message: ", event); - }); + // provider.on("message", (event) => { + // alert.info("Ethereum message: ", event); + // }); - provider.on("chainChanged", (chainId) => { - console.log("Changed chain to: ", chainId); + // provider.on("chainChanged", (chainId) => { + // console.log("Changed chain to: ", chainId); - const method = "eth_getBalance"; - const params = [userdata.eth_info.account, "latest"]; - provider - .request({ - method: method, - params, - }) - .then((result) => { - console.log("Got result: ", result); - if (result !== undefined && result !== null) { - userdata.eth_info.balance = result; - userdata.eth_info.parsed_balance = result / 1000000000000000000; - console.log("INFO: ", userdata); - setUserData(userdata); - } 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); - }); - }); - } - }); - }; + // const method = "eth_getBalance"; + // const params = [userdata.eth_info.account, "latest"]; + // provider + // .request({ + // method: method, + // params, + // }) + // .then((result) => { + // console.log("Got result: ", result); + // if (result !== undefined && result !== null) { + // userdata.eth_info.balance = result; + // userdata.eth_info.parsed_balance = result / 1000000000000000000; + // console.log("INFO: ", userdata); + // setUserData(userdata); + // } 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); + // }); + // }); + // } + // }); + // }; // This should "always" have data useEffect(() => { @@ -414,7 +411,15 @@ const Settings = (props) => { src={imageData} alt="Click to upload an image (174x174)" id="logo" + onClick={() => { + if (imageData !== theme.palette.defaultImage) { + navigate(`/creators/${userdata.public_username}`) + } else { + navigate(`/creators`) + } + }} style={{ + cursor: "pointer", maxWidth: 100, maxHeight: 100, minWidth: 100, @@ -733,7 +738,7 @@ const Settings = (props) => { {isCloud ? - By connecting your Github account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. + By connecting your Github or Metamask account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. Support: support@shuffler.io
- {userdata !== undefined && + {/*userdata !== undefined && userdata.eth_info !== undefined && userdata.eth_info.account !== undefined && userdata.eth_info.account.length > 0 ? ( @@ -868,7 +873,7 @@ const Settings = (props) => { > Authenticate Metamask Wallet - )} + )*/}
diff --git a/frontend/src/views/TempDashboard.jsx b/frontend/src/views/TempDashboard.jsx new file mode 100644 index 00000000..4d4ab368 --- /dev/null +++ b/frontend/src/views/TempDashboard.jsx @@ -0,0 +1,298 @@ +import React from "react"; +import { Grid, Container, Divider } from "@mui/material"; + +import { makeStyles } from "@material-ui/core/styles"; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; + +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import Paper from "@material-ui/core/Paper"; + +import { LineChart, LineSeries, BarChart } from "reaviz"; +import { GridStripe } from "reaviz"; +//import { GridlineSeries } from "reaviz"; + +import InputLabel from '@material-ui/core/InputLabel'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; + +const data = [ + { + key: new Date("11/29/2019"), + data: 10, + }, + { + key: new Date("11/30/2019"), + data: 14, + }, + { + key: new Date("12/01/2019"), + data: 5, + }, + { + key: new Date("12/02/2019"), + data: 18, + }, +]; + +const useStyles1 = makeStyles((theme) => ({ + formControl: { + margin: theme.spacing(1), + minWidth: 120, + }, + selectEmpty: { + marginTop: theme.spacing(2), + }, +})); + + +const useStyles = makeStyles({ + table: { + minWidth: 650, + }, + root: { + minWidth: 275, + }, + bullet: { + display: "inline-block", + margin: "0 2px", + transform: "scale(0.8)", + }, + title: { + fontSize: 14, + }, + pos: { + marginBottom: 12, + }, +}); + +function createData(name, calories, fat, carbs, protein) { + return { name, calories, fat, carbs, protein }; +} + +const rows = [ + createData("Frozen yoghurt", 159, 6.0, 24, 4.0), + createData("Ice cream sandwich", 237, 9.0, 37, 4.3), + createData("Eclair", 262, 16.0, 24, 6.0), + createData("Cupcake", 305, 3.7, 67, 4.3), + createData("Gingerbread", 356, 16.0, 49, 3.9), +]; + +const DashboardPage = () => { + const classes = useStyles(); + const classes1 = useStyles1(); + + const [age, setAge] = React.useState(0); + + const handleChange = (event) => { + setAge(event.target.value); + + }; + + return ( + + + +
+ + Dashboard + +
+ + Organization + + +
+
+
+ +
+ + + + + + Total workflows executions + + + 456 + + + + + + + + + Total Apps executions + + + 587 + + + + + + + + + Total failed executions + + + 999 + + + + + + + + + + + } + series={} + /> + + + + + + + + + Dessert (100g serving) + Calories + Fat (g) + Carbs (g) + Protein (g) + + + + {rows.map((row) => ( + + + {row.name} + + {row.calories} + {row.fat} + {row.carbs} + {row.protein} + + ))} + +
+
+
+
+
+ ); +}; + +export default DashboardPage; diff --git a/frontend/src/views/Welcome.jsx b/frontend/src/views/Welcome.jsx new file mode 100644 index 00000000..111a0076 --- /dev/null +++ b/frontend/src/views/Welcome.jsx @@ -0,0 +1,483 @@ +import React, { useState, useEffect } from 'react'; +import ReactGA from 'react-ga'; +import WelcomeForm2 from "../components/WelcomeForm2.jsx"; +import Stepper from "@material-ui/core/Stepper"; +import Step from "@material-ui/core/Step"; +import StepLabel from "@material-ui/core/StepLabel"; +import AppFramework from "../components/AppFramework.jsx"; +import { + Grid, + Container, + Fade, + Typography, + Paper, + Button, + Card, + CardContent, + CardActionArea, +} from '@mui/material'; +import theme from '../theme'; +import { useNavigate, Link } from "react-router-dom"; + +const Welcome = (props) => { + const { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded } = props; + const [skipped, setSkipped] = React.useState(new Set()); + const [inputUsecase, setInputUsecase] = useState({}); + const [frameworkData, setFrameworkData] = useState(undefined); + const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined); + const [activeStep, setActiveStep] = React.useState(0); + const [apps, setApps] = React.useState([]); + const [defaultSearch, setDefaultSearch] = React.useState("") + const [selectionOpen, setSelectionOpen] = React.useState(false) + const [showWelcome, setShowWelcome] = React.useState(false) + const [usecases, setUsecases] = React.useState([]); + const [workflows, setWorkflows] = React.useState([]); + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + const [steps, setSteps] = useState([ + "Help us get to know you", + "Find your Apps", + "Discover Usecases", + ]) + + let navigate = useNavigate(); + + const handleKeysetting = (categorydata, workflows) => { + //workflows[0].category = ["detect"] + //workflows[0].usecase_ids = ["Correlate tickets"] + + if (workflows !== undefined && workflows !== null) { + var newcategories = [] + for (var key in categorydata) { + var category = categorydata[key] + category.matches = [] + + for (var subcategorykey in category.list) { + var subcategory = category.list[subcategorykey] + subcategory.matches = [] + + for (var workflowkey in workflows) { + const workflow = workflows[workflowkey] + + if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) { + for (var usecasekey in workflow.usecase_ids) { + + if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { + //console.log("Got match: ", workflow.usecase_ids[usecasekey]) + + category.matches.push({ + "workflow": workflow.id, + "category": subcategory.name, + }) + subcategory.matches.push(workflow.id) + break + } + } + } + + if (subcategory.matches.length > 0) { + break + } + } + } + + newcategories.push(category) + } + + setUsecases(newcategories) + } else { + setUsecases(categorydata) + } + + setWorkflows(workflows) + } + + const fetchUsecases = (workflows) => { + fetch(globalUrl + "/api/v1/workflows/usecases", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for usecases"); + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson.success !== false) { + handleKeysetting(responseJson, workflows) + } else { + //setWorkflows(workflows); + //setWorkflowDone(true); + } + }) + .catch((error) => { + console.log("Usecase error: " + error.toString()) + }); + } + + const getAvailableWorkflows = () => { + fetch(globalUrl + "/api/v1/workflows", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!: ", response.status); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson !== undefined) { + var newarray = [] + for (var key in responseJson) { + const wf = responseJson[key] + if (wf.public === true) { + continue + } + + newarray.push(wf) + } + + // Workflows are set in here + fetchUsecases(newarray) + } + }) + .catch((error) => { + console.log("err in get workflows: ", error.toString()); + }) + } + + const getFramework = () => { + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { + 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) { + setFrameworkData({}) + + if (responseJson.reason !== undefined) { + //alert.error("Failed loading: " + responseJson.reason) + } else { + //alert.error("Failed to load framework for your org.") + } + } else { + setFrameworkData(responseJson) + } + }) + .catch((error) => { + console.log("err in framework: ", error.toString()); + }) + } + + const getApps = () => { + fetch(globalUrl + "/api/v1/apps", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + setApps(responseJson); + }) + .catch((error) => { + console.log("App loading error: "+error.toString()); + }); + } + + const usecaseButtons = [{ + "name": "Phishing", + "usecase": "Email management", + "color": "#C51152", + }, { + "name": "Enrichment", + "usecase": "2. Enrich", + "color": "#F4C20D", + }, { + "name": "Detection", + "usecase": "3. Detect", + "color": "#3CBA54", + }, { + "name": "Response", + "usecase": "4. Respond", + "color": "#4885ED", + }] + + const handleSetSearch = (input, orgupdate) => { + console.log("INPUT & ORGUPDATE: ", input, orgupdate, defaultSearch) + if (input !== defaultSearch) { + setDefaultSearch(input) + setSelectionOpen(false) + setTimeout(function(){ + setSelectionOpen(true) + }, 150); + + //if (userdata !== undefined && userdata.active_org !== undefined && userdata.active_org.id !== undefined) { + // sendOrgUpdate("", "", userdata.active_org.id, orgupdate) + //} + } else { + setDefaultSearch("") + setSelectionOpen(false) + } + } + + useEffect(() => { + getFramework() + getApps() + getAvailableWorkflows() + + if ( + window.location.search !== undefined && + window.location.search !== null + ) { + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundTab = params["tab"]; + if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) { + console.log("FOUND TAB: ", foundTab) + setShowWelcome(true) + if (foundTab === 3 || foundTab === "3") { + console.log("SET SEARCH!!") + + handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase) + } + + setActiveStep(foundTab-1) + } else { + navigate(`/welcome?tab=1`) + } + } + }, []) + + const isStepSkipped = step => { + return skipped.has(step) + } + + const paperObject = { + flex: 1, + padding: 0, + textAlign: "center", + maxWidth: 300, + minWidth: 300, + backgroundColor: theme.palette.surfaceColor, + color: "white", + } + + const actionObject = { + padding: "50px 35px 50px 35px", + } + + const imageStyle = { + width: 150, + height: 150, + margin: "auto", + marginTop: 30, + } + + return ( +
+ {/* +
+ +
+ */} + {showWelcome === true ? +
+
+ + {steps.map((label, index) => { + const stepProps = {} + const labelProps = {} + //if (isStepOptional(index)) { + // labelProps.optional = "optional" + //} + + if (isStepSkipped(index)) { + stepProps.completed = false; + } + + return ( + + + {label} + + + ) + })} + +
+ + +
+ {/* + + */} + +
+
+ {frameworkData === undefined || window.location.href.includes("tab=1") || window.location.href.includes("tab=3") ? null : +
+ + App Framework + + + + +
+ } +
+
+ : + +
+ + Welcome to Shuffle + + + Who do you identify with the most? + +
+ { + if (isCloud) { + ReactGA.event({ + category: "welcome", + action: "click_welcome_continue", + label: "", + }) + } else { + //setActiveStep(1) + } + + setShowWelcome(true) + }}> + + + New to Shuffle + + + + Follow our short introduction and learn some tips and tricks + + + +
+ + OR + +
+ { + if (isCloud) { + ReactGA.event({ + category: "welcome", + action: "click_getting_started", + label: "", + }) + } + + navigate("/workflows?message=Skipped intro") + }}> + + + Experienced + + + + You know Shuffle well. Head to the product right away! + + + +
+
+
+ } +
+ ) +} + +export default Welcome; diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index f844ec7d..df9ffad4 100644 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1,33 +1,36 @@ import React, { useEffect, useContext } from "react"; +import ReactDOM from "react-dom" + import { makeStyles } from "@material-ui/core/styles"; import { useTheme } from "@material-ui/core/styles"; import { Navigate } from "react-router-dom"; //import { Redirect } from "react-router-dom"; -import SecurityFramework from '../components/SecurityFramework.jsx'; -import { ShepherdTour, ShepherdTourContext } from 'react-shepherd' -import { isMobile } from "react-device-detect" +import SecurityFramework from '../components/SecurityFramework.jsx'; +import EditWorkflow from "../components/EditWorkflow.jsx" +import { ShepherdTour, ShepherdTourContext } from 'react-shepherd' + +import { isMobile } from "react-device-detect" import { Badge, + Divider, Avatar, + Drawer, Grid, InputLabel, Select, ListSubheader, Paper, Tooltip, - Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, - FormControlLabel, Chip, - Switch, Typography, Zoom, CircularProgress, @@ -35,8 +38,8 @@ import { DialogTitle, DialogActions, DialogContent, - OutlinedInput, Checkbox, + LinearProgress, ListItemText, } from "@material-ui/core"; @@ -71,23 +74,29 @@ import { CloudDownload as CloudDownloadIcon, ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, + Done as DoneIcon, + CheckCircle as CheckCircleIcon, + RadioButtonUnchecked as RadioButtonUncheckedIcon, + ArrowLeft as ArrowLeftIcon, + ArrowRight as ArrowRightIcon, } from "@material-ui/icons"; -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'; +//import NestedMenuItem from "material-ui-nested-menu-item"; +//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons'; //https://next.material-ui.com/components/material-icons/ -import { DataGrid, GridToolbar } from "@material-ui/data-grid"; +import { DataGrid, GridToolbar } from "@mui/x-data-grid"; //import JSONPretty from 'react-json-pretty'; //import JSONPrettyMon from 'react-json-pretty/dist/monikai' import Dropzone from "../components/Dropzone"; -import { Link } from "react-router-dom"; +import { useNavigate, Link } from "react-router-dom"; import { useAlert } from "react-alert"; import ChipInput from "material-ui-chip-input"; import { v4 as uuidv4 } from "uuid"; + const inputColor = "#383B40"; const surfaceColor = "#27292D"; const svgSize = 24; @@ -133,8 +142,8 @@ export const GetIconInfo = (action) => { const iconList = [ { key: "cache_add", values: ["set_cache"] }, { key: "cache_get", values: ["get_cache"] }, - { key: "filter", values: ["filter", "route", "router"] }, - { key: "merge", values: ["join", "merge"] }, + { key: "filter", values: ["filter"] }, + { key: "merge", values: ["join", "merge", "route", "router"] }, { key: "search", values: ["search", "find", "locate", "index", "analyze", "anal", "match", "check cache", "check", "verify", "validate"], @@ -406,10 +415,22 @@ export const validateJson = (showResult) => { } } + if (showResult[0] === "\"") { + return { + valid: false, + result: showResult, + } + } + var jsonvalid = true try { if (!showResult.includes("{") && !showResult.includes("[")) { jsonvalid = false + + return { + valid: jsonvalid, + result: showResult, + }; } } catch (e) { showResult = showResult.split("'").join('"'); @@ -419,13 +440,14 @@ export const validateJson = (showResult) => { jsonvalid = false; } } catch (e) { + jsonvalid = false; } } var result = showResult; try { - result = jsonvalid ? JSON.parse(showResult) : showResult; + result = jsonvalid ? JSON.parse(showResult, {"storeAsString": true}) : showResult; } catch (e) { ////console.log("Failed parsing JSON even though its valid: ", e) jsonvalid = false; @@ -462,7 +484,6 @@ export const validateJson = (showResult) => { if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) { const inside_result = validateJson(value) if (inside_result.valid) { - console.log("Replacing value since it's valid JSON!") if (typeof inside_result.result === "string") { const newres = JSON.parse(inside_result.result) result[key] = newres @@ -477,7 +498,6 @@ export const validateJson = (showResult) => { } } - //console.log("VALID: ", jsonvalid, result, typeof result) return { valid: jsonvalid, result: result, @@ -487,6 +507,8 @@ export const validateJson = (showResult) => { const Workflows = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata } = props; document.title = "Shuffle - Workflows"; + let navigate = useNavigate(); + const theme = useTheme(); const alert = useAlert(); const classes = useStyles(theme); @@ -516,6 +538,7 @@ const Workflows = (props) => { const [exportData, setExportData] = React.useState(""); const [modalOpen, setModalOpen] = React.useState(false); + const [isEditing, setIsEditing] = React.useState(true); const [newWorkflowName, setNewWorkflowName] = React.useState(""); const [newWorkflowDescription, setNewWorkflowDescription] = React.useState(""); @@ -538,12 +561,62 @@ const Workflows = (props) => { const [firstLoad, setFirstLoad] = React.useState(true); const [showMoreClicked, setShowMoreClicked] = React.useState(false); const [usecases, setUsecases] = React.useState([]); + const [appFramework, setAppFramework] = React.useState({}); + const [drawerOpen, setDrawerOpen] = React.useState(false) + const [videoViewOpen, setVideoViewOpen] = React.useState(false) + const [gettingStartedItems, setGettingStartedItems] = React.useState([]) + const drawerWidth = drawerOpen ? 325 : 0 + + const sidebarKey = "getting_started_sidebar" + if (isLoggedIn === true && gettingStartedItems.length === 0 && (userdata.tutorials !== undefined && userdata.tutorials !== null && userdata.tutorials.length > 0) && workflowDone === true) { + const activeFiltered = userdata.tutorials.filter((item) => item.active === true) + if (activeFiltered.length > 0) { + var newfiltered = [] + for (var key in activeFiltered) { + if (activeFiltered[key].name === "Discover Usecases") { + if (workflows.length > 1) { + activeFiltered[key].done = true + activeFiltered[key].description = `${workflows.length} workflows created` + } + } + + newfiltered.push(activeFiltered[key]) + } + + setGettingStartedItems(activeFiltered) + + const doneFiltered = activeFiltered.filter((item) => item.done === true) + if (doneFiltered.length > 0) { + console.log("DONE: ", doneFiltered) + } + + const sidebar = localStorage.getItem(sidebarKey); + if (sidebar === null || sidebar === undefined) { + console.log("No sidebar defined") + + localStorage.setItem(sidebarKey, "open"); + setDrawerOpen(true) + } else { + console.log("Got sidebar: ", sidebar) + + if (sidebar === "open") { + console.log("OPEN the thingy!") + setDrawerOpen(true) + } else { + console.log("Close the thingy!") + setDrawerOpen(false) + } + } + } + + } const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const findWorkflow = (filters) => { + console.log("Using filters: ", filters) if (filters.length === 0) { setFilteredWorkflows(workflows); return; @@ -558,27 +631,44 @@ const Workflows = (props) => { found = filters.map((filter) => curWorkflow.name.toLowerCase().includes(filter) ); - } else { + } + + if (found.every((v) => v !== true)) { found = filters.map((filter) => { - const newfilter = filter.toLowerCase(); - if (filter === undefined) { + if (filter === undefined || filter === null) { return false; } + const newfilter = filter.toLowerCase(); + if (curWorkflow.name.toLowerCase().includes(filter.toLowerCase())) { return true; - } else if (curWorkflow.tags.includes(filter)) { + } else if (curWorkflow.tags !== undefined && curWorkflow.tags !== null && curWorkflow.tags.includes(filter)) { return true; } else if (curWorkflow.owner === filter) { return true; } else if (curWorkflow.org_id === filter) { return true; + } else if (curWorkflow.usecase_ids !== undefined && curWorkflow.usecase_ids !== null && curWorkflow.usecase_ids.length > 0) { + // Check if the usecase is the right category + for (var key in usecases) { + if (usecases[key].name.toLowerCase() !== newfilter) { + continue + } + + for (var subkey in usecases[key].list) { + if (curWorkflow.usecase_ids.includes(usecases[key].list[subkey].name)) { + return true + } + } + } } else if ( curWorkflow.actions !== null && curWorkflow.actions !== undefined ) { for (var key in curWorkflow.actions) { const action = curWorkflow.actions[key]; + if ( action.app_name.toLowerCase() === newfilter || action.app_name.toLowerCase().includes(newfilter) @@ -586,7 +676,7 @@ const Workflows = (props) => { return true; } } - } + } return false; }); @@ -605,14 +695,17 @@ const Workflows = (props) => { const addFilter = (data) => { if (data === null || data === undefined) { + console.log("No filter data") return; } if (data.includes("<") && data.includes(">")) { + console.log("Filter includes < or >") return; } if (filters.includes(data) || filters.includes(data.toLowerCase())) { + console.log("Filter already has the data") return; } @@ -877,8 +970,43 @@ const Workflows = (props) => { if (isDropzone) { setIsDropzone(false); } + }, [isDropzone]); + + const getFramework = () => { + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { + 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) { + setAppFramework({}) + if (responseJson.reason !== undefined) { + //alert.error("Failed loading: " + responseJson.reason) + } else { + //alert.error("Failed to load framework for your org.") + } + } else { + setAppFramework(responseJson) + } + }) + .catch((error) => { + console.log("err in framework: ", error.toString()); + }) + } + const getAvailableWorkflows = () => { fetch(globalUrl + "/api/v1/workflows", { method: "GET", @@ -893,11 +1021,10 @@ const Workflows = (props) => { console.log("Status not 200 for workflows :O!: ", response.status); if (isCloud) { - window.location.pathname = "/search?tab=workflows"; + navigate("/search?tab=workflows") } alert.info("Failed getting workflows."); - setWorkflowDone(true); return; } @@ -905,55 +1032,64 @@ const Workflows = (props) => { }) .then((responseJson) => { if (responseJson !== undefined) { - setWorkflows(responseJson); - fetchUsecases(responseJson) + var newarray = [] + for (var key in responseJson) { + const wf = responseJson[key] + if (wf.public === true) { + continue + } + + newarray.push(wf) + } + + // Workflows are set in here + fetchUsecases(newarray) var setProdFilter = false - if (responseJson !== undefined) { - var actionnamelist = []; - var parsedactionlist = []; - for (var key in responseJson) { - const workflow = responseJson[key] - if (workflow.status === "production") { - setProdFilter = true + var actionnamelist = []; + var parsedactionlist = []; + for (var key in newarray) { + const workflow = newarray[key] + if (workflow.status === "production") { + setProdFilter = true + } + + for (var actionkey in newarray[key].actions) { + const action = newarray[key].actions[actionkey]; + //console.log("Action: ", action) + if (actionnamelist.includes(action.app_name)) { + continue; } - for (var actionkey in responseJson[key].actions) { - const action = responseJson[key].actions[actionkey]; - //console.log("Action: ", action) - if (actionnamelist.includes(action.app_name)) { - continue; - } + actionnamelist.push(action.app_name); + parsedactionlist.push(action); + } + } - actionnamelist.push(action.app_name); - parsedactionlist.push(action); - } - } - - //console.log(parsedactionlist) - setActionImageList(parsedactionlist); - } + //console.log(parsedactionlist) + setActionImageList(parsedactionlist); if (setProdFilter === true) { setFilters(["status:production"]); - const newWorkflows = responseJson.filter(workflow => workflow.status === "production") + const newWorkflows = newarray.filter(workflow => workflow.status === "production") console.log(newWorkflows) if (newWorkflows !== undefined && newWorkflows !== null) { setFilteredWorkflows(newWorkflows); } else { - setFilteredWorkflows(responseJson); + setFilteredWorkflows(newarray); } } else { - setFilteredWorkflows(responseJson); + setFilteredWorkflows(newarray); } // Ensures the zooming happens only once per load - setWorkflowDone(true); setTimeout(() => { setFirstLoad(false) + }, 100) + } else { if (isLoggedIn) { alert.error("An error occurred while loading workflows"); @@ -986,6 +1122,7 @@ const Workflows = (props) => { if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) { for (var usecasekey in workflow.usecase_ids) { + if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { //console.log("Got match: ", workflow.usecase_ids[usecasekey]) @@ -1012,6 +1149,8 @@ const Workflows = (props) => { } else { setUsecases(categorydata) } + setWorkflows(workflows); + setWorkflowDone(true); } const fetchUsecases = (workflows) => { @@ -1033,11 +1172,16 @@ const Workflows = (props) => { .then((responseJson) => { if (responseJson.success !== false) { handleKeysetting(responseJson, workflows) + } else { + setWorkflows(workflows); + setWorkflowDone(true); } }) .catch((error) => { //alert.error("ERROR: " + error.toString()); console.log("ERROR: " + error.toString()); + setWorkflows(workflows); + setWorkflowDone(true); }); }; @@ -1050,6 +1194,7 @@ const Workflows = (props) => { } getAvailableWorkflows(); + getFramework() } }, []) @@ -1057,9 +1202,10 @@ const Workflows = (props) => { color: "#ffffff", width: "100%", display: "flex", - minWidth: isMobile ? "100%" : 1024, - maxWidth: isMobile ? "100%" : 1024, - margin: "auto", + minWidth: isMobile ? "100%" : drawerWidth > 0 ? 824 : 1024, + maxWidth: isMobile ? "100%" : drawerWidth > 0 ? 824 : 1024, + margin: drawerWidth === 0 ? "auto" : `auto ${drawerWidth+100} auto auto`, + paddingBottom: 200, }; const emptyWorkflowStyle = { @@ -1114,10 +1260,15 @@ const Workflows = (props) => { justifyContent: "space-between", }; - const exportAllWorkflows = () => { - for (var key in workflows) { - exportWorkflow(workflows[key], false); + const exportAllWorkflows = (allWorkflows) => { + for (var i = 0; i < allWorkflows.length; i++) { + setTimeout(() => { + console.log(allWorkflows[i].name) + exportWorkflow(allWorkflows[i], false) + }, i * 200); } + + alert.info(`exporting and keeping original for all ${allWorkflows.length} workflows`); }; const deduplicateIds = (data) => { @@ -1125,9 +1276,11 @@ const Workflows = (props) => { for (var key in data.triggers) { const trigger = data.triggers[key]; if (trigger.app_name === "Shuffle Workflow") { - if (trigger.parameters.length > 2) { - trigger.parameters[2].value = ""; - } + if (trigger.parameters !== null && trigger.parameters !== undefined) { + if (trigger.parameters.length > 2) { + trigger.parameters[2].value = ""; + } + } } if (trigger.status === "running") { @@ -1180,8 +1333,12 @@ const Workflows = (props) => { for (var subkey in data.actions[key].parameters) { const param = data.actions[key].parameters[subkey]; + + // Removed October 10th, 2022 as key usually isn't + // containing anything secret, but rather necessary configurations. + // param.name.includes("key") || + // if ( - param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || @@ -1228,8 +1385,9 @@ const Workflows = (props) => { ) { for (key in data.workflow_variables) { const param = data.workflow_variables[key]; + //param.name.includes("key") || + if ( - param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || @@ -1425,11 +1583,14 @@ const Workflows = (props) => { }; return ( - + setModalOpen(true)} + onClick={() => { + setModalOpen(true) + setIsEditing(false) + }} onMouseOver={() => { setHover(true); }} @@ -1482,7 +1643,7 @@ const Workflows = (props) => { } if (!data.previously_saved) { - boxColor = "#f85a3e"; + boxColor = "#f86a3e"; } const menuClick = (event) => { @@ -1516,25 +1677,27 @@ const Workflows = (props) => { > { - setModalOpen(true); - setEditingWorkflow(JSON.parse(JSON.stringify(data))); - setNewWorkflowName(data.name); - setNewWorkflowDescription(data.description); - setDefaultReturnValue(data.default_return_value); - if (data.tags !== undefined && data.tags !== null) { - setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))); - } + onClick={(event) => { + event.stopPropagation() + ReactDOM.unstable_batchedUpdates(() => { + setModalOpen(true); + setEditingWorkflow(JSON.parse(JSON.stringify(data))); + setNewWorkflowName(data.name); + setNewWorkflowDescription(data.description); + setDefaultReturnValue(data.default_return_value); + if (data.tags !== undefined && data.tags !== null) { + setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))); + } - console.log("Editing: ", data) - if (data.usecase_ids !== undefined && data.usecase_ids !== null && data.usecase_ids.length > 0) { - setSelectedUsecases(data.usecase_ids) - } + if (data.usecase_ids !== undefined && data.usecase_ids !== null && data.usecase_ids.length > 0) { + setSelectedUsecases(data.usecase_ids) + } + }) }} key={"change"} > - {"Change details"} + {"Edit details"} { } } + var selectedCategory = "" + if (data.usecase_ids !== undefined && data.usecase_ids !== null && data.usecase_ids.length > 0 && usecases !== null && usecases !== undefined && usecases.length > 0) { + const oldcolor = boxColor.valueOf() + + // Find the first usecase and use that ones' ID + for (var key in usecases) { + var category = usecases[key] + category.matches = [] + + for (var subcategorykey in category.list) { + var subcategory = category.list[subcategorykey] + subcategory.matches = [] + + for (var usecasekey in data.usecase_ids) { + if (data.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { + boxColor = category.color + break + } + } + + if (boxColor !== oldcolor) { + break + } + } + + if (boxColor !== oldcolor) { + selectedCategory = category.name + break + } + } + } + return (
-
+ {selectedCategory !== "" ? + +
{ + addFilter(selectedCategory) + }} + /> + + : null} { overflow: "hidden", marginTop: 5, maxHeight: 28, - overflow: "hidden", }} > {data.tags !== undefined && data.tags !== null @@ -2000,9 +2202,10 @@ const Workflows = (props) => { return } - if (method === "POST" && redirect) { - window.location.pathname = "/workflows/" + responseJson["id"]; - setModalOpen(false); + if (redirect) { + //window.location.pathname = "/workflows/" + responseJson["id"]; + navigate("/workflows/" + responseJson["id"]) + //setModalOpen(false); } else if (!redirect) { // Update :) setTimeout(() => { @@ -2011,7 +2214,7 @@ const Workflows = (props) => { setImportLoading(false); setModalOpen(false); } else { - alert.info("Successfully changed basic info for workflow"); + //alert.info("Successfully changed basic info for workflow"); setModalOpen(false); } @@ -2068,35 +2271,35 @@ const Workflows = (props) => { "", data.status, ) - .then((response) => { - if (response !== undefined) { - // SET THE FULL THING - data.id = response.id; - data.first_save = false; - data.previously_saved = false; - data.is_valid = false; + .then((response) => { + if (response !== undefined) { + // SET THE FULL THING + data.id = response.id; + data.first_save = false; + data.previously_saved = false; + data.is_valid = false; - // Actually create it - setNewWorkflow( - data.name, - data.description, - data.tags, - data.default_return_value, - data, - false, - [], - "", - data.status, - ).then((response) => { - if (response !== undefined) { - alert.success("Successfully imported " + data.name); - } - }); - } - }) - .catch((error) => { - alert.error("Import error: " + error.toString()); - }); + // Actually create it + setNewWorkflow( + data.name, + data.description, + data.tags, + data.default_return_value, + data, + false, + [], + "", + data.status, + ).then((response) => { + if (response !== undefined) { + alert.success("Successfully imported " + data.name); + } + }); + } + }) + .catch((error) => { + alert.error("Import error: " + error.toString()); + }); }); // Actually reads @@ -2202,6 +2405,7 @@ const Workflows = (props) => { width: 330, renderCell: (params) => { const data = params.row.record; + return ( @@ -2737,8 +2941,8 @@ const Workflows = (props) => { const workflowViewStyle = { flex: viewSize.workflowView, - marginLeft: "10px", - marginRight: "10px", + marginLeft: 10, + marginRight: 10, }; if (viewSize.workflowView === 0) { @@ -2809,7 +3013,7 @@ const Workflows = (props) => { style={{}} variant="text" onClick={() => { - exportAllWorkflows(); + exportAllWorkflows(workflows); }} > @@ -2831,156 +3035,153 @@ const Workflows = (props) => { ); - const tourOptions = { - defaultStepOptions: { - classes: "shadow-md bg-purple-dark", - scrollTo: true - }, - useModalOverlay: true, - tourName: workflows, - exitOnEsc: true, - } + // const tourOptions = { + // defaultStepOptions: { + // classes: "shadow-md bg-purple-dark", + // scrollTo: true + // }, + // useModalOverlay: true, + // tourName: workflows, + // exitOnEsc: true, + // } - //classes: "custom-class-name-1 custom-class-name-2", - const newSteps = [ - { - id: "intro", - scrollTo: true, - beforeShowPromise: function() { - return new Promise(function(resolve) { - setTimeout(function() { - window.scrollTo(0, 0); - resolve(); - }, 500); - }); - }, - buttons: [ - { - classes: "shepherd-button-primary", - style: { - backgroundColor: "red", - color: "white", - }, - text: "Next", - type: "next" - } - ], - highlightClass: "highlight", - showCancelLink: true, - text: [ - "React-Shepherd is a JavaScript library for guiding users through your React app." - ], - when: { - show: () => { - console.log("show step 1"); - }, - hide: () => { - console.log("hide step 1"); - } - } - }, - { - id: "second", - attachTo: { - element: "second-step", - on: "top" - }, - text: [ - "Yuk eksplorasi hasil Tes Minat Bakat-mu dan rekomendasi Jurusan dan Karier." - ], - buttons: [ - { - classes: "btn btn-info", - text: "Kembali", - type: "back" - }, - { - classes: "btn btn-success", - text: "Saya Mengerti", - type: "cancel" - } - ], - when: { - show: () => { - console.log("show stepp"); - }, - hide: () => { - console.log("complete step"); - } - }, - showCancelLink: false, - scrollTo: true, - modalOverlayOpeningPadding: 4, - useModalOverlay: false, - canClickTarget: false - } - ] + // //classes: "custom-class-name-1 custom-class-name-2", + // const newSteps = [ + // { + // id: "intro", + // scrollTo: true, + // beforeShowPromise: function() { + // return new Promise(function(resolve) { + // setTimeout(function() { + // window.scrollTo(0, 0); + // resolve(); + // }, 500); + // }); + // }, + // buttons: [ + // { + // classes: "shepherd-button-primary", + // style: { + // backgroundColor: "red", + // color: "white", + // }, + // text: "Next", + // type: "next" + // } + // ], + // highlightClass: "highlight", + // showCancelLink: true, + // text: [ + // "React-Shepherd is a JavaScript library for guiding users through your React app." + // ], + // when: { + // show: () => { + // console.log("show step 1"); + // }, + // hide: () => { + // console.log("hide step 1"); + // } + // } + // }, + // { + // id: "second", + // attachTo: { + // element: "second-step", + // on: "top" + // }, + // text: [ + // "Yuk eksplorasi hasil Tes Minat Bakat-mu dan rekomendasi Jurusan dan Karier." + // ], + // buttons: [ + // { + // classes: "btn btn-info", + // text: "Kembali", + // type: "back" + // }, + // { + // classes: "btn btn-success", + // text: "Saya Mengerti", + // type: "cancel" + // } + // ], + // when: { + // show: () => { + // console.log("show stepp"); + // }, + // hide: () => { + // console.log("complete step"); + // } + // }, + // showCancelLink: false, + // scrollTo: true, + // modalOverlayOpeningPadding: 4, + // useModalOverlay: false, + // canClickTarget: false + // } + // ] - function TourButton() { - const tour = useContext(ShepherdTourContext); + // function TourButton() { + // const tour = useContext(ShepherdTourContext); - return ( - - ); - } + // return ( + // + // ); + // } const WorkflowView = () => { if (workflows.length === 0) { - console.log("USER: ", userdata) - console.log("PROPS: ", props) - if (userdata.tutorials !== undefined && userdata.tutorials !== null && !userdata.tutorials.includes("getting-started")) { - return ; - } - - return ( -
- -
-

Welcome to Shuffle

-
-
-

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

-
-
- If you want to jump straight into it, click here to create your - first workflow: -
-
- - - - ..OR - - {workflowButtons} - -
-
-
- ) - + // Not going there yet + //if ((userdata.tutorials !== undefined && userdata.tutorials !== null && !userdata.tutorials.includes("getting-started")) || userdata.tutorials === null) { + // return ; + //} + //return ( + //
+ // + //
+ //

Welcome to Shuffle

+ //
+ //
+ //

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

+ //
+ //
+ // If you want to jump straight into it, click here to create your + // first workflow: + //
+ //
+ // + // + // + // ..OR + // + // {workflowButtons} + // + //
+ //
+ //
+ //) } var workflowDelay = -150 @@ -3036,7 +3237,7 @@ const Workflows = (props) => {
} -
+
{workflowButtons}
@@ -3088,7 +3289,7 @@ const Workflows = (props) => { }} */} -
+
{!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ?
{usecases.map((usecase, index) => { @@ -3240,6 +3441,11 @@ const Workflows = (props) => { {filteredWorkflows.map((data, index) => { + // Shouldn't be a part of this list + if (data.public === true) { + return null + } + if (firstLoad) { workflowDelay += 75 } else { @@ -3453,6 +3659,158 @@ const Workflows = (props) => { ) : null; + + //const + //const [percentDone, setPercentDone] = React.useState(0) + const percentDone = gettingStartedItems.filter((item) => item.done).length / gettingStartedItems.length * 100 + + const GettingStartedItem = ({item, index}) => { + const [clicked, setClicked] = React.useState(false) + const doneIcon = item.done ? : + + return ( +
setClicked(true)} + > + + {doneIcon} {index + 1}. {item.name} + + {clicked ? + + + {item.description} + + + + + + : + null + } +
+ ) + } + + const gettingStartedDrawer = + +
+ + Getting Started + + + { + e.preventDefault(); + setDrawerOpen(false) + + localStorage.setItem(sidebarKey, "closed"); + }} + > + + + +
+
+ + Setup progress: {isNaN(percentDone) ? 0 : percentDone}% + + + + + + Follow these steps to get you up and running! + + + { + setVideoViewOpen(true) + }}> + Watch 2-min introduction video + +
+
+ {gettingStartedItems.map((item, index) => { + return ( + + ) + })} +
+
+ + const videoView = + { + setVideoViewOpen(false) + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 560, + minHeight: 415, + textAlign: "center", + }, + }} + > + + Welcome to Shuffle! + + + + { + e.preventDefault(); + setVideoViewOpen(false) + }} + > + + + + + + + const loadedCheck = isLoaded && isLoggedIn && workflowDone ? (
@@ -3471,12 +3829,45 @@ const Workflows = (props) => { > - {modalView} + {/*modalView*/} {deleteModal} {exportVerifyModal} {publishModal} {workflowDownloadModalOpen} -
+ + {!drawerOpen ?
+ + { + setDrawerOpen(true) + localStorage.setItem(sidebarKey, "open"); + }}> + + + +
: null} + {isMobile ? null : gettingStartedDrawer} + {videoView} + + {modalOpen === true ? + + : null} + {/*
+ + Need assistance? Ask our support team (it's free!). + + +
*/} +
) : (
. # Based on the Slack integration using Webhooks @@ -23,8 +23,7 @@ except Exception as e: # # Global vars - -debug_enabled = False +debug_enabled = False pwd = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) json_alert = {} now = time.strftime("%a %b %d %H:%M:%S %Z %Y") @@ -32,6 +31,12 @@ now = time.strftime("%a %b %d %H:%M:%S %Z %Y") # Set paths log_file = '{0}/logs/integrations.log'.format(pwd) +try: + with open("/tmp/shuffle_start.txt", "w+") as tmp: + tmp.write("Script started") +except: + pass + def main(args): debug("# Starting") @@ -47,10 +52,18 @@ def main(args): debug(alert_file_location) # Load alert. Parse JSON object. - with open(alert_file_location) as alert_file: - json_alert = json.load(alert_file) + try: + with open(alert_file_location) as alert_file: + json_alert = json.load(alert_file) + except: + debug("# Alert file %s doesn't exist" % alert_file_location) + debug("# Processing alert") - debug(json_alert) + try: + debug(json_alert) + except Exception as e: + debug("Failed getting json_alert %s" % e) + sys.exit(1) debug("# Generating message") msg = generate_msg(json_alert) @@ -60,6 +73,14 @@ def main(args): debug(msg) debug("# Sending message") + + try: + with open("/tmp/shuffle_end.txt", "w+") as tmp: + tmp.write("Script done pre-msg sending") + except: + pass + + send_msg(msg, webhook) @@ -137,9 +158,10 @@ def generate_msg(alert): def send_msg(msg, url): + debug("# In send msg") headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'} - res = requests.post(url, data=msg, headers=headers) - debug(res) + res = requests.post(url, data=msg, headers=headers, verify=False) + debug("# After send msg: %s" % res) if __name__ == "__main__": @@ -154,18 +176,26 @@ if __name__ == "__main__": sys.argv[3], sys.argv[4] if len(sys.argv) > 4 else '', ) - debug_enabled = (len(sys.argv) > 4 and sys.argv[4] == 'debug') + #debug_enabled = (len(sys.argv) > 4 and sys.argv[4] == 'debug') + debug_enabled = True else: msg = '{0} Wrong arguments'.format(now) bad_arguments = True # Logging the call + try: + f = open(log_file, 'a') + except: + f = open(log_file, 'w+') + f.write("") + f.close() + f = open(log_file, 'a') f.write(msg + '\n') f.close() if bad_arguments: - debug("# Exiting: Bad arguments.") + debug("# Exiting: Bad arguments. Inputted: %s" % sys.argv) sys.exit(1) # Main function diff --git a/functions/onprem/orborus/build.sh b/functions/onprem/orborus/build.sh index eff06ee0..c3d288f2 100644 --- a/functions/onprem/orborus/build.sh +++ b/functions/onprem/orborus/build.sh @@ -1,5 +1,5 @@ NAME=shuffle-orborus -VERSION=0.9.71 +VERSION=1.0.9 echo "Running docker build with $NAME:$VERSION" #docker rmi frikky/shuffle:$NAME --force @@ -10,3 +10,7 @@ docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$ docker push frikky/shuffle:$NAME docker push ghcr.io/frikky/$NAME:$VERSION docker push ghcr.io/frikky/$NAME:nightly + +docker push shuffle/shuffle:$NAME +docker push ghcr.io/shuffle/$NAME:$VERSION +docker push ghcr.io/shuffle/$NAME:nightly diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod index 3bce0939..7d11eb8c 100644 --- a/functions/onprem/orborus/go.mod +++ b/functions/onprem/orborus/go.mod @@ -8,5 +8,5 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/mackerelio/go-osstat v0.2.1 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.2.27 + github.com/shuffle/shuffle-shared v0.3.24 ) diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum index 628ecf89..d461abe7 100644 --- a/functions/onprem/orborus/go.sum +++ b/functions/onprem/orborus/go.sum @@ -130,6 +130,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +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/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= @@ -786,6 +788,18 @@ github.com/shuffle/shuffle-shared v0.2.9 h1:fh2eOD7olifW2uyC3Vlp8u3dqhgIxtYaDVja github.com/shuffle/shuffle-shared v0.2.9/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= github.com/shuffle/shuffle-shared v0.2.27 h1:YT9MtXyMSxIGMpNovjp9pCKFyt2gk40EdAXqDvldhM8= github.com/shuffle/shuffle-shared v0.2.27/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= +github.com/shuffle/shuffle-shared v0.2.41 h1:1TBP/47Xzh7ysi6I++wJxRxKpZYp7NBe7YF1DrFL2mA= +github.com/shuffle/shuffle-shared v0.2.41/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= +github.com/shuffle/shuffle-shared v0.2.63 h1:IF82o5WS4+6wEIirqAd1qEm/pBCuQMbn2CJeuAt2qFI= +github.com/shuffle/shuffle-shared v0.2.63/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= +github.com/shuffle/shuffle-shared v0.2.64 h1:WpCKiL5tNt7wTJaHkf1zXhjeUB5ltFap1dXgU6ijKq4= +github.com/shuffle/shuffle-shared v0.2.64/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= +github.com/shuffle/shuffle-shared v0.2.82 h1:V3bYw7MxHPQgydUuWLGHONpX7NwbRPPAALovKqsdaW0= +github.com/shuffle/shuffle-shared v0.2.82/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= +github.com/shuffle/shuffle-shared v0.3.5 h1:erfXVKjeSkmpoGczZ6hPETg8gDdZeXYgXsrEgVO/uqg= +github.com/shuffle/shuffle-shared v0.3.5/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= +github.com/shuffle/shuffle-shared v0.3.24 h1:zBDZan4u2XjC6TAi5BdFoVroBPGYd6PAha+3/cSfD6w= +github.com/shuffle/shuffle-shared v0.3.24/go.mod h1:yI6HCog/R3Kq1FvCIVbXedLl87rtSuDOyzolmuMswB4= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index b238a48e..727c1fc5 100644 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -28,6 +28,7 @@ import ( "net/http" "os" "os/exec" + "runtime" "strconv" "strings" "time" @@ -57,6 +58,7 @@ var workerTimeoutEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_TIMEOUT") var concurrencyEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY") var appSdkVersion = os.Getenv("SHUFFLE_APP_SDK_VERSION") var workerVersion = os.Getenv("SHUFFLE_WORKER_VERSION") +var newWorkerImage = os.Getenv("SHUFFLE_WORKER_IMAGE") //var baseimagename = "docker.pkg.github.com/frikky/shuffle" //var baseimagename = "ghcr.io/frikky" @@ -282,6 +284,35 @@ func deployServiceWorkers(image string) { } } + if len(os.Getenv("DOCKER_HOST")) > 0 { + log.Printf("[DEBUG] Deploying docker socket proxy to the network %s as the DOCKER_HOST variable is set", networkName) + //if err == nil { + containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{ + All: true, + }) + + if err == nil { + for _, container := range containers { + if strings.Contains(strings.ToLower(container.Image), "docker-socket-proxy") { + networkConfig := &network.EndpointSettings{} + err := dockercli.NetworkConnect(ctx, networkName, container.ID, networkConfig) + if err != nil { + log.Printf("[ERROR] Failed connecting Docker socket proxy to docker network %s: %s", networkName, err) + } else { + log.Printf("[INFO] Attached the docker socket proxy to the execution network") + } + + break + } + } + } else { + log.Printf("[ERROR] Failed listing containers when deploying socket proxy on swarm: %s", err) + } + //} else { + // log.Printf("[ERROR] Failed listing and finding the right image for docker socket proxy: %s", err) + //} + } + //serviceOptions := types.ServiceCreateOptions{} //service, err := dockercli.ServiceCreate( // context.Background(), @@ -385,13 +416,15 @@ func deployServiceWorkers(image string) { }, } - if defaultNetworkAttach == true { + if defaultNetworkAttach == true || strings.ToLower(os.Getenv("SHUFFLE_DEFAULT_NETWORK_ATTACH")) == "true" { + targetName := "shuffle_shuffle" + log.Printf("[DEBUG] Adding network attach for network %s to worker in swarm", targetName) serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{ - Target: "shuffle_shuffle", + Target: targetName, }) // FIXM: Remove this if deployment fails? - serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=shuffle_shuffle")) + serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=%s", targetName)) } if dockerApiVersion != "" { @@ -411,12 +444,23 @@ func deployServiceWorkers(image string) { if len(os.Getenv("DOCKER_HOST")) > 0 { serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_HOST=%s", os.Getenv("DOCKER_HOST"))) } else { - serviceSpec.TaskTemplate.ContainerSpec.Mounts = []mount.Mount{ - mount.Mount{ - Source: "/var/run/docker.sock", - Target: "/var/run/docker.sock", - Type: mount.TypeBind, - }, + if runtime.GOOS == "windows" { + serviceSpec.TaskTemplate.ContainerSpec.Mounts = []mount.Mount{ + mount.Mount{ + Source: `\\.\pipe\docker_engine`, + Target: `\\.\pipe\docker_engine`, + Type: mount.TypeBind, + }, + } + } else { + serviceSpec.TaskTemplate.ContainerSpec.Mounts = []mount.Mount{ + mount.Mount{ + Source: "/var/run/docker.sock", + Target: "/var/run/docker.sock", + Type: mount.TypeBind, + }, + } + } } @@ -475,7 +519,11 @@ func deployWorker(image string, identifier string, env []string, executionReques } if len(os.Getenv("DOCKER_HOST")) == 0 { - hostConfig.Binds = []string{"/var/run/docker.sock:/var/run/docker.sock:rw"} + if runtime.GOOS == "windows" { + hostConfig.Binds = []string{`\\.\pipe\docker_engine:\\.\pipe\docker_engine`} + } else { + hostConfig.Binds = []string{"/var/run/docker.sock:/var/run/docker.sock:rw"} + } } hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) @@ -642,12 +690,12 @@ func initializeImages() { ctx := context.Background() if appSdkVersion == "" { - appSdkVersion = "0.8.97" + appSdkVersion = "1.1.0" log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion) } if workerVersion == "" { - workerVersion = "nightly" + workerVersion = "1.1.0" log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion) } @@ -657,24 +705,23 @@ func initializeImages() { log.Printf("[DEBUG] Setting baseimageregistry") } if baseimagename == "" { - baseimagename = "frikky/shuffle" - baseimagename = "frikky" + baseimagename = "shuffle/shuffle" // Dockerhub + baseimagename = "shuffle" // Github log.Printf("[DEBUG] Setting baseimagename") } log.Printf("[DEBUG] Setting swarm config to %#v. Default is empty.", swarmConfig) + newWorker := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion) + if len(newWorkerImage) > 0 { + newWorker = newWorkerImage + } + // check whether they are the same first images := []string{ - fmt.Sprintf("frikky/shuffle:app_sdk"), + fmt.Sprintf("shuffle/shuffle:app_sdk"), fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", baseimageregistry, baseimagename, appSdkVersion), - fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion), - // fmt.Sprintf("docker.io/%s:app_sdk", baseimagename), - // fmt.Sprintf("docker.io/%s:worker", baseimagename), - - //fmt.Sprintf("%s/worker:%s", baseimagename, workerVersion), - //fmt.Sprintf("%s/app_sdk:%s", baseimagename, appSdkVersion), - //fmt.Sprintf("frikky/shuffle:app_sdk"), + newWorker, } pullOptions := types.ImagePullOptions{} @@ -787,6 +834,18 @@ func checkSwarmService(ctx context.Context) { // Initial loop etc func main() { + startupDelay := os.Getenv("SHUFFLE_ORBORUS_STARTUP_DELAY") + if len(startupDelay) > 0 { + log.Printf("[DEBUG] Setting startup delay to %#v", startupDelay) + + tmpInt, err := strconv.Atoi(startupDelay) + if err == nil { + time.Sleep(time.Duration(tmpInt) * time.Second) + } else { + log.Printf("[WARNING] Env SHUFFLE_ORBORUS_STARTUP_DELAY must be a number, not %s", startupDelay) + } + } + log.Println("[INFO] Setting up execution environment") //FIXME @@ -835,8 +894,9 @@ func main() { if len(os.Getenv("DOCKER_HOST")) > 0 { log.Printf("[DEBUG] Running docker with socket proxy %s instead of default", os.Getenv("DOCKER_HOST")) + } else { - log.Printf("[DEBUG] Running docker with default socket /var/run/docker.sock") + log.Printf(`[DEBUG] Running docker with default socket /var/run/docker.sock or `) } ctx := context.Background() @@ -859,6 +919,10 @@ func main() { initializeImages() workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion) + if len(newWorkerImage) > 0 { + workerImage = newWorkerImage + } + if swarmConfig == "run" || swarmConfig == "swarm" { checkSwarmService(ctx) @@ -896,7 +960,7 @@ func main() { } } - client.Timeout = 10 * time.Second + client.Timeout = 30 * time.Second fullUrl := fmt.Sprintf("%s/api/v1/workflows/queue", baseUrl) req, err := http.NewRequest( @@ -922,15 +986,15 @@ func main() { req.Header.Add("Org", org) } - log.Printf("[INFO] Waiting for executions at %s with Environment %s", fullUrl, environment) + log.Printf("[INFO] Waiting for executions at %s with Environment %#v", fullUrl, environment) hasStarted := false for { //go getStats() - //log.Printf("Prerequest") - //log.Printf("Postrequest") + //log.Printf("[DEBUG] Prerequest - queue") newresp, err := client.Do(req) + //log.Printf("[DEBUG] Postrequest - queue") if err != nil { - log.Printf("[WARNING] Failed making request: %s", err) + log.Printf("[WARNING] Failed making request to %s: %s", fullUrl, err) zombiecounter += 1 if zombiecounter*sleepTime > workerTimeout { @@ -1330,7 +1394,6 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { //log.Printf("[DEBUG] Data: %s", string(data)) - //streamUrl := fmt.Sprintf("http://shuffle-workers:33333/api/v1/execute", parsedBaseurl) streamUrl := fmt.Sprintf("http://shuffle-workers:33333/api/v1/execute") if containerId == "" || containerId == "shuffle-orborus" { streamUrl = fmt.Sprintf("%s:33333/api/v1/execute", parsedBaseurl) @@ -1347,6 +1410,10 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { log.Printf("[ERROR] Failed creating worker request: %s", err) if strings.Contains(fmt.Sprintf("%s", err), "connection refused") || strings.Contains(fmt.Sprintf("%s", err), "EOF") { workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion) + + if len(newWorkerImage) > 0 { + workerImage = newWorkerImage + } deployServiceWorkers(workerImage) time.Sleep(time.Duration(10) * time.Second) @@ -1361,6 +1428,11 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { log.Printf("[ERROR] Error running worker request to %s (1): %s", streamUrl, err) if strings.Contains(fmt.Sprintf("%s", err), "connection refused") || strings.Contains(fmt.Sprintf("%s", err), "EOF") { workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion) + + if len(newWorkerImage) > 0 { + workerImage = newWorkerImage + } + deployServiceWorkers(workerImage) time.Sleep(time.Duration(10) * time.Second) @@ -1384,12 +1456,6 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { return nil } - //workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion) - //deployServiceWorkers(workerImage) - - //time.Sleep(time.Duration(10) * time.Second) - //err = sendWorkerRequest(executionRequest) - return errors.New(fmt.Sprintf("Bad statuscode from worker: %d - expecting 200", newresp.StatusCode)) } diff --git a/functions/onprem/orborus/run.sh b/functions/onprem/orborus/run.sh index eac60eec..d71ac59c 100644 --- a/functions/onprem/orborus/run.sh +++ b/functions/onprem/orborus/run.sh @@ -1,10 +1,19 @@ +#docker run \ +# --env DOCKER_API_VERSION=1.40 \ +# --env ENVIRONMENT_NAME="Shuffle" \ +# --env BASE_URL="http://192.168.86.45:5001" \ +# --env HTTP_PROXY="http://192.168.86.45:8082" \ +# --env HTTPS_PROXY="https://192.168.86.45:8082" \ +# --env SHUFFLE_PASS_WORKER_PROXY=true \ +# --env SHUFFLE_PASS_APP_PROXY=true \ +# -v /var/run/docker.sock:/var/run/docker.sock \ +# ghcr.io/frikky/shuffle-orborus:nightly + docker run \ --env DOCKER_API_VERSION=1.40 \ - --env ENVIRONMENT_NAME="Shuffle" \ - --env BASE_URL="http://192.168.86.45:5001" \ - --env HTTP_PROXY="http://192.168.86.45:8082" \ - --env HTTPS_PROXY="https://192.168.86.45:8082" \ - --env SHUFFLE_PASS_WORKER_PROXY=true \ - --env SHUFFLE_PASS_APP_PROXY=true \ + --env ENVIRONMENT_NAME="Another env" \ + --env ORG="2e7b6a08-b63b-4fc2-bd70-718091509db1" \ + --env AUTH="env auth" \ + --env BASE_URL="https://shuffler.io" \ -v /var/run/docker.sock:/var/run/docker.sock \ ghcr.io/frikky/shuffle-orborus:nightly diff --git a/functions/onprem/worker/build.sh b/functions/onprem/worker/build.sh index 2fac6a31..25fc3778 100644 --- a/functions/onprem/worker/build.sh +++ b/functions/onprem/worker/build.sh @@ -1,5 +1,5 @@ NAME=shuffle-worker -VERSION=0.9.71 +VERSION=1.1.0 echo "Running docker build with $NAME:$VERSION" #CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin . diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod index 3399904f..0808095a 100644 --- a/functions/onprem/worker/go.mod +++ b/functions/onprem/worker/go.mod @@ -10,6 +10,6 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/gorilla/mux v1.8.0 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/shuffle/shuffle-shared v0.2.27 + github.com/shuffle/shuffle-shared v0.3.24 go4.org v0.0.0-20201209231011-d4a079459e60 // indirect ) diff --git a/functions/onprem/worker/go.sum b/functions/onprem/worker/go.sum index 78d4ef3d..02ba7801 100644 --- a/functions/onprem/worker/go.sum +++ b/functions/onprem/worker/go.sum @@ -1,4 +1,5 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= 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= @@ -16,8 +17,13 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY 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.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 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= @@ -28,6 +34,7 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.4.0 h1:CFDJm15RpYXeEblQ0TMDUrYtqmBmbAWTy536nA8JIc8= cloud.google.com/go/datastore v1.4.0/go.mod h1:d18825/a9bICdAIJy2EkHs9joU4RlIZ1t6l8WDdbdY0= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 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= @@ -40,17 +47,23 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4= cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +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.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +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= @@ -64,6 +77,7 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= @@ -71,13 +85,18 @@ github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg3 github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= +github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= @@ -86,12 +105,19 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= 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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -99,22 +125,31 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +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/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +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= @@ -123,10 +158,20 @@ github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLI github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +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/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-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +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/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -141,11 +186,13 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -159,8 +206,13 @@ github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7 github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8 h1:NmkCC1/QxyZFBny8JogwLpOy2f+VEbO/f6bV2Mqtwuw= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= +github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= +github.com/containerd/containerd v1.6.3 h1:JfgUEIAH07xDWk6kqz0P3ArZt+KJ9YeihSC9uyFtSKg= +github.com/containerd/containerd v1.6.3/go.mod h1:gCVGrYRYFm2E8GmuUIbj/NGD7DLZQLzSJQazjVKDOig= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -168,6 +220,7 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -176,6 +229,9 @@ github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1S github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.4/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= @@ -185,9 +241,12 @@ github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= +github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= @@ -206,15 +265,22 @@ github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNR github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= +github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -229,7 +295,10 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/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.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= @@ -241,14 +310,17 @@ github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.9+incompatible h1:JlsVnETOjM2RLQa0Cc1XCIspUdXW3Zenq9P54uXBm6k= github.com/docker/docker v20.10.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= 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-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= @@ -269,10 +341,17 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF 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.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +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.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 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 h1:GB+Wr0Yx8efG7D1jc9fGGiqjjRRngWJTcMSua3QIDaM= github.com/frikky/go-elasticsearch/v8 v8.13.1/go.mod h1:RPq0JXPQVVSFHTlPwj/go8BZ1hegRf+StaSpT2iGIoQ= @@ -284,6 +363,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -293,26 +373,43 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +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.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 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.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -329,6 +426,8 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -336,6 +435,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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= @@ -352,8 +453,12 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/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= @@ -362,15 +467,20 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= 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/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= @@ -383,7 +493,10 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf 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-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -393,9 +506,12 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 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 v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -404,16 +520,35 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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= @@ -423,17 +558,27 @@ github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -450,53 +595,91 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= +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/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 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.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -506,8 +689,11 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/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 v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -515,6 +701,8 @@ github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rm github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -525,10 +713,14 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 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-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -536,6 +728,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -543,6 +736,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -554,6 +749,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -565,16 +762,24 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shuffle/shuffle-shared v0.1.27 h1:dFISdLvQF0cpAuFzNgVk85Jy5uTc+QVkK1n2LjA8r9c= github.com/shuffle/shuffle-shared v0.1.27/go.mod h1:0QrK51T12CpCj/be8hXduj/RtDnoeaZ3rfogELZE2IU= github.com/shuffle/shuffle-shared v0.1.30 h1:YFEVVw6ENl1GxN4hVndSrbLXeIVM9JtcPqoJKDyqYqM= @@ -609,6 +814,10 @@ github.com/shuffle/shuffle-shared v0.2.9 h1:fh2eOD7olifW2uyC3Vlp8u3dqhgIxtYaDVja github.com/shuffle/shuffle-shared v0.2.9/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= github.com/shuffle/shuffle-shared v0.2.20 h1:1f0oBOKYk1Yteve6e9zaXRfd0iHO3EWAAbC7g3o1Vjs= github.com/shuffle/shuffle-shared v0.2.20/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= +github.com/shuffle/shuffle-shared v0.2.27 h1:YT9MtXyMSxIGMpNovjp9pCKFyt2gk40EdAXqDvldhM8= +github.com/shuffle/shuffle-shared v0.2.27/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk= +github.com/shuffle/shuffle-shared v0.3.24 h1:zBDZan4u2XjC6TAi5BdFoVroBPGYd6PAha+3/cSfD6w= +github.com/shuffle/shuffle-shared v0.3.24/go.mod h1:yI6HCog/R3Kq1FvCIVbXedLl87rtSuDOyzolmuMswB4= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -623,7 +832,9 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -631,6 +842,7 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -638,7 +850,9 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn 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/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -650,12 +864,15 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -664,9 +881,11 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -678,13 +897,22 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -693,14 +921,43 @@ 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 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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= @@ -710,8 +967,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/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-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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= @@ -735,6 +995,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu 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/lint v0.0.0-20210508222113-6edffad5e616/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= @@ -745,11 +1006,14 @@ 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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= @@ -777,6 +1041,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ 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= @@ -786,9 +1051,21 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwY 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-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/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 h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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= @@ -799,6 +1076,12 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423 h1:/hEknzWkMPCjTo7StMHRrBRa8YBbXuBWfck8680k3RE= golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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= @@ -809,9 +1092,12 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -827,6 +1113,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -857,28 +1144,57 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/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-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b/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-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-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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= @@ -887,13 +1203,21 @@ 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 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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= @@ -909,9 +1233,12 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw 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-20190706070813-72ffa07ba3db/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-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/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= @@ -931,8 +1258,10 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs 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-20200505023115-26f46d2f7ef8/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-20200616133436-c1934b75d054/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= @@ -941,12 +1270,21 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc 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-20200916195026-c9a70fc28ce3/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-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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= @@ -973,6 +1311,10 @@ google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0 h1:4sAyIHT6ZohtAQDoxws+ez7bROYmUlOVvsUscYCDTqA= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 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= @@ -1005,10 +1347,13 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG 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-20200423170343-7949de9c1215/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-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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= @@ -1017,12 +1362,24 @@ google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6D 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-20201110150050-8816d57aaa9a/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-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/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 h1:x7nk+/4+SvuTDI4wnzQUlhvi+DTpyfncXBo3QWTFs7U= google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1041,10 +1398,20 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM 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 h1:ugq+9++ZQPFzM2pKUMCIK8gj9M0pFyuUWO9Q8kwEDQw= google.golang.org/grpc v1.34.1/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 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= @@ -1058,17 +1425,22 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 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/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -1078,6 +1450,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1085,6 +1458,7 @@ 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 h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= @@ -1100,34 +1474,55 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= +k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +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/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index fe104e7e..c32c26cf 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -16,7 +16,6 @@ import ( "net/http" "net/url" "os" - "strconv" "strings" "time" @@ -24,7 +23,6 @@ import ( "github.com/docker/docker/api/types/container" //"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/api/types/swarm" dockerclient "github.com/docker/docker/client" //"github.com/go-git/go-billy/v5/memfs" @@ -113,31 +111,6 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason } // Might not be necessary because of cleanupEnv hostconfig autoremoval - //if cleanupEnv == "true" && len(containerIds) > 0 && (os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm") { - if cleanupEnv == "true" && (os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm") { - /* - ctx := context.Background() - dockercli, err := dockerclient.NewEnvClient() - if err == nil { - log.Printf("[INFO] Cleaning up %d containers", len(containerIds)) - removeOptions := types.ContainerRemoveOptions{ - RemoveVolumes: true, - Force: true, - } - - for _, containername := range containerIds { - log.Printf("[INFO] Should stop and and remove container %s (deprecated)", containername) - //dockercli.ContainerStop(ctx, containername, nil) - //dockercli.ContainerRemove(ctx, containername, removeOptions) - //removeContainers = append(removeContainers, containername) - } - } - */ - } else { - if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" { - log.Printf("[DEBUG][%s] NOT cleaning up containers. IDS: %d, CLEANUP env: %s", workflowExecution.ExecutionId, 0, cleanupEnv) - } - } if len(reason) > 0 && len(nodeId) > 0 { //log.Printf("[INFO] Running abort of workflow because it should be finished") @@ -165,16 +138,11 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason log.Printf("[INFO][%s] Failed building request: %s", workflowExecution.ExecutionId, err) } - // FIXME: Add an API call to the backend - if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" { - authorization := os.Getenv("AUTHORIZATION") - if len(authorization) > 0 { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization)) - } else { - log.Printf("[ERROR][%s] No authorization specified for abort", workflowExecution.ExecutionId) - } + authorization := os.Getenv("AUTHORIZATION") + if len(authorization) > 0 { + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization)) } else { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", workflowExecution.Authorization)) + log.Printf("[ERROR][%s] No authorization specified for abort", workflowExecution.ExecutionId) } req.Header.Add("Content-Type", "application/json") @@ -210,29 +178,8 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason //Finished shutdown (after %d seconds). ", sleepDuration) // Allows everything to finish in subprocesses (apps) - if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" { - time.Sleep(time.Duration(sleepDuration) * time.Second) - os.Exit(3) - } else { - log.Printf("[DEBUG][%s] Sending result and resetting values (K8s & Swarm).", workflowExecution.ExecutionId) - //UpdateExecutionVariables(ctx, workflowExecution.ExecutionId, startAction, children, parents, visited, executed, nextActions, environments, extra) - - /* - environments = []string{} - parents = map[string][]string{} - children = map[string][]string{} - visited = []string{} - executed = []string{} - nextActions = []string{} - containerIds = []string{} - extra = 0 - startAction = "" - results = []shuffle.ActionResult{} - allLogs = map[string]string{} - */ - //requestsSent = 0 - //executionRunning = false - } + time.Sleep(time.Duration(sleepDuration) * time.Second) + os.Exit(3) //cacheKey := fmt.Sprintf("workflowexecution-%s", workflowExecution.ExecutionId) } @@ -241,58 +188,6 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] // form basic hostConfig ctx := context.Background() - if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { - //identifier := fmt.Sprintf("%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId) - - appName := strings.Replace(identifier, fmt.Sprintf("_%s", action.ID), "", -1) - appName = strings.Replace(appName, fmt.Sprintf("_%s", workflowExecution.ExecutionId), "", -1) - appName = strings.ToLower(appName) - //log.Printf("[INFO][%s] New appname: %s, image: %s", workflowExecution.ExecutionId, appName, image) - - if !shuffle.ArrayContains(downloadedImages, image) { - log.Printf("[DEBUG] Downloading image %s from backend as it's first iteration for this image on the worker.", image) - // FIXME: Not caring if it's ok or not. Just continuing - // This is working as intended, just designed to download an updated - // image on every Orborus/new worker restart. - - // Running as coroutine for eventual completeness - //go downloadDockerImageBackend(&http.Client{}, image) - // FIXME: With goroutines it got too much trouble of deploying with an older version - // Allowing slow startups, as long as it's eventually fast, and uses the same registry as on host. - downloadDockerImageBackend(&http.Client{}, image) - } - - exposedPort, err := findAppInfo(image, appName) - if err != nil { - log.Printf("[ERROR] Failed finding and creating port for %s: %s", appName, err) - return err - } - - log.Printf("[DEBUG][%s] Should run towards port %d for app %s. DELAY: %d", workflowExecution.ExecutionId, exposedPort, appName, action.ExecutionDelay) - if action.ExecutionDelay > 0 { - //log.Printf("[DEBUG] Running app %s with delay of %d", action.Name, action.ExecutionDelay) - waitTime := time.Duration(action.ExecutionDelay) * time.Second - - time.AfterFunc(waitTime, func() { - err = sendAppRequest(baseUrl, appName, exposedPort, action, workflowExecution) - if err != nil { - log.Printf("[ERROR] Failed sending SCHEDULED request to app %s on port %d: %s", appName, exposedPort, err) - } - }) - - } else { - //log.Printf("[DEBUG] Running app %s NORMALLY as there is no delay set", action.Name) - err = sendAppRequest(baseUrl, appName, exposedPort, action, workflowExecution) - if err != nil { - log.Printf("[ERROR] Failed sending request to app %s on port %d: %s", appName, exposedPort, err) - return err - } - } - - //log.Printf("[DEBUG] Successfully ran request towards port %d for app %s", exposedPort, appName) - return nil - } - // Max 10% CPU every second //CPUShares: 128, //CPUQuota: 10000, @@ -307,10 +202,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] Resources: container.Resources{}, } - if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" { - hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:worker-%s", workflowExecution.ExecutionId)) - //log.Printf("Environments: %#v", env) - } + hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:worker-%s", workflowExecution.ExecutionId)) // Removing because log extraction should happen first if cleanupEnv == "true" { @@ -2104,7 +1996,7 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl resultLength := len(workflowExecution.Results) setExecution := true - workflowExecution, dbSave, err := shuffle.ParsedExecutionResult(ctx, *workflowExecution, actionResult, true) + workflowExecution, dbSave, err := shuffle.ParsedExecutionResult(ctx, *workflowExecution, actionResult, true, 0) if err != nil { log.Printf("[DEBUG] Rerunning transaction? %s", err) if strings.Contains(fmt.Sprintf("%s", err), "Rerun this transaction") { @@ -2119,7 +2011,7 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl resultLength = len(workflowExecution.Results) setExecution = true - workflowExecution, dbSave, err = shuffle.ParsedExecutionResult(ctx, *workflowExecution, actionResult, false) + workflowExecution, dbSave, err = shuffle.ParsedExecutionResult(ctx, *workflowExecution, actionResult, false, 0) if err != nil { log.Printf("[ERROR] Failed execution of parsedexecution (2): %s", err) resp.WriteHeader(401) @@ -2171,13 +2063,6 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl return } - if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { - finished := validateFinished(*workflowExecution) - if !finished { - log.Printf("[DEBUG][%s] Handling next node since it's not finished!", workflowExecution.ExecutionId) - handleExecutionResult(*workflowExecution) - } - } } else { log.Printf("[INFO][%s] Skipping setexec with status %s", workflowExecution.ExecutionId, workflowExecution.Status) @@ -2211,7 +2096,7 @@ func getWorkflowExecution(ctx context.Context, id string) (*shuffle.WorkflowExec } func sendResult(workflowExecution shuffle.WorkflowExecution, data []byte) { - if workflowExecution.ExecutionSource == "default" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" { + if workflowExecution.ExecutionSource == "default" { log.Printf("[INFO][%s] Not sending backend info since source is default", workflowExecution.ExecutionId) return } @@ -2253,7 +2138,7 @@ func validateFinished(workflowExecution shuffle.WorkflowExecution) bool { log.Printf("[INFO][%s] VALIDATION. Status: %s, shuffle.Actions: %d, Extra: %d, Results: %d. Parent: %#v\n", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Workflow.Actions), extra, len(workflowExecution.Results), workflowExecution.ExecutionParent) //if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions)+extra { - if (len(environments) == 1 && requestsSent == 0 && len(workflowExecution.Results) >= 1 && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm") || (len(workflowExecution.Results) >= len(workflowExecution.Workflow.Actions)+extra && len(workflowExecution.Workflow.Actions) > 0) { + if (len(environments) == 1 && requestsSent == 0 && len(workflowExecution.Results) >= 1) || (len(workflowExecution.Results) >= len(workflowExecution.Workflow.Actions)+extra && len(workflowExecution.Workflow.Actions) > 0) { if workflowExecution.Status == "FINISHED" { for _, result := range workflowExecution.Results { if result.Status == "EXECUTING" || result.Status == "WAITING" { @@ -2263,9 +2148,7 @@ func validateFinished(workflowExecution shuffle.WorkflowExecution) bool { } } - if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" { - requestsSent += 1 - } + requestsSent += 1 log.Printf("[DEBUG][%s] Should send full result to %s", workflowExecution.ExecutionId, baseUrl) @@ -2340,10 +2223,6 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo cacheKey := fmt.Sprintf("workflowexecution-%s", workflowExecution.ExecutionId) requestCache.Set(cacheKey, &workflowExecution, cache.DefaultExpiration) - if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { - return nil - } - handleExecutionResult(workflowExecution) validateFinished(workflowExecution) @@ -2366,69 +2245,6 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo // GetLocalIP returns the non loopback local IP of the host func getLocalIP() string { - if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { - name, err := os.Hostname() - if err != nil { - log.Printf("[ERROR] Couldn't find hostanme of worker: %s", err) - os.Exit(3) - } - - log.Printf("[DEBUG] Found hostname %s since worker is running with \"run\" command", name) - return name - - /** - Everything below was a test to see if we needed to match directly to a network interface. May require docker network API. - **/ - - log.Printf("[DEBUG] Looking for IP for the external docker-network %s", swarmNetworkName) - // Different process to ensure we find the right IP. - // Necessary due to Ingress being added to docker ser - ifaces, err := net.Interfaces() - if err != nil { - log.Printf("[ERROR] FATAL: networks the container is listening in %s: %s", swarmNetworkName, err) - os.Exit(3) - } - - foundIP := "" - for _, i := range ifaces { - log.Printf("NETWORK: %s", i.Name) - //If i.Name != swarmNetworkName { - // continue - //} - - addrs, err := i.Addrs() - if err != nil { - log.Printf("[ERROR] FATAL: Failed getting address for listener in network %s: %s", swarmNetworkName, err) - continue - } - - for _, addr := range addrs { - var ip net.IP - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - } - - log.Printf("%s: IP: %#v", i.Name, ip) - - // FIXME: Allow for IPv6 too! - //if strings.Count(ip.String(), ".") == 3 { - // foundIP = ip.String() - // break - //} - // process IP address - } - } - - if len(foundIP) == 0 { - log.Printf("[ERROR] FATAL: No valid IP found for network %s. Defaulting to base IP", swarmNetworkName) - } else { - return foundIP - } - } - addrs, err := net.InterfaceAddrs() if err != nil { return "" @@ -2470,23 +2286,10 @@ func webserverSetup(workflowExecution shuffle.WorkflowExecution) net.Listener { } log.Printf("[DEBUG] OLD HOSTNAME: %s", appCallbackUrl) - if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { - log.Printf("\n\nStarting webserver on port %d with hostname: %s\n\n", baseport, hostname) + port := listener.Addr().(*net.TCPAddr).Port - appCallbackUrl = fmt.Sprintf("http://%s:%d", hostname, baseport) - listener, err = net.Listen("tcp", fmt.Sprintf(":%d", baseport)) - if err != nil { - log.Printf("[ERROR] Failed to assign port to %d: %s", baseport, err) - return nil - } - - return listener - } else { - port := listener.Addr().(*net.TCPAddr).Port - - log.Printf("\n\nStarting webserver on port %d with hostname: %s\n\n", port, hostname) - appCallbackUrl = fmt.Sprintf("http://%s:%d", hostname, port) - } + log.Printf("\n\nStarting webserver on port %d with hostname: %s\n\n", port, hostname) + appCallbackUrl = fmt.Sprintf("http://%s:%d", hostname, port) log.Printf("NEW HOSTNAME: %s", appCallbackUrl) return listener @@ -2570,420 +2373,6 @@ func downloadDockerImageBackend(client *http.Client, imageName string) error { return nil } -func deploySwarmService(dockercli *dockerclient.Client, name, image string, deployport int) error { - log.Printf("[DEBUG] Deploying service for %s to swarm on port %d", name, deployport) - //containerName := fmt.Sprintf("shuffle-worker-%s", parsedUuid) - - if len(baseimagename) == 0 { - baseimagename = "frikky/shuffle" - //var baseimagename = "frikky/shuffle" - //var registryName = "registry.hub.docker.com" - } - - //image := fmt.Sprintf("%s:%s", baseimagename, name) - networkName := "shuffle-executions" - if len(swarmNetworkName) > 0 { - networkName = swarmNetworkName - } - - replicatedJobs := uint64(1) - - // Sent from Orborus - // Should be equal to - scaleReplicas := os.Getenv("SHUFFLE_APP_REPLICAS") - if len(scaleReplicas) > 0 { - tmpInt, err := strconv.Atoi(scaleReplicas) - if err != nil { - log.Printf("[ERROR] %s is not a valid number for replication", scaleReplicas) - } else { - replicatedJobs = uint64(tmpInt) - } - - log.Printf("[DEBUG] SHUFFLE_APP_REPLICAS set to value %#v. Trying to overwrite default (%d/node)", scaleReplicas, replicatedJobs) - } - - log.Printf("[DEBUG] Deploying app with name %s with image %s", name, image) - - containerName := fmt.Sprintf(strings.Replace(name, ".", "-", -1)) - serviceSpec := swarm.ServiceSpec{ - Annotations: swarm.Annotations{ - Name: containerName, - Labels: map[string]string{}, - }, - Mode: swarm.ServiceMode{ - Replicated: &swarm.ReplicatedService{ - // Max total - Replicas: &replicatedJobs, - }, - }, - Networks: []swarm.NetworkAttachmentConfig{ - swarm.NetworkAttachmentConfig{ - Target: networkName, - }, - }, - EndpointSpec: &swarm.EndpointSpec{ - Ports: []swarm.PortConfig{ - swarm.PortConfig{ - Protocol: swarm.PortConfigProtocolTCP, - PublishMode: swarm.PortConfigPublishModeIngress, - Name: "app-port", - PublishedPort: uint32(deployport), - TargetPort: uint32(deployport), - }, - }, - }, - TaskTemplate: swarm.TaskSpec{ - Resources: &swarm.ResourceRequirements{ - Reservations: &swarm.Resources{}, - }, - LogDriver: &swarm.Driver{ - Name: "json-file", - Options: map[string]string{ - "max-size": "10m", - }, - }, - ContainerSpec: &swarm.ContainerSpec{ - Image: image, - Env: []string{ - fmt.Sprintf("SHUFFLE_APP_EXPOSED_PORT=%d", deployport), - fmt.Sprintf("SHUFFLE_SWARM_CONFIG=%s", os.Getenv("SHUFFLE_SWARM_CONFIG")), - fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", os.Getenv("SHUFFLE_LOGS_DISABLED")), - }, - Hosts: []string{ - containerName, - }, - }, - RestartPolicy: &swarm.RestartPolicy{ - Condition: swarm.RestartPolicyConditionNone, - }, - Placement: &swarm.Placement{ - // Max per node - MaxReplicas: 1, - }, - }, - } - - if len(os.Getenv("SHUFFLE_SWARM_OTHER_NETWORK")) > 0 { - serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{ - Target: "shuffle_shuffle", - }) - } - - if strings.ToLower(os.Getenv("SHUFFLE_PASS_APP_PROXY")) == "true" { - serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("HTTP_PROXY=%s", os.Getenv("HTTP_PROXY"))) - serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("HTTPS_PROXY=%s", os.Getenv("HTTPS_PROXY"))) - serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("NO_PROXY=%s", os.Getenv("NO_PROXY"))) - } - - /* - Mounts: []mount.Mount{ - mount.Mount{ - Source: "/var/run/docker.sock", - Target: "/var/run/docker.sock", - Type: mount.TypeBind, - }, - }, - */ - - if dockerApiVersion != "" { - serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion)) - } - - // Required for certain apps - if timezone == "" { - timezone = "Europe/Amsterdam" - } - - serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("TZ=%s", timezone)) - - serviceOptions := types.ServiceCreateOptions{} - service, err := dockercli.ServiceCreate( - context.Background(), - serviceSpec, - serviceOptions, - ) - _ = service - - if err != nil { - log.Printf("[DEBUG] Failed deploying %s with image %s: %s", name, image, err) - return err - } - - log.Printf("[DEBUG] Successfully deployed service %s with image %s on port %d", name, image, deployport) - - return nil -} - -// Runs data discovery -func findAppInfo(image, name string) (int, error) { - dockercli, err := dockerclient.NewEnvClient() - if err != nil { - log.Printf("[ERROR] Unable to create docker client (2): %s", err) - return -1, err - } - - highest := baseport - exposedPort := -1 - - // Exists as a "cache" layer - if portMappings != nil { - for key, value := range portMappings { - if value > highest { - highest = value - } - - if key == name { - exposedPort = value - break - } - } - } else { - portMappings = make(map[string]int) - } - - //Filters: - if exposedPort == -1 { - serviceListOptions := types.ServiceListOptions{} - services, err := dockercli.ServiceList( - context.Background(), - serviceListOptions, - ) - - // Basic self-correction - if err != nil { - log.Printf("[ERROR] Unable to list services: %s (may continue anyway?)", err) - if strings.Contains(fmt.Sprintf("%s", err), "is too new") { - // Static for some reason - defaultVersion := "1.40" - dockerApiVersion = defaultVersion - os.Setenv("DOCKER_API_VERSION", defaultVersion) - log.Printf("[DEBUG] Setting Docker API to %s default and retrying listing requests", defaultVersion) - } else { - return -1, err - } - - services, err = dockercli.ServiceList( - context.Background(), - serviceListOptions, - ) - - if err != nil { - log.Printf("[ERROR] Unable to list services (2): %s", err) - return -1, err - } - } - - for _, service := range services { - //log.Printf("[INFO] Service: %#v", service.Spec.Annotations.Name) - - for _, endpoint := range service.Spec.EndpointSpec.Ports { - if strings.Contains(endpoint.Name, "port") { - portMappings[service.Spec.Annotations.Name] = int(endpoint.PublishedPort) - if int(endpoint.PublishedPort) > highest { - highest = int(endpoint.PublishedPort) - } - - if service.Spec.Annotations.Name == name || service.Spec.Annotations.Name == strings.Replace(name, ".", "-", -1) { - exposedPort = int(endpoint.PublishedPort) - //break - } - } - } - - //log.Printf("%s - %s", service.Spec.Annotations.Name, strings.Replace(name, ".", "-", -1)) - if service.Spec.Annotations.Name != name && service.Spec.Annotations.Name != strings.Replace(name, ".", "-", -1) { - continue - } - - // Break if it's the correct port, as it's the right service - if exposedPort >= 0 { - break - } - } - } - - //log.Printf("[DEBUG] Portmappings: %#v", portMappings) - - if exposedPort >= 0 { - //log.Printf("[INFO] Found service %s on port %d - no need to deploy another", name, exposedPort) - } else { - // Increment by 1 for highest port - if highest <= baseport { - highest = baseport - } - - highest += 1 - err = deploySwarmService(dockercli, name, image, highest) - if err != nil { - log.Printf("[WARNING] NOT Found service: %s. error: %s", name, err) - return highest, err - } else { - log.Printf("[INFO] Deployed app with name %s", name) - } - - exposedPort = highest - - if appsInitialized { - log.Printf("[DEBUG] Waiting 30 seconds before moving on to let app start") - time.Sleep(time.Duration(30) * time.Second) - } - } - - return exposedPort, nil -} - -func sendAppRequest(incomingUrl, appName string, port int, action shuffle.Action, workflowExecution shuffle.WorkflowExecution) error { - parsedRequest := shuffle.OrborusExecutionRequest{ - ExecutionId: workflowExecution.ExecutionId, - Authorization: workflowExecution.Authorization, - EnvironmentName: os.Getenv("ENVIRONMENT_NAME"), - Timezone: os.Getenv("TZ"), - Cleanup: os.Getenv("CLEANUP"), - HTTPProxy: os.Getenv("HTTP_PROXY"), - HTTPSProxy: os.Getenv("HTTPS_PROXY"), - ShufflePassProxyToApp: os.Getenv("SHUFFLE_PASS_APP_PROXY"), - BaseUrl: baseUrl, - Action: action, - FullExecution: workflowExecution, - } - //var baseUrl = os.Getenv("BASE_URL") - //var appCallbackUrl = os.Getenv("BASE_URL") - - parsedBaseurl := incomingUrl - if strings.Count(baseUrl, ":") >= 2 { - baseUrlSplit := strings.Split(baseUrl, ":") - if len(baseUrlSplit) >= 3 { - parsedBaseurl = strings.Join(baseUrlSplit[0:2], ":") - //parsedRequest.BaseUrl = fmt.Sprintf("%s:33333", parsedBaseurl) - } - } - - if len(parsedRequest.Url) == 0 { - // Fixed callback url to the worker itself - if strings.Count(parsedBaseurl, ":") >= 2 { - parsedRequest.Url = parsedBaseurl - } else { - // Callback to worker - parsedRequest.Url = fmt.Sprintf("%s:%d", parsedBaseurl, baseport) - - //parsedRequest.Url - } - - //log.Printf("[DEBUG][%s] Should add a baseurl for the app to get back to: %s", workflowExecution.ExecutionId, parsedRequest.Url) - } - - // FIXME: Swapping because this was confusing during dev - tmp := parsedRequest.Url - parsedRequest.Url = parsedRequest.BaseUrl - parsedRequest.BaseUrl = tmp - - //http://3e05d1e7d7a0:33333, - - // Run with proper hostname, but set to shuffle-worker to avoid specific host target. - // This means running with VIP instead. - if len(hostname) > 0 { - parsedRequest.BaseUrl = fmt.Sprintf("http://%s:%d", hostname, baseport) - //parsedRequest.BaseUrl = fmt.Sprintf("http://shuffle-workers:%d", baseport) - //log.Printf("[DEBUG][%s] Changing hostname to local hostname in Docker network for WORKER URL: %s", workflowExecution.ExecutionId, parsedRequest.BaseUrl) - } - - data, err := json.Marshal(parsedRequest) - if err != nil { - log.Printf("[ERROR] Failed marshalling worker request: %s", err) - return err - } - - //streamUrl := fmt.Sprintf("%s:%d/api/v1/run", parsedBaseurl, port) - streamUrl := fmt.Sprintf("http://%s:%d/api/v1/run", appName, port) - log.Printf("[DEBUG][%s] Worker URL: %s, Backend URL: %s, Target App: %s", workflowExecution.ExecutionId, parsedRequest.BaseUrl, parsedRequest.Url, streamUrl) - req, err := http.NewRequest( - "POST", - streamUrl, - bytes.NewBuffer([]byte(data)), - ) - - client := &http.Client{} - if err != nil { - log.Printf("[ERROR] Failed creating app run request: %s", err) - return err - } - - // Checking as LATE as possible, ensuring we don't rerun what's already ran - ctx := context.Background() - newExecId := fmt.Sprintf("%s_%s", workflowExecution.ExecutionId, action.ID) - _, err = shuffle.GetCache(ctx, newExecId) - if err == nil { - log.Printf("\n\n[DEBUG] Result for %s already found (PRE REQUEST) - returning\n\n", newExecId) - return nil - } - - cacheData := []byte("1") - err = shuffle.SetCache(ctx, newExecId, cacheData) - if err != nil { - log.Printf("[WARNING] Failed setting cache for action %s: %s", newExecId, err) - } else { - log.Printf("[DEBUG] Adding %s to cache (%s)", newExecId, action.Name) - } - - // FIXME: - - newresp, err := client.Do(req) - if err != nil { - if strings.Contains(fmt.Sprintf("%s", err), "timeout awaiting response") { - return nil - } - - log.Printf("[ERROR] Error running app run request: %s", err) - - return err - } - - body, err := ioutil.ReadAll(newresp.Body) - if err != nil { - log.Printf("[ERROR] Failed reading app request body body: %s", err) - return err - } else { - log.Printf("[INFO][%s] NEWRESP (from app): %s", workflowExecution.ExecutionId, string(body)) - } - - // FIXME: Remove - /* - if len(hostname) > 0 { - //streamUrl := fmt.Sprintf("%s:%d/api/v1/run", parsedBaseurl, port) - streamUrl := fmt.Sprintf("http://%s:%d/api/v1/run", appName, port) - log.Printf("\n\n[DEBUG] Trying execution towards %s", streamUrl) - req, err := http.NewRequest( - "POST", - streamUrl, - bytes.NewBuffer([]byte(data)), - ) - - client := &http.Client{} - if err != nil { - log.Printf("[ERROR] Failed creating app run request: %s", err) - return err - } - - newresp, err := client.Do(req) - if err != nil { - log.Printf("[ERROR] Error running app run request: %s", err) - return err - } - - body, err := ioutil.ReadAll(newresp.Body) - if err != nil { - log.Printf("[ERROR] Failed reading body: %s", err) - return err - } else { - log.Printf("[INFO] NEWRESP (from app): %s", string(body)) - } - } - */ - - return nil -} - -// Function to auto-deploy certain apps if "run" is set // Has some issues with loading when running multiple workers and such. func baseDeploy() { //return @@ -3048,22 +2437,6 @@ func baseDeploy() { // Initial loop etc func main() { - /* - appName := "shuffle-tools_1.1.0" - image := "frikky/shuffle:shuffle-tools_1.1.0" - exposedPort, err := findAppInfo(image, appName) - if err != nil { - log.Printf("[ERROR] Failed finding and creating port for %s: %s", appName, err) - os.Exit(3) - } - - log.Printf("[DEBUG] Should run towards port %d for app %s", exposedPort, appName) - err = sendAppRequest(appCallbackUrl, exposedPort, shuffle.Action{}, shuffle.WorkflowExecution{}) - if err != nil { - log.Printf("[ERROR] Failed sending request to app %s on port %d: %s", appName, exposedPort, err) - os.Exit(3) - } - */ // Elasticsearch necessary to ensure we'ren ot running with Datastore configurations for minimal/maximal data sizes _, err := shuffle.RunInit(datastore.Client{}, storage.Client{}, "", "", true, "elasticsearch") @@ -3099,21 +2472,6 @@ func main() { } log.Printf("[INFO] Running with timezone %s and swarm config %#v", timezone, os.Getenv("SHUFFLE_SWARM_CONFIG")) - if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { - // Forcing download just in case on the first iteration. - workflowExecution := shuffle.WorkflowExecution{} - - //var autoDeploy = []string{"frikky/shuffle:shuffle-subflow_1.0.0", "frikky/shuffle:http_1.1.0", "frikky/shuffle:shuffle-tools_1.1.0", "frikky/shuffle:testing_1.0.0"} - - go baseDeploy() - //baseDeploy() - - listener := webserverSetup(workflowExecution) - runWebserver(listener) - log.Printf("[ERROR] Stopped listener %#v - exiting.", listener) - os.Exit(3) - } - //imageName := fmt.Sprintf("%s/%s:shuffle_openapi_1.0.0", registryName, baseimagename) // WORKER_TESTING_WORKFLOW should be a workflow ID @@ -3467,17 +2825,6 @@ func runWebserver(listener net.Listener) { r.HandleFunc("/api/v1/streams", handleWorkflowQueue).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/streams/results", handleGetStreamResults).Methods("POST", "OPTIONS") - if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { - /* - err = dockercli.ServiceRemove(ctx, "shuffle-workers") - if err != nil {} - */ - - requestCache = cache.New(60*time.Minute, 120*time.Minute) - log.Printf("[DEBUG] Running webserver config for SWARM and K8s") - r.HandleFunc("/api/v1/execute", handleRunExecution).Methods("POST", "OPTIONS") - } - //log.Fatal(http.ListenAndServe(port, nil)) http.Handle("/", r) log.Fatal(http.Serve(listener, nil)) diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 00f9bd6e..00000000 --- a/package-lock.json +++ /dev/null @@ -1,1182 +0,0 @@ -{ - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", - "optional": true, - "requires": { - "@emotion/memoize": "0.7.4" - } - }, - "@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "optional": true - }, - "@upsetjs/venn.js": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-1.4.2.tgz", - "integrity": "sha512-sFyczoc4T0FonsMiHo/7AXqTpuBOOlIGTIMli5tFdfTXUECogGsnHY4+BQfHX9EaYk4zxBno/9PKsxEfY3CZLA==", - "requires": { - "d3-selection": "^3.0.0", - "d3-transition": "^3.0.1", - "fmin": "^0.0.2" - } - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "big-integer": { - "version": "1.6.49", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.49.tgz", - "integrity": "sha512-KJ7VhqH+f/BOt9a3yMwJNmcZjG53ijWMTjSAGMveQWyLwqIiwkjNP5PFgDob3Snnx86SjDj6I89fIbv0dkQeNw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "calculate-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/calculate-size/-/calculate-size-1.1.1.tgz", - "integrity": "sha1-rnyqHHeV+CxPA13HvicONYHa4+4=" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "chroma-js": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", - "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" - }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "codemirror": { - "version": "5.65.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.0.tgz", - "integrity": "sha512-gWEnHKEcz1Hyz7fsQWpK7P0sPI2/kSkRX2tc7DFA6TmZuDN75x/1ejnH/Pn8adYKrLEA1V2ww6L00GudHZbSKw==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "contour_plot": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/contour_plot/-/contour_plot-0.0.1.tgz", - "integrity": "sha1-R1hw8DK44zhBKqX8UHiA8L9JXHc=" - }, - "countup.js": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-1.9.3.tgz", - "integrity": "sha1-zj5QzXFgRB5HjwfaMYle3MDxyd0=" - }, - "create-global-state-hook": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/create-global-state-hook/-/create-global-state-hook-0.0.2.tgz", - "integrity": "sha512-+1gRNwtuSQIC9lQQngfcY1VARKs6R32KJjI1bFrsp0W5MbauupRW/uQF0f+ElXx8xMmuEK9wl5zqAslzj6GvCA==" - }, - "d3-array": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.1.1.tgz", - "integrity": "sha512-33qQ+ZoZlli19IFiQx4QEpf2CBEayMRzhlisJHSCsSUbDXv6ZishqS1x7uFVClKG4Wr7rZVHvaAttoLow6GqdQ==", - "requires": { - "internmap": "1 - 2" - } - }, - "d3-color": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.0.1.tgz", - "integrity": "sha512-6/SlHkDOBLyQSJ1j1Ghs82OIUXpKWlR0hCsw0XrLSQhuUPuCSmLQ1QPH98vpnQxMUQM2/gfAkUEWsupVpd9JGw==" - }, - "d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "optional": true - }, - "d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "optional": true - }, - "d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" - }, - "d3-geo": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", - "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", - "requires": { - "d3-array": "2.5.0 - 3" - } - }, - "d3-hierarchy": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.1.tgz", - "integrity": "sha512-LtAIu54UctRmhGKllleflmHalttH3zkfSi4NlKrTAoFKjC+AFBJohsCAdgCBYQwH0F8hIOGY89X1pPqAchlMkA==" - }, - "d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "requires": { - "d3-color": "1 - 3" - } - }, - "d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" - }, - "d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "requires": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - }, - "dependencies": { - "d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "requires": { - "internmap": "^1.0.0" - } - }, - "d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "requires": { - "d3-path": "1" - } - }, - "internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" - } - } - }, - "d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "requires": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - } - }, - "d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "optional": true - }, - "d3-shape": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", - "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", - "requires": { - "d3-path": "1 - 3" - } - }, - "d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", - "requires": { - "d3-array": "2 - 3" - } - }, - "d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "requires": { - "d3-time": "1 - 3" - } - }, - "d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "optional": true - }, - "d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "optional": true, - "requires": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" - }, - "dotignore": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", - "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", - "requires": { - "minimatch": "^3.0.4" - } - }, - "ellipsize": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ellipsize/-/ellipsize-0.2.0.tgz", - "integrity": "sha512-InJhblLPZbBjw3N49knOWonfprgKPLKGySmG6bGHi7WsD5OkXIIlLkU4AguROmaMZ0v1BRdo267wEc0Pexw8ww==", - "requires": { - "tape": "^4.9.0" - } - }, - "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", - "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "exenv": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" - }, - "fmin": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/fmin/-/fmin-0.0.2.tgz", - "integrity": "sha1-Wbu0DUP/3ByUzQClaMQflfGXMBc=", - "requires": { - "contour_plot": "^0.0.1", - "json2module": "^0.0.3", - "rollup": "^0.25.8", - "tape": "^4.5.1", - "uglify-js": "^2.6.2" - } - }, - "focus-trap": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.7.3.tgz", - "integrity": "sha512-8xCEKndV4KrseGhFKKKmczVA14yx1/hnmFICPOjcFjToxCJYj/NHH43tPc3YE/PLnLRNZoFug0EcWkGQde/miQ==", - "requires": { - "tabbable": "^5.2.1" - } - }, - "focus-trap-react": { - "version": "8.9.2", - "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-8.9.2.tgz", - "integrity": "sha512-m6TQQHLcqkisc6Qq92q1yYzzOaxo34Szh5FQOXzky7028PXFEhuUxScTbT7EG9qL0QG7B+oR2ZbxyU0lK4kIwQ==", - "requires": { - "focus-trap": "^6.7.3" - } - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "^1.1.3" - } - }, - "framer-motion": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-4.1.17.tgz", - "integrity": "sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==", - "requires": { - "@emotion/is-prop-valid": "^0.8.2", - "framesync": "5.3.0", - "hey-listen": "^1.0.8", - "popmotion": "9.3.6", - "style-value-types": "4.1.4", - "tslib": "^2.1.0" - } - }, - "framesync": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz", - "integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==", - "requires": { - "tslib": "^2.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hey-listen": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", - "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" - }, - "human-format": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/human-format/-/human-format-0.11.0.tgz", - "integrity": "sha512-g4UtoBnhfitCjkGjjiOlY8tkmArIcrstBa5adihxSJwSde1A7iQzvrNLB5ceX89FHXzYi9yTi04t3m82J/XIag==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" - }, - "invert-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-color/-/invert-color-2.0.0.tgz", - "integrity": "sha512-9s6IATlhOAr0/0MPUpLdMpk81ixIu8IqwPwORssXBauFT/4ff/iyEOcojd0UYuPwkDbJvL1+blIZGhqVIaAm5Q==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" - }, - "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" - }, - "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "requires": { - "call-bind": "^1.0.2" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "json2module": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/json2module/-/json2module-0.0.3.tgz", - "integrity": "sha1-APtfSpt638PwZHwpyxe80Zeb6bI=", - "requires": { - "rw": "^1.3.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "memoize-bind": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/memoize-bind/-/memoize-bind-1.0.3.tgz", - "integrity": "sha1-/kzl9KE/7dEZmBgic1qwiV3l5cc=", - "requires": { - "memoize-weak": "^1.0.0" - } - }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "memoize-weak": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/memoize-weak/-/memoize-weak-1.0.2.tgz", - "integrity": "sha1-0AFaTHxs/yJj27tJ2x3CBuu5SRY=" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "popmotion": { - "version": "9.3.6", - "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.6.tgz", - "integrity": "sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==", - "requires": { - "framesync": "5.3.0", - "hey-listen": "^1.0.8", - "style-value-types": "4.1.4", - "tslib": "^2.1.0" - } - }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "rdk": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/rdk/-/rdk-5.1.6.tgz", - "integrity": "sha512-fUzlSJjwD5MtXOOBvopdxdWBhztl7WTlE27WALNtbUD66ApGjZffGbhBMN6i9A3e4Dd8pDnGVe590Nk2lEN4lw==", - "requires": { - "classnames": "^2.3.1", - "popper.js": "^1.16.1", - "react-scrolllock": "^5.0.1" - } - }, - "react-codemirror-runmode": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/react-codemirror-runmode/-/react-codemirror-runmode-1.0.5.tgz", - "integrity": "sha512-6TzbODi0WSllc0VOwOjTpZxE9wUheMMpmBNydzR5o0NbbBLX0j73xutH/o85BTG9NKs06sFEBMvCsRKT6jRz8A==" - }, - "react-cool-dimensions": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/react-cool-dimensions/-/react-cool-dimensions-2.0.7.tgz", - "integrity": "sha512-z1VwkAAJ5d8QybDRuYIXTE41RxGr5GYsv1bQhbOBE8cMfoZQZpcF0odL64vdgrQVzat2jayedj1GoYi80FWcbA==" - }, - "react-countup": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-4.2.0.tgz", - "integrity": "sha512-CkmtvYOZV0iW8byfrZ96OKPDWwmvpS7rcfTKU9Ngn+oYRVoZrZ5quoKC2gbUS66Iona6vAoko6f8YxXJJuP2FQ==", - "requires": { - "countup.js": "^1.9.3", - "prop-types": "^15.6.2", - "warning": "^4.0.2" - } - }, - "react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-scrolllock": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-scrolllock/-/react-scrolllock-5.0.1.tgz", - "integrity": "sha512-poeEsjnZAlpA6fJlaNo4rZtcip2j6l5mUGU/SJe1FFlicEudS943++u7ZSdA7lk10hoyYK3grOD02/qqt5Lxhw==", - "requires": { - "exenv": "^1.2.2" - } - }, - "realayers": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/realayers/-/realayers-2.7.1.tgz", - "integrity": "sha512-F6h+fPKrohge8ZiQAatWRxQpRyWIqB+dlNEEm9TypI8U9mEC4R8BiCe8jiZphHCfDFQM2k0rAT/yBE5V8LgjRQ==", - "requires": { - "classnames": "^2.3.1", - "create-global-state-hook": "^0.0.2", - "focus-trap-react": "^8.7.1", - "rdk": "^5.1.6" - } - }, - "reaviz": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/reaviz/-/reaviz-12.2.0.tgz", - "integrity": "sha512-AIP6T4NOnhXlanwEa3LwWvBvjsIRAOGXBuwcSNsHPlQonAWI/1V7pw5sfIfmtyXCdtANCQY4QWmAjY9Y0Ofl9g==", - "requires": { - "@upsetjs/venn.js": "^1.3.0", - "big-integer": "1.6.49", - "calculate-size": "^1.1.1", - "chroma-js": "^2.1.2", - "classnames": "^2.2.6", - "d3-array": "^3.0.4", - "d3-format": "^3.0.1", - "d3-geo": "^3.0.1", - "d3-hierarchy": "^3.0.1", - "d3-interpolate": "^3.0.1", - "d3-sankey": "^0.12.3", - "d3-scale": "^4.0.2", - "d3-shape": "^3.0.1", - "d3-time": "^3.0.0", - "ellipsize": "^0.2.0", - "framer-motion": "^4.1.17", - "human-format": "^0.11.0", - "invert-color": "^2.0.0", - "memoize-bind": "^1.0.3", - "memoize-one": "^5.2.1", - "rdk": "^5.1.6", - "react-cool-dimensions": "^2.0.7", - "react-countup": "4.2.0", - "react-fast-compare": "^3.2.0", - "realayers": "^2.4.6", - "transformation-matrix": "^2.9.0" - } - }, - "regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resumer": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", - "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", - "requires": { - "through": "~2.3.4" - } - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "requires": { - "align-text": "^0.1.1" - } - }, - "rollup": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.25.8.tgz", - "integrity": "sha1-v2zoO4dRDRY0Ru6qV37WpvxYNeA=", - "requires": { - "chalk": "^1.1.1", - "minimist": "^1.2.0", - "source-map-support": "^0.3.2" - } - }, - "rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "source-map": { - "version": "0.1.32", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz", - "integrity": "sha1-yLbBZ3l7pHQKjqMyUhYv8IWRsmY=", - "requires": { - "amdefine": ">=0.0.4" - } - }, - "source-map-support": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.3.3.tgz", - "integrity": "sha1-NJAJd9W6PwfHdX7nLnO7GptTdU8=", - "requires": { - "source-map": "0.1.32" - } - }, - "string.prototype.trim": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz", - "integrity": "sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "style-value-types": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz", - "integrity": "sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==", - "requires": { - "hey-listen": "^1.0.8", - "tslib": "^2.1.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "tabbable": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.2.1.tgz", - "integrity": "sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ==" - }, - "tape": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/tape/-/tape-4.15.0.tgz", - "integrity": "sha512-SfRmG2I8QGGgJE/MCiLH8c11L5XxyUXxwK9xLRD0uiK5fehRkkSZGmR6Y1pxOt8vJ19m3sY+POTQpiaVv45/LQ==", - "requires": { - "call-bind": "~1.0.2", - "deep-equal": "~1.1.1", - "defined": "~1.0.0", - "dotignore": "~0.1.2", - "for-each": "~0.3.3", - "glob": "~7.2.0", - "has": "~1.0.3", - "inherits": "~2.0.4", - "is-regex": "~1.1.4", - "minimist": "~1.2.5", - "object-inspect": "~1.12.0", - "resolve": "~1.22.0", - "resumer": "~0.0.0", - "string.prototype.trim": "~1.2.5", - "through": "~2.3.8" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "transformation-matrix": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.11.1.tgz", - "integrity": "sha512-srlkTrmetYwTBJ1RHdukkwJ8S8D+2JjgSb1DbvmTwj+DsIpCpRYHbWgOXe/Ql2rX37WqlKLIgidpYlHPGsrwgA==" - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "optional": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - } - } -}