Merge branch '2.0.0'
@@ -40,6 +40,7 @@ BACKEND_HOSTNAME=shuffle-backend
|
|||||||
BACKEND_PORT=5001
|
BACKEND_PORT=5001
|
||||||
FRONTEND_PORT=3001
|
FRONTEND_PORT=3001
|
||||||
FRONTEND_PORT_HTTPS=3443
|
FRONTEND_PORT_HTTPS=3443
|
||||||
|
AUTH_FOR_ORBORUS =
|
||||||
|
|
||||||
# CHANGE THIS IF YOU WANT GOOD LOCAL EXECUTIONS:
|
# CHANGE THIS IF YOU WANT GOOD LOCAL EXECUTIONS:
|
||||||
OUTER_HOSTNAME=shuffle-backend
|
OUTER_HOSTNAME=shuffle-backend
|
||||||
@@ -51,8 +52,8 @@ HTTP_PROXY=
|
|||||||
HTTPS_PROXY=
|
HTTPS_PROXY=
|
||||||
SHUFFLE_PASS_WORKER_PROXY=TRUE
|
SHUFFLE_PASS_WORKER_PROXY=TRUE
|
||||||
SHUFFLE_PASS_APP_PROXY=TRUE
|
SHUFFLE_PASS_APP_PROXY=TRUE
|
||||||
SHUFFLE_INTERNAL_HTTP_PROXY=NOPROXY
|
SHUFFLE_INTERNAL_HTTP_PROXY=noproxy
|
||||||
SHUFFLE_INTERNAL_HTTPS_PROXY=NOPROXY
|
SHUFFLE_INTERNAL_HTTPS_PROXY=noproxy
|
||||||
# Timezone-handler in Orborus, Worker and Apps
|
# Timezone-handler in Orborus, Worker and Apps
|
||||||
TZ=Europe/Amsterdam
|
TZ=Europe/Amsterdam
|
||||||
# Used to FIND the containername. cgroup v2: issue 501
|
# Used to FIND the containername. cgroup v2: issue 501
|
||||||
@@ -68,6 +69,10 @@ IS_KUBERNETES=false
|
|||||||
SHUFFLE_BASE_IMAGE_REPOSITORY=frikky
|
SHUFFLE_BASE_IMAGE_REPOSITORY=frikky
|
||||||
#SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.4.0"
|
#SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.4.0"
|
||||||
|
|
||||||
|
# For environments using their own docker registry
|
||||||
|
# where they don't want to update http, subflow and shuffle tools again
|
||||||
|
SHUFFLE_USE_GCHR_OVERRIDE_FOR_AUTODEPLOY=true
|
||||||
|
|
||||||
# The eth0 interface inside a container corresponds
|
# The eth0 interface inside a container corresponds
|
||||||
# to the virtual Ethernet interface that connects
|
# to the virtual Ethernet interface that connects
|
||||||
# the container to the docker0
|
# the container to the docker0
|
||||||
@@ -97,14 +102,15 @@ SHUFFLE_MAX_EXECUTION_DEPTH=
|
|||||||
DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
||||||
#SHUFFLE_OPENSEARCH_URL=http://shuffle-opensearch:9200
|
#SHUFFLE_OPENSEARCH_URL=http://shuffle-opensearch:9200
|
||||||
SHUFFLE_OPENSEARCH_URL=https://shuffle-opensearch:9200
|
SHUFFLE_OPENSEARCH_URL=https://shuffle-opensearch:9200
|
||||||
SHUFFLE_OPENSEARCH_USERNAME="admin"
|
|
||||||
SHUFFLE_OPENSEARCH_PASSWORD="StrongShufflePassword321!"
|
|
||||||
SHUFFLE_OPENSEARCH_CERTIFICATE_FILE=
|
SHUFFLE_OPENSEARCH_CERTIFICATE_FILE=
|
||||||
SHUFFLE_OPENSEARCH_APIKEY=
|
SHUFFLE_OPENSEARCH_APIKEY=
|
||||||
SHUFFLE_OPENSEARCH_CLOUDID=
|
SHUFFLE_OPENSEARCH_CLOUDID=
|
||||||
SHUFFLE_OPENSEARCH_PROXY=
|
SHUFFLE_OPENSEARCH_PROXY=
|
||||||
SHUFFLE_OPENSEARCH_INDEX_PREFIX=
|
SHUFFLE_OPENSEARCH_INDEX_PREFIX=
|
||||||
SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true
|
SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true
|
||||||
|
SHUFFLE_OPENSEARCH_USERNAME="admin"
|
||||||
|
SHUFFLE_OPENSEARCH_PASSWORD="StrongShufflePassword321!" # In use for the first time setup of OpenSearch + backend of Shuffle
|
||||||
|
OPENSEARCH_INITIAL_ADMIN_PASSWORD="StrongShufflePassword321!" # In use for the first time setup of OpenSearch
|
||||||
|
|
||||||
#Tenzir related
|
#Tenzir related
|
||||||
SHUFFLE_TENZIR_URL=
|
SHUFFLE_TENZIR_URL=
|
||||||
|
|||||||
@@ -19,23 +19,19 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- app: frontend
|
- app: frontend
|
||||||
path: frontend
|
path: frontend
|
||||||
version: 1.4.2
|
version: 2.0.0
|
||||||
experimental: true
|
experimental: true
|
||||||
- app: backend
|
- app: backend
|
||||||
path: backend
|
path: backend
|
||||||
version: 1.4.2
|
version: nightly
|
||||||
experimental: true
|
|
||||||
- app: app_sdk
|
|
||||||
path: backend/app_sdk
|
|
||||||
version: 1.4.2
|
|
||||||
experimental: true
|
experimental: true
|
||||||
- app: orborus
|
- app: orborus
|
||||||
path: functions/onprem/orborus
|
path: functions/onprem/orborus
|
||||||
version: 1.4.2
|
version: 2.0.0
|
||||||
experimental: true
|
experimental: true
|
||||||
- app: worker
|
- app: worker
|
||||||
path: functions/onprem/worker
|
path: functions/onprem/worker
|
||||||
version: 1.4.2
|
version: 2.0.0
|
||||||
experimental: true
|
experimental: true
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
name: Nightly Release
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
branches:
|
||||||
|
- 2.0.0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- app: frontend
|
||||||
|
path: frontend
|
||||||
|
experimental: true
|
||||||
|
- app: backend
|
||||||
|
path: backend
|
||||||
|
experimental: true
|
||||||
|
- app: app_sdk
|
||||||
|
path: backend/app_sdk
|
||||||
|
experimental: true
|
||||||
|
- app: orborus
|
||||||
|
path: functions/onprem/orborus
|
||||||
|
experimental: true
|
||||||
|
- app: worker
|
||||||
|
path: functions/onprem/worker
|
||||||
|
experimental: true
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set version
|
||||||
|
id: set_version
|
||||||
|
run: |
|
||||||
|
if [[ ${{ github.event_name }} == 'release' ]]; then
|
||||||
|
echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "VERSION=nightly-untagged-latest" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: "amd64,arm64,arm"
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to Ghcr
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Ghcr Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
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 }}:${{ steps.set_version.outputs.VERSION }}
|
||||||
|
${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ steps.set_version.outputs.VERSION }}
|
||||||
|
frikky/shuffle-${{ matrix.app }}:${{ steps.set_version.outputs.VERSION }}
|
||||||
|
frikky/shuffle:${{ matrix.app }}
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- launch
|
|
||||||
name: release-please
|
|
||||||
jobs:
|
|
||||||
release-please:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: google-github-actions/release-please-action@v3
|
|
||||||
with:
|
|
||||||
release-type: node
|
|
||||||
package-name: release-please-action
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# This is a basic workflow to help you get started with Actions
|
|
||||||
|
|
||||||
name: App SDK upload
|
|
||||||
|
|
||||||
# Controls when the workflow will run
|
|
||||||
on:
|
|
||||||
# Triggers the workflow on push or pull request events but only for the main branch
|
|
||||||
push:
|
|
||||||
branches: [ master, launch ]
|
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
||||||
jobs:
|
|
||||||
# This workflow contains a single job called "build"
|
|
||||||
build:
|
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- id: 'auth'
|
|
||||||
name: 'Authenticate to Google Cloud'
|
|
||||||
uses: 'google-github-actions/auth@v0'
|
|
||||||
with:
|
|
||||||
credentials_json: '${{ secrets.SANDBOX_CREDENTIALS }}'
|
|
||||||
|
|
||||||
- id: 'upload_sdk'
|
|
||||||
name: Cloud Storage Uploader
|
|
||||||
uses: google-github-actions/upload-cloud-storage@v0.9.0
|
|
||||||
with:
|
|
||||||
path: 'backend/app_sdk/app_base.py'
|
|
||||||
destination: 'shuffle-sandbox-337810.appspot.com/generated_apps/baseline'
|
|
||||||
|
|
||||||
- id: 'upload_requirement'
|
|
||||||
name: Cloud Storage Uploader
|
|
||||||
uses: google-github-actions/upload-cloud-storage@v0.9.0
|
|
||||||
with:
|
|
||||||
path: 'backend/app_sdk/requirements.txt'
|
|
||||||
destination: 'shuffle-sandbox-337810.appspot.com/generated_apps/baseline'
|
|
||||||
|
|
||||||
- id: 'upload_Dockerfile'
|
|
||||||
name: Cloud Storage Uploader
|
|
||||||
uses: google-github-actions/upload-cloud-storage@v0.9.0
|
|
||||||
with:
|
|
||||||
path: 'backend/app_sdk/Dockerfile'
|
|
||||||
destination: 'shuffle-sandbox-337810.appspot.com/generated_apps/baseline'
|
|
||||||
@@ -4,15 +4,22 @@
|
|||||||
|
|
||||||
Shuffle Automation
|
Shuffle Automation
|
||||||
|
|
||||||
|
[](https://github.com/Shuffle/Shuffle/actions/workflows/codeql-analysis.yml)
|
||||||
|
[](https://github.com/Shuffle/Shuffle/actions/workflows/dockerbuild.yaml)
|
||||||
|
[](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=Shuffle-Instance&templateURL=https://shuffle-public-amis.s3.eu-north-1.amazonaws.com/template.yaml)
|
||||||
|
|
||||||
</h1><h4 align="center">
|
</h1><h4 align="center">
|
||||||
|
|
||||||
[Shuffle](https://shuffler.io) is an open source automation platform, built for and by the security professionals. Security operations is complex, but it doesn't have to be. Built to work well with MSSP's and other service providers in mind.
|
[Shuffle](https://shuffler.io) is an open source automation platform, built for and by the security professionals. Security operations is complex, but it doesn't have to be. Built to work well with MSSP's and other service providers in mind.
|
||||||
|
|
||||||
|
[ Get training ](https://shuffler.io/training)
|
||||||
[_Key Features_](https://shuffler.io/docs/features) —
|
[_Key Features_](https://shuffler.io/docs/features) —
|
||||||
[_Community & Support_](https://discord.gg/B2CBzUm) —
|
[_Community & Support_](https://discord.gg/B2CBzUm) —
|
||||||
[ Get training ](https://shuffler.io/training) -
|
[ Get training ](https://shuffler.io/training) -
|
||||||
[_Documentation_](https://shuffler.io/docs) —
|
[_Documentation_](https://shuffler.io/docs) —
|
||||||
[_Getting Started_](https://shuffler.io/docs/getting_started)
|
[_Getting Started_](https://shuffler.io/docs/getting_started) —
|
||||||
|
[_Development_](https://github.com/shuffle/Shuffle/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
[ Set up a demo call ](https://shuffler.io/contact)
|
||||||
|
|
||||||
Follow us on Twitter at [@shuffleio](https://twitter.com/shuffleio).
|
Follow us on Twitter at [@shuffleio](https://twitter.com/shuffleio).
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ADD ./go-app/docker.go /app
|
|||||||
ADD ./go-app/go.mod /app
|
ADD ./go-app/go.mod /app
|
||||||
|
|
||||||
# Required files for code generation
|
# Required files for code generation
|
||||||
ADD ./app_sdk/app_base.py /app_sdk
|
RUN wget -O /app_sdk/app_base.py https://raw.githubusercontent.com/Shuffle/app_sdk/refs/heads/main/shuffle_sdk/shuffle_sdk.py
|
||||||
ADD ./app_gen /app_gen
|
ADD ./app_gen /app_gen
|
||||||
|
|
||||||
RUN go get -v
|
RUN go get -v
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
#FROM python:3.9.1-alpine as base
|
|
||||||
FROM python:3.10.0-alpine as base
|
|
||||||
#FROM python:3.11.3-alpine as base
|
|
||||||
|
|
||||||
FROM base as builder
|
|
||||||
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev tzdata coreutils
|
|
||||||
|
|
||||||
RUN mkdir /install
|
|
||||||
WORKDIR /install
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
|
|
||||||
#--no-cache
|
|
||||||
RUN apk update && apk add --update tzdata libmagic alpine-sdk libffi libffi-dev musl-dev openssl-dev coreutils
|
|
||||||
|
|
||||||
COPY --from=builder /install /usr/local
|
|
||||||
COPY requirements.txt /requirements.txt
|
|
||||||
RUN pip3 install -r /requirements.txt
|
|
||||||
|
|
||||||
COPY __init__.py /app/walkoff_app_sdk/__init__.py
|
|
||||||
COPY app_base.py /app/walkoff_app_sdk/app_base.py
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
FROM python:3.10.0-alpine as base
|
|
||||||
|
|
||||||
FROM base as builder
|
|
||||||
RUN apk --no-cache add --update \
|
|
||||||
alpine-sdk \
|
|
||||||
build-base \
|
|
||||||
g++ \
|
|
||||||
gcc \
|
|
||||||
libffi \
|
|
||||||
libffi-dev \
|
|
||||||
libstdc++ \
|
|
||||||
linux-headers \
|
|
||||||
musl-dev \
|
|
||||||
openssl-dev \
|
|
||||||
tzdata \
|
|
||||||
coreutils
|
|
||||||
|
|
||||||
RUN pip install --upgrade pip && \
|
|
||||||
pip install --prefix="/install" --no-cache-dir grpcio grpcio-tools && \
|
|
||||||
apk del --purge \
|
|
||||||
g++ \
|
|
||||||
gcc \
|
|
||||||
musl-dev \
|
|
||||||
libffi-dev \
|
|
||||||
libstdc++ \
|
|
||||||
build-base \
|
|
||||||
linux-headers
|
|
||||||
|
|
||||||
RUN mkdir -p /install
|
|
||||||
WORKDIR /install
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
|
|
||||||
#--no-cache
|
|
||||||
RUN apk update && apk add --update tzdata libmagic alpine-sdk libffi libffi-dev musl-dev openssl-dev coreutils
|
|
||||||
|
|
||||||
COPY --from=builder /install /usr/local
|
|
||||||
COPY requirements.txt /requirements.txt
|
|
||||||
RUN pip3 install -r /requirements.txt
|
|
||||||
|
|
||||||
COPY __init__.py /app/walkoff_app_sdk/__init__.py
|
|
||||||
COPY app_base.py /app/walkoff_app_sdk/app_base.py
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
FROM blackarchlinux/blackarch as base
|
|
||||||
|
|
||||||
FROM base as builder
|
|
||||||
|
|
||||||
RUN /bin/pacman -Syu --noconfirm
|
|
||||||
|
|
||||||
RUN /bin/pacman -Sy --noconfirm base-devel libffi musl openssl python python-pip -y
|
|
||||||
|
|
||||||
RUN mkdir /install
|
|
||||||
WORKDIR /install
|
|
||||||
|
|
||||||
COPY requirements.txt /requirements.txt
|
|
||||||
RUN pip install --prefix="/install" -r /requirements.txt
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
|
|
||||||
COPY --from=builder /install /usr/local
|
|
||||||
COPY __init__.py /app/walkoff_app_sdk/__init__.py
|
|
||||||
COPY app_base.py /app/walkoff_app_sdk/app_base.py
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
FROM kalilinux/kali-rolling as base
|
|
||||||
|
|
||||||
FROM base as builder
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get dist-upgrade -y
|
|
||||||
RUN apt install build-essential libffi-dev musl-dev openssl python3 python3-pip -y
|
|
||||||
|
|
||||||
RUN mkdir /install
|
|
||||||
WORKDIR /install
|
|
||||||
|
|
||||||
COPY requirements.txt /requirements.txt
|
|
||||||
RUN pip install --prefix="/install" -r /requirements.txt
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
|
|
||||||
COPY --from=builder /install /usr/local
|
|
||||||
COPY __init__.py /app/walkoff_app_sdk/__init__.py
|
|
||||||
COPY app_base.py /app/walkoff_app_sdk/app_base.py
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
FROM ubuntu as base
|
|
||||||
|
|
||||||
FROM base as builder
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get dist-upgrade -y
|
|
||||||
RUN apt install build-essential libffi-dev musl-dev openssl python3 python3-pip -y
|
|
||||||
|
|
||||||
RUN mkdir /install
|
|
||||||
WORKDIR /install
|
|
||||||
|
|
||||||
COPY requirements.txt /requirements.txt
|
|
||||||
RUN pip install --prefix="/install" -r /requirements.txt
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get dist-upgrade -y
|
|
||||||
RUN apt install build-essential libffi-dev musl-dev openssl python3 python3-pip -y
|
|
||||||
|
|
||||||
COPY --from=builder /install /usr/local
|
|
||||||
COPY __init__.py /app/walkoff_app_sdk/__init__.py
|
|
||||||
COPY app_base.py /app/walkoff_app_sdk/app_base.py
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Frikkylikeme
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,22 +1,2 @@
|
|||||||
# app_sdk.py
|
## CHANGES
|
||||||
This is the SDK used for apps to behave like they should.
|
In November 2024, we moved this to its own repistory: https://github.com/shuffle/app_sdk
|
||||||
|
|
||||||
## If you want to update apps.. PS: downloads from docker hub do overrides.. :)
|
|
||||||
1. Write your code & check if runtime works
|
|
||||||
2. Build app_base image
|
|
||||||
3. docker rm $(docker ps -aq) # Remove all stopped containers
|
|
||||||
4. Delete the specific app's Docker image (docker rmi frikky/shuffle:...)
|
|
||||||
5. Rebuild the Docker image (click load in GUI?)
|
|
||||||
|
|
||||||
## Cloud updates
|
|
||||||
1. Go to shuffle cloud on GCP
|
|
||||||
2. Go to Cloud Storage
|
|
||||||
3. Find shuffler.appspot.com
|
|
||||||
4. Navigate to generated_apps/baseline
|
|
||||||
5. Update SDK there. This will make all new apps run with the new SDK
|
|
||||||
|
|
||||||
## Cloud app force-updates
|
|
||||||
1. Run the "stitcher.go" program in the public shuffle-shared repository.
|
|
||||||
|
|
||||||
# LICENSE
|
|
||||||
Everything in here is MIT, not AGPLv3 as indicated by the license.
|
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
input_data = """{
|
|
||||||
"test4": $test,
|
|
||||||
"test5": ,
|
|
||||||
"test6": "what"
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
input_data = """{
|
|
||||||
"test0": {{ '' | default: [] }},
|
|
||||||
"test": {{ | default: [] }},
|
|
||||||
"test2": {{ $test.asd | default: [] }},
|
|
||||||
"test3": {{ {"key": "val} | default: [] }},
|
|
||||||
"test4": $test,
|
|
||||||
"test5": ,
|
|
||||||
"test6": "what"
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
liquiddata = "{{ $test.asd | some other stuff {{ $test.xyz | more stuff"
|
|
||||||
pattern = r'\{\{\s*\$[^|}]+\s*\|'
|
|
||||||
|
|
||||||
replaced_data = re.sub(pattern, "{{ '' |", liquiddata)
|
|
||||||
print(replaced_data)
|
|
||||||
|
|
||||||
|
|
||||||
def patternfix_string(liquiddata, patterns, regex_patterns, inputtype="liquid"):
|
|
||||||
if not inputtype or inputtype == "liquid":
|
|
||||||
if "{{" not in liquiddata or "}}" not in liquiddata:
|
|
||||||
return liquiddata
|
|
||||||
elif inputtype == "json":
|
|
||||||
liquiddata = liquiddata.strip()
|
|
||||||
|
|
||||||
# Validating if it looks like json or not
|
|
||||||
if liquiddata[0] == "{" and liquiddata[len(liquiddata)-1] == "}":
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if liquiddata[0] == "[" and liquiddata[len(liquiddata)-1] == "]":
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
return liquiddata
|
|
||||||
|
|
||||||
# If it's already json, don't touch it
|
|
||||||
try:
|
|
||||||
json.loads(liquiddata)
|
|
||||||
return liquiddata
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
print("No replace handler for %s" % inputtype)
|
|
||||||
return liquiddata
|
|
||||||
|
|
||||||
skipkeys = [" "]
|
|
||||||
newoutput = liquiddata[:]
|
|
||||||
for pattern in patterns:
|
|
||||||
keylocations = []
|
|
||||||
parsedvalue = ""
|
|
||||||
record = False
|
|
||||||
index = -1
|
|
||||||
for key in liquiddata:
|
|
||||||
|
|
||||||
# Return instant if possible
|
|
||||||
if inputtype == "json":
|
|
||||||
try:
|
|
||||||
json.loads(newoutput)
|
|
||||||
return newoutput
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
index += 1
|
|
||||||
if not key:
|
|
||||||
if record:
|
|
||||||
keylocations.append(index)
|
|
||||||
parsedvalue += key
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
if key in skipkeys:
|
|
||||||
if record:
|
|
||||||
keylocations.append(index)
|
|
||||||
parsedvalue += key
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
if key == pattern[0] and not record:
|
|
||||||
record = True
|
|
||||||
|
|
||||||
if key not in pattern:
|
|
||||||
keylocations = []
|
|
||||||
parsedvalue = ""
|
|
||||||
record = False
|
|
||||||
|
|
||||||
if record:
|
|
||||||
keylocations.append(index)
|
|
||||||
parsedvalue += key
|
|
||||||
|
|
||||||
if len(parsedvalue) == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
evaluated_value = parsedvalue[:]
|
|
||||||
for skipkey in skipkeys:
|
|
||||||
evaluated_value = "".join(evaluated_value.split(skipkey))
|
|
||||||
|
|
||||||
if evaluated_value == pattern:
|
|
||||||
#print("Found matching: %s (%s)" % (parsedvalue, keylocations))
|
|
||||||
#print("Should replace with: %s" % patterns[pattern])
|
|
||||||
|
|
||||||
newoutput = newoutput.replace(parsedvalue, patterns[pattern], -1)
|
|
||||||
|
|
||||||
# Return instant if possible
|
|
||||||
if inputtype == "json":
|
|
||||||
try:
|
|
||||||
json.loads(newoutput)
|
|
||||||
return newoutput
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
for pattern in regex_patterns:
|
|
||||||
newlines = []
|
|
||||||
for line in newoutput.split("\n"):
|
|
||||||
replaced_line = re.sub(pattern, regex_patterns[pattern], line)
|
|
||||||
newlines.append(replaced_line)
|
|
||||||
|
|
||||||
newoutput = "\n".join(newlines)
|
|
||||||
|
|
||||||
# Return instant if possible
|
|
||||||
if inputtype == "json":
|
|
||||||
try:
|
|
||||||
json.loads(newoutput)
|
|
||||||
return newoutput
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Dont return json properly unless actually json
|
|
||||||
if inputtype == "json":
|
|
||||||
try:
|
|
||||||
json.loads(newoutput)
|
|
||||||
return newoutput
|
|
||||||
except:
|
|
||||||
# Returns original if json fixing didn't work
|
|
||||||
return liquiddata
|
|
||||||
|
|
||||||
return newoutput
|
|
||||||
|
|
||||||
print("Start:\n%s" % input_data)
|
|
||||||
|
|
||||||
try:
|
|
||||||
newinput = patternfix_string(input_data,
|
|
||||||
{
|
|
||||||
"{{|": '{{ "" |',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
#r'\{\{\s*|': "{{ '' |",
|
|
||||||
r'\{\{\s*\$[^|}]+\s*\|': '{{ "" |',
|
|
||||||
}
|
|
||||||
,
|
|
||||||
inputtype="liquid"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print("[ERROR} Failed liquid parsing fix: %s" % e)
|
|
||||||
newinput = input_data
|
|
||||||
|
|
||||||
try:
|
|
||||||
newinput = patternfix_string(newinput,
|
|
||||||
{
|
|
||||||
},
|
|
||||||
{
|
|
||||||
r'\"\s*\:\s*,': '\": "",',
|
|
||||||
r'\"\s*\:\s*\$[^,]+\w*\,': '\": "",',
|
|
||||||
}
|
|
||||||
,
|
|
||||||
inputtype="json"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
json.loads(newinput)
|
|
||||||
print("It's json! Override.")
|
|
||||||
except Exception as e:
|
|
||||||
print("Bad json. DONT use the value at all: %s" % e)
|
|
||||||
except Exception as e:
|
|
||||||
print("[ERROR} Failed json parsing fix: %s" % e)
|
|
||||||
|
|
||||||
print("\nEnd:\n%s" % newinput)
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
### DEFAULT
|
|
||||||
NAME=shuffle-app_sdk
|
|
||||||
VERSION=1.2.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 -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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### UBUNTU
|
|
||||||
NAME=shuffle-app_sdk_ubuntu
|
|
||||||
docker build . -f Dockerfile_ubuntu -t frikky/shuffle:app_sdk_ubuntu -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
docker push frikky/shuffle:app_sdk_ubuntu
|
|
||||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
|
|
||||||
#### Alpine GRPC
|
|
||||||
NAME=shuffle-app_sdk_grpc
|
|
||||||
docker build . -f Dockerfile_alpine_grpc -t frikky/shuffle:app_sdk_grpc -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
docker push frikky/shuffle:app_sdk_grpc
|
|
||||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### KALI ###
|
|
||||||
#NAME=shuffle-app_sdk_kali
|
|
||||||
#docker build . -f Dockerfile_kali -t frikky/shuffle:app_sdk_kali -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
#
|
|
||||||
#docker push frikky/shuffle:app_sdk_kali
|
|
||||||
#docker push ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
#docker push ghcr.io/frikky/$NAME:nightly
|
|
||||||
|
|
||||||
### BLACKARCH ###
|
|
||||||
#NAME=shuffle-app_sdk_blackarch
|
|
||||||
#docker build . -f Dockerfile_blackarch -t frikky/shuffle:app_sdk_blackarch -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
#
|
|
||||||
#docker push frikky/shuffle:app_sdk_blackarch
|
|
||||||
#docker push ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
#docker push ghcr.io/frikky/$NAME:nightly
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
## A test script of the recurse_json function
|
|
||||||
## to validate that it can handle the different types of data
|
|
||||||
## and follow the dot formation format
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
def recurse_json(basejson, parsersplit):
|
|
||||||
match = "#([0-9a-z]+):?-?([0-9a-z]+)?#?"
|
|
||||||
try:
|
|
||||||
outercnt = 0
|
|
||||||
|
|
||||||
# Loops over split values
|
|
||||||
splitcnt = -1
|
|
||||||
for value in parsersplit:
|
|
||||||
splitcnt += 1
|
|
||||||
#if " " in value:
|
|
||||||
# value = value.replace(" ", "_", -1)
|
|
||||||
|
|
||||||
actualitem = re.findall(match, value, re.MULTILINE)
|
|
||||||
# Goes here if loop
|
|
||||||
if value == "#":
|
|
||||||
newvalue = []
|
|
||||||
|
|
||||||
if basejson == None:
|
|
||||||
return "", False
|
|
||||||
|
|
||||||
for innervalue in basejson:
|
|
||||||
# 1. Check the next item (message)
|
|
||||||
# 2. Call this function again
|
|
||||||
|
|
||||||
try:
|
|
||||||
ret, is_loop = recurse_json(innervalue, parsersplit[outercnt+1:])
|
|
||||||
except IndexError:
|
|
||||||
# Only in here if it's the last loop without anything in it?
|
|
||||||
ret, is_loop = recurse_json(innervalue, parsersplit[outercnt:])
|
|
||||||
|
|
||||||
newvalue.append(ret)
|
|
||||||
|
|
||||||
# Magical way of returning which makes app sdk identify
|
|
||||||
# it as multi execution
|
|
||||||
return newvalue, True
|
|
||||||
|
|
||||||
# Checks specific regex like #1-2 for index 1-2 in a loop
|
|
||||||
elif len(actualitem) > 0:
|
|
||||||
|
|
||||||
is_loop = True
|
|
||||||
newvalue = []
|
|
||||||
firstitem = actualitem[0][0]
|
|
||||||
seconditem = actualitem[0][1]
|
|
||||||
if isinstance(firstitem, int):
|
|
||||||
firstitem = str(firstitem)
|
|
||||||
if isinstance(seconditem, int):
|
|
||||||
seconditem = str(seconditem)
|
|
||||||
|
|
||||||
#print("[DEBUG] ACTUAL PARSED: %s" % actualitem)
|
|
||||||
|
|
||||||
# Means it's a single item -> continue
|
|
||||||
if seconditem == "":
|
|
||||||
#print("[INFO] In first - handling %s. Len: %d" % (firstitem, len(basejson)))
|
|
||||||
if str(firstitem).lower() == "max" or str(firstitem).lower() == "last" or str(firstitem).lower() == "end":
|
|
||||||
firstitem = len(basejson)-1
|
|
||||||
elif str(firstitem).lower() == "min" or str(firstitem).lower() == "first":
|
|
||||||
firstitem = 0
|
|
||||||
else:
|
|
||||||
firstitem = int(firstitem)
|
|
||||||
|
|
||||||
#print(f"[DEBUG] Post lower checks with item {firstitem}")
|
|
||||||
tmpitem = basejson[int(firstitem)]
|
|
||||||
try:
|
|
||||||
newvalue, is_loop = recurse_json(tmpitem, parsersplit[outercnt+1:])
|
|
||||||
except IndexError:
|
|
||||||
newvalue, is_loop = (tmpitem, parsersplit[outercnt+1:])
|
|
||||||
else:
|
|
||||||
#print("[INFO] In ELSE - handling %s and %s" % (firstitem, seconditem))
|
|
||||||
if isinstance(firstitem, str):
|
|
||||||
if firstitem.lower() == "max" or firstitem.lower() == "last" or firstitem.lower() == "end":
|
|
||||||
firstitem = len(basejson)-1
|
|
||||||
elif firstitem.lower() == "min" or firstitem.lower() == "first":
|
|
||||||
firstitem = 0
|
|
||||||
else:
|
|
||||||
firstitem = int(firstitem)
|
|
||||||
else:
|
|
||||||
firstitem = int(firstitem)
|
|
||||||
|
|
||||||
if isinstance(seconditem, str):
|
|
||||||
if str(seconditem).lower() == "max" or str(seconditem).lower() == "last" or str(firstitem).lower() == "end":
|
|
||||||
seconditem = len(basejson)-1
|
|
||||||
elif str(seconditem).lower() == "min" or str(seconditem).lower() == "first":
|
|
||||||
seconditem = 0
|
|
||||||
else:
|
|
||||||
seconditem = int(seconditem)
|
|
||||||
else:
|
|
||||||
seconditem = int(seconditem)
|
|
||||||
|
|
||||||
#print(f"[DEBUG] Post lower checks 2: {firstitem} AND {seconditem}")
|
|
||||||
newvalue = []
|
|
||||||
if int(seconditem) > len(basejson):
|
|
||||||
seconditem = len(basejson)
|
|
||||||
|
|
||||||
for i in range(int(firstitem), int(seconditem)+1):
|
|
||||||
# 1. Check the next item (message)
|
|
||||||
# 2. Call this function again
|
|
||||||
|
|
||||||
try:
|
|
||||||
ret, tmp_loop = recurse_json(basejson[i], parsersplit[outercnt+1:])
|
|
||||||
except IndexError:
|
|
||||||
#print("[DEBUG] INDEXERROR (1): ", parsersplit[outercnt])
|
|
||||||
#ret = innervalue
|
|
||||||
ret, tmp_loop = recurse_json(basejson[i], parsersplit[outercnt:])
|
|
||||||
|
|
||||||
newvalue.append(ret)
|
|
||||||
|
|
||||||
return newvalue, is_loop
|
|
||||||
|
|
||||||
else:
|
|
||||||
if len(value) == 0:
|
|
||||||
return basejson, False
|
|
||||||
|
|
||||||
try:
|
|
||||||
if isinstance(basejson, list):
|
|
||||||
#print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (list): %s" % value)
|
|
||||||
return basejson, False
|
|
||||||
elif isinstance(basejson, bool):
|
|
||||||
#print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (bool): %s" % value)
|
|
||||||
return basejson, False
|
|
||||||
elif isinstance(basejson, int):
|
|
||||||
#print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (int): %s" % value)
|
|
||||||
return basejson, False
|
|
||||||
elif isinstance(basejson[value], str):
|
|
||||||
try:
|
|
||||||
if (basejson[value].endswith("}") and basejson[value].endswith("}")) or (basejson[value].startswith("[") and basejson[value].endswith("]")):
|
|
||||||
basejson = json.loads(basejson[value])
|
|
||||||
else:
|
|
||||||
# Should we sanitize here?
|
|
||||||
#print("[DEBUG] VALUE TO SANITIZE FOR KEY '%s'?: %s" % (value, basejson[value]))
|
|
||||||
|
|
||||||
# Check if we are on the last item?
|
|
||||||
if outercnt == len(parsersplit)-1:
|
|
||||||
#print("[DEBUG] LAST KEY")
|
|
||||||
return str(basejson[value]), False
|
|
||||||
else:
|
|
||||||
#print("[DEBUG] NOT LAST KEY")
|
|
||||||
pass
|
|
||||||
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
|
||||||
return str(basejson[value]), False
|
|
||||||
else:
|
|
||||||
basejson = basejson[value]
|
|
||||||
except KeyError as e:
|
|
||||||
print("[WARNING] Running secondary value check with replacement of underscore in %s: %s" % (value, e))
|
|
||||||
if "_" in value:
|
|
||||||
value = value.replace("_", " ", -1)
|
|
||||||
elif " " in value:
|
|
||||||
value = value.replace(" ", "_", -1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if isinstance(basejson, list):
|
|
||||||
#print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (list): %s" % value)
|
|
||||||
return basejson, False
|
|
||||||
elif isinstance(basejson, bool):
|
|
||||||
#print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (bool): %s" % value)
|
|
||||||
return basejson, False
|
|
||||||
elif isinstance(basejson, int):
|
|
||||||
#print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (int): %s" % value)
|
|
||||||
return basejson, False
|
|
||||||
elif isinstance(basejson[value], str):
|
|
||||||
#print(f"[INFO] LOADING STRING '%s' AS JSON" % basejson[value])
|
|
||||||
try:
|
|
||||||
#print("[DEBUG] BASEJSON: %s" % basejson)
|
|
||||||
if (basejson[value].endswith("}") and basejson[value].endswith("}")) or (basejson[value].startswith("[") and basejson[value].endswith("]")):
|
|
||||||
basejson = json.loads(basejson[value])
|
|
||||||
else:
|
|
||||||
|
|
||||||
if outercnt == len(parsersplit)-1:
|
|
||||||
#print("LAST KEY (2)")
|
|
||||||
return str(basejson[value]), False
|
|
||||||
else:
|
|
||||||
#print("NOT LAST KEY (2)")
|
|
||||||
pass
|
|
||||||
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
|
||||||
#print("[DEBUG] RETURNING BECAUSE '%s' IS A NORMAL STRING (1)" % basejson[value])
|
|
||||||
return str(basejson[value]), False
|
|
||||||
else:
|
|
||||||
basejson = basejson[value]
|
|
||||||
except KeyError as e:
|
|
||||||
# Check if previous key was handled or not
|
|
||||||
previouskey = parsersplit[outercnt-1]
|
|
||||||
#print("[DEBUG] PREVIOUS KEY: ", previouskey)
|
|
||||||
|
|
||||||
tmpval = previouskey + "." + value
|
|
||||||
#print("\n\n[WARNING] Running third dot notation fix '%s' on data %s: %s" % (value, basejson, e))
|
|
||||||
if tmpval in basejson:
|
|
||||||
return basejson[tmpval], False
|
|
||||||
|
|
||||||
try:
|
|
||||||
currentsplitcnt = splitcnt
|
|
||||||
|
|
||||||
recursed_value = value
|
|
||||||
handled = False
|
|
||||||
|
|
||||||
#tmpbase = basejson
|
|
||||||
previouskey = value
|
|
||||||
while True:
|
|
||||||
#print("\n\n[DEBUG] CURRENTSPLITCNT: ", currentsplitcnt)
|
|
||||||
newvalue = parsersplit[currentsplitcnt+1]
|
|
||||||
if newvalue == "#" or newvalue == "":
|
|
||||||
break
|
|
||||||
|
|
||||||
recursed_value += "." + newvalue
|
|
||||||
#print("\n\nRECURSED: ", recursed_value)
|
|
||||||
|
|
||||||
found = False
|
|
||||||
for key, value in basejson.items():
|
|
||||||
if recursed_value.lower() in key.lower():
|
|
||||||
found = True
|
|
||||||
|
|
||||||
if found == False:
|
|
||||||
#print("[INFO] DIDN'T FIND similar VALUE: ", recursed_value)
|
|
||||||
|
|
||||||
# Check if we are on the last key or not
|
|
||||||
return "", False
|
|
||||||
#if outercnt == len(parsersplit)-1:
|
|
||||||
# print("[DEBUG] LAST KEY (3)")
|
|
||||||
# break
|
|
||||||
#else:
|
|
||||||
# print("[DEBUG] NOT LAST KEY (3)")
|
|
||||||
# return "", False
|
|
||||||
|
|
||||||
if recursed_value in basejson:
|
|
||||||
#print("[INFO] FOUND RECURSED VALUE: ", recursed_value)
|
|
||||||
basejson = basejson[recursed_value]
|
|
||||||
|
|
||||||
# Whether to dig deeper or not
|
|
||||||
if isinstance(basejson, bool) or isinstance(basejson, int) or isinstance(basejson, str):
|
|
||||||
handled = False
|
|
||||||
else:
|
|
||||||
handled = True
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
currentsplitcnt += 1
|
|
||||||
|
|
||||||
if handled:
|
|
||||||
continue
|
|
||||||
|
|
||||||
break
|
|
||||||
except IndexError as e:
|
|
||||||
print("[DEBUG] INDEXERROR (2):", parsersplit[outercnt])
|
|
||||||
return "", False
|
|
||||||
|
|
||||||
outercnt += 1
|
|
||||||
|
|
||||||
except KeyError as e:
|
|
||||||
print("[INFO] Lower keyerror: %s" % e)
|
|
||||||
return "", False
|
|
||||||
except Exception as e:
|
|
||||||
print("[WARNING] Exception: %s" % e)
|
|
||||||
return "", False
|
|
||||||
|
|
||||||
return basejson, False
|
|
||||||
|
|
||||||
print("[INFO] Starting")
|
|
||||||
|
|
||||||
#input_data = "test"
|
|
||||||
#input_data = "test2.data"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Matchwith
|
|
||||||
basejson = {
|
|
||||||
"test": "hello",
|
|
||||||
"test2": {
|
|
||||||
"test3": "hello2",
|
|
||||||
"test3.data": "hello3",
|
|
||||||
"test4.data.testing": {
|
|
||||||
"value": "hello4"
|
|
||||||
},
|
|
||||||
"test5.data.hello": "wut",
|
|
||||||
},
|
|
||||||
"test3": ["hello", "hello2", "hello3"],
|
|
||||||
"test4": [{
|
|
||||||
"id": "1",
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Inputexamples (ALL should be True)
|
|
||||||
inputs = {
|
|
||||||
#"": "",
|
|
||||||
"badkey": "",
|
|
||||||
"test": "hello",
|
|
||||||
"test2.badkey": "",
|
|
||||||
"test2.test3": "hello2",
|
|
||||||
"test2.test3.data": "hello3",
|
|
||||||
"test2.test4.data.testing": "{'value': 'hello4'}", # FIXME: Doesn't work due to break vs return "", False in last exception
|
|
||||||
"test2.test4.data.testing.value": "hello4", # FIXME: Doesn't work due to break vs return "", False in last exception. Not fixed as we didn't find one of these yet.
|
|
||||||
"test2.test5.data.hello": "wut",
|
|
||||||
"test2.test5.data.badkey": "",
|
|
||||||
"test3.#1": "hello2",
|
|
||||||
"test4.#0.id": "1",
|
|
||||||
"test4.#1.id": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs = []
|
|
||||||
for key, value in inputs.items():
|
|
||||||
parsersplit = key.split(".")
|
|
||||||
ret, is_loop = recurse_json(basejson, parsersplit)
|
|
||||||
print("\n\nOUTPUT RET (%s): %s" % (key, ret))
|
|
||||||
|
|
||||||
outputs.append("[%s]: %s = '%s' vs '%s'" % (str(ret) == str(value), key, ret, value))
|
|
||||||
|
|
||||||
print("\n\n%s" % "\n".join(outputs))
|
|
||||||
|
|
||||||
#input_data = ""
|
|
||||||
#input_data = "badkey"
|
|
||||||
#input_data = "test"
|
|
||||||
#input_data = "test2.data"
|
|
||||||
#input_data = "test2.test3.data"
|
|
||||||
#input_data = "test2.test4.data.testing.value.as"
|
|
||||||
#input_data = "test2.test5.data.hello"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -6,3 +6,4 @@ flask[async]==2.0.2
|
|||||||
waitress==2.1.0
|
waitress==2.1.0
|
||||||
#flask==1.1.2
|
#flask==1.1.2
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ func fixTags(tags []string) []string {
|
|||||||
func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error {
|
func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := client.NewEnvClient()
|
client, err := client.NewEnvClient()
|
||||||
|
defer client.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to create docker client: %s", err)
|
log.Printf("Unable to create docker client: %s", err)
|
||||||
return err
|
return err
|
||||||
@@ -349,7 +350,7 @@ func deleteJob(client *kubernetes.Clientset, jobName, namespace string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildImage(tags []string, dockerfileFolder string) error {
|
func buildImage(tags []string, dockerfileLocation string) error {
|
||||||
|
|
||||||
isKubernetes := false
|
isKubernetes := false
|
||||||
if os.Getenv("IS_KUBERNETES") == "true" {
|
if os.Getenv("IS_KUBERNETES") == "true" {
|
||||||
@@ -369,10 +370,8 @@ func buildImage(tags []string, dockerfileFolder string) error {
|
|||||||
|
|
||||||
log.Printf("[INFO] registry name: %s", registryName)
|
log.Printf("[INFO] registry name: %s", registryName)
|
||||||
|
|
||||||
contextDir := strings.Replace(dockerfileFolder, "Dockerfile", "", -1)
|
contextDir := filepath.Join("/app/", filepath.Dir(dockerfileLocation))
|
||||||
contextDir = "/app/" + contextDir
|
|
||||||
log.Print("contextDir: ", contextDir)
|
log.Print("contextDir: ", contextDir)
|
||||||
dockerFile := "./Dockerfile"
|
|
||||||
|
|
||||||
client, err := getK8sClient()
|
client, err := getK8sClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -407,7 +406,7 @@ func buildImage(tags []string, dockerfileFolder string) error {
|
|||||||
Image: "gcr.io/kaniko-project/executor:latest",
|
Image: "gcr.io/kaniko-project/executor:latest",
|
||||||
Args: []string{
|
Args: []string{
|
||||||
"--verbosity=debug",
|
"--verbosity=debug",
|
||||||
"--dockerfile=" + dockerFile,
|
"--dockerfile=Dockerfile",
|
||||||
"--context=dir://" + contextDir,
|
"--context=dir://" + contextDir,
|
||||||
"--skip-tls-verify",
|
"--skip-tls-verify",
|
||||||
"--destination=" + registryName + "/" + tags[1],
|
"--destination=" + registryName + "/" + tags[1],
|
||||||
@@ -420,9 +419,7 @@ func buildImage(tags []string, dockerfileFolder string) error {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NodeSelector: map[string]string{
|
NodeName: backendNodeName,
|
||||||
"node": backendNodeName,
|
|
||||||
},
|
|
||||||
RestartPolicy: corev1.RestartPolicyNever,
|
RestartPolicy: corev1.RestartPolicyNever,
|
||||||
Volumes: []corev1.Volume{
|
Volumes: []corev1.Volume{
|
||||||
{
|
{
|
||||||
@@ -480,13 +477,14 @@ func buildImage(tags []string, dockerfileFolder string) error {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := client.NewEnvClient()
|
client, err := client.NewEnvClient()
|
||||||
|
defer client.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to create docker client: %s", err)
|
log.Printf("Unable to create docker client: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Docker Tags: %s", tags)
|
log.Printf("[INFO] Docker Tags: %s", tags)
|
||||||
dockerfileSplit := strings.Split(dockerfileFolder, "/")
|
dockerfileSplit := strings.Split(dockerfileLocation, "/")
|
||||||
|
|
||||||
// Create a buffer
|
// Create a buffer
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
@@ -836,14 +834,23 @@ func handleRemoteDownloadApp(resp http.ResponseWriter, ctx context.Context, user
|
|||||||
type tmpapp struct {
|
type tmpapp struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
OpenAPI string `json:"openapi"`
|
OpenAPI string `json:"openapi"`
|
||||||
|
App string `json:"app"`
|
||||||
}
|
}
|
||||||
|
|
||||||
app := tmpapp{}
|
app := tmpapp{}
|
||||||
err := json.Unmarshal(respBody, &app)
|
err := json.Unmarshal(respBody, &app)
|
||||||
if err != nil || app.Success == false || len(app.OpenAPI) == 0 {
|
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)
|
log.Printf("[ERROR] Failed app unmarshal during auto-download. Success: %#v. Applength: %d: %s", app.Success, len(app.OpenAPI), err)
|
||||||
|
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
|
if len(app.App) > 0 {
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Not an OpenAPI app, but a Python app. Please download the app using the Remote Download system: https://shuffler.io/docs/apps#importing-remote-apps"}`)))
|
||||||
|
} else {
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||||
|
}
|
||||||
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module shuffle
|
|||||||
|
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
// replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
||||||
|
|
||||||
toolchain go1.22.2
|
toolchain go1.22.2
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shuffle/shuffle-shared v0.6.50
|
github.com/shuffle/shuffle-shared v0.6.90
|
||||||
golang.org/x/crypto v0.22.0
|
golang.org/x/crypto v0.22.0
|
||||||
google.golang.org/api v0.176.1
|
google.golang.org/api v0.176.1
|
||||||
google.golang.org/grpc v1.63.2
|
google.golang.org/grpc v1.63.2
|
||||||
|
|||||||
@@ -334,8 +334,10 @@ github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdR
|
|||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/shuffle/shuffle-shared v0.6.50 h1:MBeGAiBNkw9Eg+3YTJIlBOuskWntGvT0uefFUYOBhbY=
|
github.com/shuffle/shuffle-shared v0.6.77 h1:KKtM50xW2DLuRHINxhp3uXrNH0AhiwkeiiU93a8fB3A=
|
||||||
github.com/shuffle/shuffle-shared v0.6.50/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus=
|
github.com/shuffle/shuffle-shared v0.6.77/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus=
|
||||||
|
github.com/shuffle/shuffle-shared v0.6.90 h1:FzIYtEt44eWgEsW/9tj2ki7qq8FEm/HWXUok+THp72M=
|
||||||
|
github.com/shuffle/shuffle-shared v0.6.90/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/go-git/go-billy/v5/memfs"
|
"github.com/go-git/go-billy/v5/memfs"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
gitProxy "github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
"github.com/go-git/go-git/v5/storage/memory"
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
|
|
||||||
// Random
|
// Random
|
||||||
@@ -256,7 +257,6 @@ type Hook struct {
|
|||||||
Environment string `json:"environment" datastore:"environment"`
|
Environment string `json:"environment" datastore:"environment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func GetUsersHandler(w http.ResponseWriter, r *http.Request) {
|
func GetUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"id": "12345",
|
"id": "12345",
|
||||||
@@ -396,6 +396,52 @@ func checkUsername(Username string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isGitNoProxy(rawURL string) bool {
|
||||||
|
noProxy := os.Getenv("NO_PROXY")
|
||||||
|
if noProxy == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if noProxy == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
noProxyList := strings.Split(noProxy, ",")
|
||||||
|
parsedURL, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
host := parsedURL.Hostname()
|
||||||
|
|
||||||
|
for _, value := range noProxyList {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
|
||||||
|
if host == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(value, "*.") && strings.HasSuffix(host, value[2:]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkGitProxy(cloneOptions *git.CloneOptions) *git.CloneOptions {
|
||||||
|
if os.Getenv("HTTP_PROXY") != "" && !isGitNoProxy(cloneOptions.URL) {
|
||||||
|
cloneOptions.ProxyOptions = gitProxy.ProxyOptions{
|
||||||
|
URL: os.Getenv("HTTP_PROXY"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("HTTPS_PROXY") != "" && !isGitNoProxy(cloneOptions.URL) {
|
||||||
|
cloneOptions.ProxyOptions = gitProxy.ProxyOptions{
|
||||||
|
URL: os.Getenv("HTTPS_PROXY"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloneOptions
|
||||||
|
}
|
||||||
|
|
||||||
func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) error {
|
func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) error {
|
||||||
// Returns false if there is an issue
|
// Returns false if there is an issue
|
||||||
// Use this for register
|
// Use this for register
|
||||||
@@ -450,6 +496,7 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
|
|||||||
newUser.ActiveOrg = shuffle.OrgMini{
|
newUser.ActiveOrg = shuffle.OrgMini{
|
||||||
Id: org.Id,
|
Id: org.Id,
|
||||||
Name: org.Name,
|
Name: org.Name,
|
||||||
|
Role: newUser.Role,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(apikey) > 0 {
|
if len(apikey) > 0 {
|
||||||
@@ -511,7 +558,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,7 +661,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
Name: newOrg.Name,
|
Name: newOrg.Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
user.ActiveOrg = currentOrg
|
user.ActiveOrg = currentOrg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -884,18 +930,58 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
log.Printf("[DEBUG] Failed to get org during getinfo: %s", err)
|
log.Printf("[DEBUG] Failed to get org during getinfo: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//if err == nil {
|
//if err == nil {
|
||||||
if len(org.Id) > 0 {
|
if len(org.Id) > 0 {
|
||||||
|
if userInfo.Role == "" {
|
||||||
|
//err = shuffle.SetUser(ctx, &userInfo, false)
|
||||||
|
for _, user := range org.Users {
|
||||||
|
if user.Id != userInfo.Id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo.ActiveOrg.Role = user.Role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userInfo.ActiveOrg = shuffle.OrgMini{
|
userInfo.ActiveOrg = shuffle.OrgMini{
|
||||||
Id: org.Id,
|
Id: org.Id,
|
||||||
Name: org.Name,
|
Name: org.Name,
|
||||||
CreatorOrg: org.CreatorOrg,
|
CreatorOrg: org.CreatorOrg,
|
||||||
|
ChildOrgs: org.ChildOrgs,
|
||||||
Role: userInfo.ActiveOrg.Role,
|
Role: userInfo.ActiveOrg.Role,
|
||||||
Image: org.Image,
|
Image: org.Image,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parsedAdmin == "false" {
|
||||||
|
// Validating admin user again just to make sure
|
||||||
|
// This is to avoid issues for the first org ever
|
||||||
|
for _, user := range org.Users {
|
||||||
|
if user.Id != userInfo.Id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Role == "admin" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//}
|
|
||||||
|
orgPriorities := org.Priorities
|
||||||
|
if len(org.Priorities) < 10 {
|
||||||
|
//log.Printf("[WARNING] Should find and add priorities as length is less than 10 for org %s", userInfo.ActiveOrg.Id)
|
||||||
|
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
|
||||||
|
|
||||||
|
// A way to manage them over time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
orgInterests := org.Interests
|
||||||
|
|
||||||
userInfo.ActiveOrg.Users = []shuffle.UserMini{}
|
userInfo.ActiveOrg.Users = []shuffle.UserMini{}
|
||||||
userOrgs := []shuffle.OrgMini{}
|
userOrgs := []shuffle.OrgMini{}
|
||||||
@@ -942,19 +1028,6 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userOrgs = shuffle.SortOrgList(userOrgs)
|
userOrgs = shuffle.SortOrgList(userOrgs)
|
||||||
orgPriorities := org.Priorities
|
|
||||||
if len(org.Priorities) < 10 {
|
|
||||||
//log.Printf("[WARNING] Should find and add priorities as length is less than 10 for org %s", userInfo.ActiveOrg.Id)
|
|
||||||
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
|
|
||||||
|
|
||||||
// A way to manage them over time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tutorialsFinished := []shuffle.Tutorial{}
|
tutorialsFinished := []shuffle.Tutorial{}
|
||||||
for _, tutorial := range userInfo.PersonalInfo.Tutorials {
|
for _, tutorial := range userInfo.PersonalInfo.Tutorials {
|
||||||
@@ -993,8 +1066,9 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
ChatDisabled: chatDisabled,
|
ChatDisabled: chatDisabled,
|
||||||
Tutorials: tutorialsFinished,
|
Tutorials: tutorialsFinished,
|
||||||
|
|
||||||
|
Interests: orgInterests,
|
||||||
Priorities: orgPriorities,
|
Priorities: orgPriorities,
|
||||||
Licensed: licensed,
|
Licensed: licensed,
|
||||||
}
|
}
|
||||||
|
|
||||||
returnData, err := json.Marshal(returnValue)
|
returnData, err := json.Marshal(returnValue)
|
||||||
@@ -1015,7 +1089,6 @@ type passwordReset struct {
|
|||||||
Reference string `json:"reference"`
|
Reference string `json:"reference"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
|
func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := shuffle.HandleCors(resp, request)
|
cors := shuffle.HandleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -1054,9 +1127,9 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No childorg setup, only parent org
|
// No childorg setup, only parent org
|
||||||
if len(org.ManagerOrgs) > 0 || len(org.CreatorOrg) > 0 {
|
// if len(org.ManagerOrgs) > 0 || len(org.CreatorOrg) > 0 {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Should run calculations
|
// Should run calculations
|
||||||
if len(org.SSOConfig.OpenIdAuthorization) > 0 {
|
if len(org.SSOConfig.OpenIdAuthorization) > 0 {
|
||||||
@@ -1978,7 +2051,6 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
|
||||||
if request.Method != "POST" {
|
if request.Method != "POST" {
|
||||||
request.Method = "POST"
|
request.Method = "POST"
|
||||||
}
|
}
|
||||||
@@ -1999,7 +2071,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
location := strings.Split(request.URL.String(), "/")
|
location := strings.Split(request.URL.String(), "/")
|
||||||
|
|
||||||
var pipelineId string
|
var pipelineId string
|
||||||
|
|
||||||
if location[1] == "api" {
|
if location[1] == "api" {
|
||||||
if len(location) <= 4 {
|
if len(location) <= 4 {
|
||||||
log.Printf("[INFO] Couldn't handle location. Too short in pipeline: %d", len(location))
|
log.Printf("[INFO] Couldn't handle location. Too short in pipeline: %d", len(location))
|
||||||
@@ -2013,7 +2085,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
userAgent := request.Header.Get("User-Agent")
|
userAgent := request.Header.Get("User-Agent")
|
||||||
if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") {
|
if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") {
|
||||||
log.Printf("[AUDIT] Blocking googlebot and microsoftbot for pielines. UA: '%s'", userAgent)
|
log.Printf("[AUDIT] Blocking googlebot and microsoftbot for pipelines. UA: '%s'", userAgent)
|
||||||
resp.WriteHeader(400)
|
resp.WriteHeader(400)
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Google/Microsoft preview bots not allowed. Please change the useragent."}`))
|
resp.Write([]byte(`{"success": false, "reason": "Google/Microsoft preview bots not allowed. Please change the useragent."}`))
|
||||||
return
|
return
|
||||||
@@ -2058,11 +2130,27 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedBody := shuffle.GetExecutionbody(body)
|
// Parse concatenated JSON logs
|
||||||
|
jsonList, err := parseConcatenatedJSONLogs(string(body))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[DEBUG] JSON parsing error: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedBody, err := json.Marshal(jsonList)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to marshal jsonList: %s", err)
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
newBody := shuffle.ExecutionStruct{
|
newBody := shuffle.ExecutionStruct{
|
||||||
Start: pipeline.StartNode,
|
Start: pipeline.StartNode,
|
||||||
ExecutionSource: "pipeline",
|
ExecutionSource: "pipeline",
|
||||||
ExecutionArgument: parsedBody,
|
ExecutionArgument: string(parsedBody),
|
||||||
}
|
}
|
||||||
|
|
||||||
workflow, err := shuffle.GetWorkflow(ctx, pipeline.WorkflowId)
|
workflow, err := shuffle.GetWorkflow(ctx, pipeline.WorkflowId)
|
||||||
@@ -2093,8 +2181,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(pipeline.StartNode) == 0 {
|
if len(pipeline.StartNode) == 0 {
|
||||||
log.Printf("[WARNING] No start node for pipeline %s - running with workflow default.", pipeline.TriggerId)
|
log.Printf("[WARNING] No start node for pipeline %s - running with workflow default.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newRequest := &http.Request{
|
newRequest := &http.Request{
|
||||||
@@ -2108,6 +2195,9 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId)))
|
||||||
|
|
||||||
|
// Track Sigma rules
|
||||||
|
trackSigmaRules(ctx, pipeline.OrgId, jsonList)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2115,6 +2205,42 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseConcatenatedJSONLogs(logs string) ([]map[string]interface{}, error) {
|
||||||
|
var jsonList []map[string]interface{}
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(logs))
|
||||||
|
|
||||||
|
for decoder.More() {
|
||||||
|
var jsonObject map[string]interface{}
|
||||||
|
if err := decoder.Decode(&jsonObject); err != nil {
|
||||||
|
log.Printf("[WARNING] JSON decoding error: %s. Skipping this object.", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jsonList = append(jsonList, jsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(&struct{}{}); err != io.EOF {
|
||||||
|
return nil, fmt.Errorf("error after decoding all JSON objects: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trackSigmaRules(ctx context.Context, orgId string, jsonList []map[string]interface{}) {
|
||||||
|
ruleCount := make(map[string]int)
|
||||||
|
for _, logEntry := range jsonList {
|
||||||
|
if rule, ok := logEntry["rule"].(map[string]interface{}); ok {
|
||||||
|
if ruleName, ok := rule["title"].(string); ok {
|
||||||
|
ruleCount[ruleName]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ruleName, count := range ruleCount {
|
||||||
|
shuffle.IncrementCache(ctx, orgId, ruleName, count)
|
||||||
|
log.Printf("[INFO] Rule %s incremented by %d", ruleName, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error {
|
func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error {
|
||||||
data, err := json.Marshal(action)
|
data, err := json.Marshal(action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3208,7 +3334,6 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] Successfully built app %s (%s)", api.Name, api.ID)
|
log.Printf("[DEBUG] Successfully built app %s (%s)", api.Name, api.ID)
|
||||||
if len(user.Id) > 0 {
|
if len(user.Id) > 0 {
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
@@ -3249,8 +3374,6 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) {
|
|||||||
buildSwaggerApp(resp, body, user, false)
|
buildSwaggerApp(resp, body, user, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Hotloads new apps from a folder
|
// Hotloads new apps from a folder
|
||||||
func handleAppHotload(ctx context.Context, location string, forceUpdate bool) error {
|
func handleAppHotload(ctx context.Context, location string, forceUpdate bool) error {
|
||||||
|
|
||||||
@@ -3597,11 +3720,10 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func remoteOrgJobHandler(org shuffle.Org, interval int) error {
|
func remoteOrgJobHandler(org shuffle.Org, interval int) error {
|
||||||
|
|
||||||
// Check if it's 1 in 10 (10% chance random)
|
// Check if it's 1 in 10 (10% chance random)
|
||||||
backupJob := shuffle.BackupJob{}
|
backupJob := shuffle.BackupJob{}
|
||||||
|
|
||||||
// Check if workflow backup is active
|
// Check if workflow backup is active
|
||||||
// Check if app backup is active
|
// Check if app backup is active
|
||||||
@@ -3647,7 +3769,6 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error {
|
|||||||
backupJobData = []byte{}
|
backupJobData = []byte{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
syncUrl := fmt.Sprintf("%s/api/v1/cloud/sync", syncUrl)
|
syncUrl := fmt.Sprintf("%s/api/v1/cloud/sync", syncUrl)
|
||||||
client := shuffle.GetExternalClient(syncUrl)
|
client := shuffle.GetExternalClient(syncUrl)
|
||||||
req, err := http.NewRequest(
|
req, err := http.NewRequest(
|
||||||
@@ -3809,7 +3930,7 @@ func runInitEs(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") {
|
if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") {
|
||||||
log.Printf("[INFO] Waiting during init to make sure the opensearch instance is up and running with security features properly")
|
log.Printf("[INFO] Waiting 30 seconds during init to make sure the opensearch instance is up and running with security features enabled")
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3853,7 +3974,7 @@ func runInitEs(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Add a randomized timer to avoid all schedules running at the same time
|
// FIXME: Add a randomized timer to avoid all schedules running at the same time
|
||||||
// Many are at 5 minutes / 1 hour. The point is to spread these out
|
// Many are at 5 minutes / 1 hour. The point is to spread these out
|
||||||
// a bit instead of all of them starting at the exact same time
|
// a bit instead of all of them starting at the exact same time
|
||||||
|
|
||||||
//log.Printf("Schedule: %#v", schedule)
|
//log.Printf("Schedule: %#v", schedule)
|
||||||
@@ -3888,22 +4009,32 @@ func runInitEs(ctx context.Context) {
|
|||||||
log.Printf("[DEBUG] Creating org for default user %s", username)
|
log.Printf("[DEBUG] Creating org for default user %s", username)
|
||||||
orgId := uuid.NewV4().String()
|
orgId := uuid.NewV4().String()
|
||||||
orgSetupName := "default"
|
orgSetupName := "default"
|
||||||
|
tmpOrg := shuffle.OrgMini{
|
||||||
|
Name: orgSetupName,
|
||||||
|
Id: orgId,
|
||||||
|
}
|
||||||
|
err = createNewUser(username, password, "admin", apikey, tmpOrg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to create default user %s: %s", username, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("[INFO] Successfully created user %s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := shuffle.GetUser(ctx, username)
|
||||||
newOrg := shuffle.Org{
|
newOrg := shuffle.Org{
|
||||||
Name: orgSetupName,
|
Name: orgSetupName,
|
||||||
Id: orgId,
|
Id: orgId,
|
||||||
Org: orgSetupName,
|
Org: orgSetupName,
|
||||||
Users: []shuffle.User{},
|
Users: []shuffle.User{*user},
|
||||||
Roles: []string{"admin", "user"},
|
Roles: []string{"admin", "user"},
|
||||||
CloudSync: false,
|
CloudSync: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = shuffle.SetOrg(ctx, newOrg, newOrg.Id)
|
err = shuffle.SetOrg(ctx, newOrg, newOrg.Id)
|
||||||
setUsers := false
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARNING] Failed setting organization when creating original user: %s", err)
|
log.Printf("[ERROR] Failed setting organization when creating original user: %s", err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[DEBUG] Successfully created the default org with id %s!", orgId)
|
log.Printf("[DEBUG] Successfully created the default org with id %s!", orgId)
|
||||||
setUsers = true
|
|
||||||
|
|
||||||
item := shuffle.Environment{
|
item := shuffle.Environment{
|
||||||
Name: defaultEnv,
|
Name: defaultEnv,
|
||||||
@@ -3918,20 +4049,6 @@ func runInitEs(ctx context.Context) {
|
|||||||
log.Printf("[WARNING] Failed setting up new environment")
|
log.Printf("[WARNING] Failed setting up new environment")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if setUsers {
|
|
||||||
tmpOrg := shuffle.OrgMini{
|
|
||||||
Name: orgSetupName,
|
|
||||||
Id: orgId,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = createNewUser(username, password, "admin", apikey, tmpOrg)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[INFO] Failed to create default user %s: %s", username, err)
|
|
||||||
} else {
|
|
||||||
log.Printf("[INFO] Successfully created user %s", username)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
@@ -4145,6 +4262,8 @@ func runInitEs(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneOptions = checkGitProxy(cloneOptions)
|
||||||
|
|
||||||
branch := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_BRANCH")
|
branch := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_BRANCH")
|
||||||
if len(branch) > 0 && branch != "master" && branch != "main" {
|
if len(branch) > 0 && branch != "master" && branch != "main" {
|
||||||
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
|
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
|
||||||
@@ -4189,6 +4308,9 @@ func runInitEs(ctx context.Context) {
|
|||||||
cloneOptions := &git.CloneOptions{
|
cloneOptions := &git.CloneOptions{
|
||||||
URL: apis,
|
URL: apis,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneOptions = checkGitProxy(cloneOptions)
|
||||||
|
|
||||||
_, err = git.Clone(storer, fs, cloneOptions)
|
_, err = git.Clone(storer, fs, cloneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed loading repo %s into memory: %s", apis, err)
|
log.Printf("[ERROR] Failed loading repo %s into memory: %s", apis, err)
|
||||||
@@ -4205,17 +4327,16 @@ func runInitEs(ctx context.Context) {
|
|||||||
log.Printf("[INFO] Skipping download of extra API samples as %d were found", len(workflowapps))
|
log.Printf("[INFO] Skipping download of extra API samples as %d were found", len(workflowapps))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if os.Getenv("SHUFFLE_HEALTHCHECK_DISABLED") != "true" {
|
if os.Getenv("SHUFFLE_HEALTHCHECK_DISABLED") != "true" {
|
||||||
healthcheckInterval := 30
|
healthcheckInterval := 30
|
||||||
log.Printf("[INFO] Starting healthcheck job every %d minute. Stats available on /api/v1/health/stats. Disable with SHUFFLE_HEALTHCHECK_DISABLED=true", healthcheckInterval)
|
log.Printf("[INFO] Starting healthcheck job every %d minute. Stats available on /api/v1/health/stats. Disable with SHUFFLE_HEALTHCHECK_DISABLED=true", healthcheckInterval)
|
||||||
job := func() {
|
job := func() {
|
||||||
// Prepare a fake http.responsewriter
|
// Prepare a fake http.responsewriter
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
request := http.Request{}
|
request := http.Request{}
|
||||||
// Add the "force=true" query to the fake request
|
// Add the "force=true" query to the fake request
|
||||||
request.URL, err = url.Parse("/api/v1/health/stats?force=true")
|
request.URL, err = url.Parse("/api/v1/health/stats?force=true")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed to parse test url for healthstats: %s", err)
|
log.Printf("[ERROR] Failed to parse test url for healthstats: %s", err)
|
||||||
}
|
}
|
||||||
@@ -4234,7 +4355,6 @@ func runInitEs(ctx context.Context) {
|
|||||||
log.Printf("[INFO] Finished INIT (ES)")
|
log.Printf("[INFO] Finished INIT (ES)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func handleVerifyCloudsync(orgId string) (shuffle.SyncFeatures, error) {
|
func handleVerifyCloudsync(orgId string) (shuffle.SyncFeatures, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
org, err := shuffle.GetOrg(ctx, orgId)
|
org, err := shuffle.GetOrg(ctx, orgId)
|
||||||
@@ -4813,8 +4933,6 @@ func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) {
|
func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := shuffle.HandleCors(resp, request)
|
cors := shuffle.HandleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -4873,8 +4991,6 @@ func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte("OK"))
|
resp.Write([]byte("OK"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func initHandlers() {
|
func initHandlers() {
|
||||||
var err error
|
var err error
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -4906,7 +5022,7 @@ func initHandlers() {
|
|||||||
go runInitEs(ctx)
|
go runInitEs(ctx)
|
||||||
} else {
|
} else {
|
||||||
//go shuffle.runInit(ctx)
|
//go shuffle.runInit(ctx)
|
||||||
log.Printf("[ERROR] Opensearch is the only viable option. Please set SHUFFLE_ELASTIC=true")
|
log.Printf("[ERROR] Opensearch is the only viable option. Please set SHUFFLE_ELASTIC=true")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4921,7 +5037,7 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/users/register", handleRegister).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/register", handleRegister).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/checkusers", checkAdminLogin).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/checkusers", checkAdminLogin).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/users/{userId}/apps", shuffle.HandleGetUserApps).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/apps", shuffle.HandleGetUserApps).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/apps", shuffle.HandleGetUserApps).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS")
|
||||||
@@ -4942,6 +5058,7 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/register", handleRegister).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/register", handleRegister).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/checkusers", checkAdminLogin).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/checkusers", checkAdminLogin).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/me", handleInfo).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/getsettings", shuffle.HandleSettings).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/getsettings", shuffle.HandleSettings).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
r.HandleFunc("/api/v1/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/passwordchange", shuffle.HandlePasswordChange).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/passwordchange", shuffle.HandlePasswordChange).Methods("POST", "OPTIONS")
|
||||||
@@ -4976,7 +5093,8 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/apps/{appId}", shuffle.UpdateWorkflowAppConfig).Methods("PATCH", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/{appId}", shuffle.UpdateWorkflowAppConfig).Methods("PATCH", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/apps/{appId}", shuffle.DeleteWorkflowApp).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/{appId}", shuffle.DeleteWorkflowApp).Methods("DELETE", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/apps/{appId}/config", shuffle.GetWorkflowAppConfig).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/{appId}/config", shuffle.GetWorkflowAppConfig).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "POST", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/apps/{appName}/run_hotload", handleSingleAppHotloadRequest).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/apps/get_existing", LoadSpecificApps).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/get_existing", LoadSpecificApps).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/apps/download_remote", LoadSpecificApps).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/download_remote", LoadSpecificApps).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/apps/validate", validateAppInput).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/validate", validateAppInput).Methods("POST", "OPTIONS")
|
||||||
@@ -5055,7 +5173,7 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS")
|
||||||
//r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS")
|
//r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/pipelines/{key}", handlePipelineCallback).Methods("POST", "GET", "PATCH", "PUT", "DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/pipelines/{key}", handlePipelineCallback).Methods("POST", "GET", "PATCH", "PUT", "DELETE", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS")
|
||||||
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
|
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
|
||||||
@@ -5077,12 +5195,13 @@ func initHandlers() {
|
|||||||
//r.HandleFunc("/api/v1/orgs/", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS")
|
//r.HandleFunc("/api/v1/orgs/", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleGetOrg).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleGetOrg).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleEditOrg).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleEditOrg).Methods("POST", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/orgs/{orgid}/forms", shuffle.HandleGetOrgForms).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/create_sub_org", shuffle.HandleCreateSubOrg).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/create_sub_org", shuffle.HandleCreateSubOrg).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/change", shuffle.HandleChangeUserOrg).Methods("POST", "OPTIONS") // Swaps to the org
|
r.HandleFunc("/api/v1/orgs/{orgId}/change", shuffle.HandleChangeUserOrg).Methods("POST", "OPTIONS") // Swaps to the org
|
||||||
|
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleDeleteOrg).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleDeleteOrg).Methods("DELETE", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/suborgs", shuffle.HandleGetSubOrgs).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/suborgs", shuffle.HandleGetSubOrgs).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
// This is a new API that validates if a key has been seen before.
|
// This is a new API that validates if a key has been seen before.
|
||||||
// Not sure what the best course of action is for it.
|
// Not sure what the best course of action is for it.
|
||||||
r.HandleFunc("/api/v1/environments/{key}/stop", shuffle.HandleStopExecutions).Methods("GET", "POST", "OPTIONS")
|
r.HandleFunc("/api/v1/environments/{key}/stop", shuffle.HandleStopExecutions).Methods("GET", "POST", "OPTIONS")
|
||||||
@@ -5090,11 +5209,13 @@ func initHandlers() {
|
|||||||
|
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleGetCacheKey).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).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}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/delete_cache", shuffle.HandleDeleteCacheKeyPost).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/delete_cache", shuffle.HandleDeleteCacheKeyPost).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleAppendStatistics).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/statistics", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/statistics", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
|
||||||
@@ -5104,7 +5225,6 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/orgs/{orgId}/datastore/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/orgs/{orgId}/datastore/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
|
||||||
// Docker orborus specific - downloads an image
|
// Docker orborus specific - downloads an image
|
||||||
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("GET", "POST", "OPTIONS")
|
r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("GET", "POST", "OPTIONS")
|
||||||
@@ -5123,6 +5243,17 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
|
// This structure is horrendous. Needs fixing after we got the prototype up
|
||||||
|
r.HandleFunc("/api/v1/detections/{detectionType}/connect", shuffle.HandleDetectionAutoConnect).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/detections/{detection_type}", shuffle.HandleGetDetectionRules).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/detections/{triggerId}/selected_rules", shuffle.HandleGetSelectedRules).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/detections/{triggerId}/selected_rules/save", shuffle.HandleSaveSelectedRules).Methods("POST", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/detections/{action}", shuffle.HandleFolderToggle).Methods("PUT", "OPTIONS")
|
||||||
|
|
||||||
|
// This is weird.
|
||||||
|
r.HandleFunc("/api/v1/detections/{fileId}/{action}", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS")
|
||||||
|
//r.HandleFunc("/api/v1/detections/siem/node_health", shuffle.HandleTenzirHealthUpdate).Methods("POST","OPTIONS")
|
||||||
|
|
||||||
// Introduced in 0.9.21 to handle notifications for e.g. failed Workflow
|
// Introduced in 0.9.21 to handle notifications for e.g. failed Workflow
|
||||||
r.HandleFunc("/api/v1/notifications", shuffle.HandleCreateNotification).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/notifications", shuffle.HandleCreateNotification).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/notifications", shuffle.HandleGetNotifications).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/notifications", shuffle.HandleGetNotifications).Methods("GET", "OPTIONS")
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Starting frequency for execution: %d", newfrequency)
|
log.Printf("[INFO] Starting frequency for execution: %d", newfrequency)
|
||||||
|
|
||||||
|
|
||||||
//jobret, err := newscheduler.Every(newfrequency).Seconds().NotImmediately().Run(job)
|
//jobret, err := newscheduler.Every(newfrequency).Seconds().NotImmediately().Run(job)
|
||||||
jobret, err := newscheduler.Every(newfrequency).Seconds().Run(job)
|
jobret, err := newscheduler.Every(newfrequency).Seconds().Run(job)
|
||||||
@@ -292,29 +291,44 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
ctx := shuffle.GetContext(request)
|
ctx := shuffle.GetContext(request)
|
||||||
env, err := shuffle.GetEnvironment(ctx, orgId, "")
|
env, err := shuffle.GetEnvironment(ctx, orgId, "")
|
||||||
timeNow := time.Now().Unix()
|
timeNow := time.Now().Unix()
|
||||||
if err == nil && len(env.Id) > 0 && len(env.Name) > 0 {
|
if err == nil && len(env.Id) > 0 && len(env.Name) > 0 && request.Method == "POST" {
|
||||||
// Updates every 60 seconds~
|
// Updates every 60 seconds~
|
||||||
if time.Now().Unix() > env.Edited+60 {
|
if time.Now().Unix() > env.Edited+60 {
|
||||||
env.RunningIp = shuffle.GetRequestIp(request)
|
env.RunningIp = shuffle.GetRequestIp(request)
|
||||||
|
|
||||||
|
// Orborus label = custom label for Orborus
|
||||||
if len(orborusLabel) > 0 {
|
if len(orborusLabel) > 0 {
|
||||||
env.RunningIp = orborusLabel
|
env.RunningIp = orborusLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.Method == "POST" {
|
// Set the checkin cache
|
||||||
body, err := ioutil.ReadAll(request.Body)
|
|
||||||
if err == nil {
|
|
||||||
var envData shuffle.OrborusStats
|
|
||||||
err = json.Unmarshal(body, &envData)
|
|
||||||
if err == nil {
|
|
||||||
if envData.Swarm {
|
|
||||||
env.Licensed = true
|
|
||||||
env.RunType = "docker"
|
|
||||||
}
|
|
||||||
|
|
||||||
if envData.Kubernetes {
|
|
||||||
env.RunType = "k8s"
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
}
|
if err == nil {
|
||||||
|
var envData shuffle.OrborusStats
|
||||||
|
err = json.Unmarshal(body, &envData)
|
||||||
|
if err == nil {
|
||||||
|
envData.RunningIp = env.RunningIp
|
||||||
|
|
||||||
|
marshalled, err := json.Marshal(envData)
|
||||||
|
if err == nil {
|
||||||
|
cacheKey := fmt.Sprintf("queueconfig-%s-%s", env.Name, env.OrgId)
|
||||||
|
go shuffle.SetCache(context.Background(), cacheKey, marshalled, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if envData.Swarm {
|
||||||
|
env.Licensed = true
|
||||||
|
env.RunType = "docker"
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.Kubernetes {
|
||||||
|
env.RunType = "k8s"
|
||||||
|
}
|
||||||
|
|
||||||
|
envData.DataLake = env.DataLake
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,14 +586,20 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
|
|||||||
//return
|
//return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(actionResult.ExecutionId) == 0 {
|
||||||
|
resp.WriteHeader(400)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Provide execution_id and authorization"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
|
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
|
||||||
if err != nil {
|
if err != nil || workflowExecution.ExecutionId != actionResult.ExecutionId {
|
||||||
if len(actionResult.ExecutionId) > 0 {
|
if len(actionResult.ExecutionId) > 0 {
|
||||||
log.Printf("[WARNING][%s] Failed getting execution (streamresult): %s", actionResult.ExecutionId, err)
|
log.Printf("[WARNING][%s] Failed getting execution (streamresult): %s", actionResult.ExecutionId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(400)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -638,9 +658,27 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if workflowExecution.Workflow.Sharing == "form" {
|
||||||
|
newWorkflow := shuffle.Workflow{
|
||||||
|
Name: workflowExecution.Workflow.Name,
|
||||||
|
ID: workflowExecution.Workflow.ID,
|
||||||
|
Owner: workflowExecution.Workflow.Owner,
|
||||||
|
OrgId: workflowExecution.Workflow.OrgId,
|
||||||
|
|
||||||
|
Sharing: workflowExecution.Workflow.Sharing,
|
||||||
|
Description: workflowExecution.Workflow.Description,
|
||||||
|
InputQuestions: workflowExecution.Workflow.InputQuestions,
|
||||||
|
|
||||||
|
FormControl: workflowExecution.Workflow.FormControl,
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowExecution.Results = []shuffle.ActionResult{}
|
||||||
|
workflowExecution.Workflow = newWorkflow
|
||||||
|
}
|
||||||
|
|
||||||
newjson, err := json.Marshal(workflowExecution)
|
newjson, err := json.Marshal(workflowExecution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(500)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -670,7 +708,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("Actionresult unmarshal: %s", string(body))
|
//log.Printf("Actionresult unmarshal: %s", string(body))
|
||||||
log.Printf("[DEBUG] Got workflow result from %s of length %d", request.RemoteAddr, len(body))
|
//log.Printf("[DEBUG] Got workflow result from %s of length %d", request.RemoteAddr, len(body))
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
err = shuffle.ValidateNewWorkerExecution(ctx, body)
|
err = shuffle.ValidateNewWorkerExecution(ctx, body)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -681,7 +719,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
log.Printf("[DEBUG] Handling other execution variant (subflow?): %s", err)
|
log.Printf("[DEBUG] Handling other execution variant (subflow?): %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
|
//log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
|
||||||
|
|
||||||
var actionResult shuffle.ActionResult
|
var actionResult shuffle.ActionResult
|
||||||
err = json.Unmarshal(body, &actionResult)
|
err = json.Unmarshal(body, &actionResult)
|
||||||
@@ -739,8 +777,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
// Will make sure transactions are always ran for an execution. This is recursive if it fails. Allowed to fail up to 5 times
|
// Will make sure transactions are always ran for an execution. This is recursive if it fails. Allowed to fail up to 5 times
|
||||||
func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workflowExecutionId string, actionResult shuffle.ActionResult, resp http.ResponseWriter) {
|
func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workflowExecutionId string, actionResult shuffle.ActionResult, resp http.ResponseWriter) {
|
||||||
log.Printf("[DEBUG][%s] Running workflow execution update", workflowExecutionId)
|
log.Printf("[DEBUG][%s] Running workflow execution update with result from %s (%s) of status %s", workflowExecutionId, actionResult.Action.Label, actionResult.Action.ID, actionResult.Status)
|
||||||
|
|
||||||
|
|
||||||
// Should start a tx for the execution here
|
// Should start a tx for the execution here
|
||||||
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, workflowExecutionId)
|
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, workflowExecutionId)
|
||||||
@@ -771,7 +808,6 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl
|
|||||||
setExecution := true
|
setExecution := true
|
||||||
if setExecution || workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" {
|
if setExecution || workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" {
|
||||||
err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
|
err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
|
||||||
//err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, dbSave)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult: %s"}`, err)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult: %s"}`, err)))
|
||||||
@@ -925,7 +961,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
if len(workflow.ParentWorkflowId) > 0 {
|
if len(workflow.ParentWorkflowId) > 0 {
|
||||||
resp.WriteHeader(403)
|
resp.WriteHeader(403)
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Can't delete a workflow distributed from your parent org"}`))
|
resp.Write([]byte(`{"success": false, "reason": "Can't delete a workflow distributed from your parent org"}`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
||||||
@@ -939,6 +975,27 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look for Child workflows and delete them
|
||||||
|
if workflow.ParentWorkflowId == "" {
|
||||||
|
log.Printf("[DEBUG] Looking for child workflows for workflow %s to delete. User %s (%s) in org %s (%s)", workflow.ID, user.Username, user.Id, user.ActiveOrg.Name, user.ActiveOrg.Id)
|
||||||
|
|
||||||
|
childWorkflows, err := shuffle.ListChildWorkflows(ctx, workflow.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to list child workflows: %s", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("\n\n[DEBUG] Found %d child workflows for workflow %s\n\n", len(childWorkflows), workflow.ID)
|
||||||
|
|
||||||
|
// Find cookies and append them to request.Header to replicate current request as closely as possible
|
||||||
|
for _, childWorkflow := range childWorkflows {
|
||||||
|
if childWorkflow.ID == workflow.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go shuffle.SendDeleteWorkflowRequest(childWorkflow, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up triggers and executions
|
// Clean up triggers and executions
|
||||||
for _, item := range workflow.Triggers {
|
for _, item := range workflow.Triggers {
|
||||||
if item.TriggerType == "SCHEDULE" && item.Status != "uninitialized" {
|
if item.TriggerType == "SCHEDULE" && item.Status != "uninitialized" {
|
||||||
@@ -984,8 +1041,6 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(`{"success": true}`))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func handleExecution(id string, workflow shuffle.Workflow, request *http.Request, orgId string) (shuffle.WorkflowExecution, string, error) {
|
func handleExecution(id string, workflow shuffle.Workflow, request *http.Request, orgId string) (shuffle.WorkflowExecution, string, error) {
|
||||||
//go func() {
|
//go func() {
|
||||||
// log.Printf("\n\nPRE TIME: %s\n\n", time.Now().Format("2006-01-02 15:04:05"))
|
// log.Printf("\n\nPRE TIME: %s\n\n", time.Now().Format("2006-01-02 15:04:05"))
|
||||||
@@ -1004,17 +1059,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
|||||||
workflow = *tmpworkflow
|
workflow = *tmpworkflow
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
if len(workflow.ExecutingOrg.Id) == 0 {
|
|
||||||
if len(orgId) > 0 {
|
|
||||||
workflow.ExecutingOrg.Id = orgId
|
|
||||||
} else {
|
|
||||||
log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID)
|
|
||||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if len(workflow.Actions) == 0 {
|
if len(workflow.Actions) == 0 {
|
||||||
workflow.Actions = []shuffle.Action{}
|
workflow.Actions = []shuffle.Action{}
|
||||||
} else {
|
} else {
|
||||||
@@ -1065,28 +1109,31 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
|||||||
|
|
||||||
workflowExecution, execInfo, _, workflowExecErr := shuffle.PrepareWorkflowExecution(ctx, workflow, request, int64(maxExecutionDepth))
|
workflowExecution, execInfo, _, workflowExecErr := shuffle.PrepareWorkflowExecution(ctx, workflow, request, int64(maxExecutionDepth))
|
||||||
if workflowExecErr != nil {
|
if workflowExecErr != nil {
|
||||||
err := shuffle.SetWorkflowExecution(ctx, workflowExecution, true)
|
if len(workflowExecution.Workflow.Actions) > 0 && len(workflowExecution.Results) > 0 && len(workflowExecution.ExecutionId) > 0 {
|
||||||
if err != nil {
|
err := shuffle.SetWorkflowExecution(ctx, workflowExecution, true)
|
||||||
log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err)
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(fmt.Sprintf("%s", workflowExecErr), "User Input") {
|
if strings.Contains(fmt.Sprintf("%s", workflowExecErr), "User Input") {
|
||||||
// Special for user input callbacks
|
// Special for user input callbacks
|
||||||
log.Printf("[INFO] User input callback: %s", workflowExecErr)
|
|
||||||
// return workflowExecution, fmt.Sprintf("%s", err), nil
|
// return workflowExecution, fmt.Sprintf("%s", err), nil
|
||||||
|
//log.Printf("[INFO] User input callback: %s", workflowExecErr)
|
||||||
|
return shuffle.WorkflowExecution{}, "", nil
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[ERROR] Failed in prepareExecution: '%s'", err)
|
log.Printf("[ERROR] Failed in prepareExecution: '%s'", workflowExecErr)
|
||||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed running: %s", err), err
|
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed running: %s", workflowExecErr), workflowExecErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
err := imageCheckBuilder(execInfo.ImageNames)
|
err := imageCheckBuilder(execInfo.ImageNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed building the required images from %#v: %s", execInfo.ImageNames, err)
|
log.Printf("[ERROR] Failed building the required images from %#v: %s", execInfo.ImageNames, err)
|
||||||
return shuffle.WorkflowExecution{}, "Failed unmarshal during execution", err
|
return shuffle.WorkflowExecution{}, "Failed unmarshal during execution", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
makeNew := true
|
makeNew := true
|
||||||
start, startok := request.URL.Query()["start"]
|
start, startok := request.URL.Query()["start"]
|
||||||
if request.Method == "POST" {
|
if request.Method == "POST" {
|
||||||
@@ -1206,7 +1253,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
|||||||
|
|
||||||
answer, answerok := request.URL.Query()["answer"]
|
answer, answerok := request.URL.Query()["answer"]
|
||||||
referenceId, referenceok := request.URL.Query()["reference_execution"]
|
referenceId, referenceok := request.URL.Query()["reference_execution"]
|
||||||
if answerok && referenceok {
|
if answerok && referenceok && len(answer) > 0 && len(referenceId) > 0 {
|
||||||
// If answer is false, reference execution with result
|
// If answer is false, reference execution with result
|
||||||
log.Printf("[INFO] Answer is OK AND reference is OK!")
|
log.Printf("[INFO] Answer is OK AND reference is OK!")
|
||||||
if answer[0] == "false" {
|
if answer[0] == "false" {
|
||||||
@@ -1230,7 +1277,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
|||||||
log.Printf("%s - %s", result.Action.ID, start[0])
|
log.Printf("%s - %s", result.Action.ID, start[0])
|
||||||
if result.Action.ID == start[0] {
|
if result.Action.ID == start[0] {
|
||||||
note, noteok := request.URL.Query()["note"]
|
note, noteok := request.URL.Query()["note"]
|
||||||
if noteok {
|
if noteok && len(note) > 0 {
|
||||||
result.Result = fmt.Sprintf("User note: %s", note[0])
|
result.Result = fmt.Sprintf("User note: %s", note[0])
|
||||||
} else {
|
} else {
|
||||||
result.Result = fmt.Sprintf("User clicked %s", answer[0])
|
result.Result = fmt.Sprintf("User clicked %s", answer[0])
|
||||||
@@ -1354,7 +1401,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
childNodes := shuffle.FindChildNodes(workflowExecution, workflowExecution.Start, []string{}, []string{})
|
childNodes := shuffle.FindChildNodes(workflowExecution.Workflow, workflowExecution.Start, []string{}, []string{})
|
||||||
|
|
||||||
startFound := false
|
startFound := false
|
||||||
newActions := []shuffle.Action{}
|
newActions := []shuffle.Action{}
|
||||||
@@ -1560,7 +1607,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
|||||||
// newTriggers = append(newTriggers, trigger)
|
// newTriggers = append(newTriggers, trigger)
|
||||||
//}
|
//}
|
||||||
//workflowExecution.Workflow.Triggers = newTriggers
|
//workflowExecution.Workflow.Triggers = newTriggers
|
||||||
_ = removeTriggers
|
|
||||||
|
|
||||||
if !startFound {
|
if !startFound {
|
||||||
if len(workflowExecution.Start) == 0 && len(workflowExecution.Workflow.Start) > 0 {
|
if len(workflowExecution.Start) == 0 && len(workflowExecution.Workflow.Start) > 0 {
|
||||||
@@ -1585,12 +1631,17 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
|||||||
if len(workflowExecution.ExecutionOrg) == 0 && len(workflow.ExecutingOrg.Id) > 0 {
|
if len(workflowExecution.ExecutionOrg) == 0 && len(workflow.ExecutingOrg.Id) > 0 {
|
||||||
workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id
|
workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//workflowExecution, execInfo, _, workflowExecErr := shuffle.PrepareWorkflowExecution(ctx, workflow, request, int64(maxExecutionDepth))
|
||||||
err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true)
|
err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err)
|
log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onpremExecution := execInfo.OnpremExecution
|
||||||
|
_ = onpremExecution
|
||||||
|
environments := execInfo.Environments
|
||||||
var allEnvs []shuffle.Environment
|
var allEnvs []shuffle.Environment
|
||||||
if len(workflowExecution.ExecutionOrg) > 0 {
|
if len(workflowExecution.ExecutionOrg) > 0 {
|
||||||
//log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg)
|
//log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg)
|
||||||
@@ -1854,7 +1905,6 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Inside execute workflow for ID %s", fileId)
|
log.Printf("[INFO] Inside execute workflow for ID %s", fileId)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
workflow, err := shuffle.GetWorkflow(ctx, fileId)
|
workflow, err := shuffle.GetWorkflow(ctx, fileId)
|
||||||
if err != nil && workflow.ID == "" {
|
if err != nil && workflow.ID == "" {
|
||||||
@@ -2408,7 +2458,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
workflow.Schedules = append(workflow.Schedules, schedule)
|
//workflow.Schedules = append(workflow.Schedules, schedule)
|
||||||
err = shuffle.SetWorkflow(ctx, *workflow, workflow.ID)
|
err = shuffle.SetWorkflow(ctx, *workflow, workflow.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed setting workflow for schedule: %s", err)
|
log.Printf("Failed setting workflow for schedule: %s", err)
|
||||||
@@ -2665,6 +2715,8 @@ func loadGithubWorkflows(url, username, password, userId, branch, orgId string)
|
|||||||
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
|
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneOptions = checkGitProxy(cloneOptions)
|
||||||
|
|
||||||
storer := memory.NewStorage()
|
storer := memory.NewStorage()
|
||||||
r, err := git.Clone(storer, fs, cloneOptions)
|
r, err := git.Clone(storer, fs, cloneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2766,6 +2818,70 @@ func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleSingleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
cors := shuffle.HandleCors(resp, request)
|
||||||
|
if cors {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
cacheKey := fmt.Sprintf("workflowapps-sorted-1000")
|
||||||
|
shuffle.DeleteCache(ctx, cacheKey)
|
||||||
|
cacheKey = fmt.Sprintf("workflowapps-sorted-500")
|
||||||
|
shuffle.DeleteCache(ctx, cacheKey)
|
||||||
|
cacheKey = fmt.Sprintf("workflowapps-sorted-0")
|
||||||
|
shuffle.DeleteCache(ctx, cacheKey)
|
||||||
|
// Just need to be logged in
|
||||||
|
// FIXME - should have some permissions?
|
||||||
|
user, err := shuffle.HandleApiAuthentication(resp, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Api authentication failed in app hotload: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user.Role != "admin" {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Must be admin to hotload apps"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
location := os.Getenv("SHUFFLE_APP_HOTLOAD_FOLDER")
|
||||||
|
if len(location) == 0 {
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "SHUFFLE_APP_HOTLOAD_FOLDER not specified in .env"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requestUrlFields := strings.Split(request.URL.String(), "/")
|
||||||
|
var appName string
|
||||||
|
if requestUrlFields[1] == "api" {
|
||||||
|
if len(requestUrlFields) <= 4 {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appName = requestUrlFields[4]
|
||||||
|
if strings.Contains(appName, "?") {
|
||||||
|
appName = strings.Split(appName, "?")[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
location = location + "/" + appName
|
||||||
|
log.Printf("[INFO] Starting hotloading from %s", location)
|
||||||
|
err = handleAppHotload(ctx, location, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[WARNING] Failed app hotload: %s", err)
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
|
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)
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
|
}
|
||||||
|
|
||||||
func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) {
|
func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := shuffle.HandleCors(resp, request)
|
cors := shuffle.HandleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -3374,7 +3490,15 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
workflowExecution, err := shuffle.PrepareSingleAction(ctx, user, fileId, body)
|
|
||||||
|
runValidationAction := false
|
||||||
|
query := request.URL.Query()
|
||||||
|
validation, ok := query["validation"]
|
||||||
|
if ok && validation[0] == "true" {
|
||||||
|
runValidationAction = true
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowExecution, err := shuffle.PrepareSingleAction(ctx, user, fileId, body, runValidationAction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] Failed workflowrequest POST read: %s", err)
|
log.Printf("[INFO] Failed workflowrequest POST read: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -3410,7 +3534,7 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
|
|||||||
// FIXME: Should use environment that is in the source workflow if it exists
|
// FIXME: Should use environment that is in the source workflow if it exists
|
||||||
for i, _ := range workflowExecution.Workflow.Actions {
|
for i, _ := range workflowExecution.Workflow.Actions {
|
||||||
workflowExecution.Workflow.Actions[i].Environment = environment
|
workflowExecution.Workflow.Actions[i].Environment = environment
|
||||||
workflowExecution.Workflow.Actions[i].Label = "TMP"
|
workflowExecution.Workflow.Actions[i].Label = "TMP"
|
||||||
}
|
}
|
||||||
shuffle.SetWorkflowExecution(ctx, workflowExecution, false)
|
shuffle.SetWorkflowExecution(ctx, workflowExecution, false)
|
||||||
|
|
||||||
@@ -3940,6 +4064,8 @@ func LoadSpecificApps(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneOptions = checkGitProxy(cloneOptions)
|
||||||
|
|
||||||
storer := memory.NewStorage()
|
storer := memory.NewStorage()
|
||||||
r, err := git.Clone(storer, fs, cloneOptions)
|
r, err := git.Clone(storer, fs, cloneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4193,7 +4319,6 @@ func checkUnfinishedExecution(resp http.ResponseWriter, request *http.Request) {
|
|||||||
log.Printf("[ERROR] Failed adding execution to db: %s", err)
|
log.Printf("[ERROR] Failed adding execution to db: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Reran workflow in %s"}`, parsedEnv)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Reran workflow in %s"}`, parsedEnv)))
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
version: '3'
|
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
image: ghcr.io/shuffle/shuffle-frontend:latest
|
image: ghcr.io/shuffle/shuffle-frontend:nightly
|
||||||
container_name: shuffle-frontend
|
container_name: shuffle-frontend
|
||||||
hostname: shuffle-frontend
|
hostname: shuffle-frontend
|
||||||
ports:
|
ports:
|
||||||
@@ -15,7 +14,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
backend:
|
backend:
|
||||||
image: ghcr.io/shuffle/shuffle-backend:latest
|
image: ghcr.io/shuffle/shuffle-backend:nightly
|
||||||
container_name: shuffle-backend
|
container_name: shuffle-backend
|
||||||
hostname: ${BACKEND_HOSTNAME}
|
hostname: ${BACKEND_HOSTNAME}
|
||||||
# Here for debugging:
|
# Here for debugging:
|
||||||
@@ -34,7 +33,7 @@ services:
|
|||||||
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
orborus:
|
orborus:
|
||||||
image: ghcr.io/shuffle/shuffle-orborus:latest
|
image: ghcr.io/shuffle/shuffle-orborus:nightly
|
||||||
container_name: shuffle-orborus
|
container_name: shuffle-orborus
|
||||||
hostname: shuffle-orborus
|
hostname: shuffle-orborus
|
||||||
networks:
|
networks:
|
||||||
@@ -45,12 +44,10 @@ services:
|
|||||||
- SHUFFLE_APP_SDK_TIMEOUT=300
|
- SHUFFLE_APP_SDK_TIMEOUT=300
|
||||||
- SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY=7 # The amount of concurrent executions Orborus can handle.
|
- SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY=7 # The amount of concurrent executions Orborus can handle.
|
||||||
#- DOCKER_HOST=tcp://docker-socket-proxy:2375
|
#- DOCKER_HOST=tcp://docker-socket-proxy:2375
|
||||||
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
|
- ENVIRONMENT_NAME=Shuffle
|
||||||
|
- ORG_ID=Shuffle
|
||||||
- BASE_URL=http://${OUTER_HOSTNAME}:5001
|
- BASE_URL=http://${OUTER_HOSTNAME}:5001
|
||||||
- DOCKER_API_VERSION=1.40
|
- DOCKER_API_VERSION=1.40
|
||||||
- SHUFFLE_BASE_IMAGE_NAME=${SHUFFLE_BASE_IMAGE_NAME}
|
|
||||||
- SHUFFLE_BASE_IMAGE_REGISTRY=${SHUFFLE_BASE_IMAGE_REGISTRY}
|
|
||||||
- SHUFFLE_BASE_IMAGE_TAG_SUFFIX=${SHUFFLE_BASE_IMAGE_TAG_SUFFIX}
|
|
||||||
- HTTP_PROXY=${HTTP_PROXY}
|
- HTTP_PROXY=${HTTP_PROXY}
|
||||||
- HTTPS_PROXY=${HTTPS_PROXY}
|
- HTTPS_PROXY=${HTTPS_PROXY}
|
||||||
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
||||||
@@ -58,7 +55,8 @@ services:
|
|||||||
- SHUFFLE_STATS_DISABLED=true
|
- SHUFFLE_STATS_DISABLED=true
|
||||||
- SHUFFLE_SWARM_CONFIG=run
|
- SHUFFLE_SWARM_CONFIG=run
|
||||||
- SHUFFLE_LOGS_DISABLED=true
|
- SHUFFLE_LOGS_DISABLED=true
|
||||||
- SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:latest
|
- SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:nightly
|
||||||
|
env_file: .env
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
security_opt:
|
security_opt:
|
||||||
- seccomp:unconfined
|
- seccomp:unconfined
|
||||||
@@ -66,7 +64,6 @@ services:
|
|||||||
image: opensearchproject/opensearch:2.14.0
|
image: opensearchproject/opensearch:2.14.0
|
||||||
hostname: shuffle-opensearch
|
hostname: shuffle-opensearch
|
||||||
container_name: shuffle-opensearch
|
container_name: shuffle-opensearch
|
||||||
env_file: .env
|
|
||||||
environment:
|
environment:
|
||||||
- "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
|
- "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
|
||||||
- bootstrap.memory_lock=true
|
- bootstrap.memory_lock=true
|
||||||
@@ -86,7 +83,7 @@ services:
|
|||||||
soft: 65536
|
soft: 65536
|
||||||
hard: 65536
|
hard: 65536
|
||||||
volumes:
|
volumes:
|
||||||
- ${DB_LOCATION}:/usr/share/opensearch/data:z
|
- shuffle-database:/usr/share/opensearch/data:z
|
||||||
ports:
|
ports:
|
||||||
- 9200:9200
|
- 9200:9200
|
||||||
networks:
|
networks:
|
||||||
@@ -132,13 +129,18 @@ services:
|
|||||||
# networks:
|
# networks:
|
||||||
# - shuffle
|
# - shuffle
|
||||||
#
|
#
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
shuffle-database:
|
||||||
|
driver: local
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
device: ${DB_LOCATION}
|
||||||
|
o: bind
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
shuffle:
|
shuffle:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
# uncomment to set MTU for swarm mode.
|
|
||||||
# MTU should be whatever is your host's preferred MTU is.
|
|
||||||
# Refer to this doc to figure out what your host's MTU is:
|
|
||||||
# https://shuffler.io/docs/troubleshooting#TLS_timeout_error/Timeout_Errors/EOF_Errors
|
|
||||||
# driver_opts:
|
# driver_opts:
|
||||||
# com.docker.network.driver.mtu: 1460
|
# com.docker.network.driver.mtu: 1460
|
||||||
|
# uncomment to set MTU for swarm mode. MTU should be whatever is your host's preferred MTU is: https://shuffler.io/docs/troubleshooting#TLS_timeout_error/Timeout_Errors/EOF_Errors
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Build environment
|
# Build environment
|
||||||
FROM node:21 as builder
|
FROM node:21 as builder
|
||||||
|
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
|
|
||||||
RUN mkdir /usr/src/app
|
RUN mkdir /usr/src/app
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||||
@@ -11,7 +13,7 @@ COPY package.json /usr/src/app/package.json
|
|||||||
#RUN yarn config set "strict-ssl" false -g
|
#RUN yarn config set "strict-ssl" false -g
|
||||||
#RUN yarn install --network-timeout 1000000
|
#RUN yarn install --network-timeout 1000000
|
||||||
|
|
||||||
RUN npm install --legacy-peer-deps
|
RUN npm install --timeout=60000 --legacy-peer-deps
|
||||||
|
|
||||||
# copy only required files to not trigger rebuilding every time
|
# copy only required files to not trigger rebuilding every time
|
||||||
COPY ./certs /usr/src/app/certs/
|
COPY ./certs /usr/src/app/certs/
|
||||||
@@ -25,28 +27,33 @@ COPY ./*.json /usr/src/app/
|
|||||||
RUN npm run build --loglevel verbose 2>&1
|
RUN npm run build --loglevel verbose 2>&1
|
||||||
|
|
||||||
# Production environment
|
# Production environment
|
||||||
FROM nginx:1.21.5
|
FROM nginx:1.26.0
|
||||||
|
|
||||||
RUN mkdir -p /usr/share/nginx/html/build
|
RUN mkdir -p /usr/share/nginx/html/build
|
||||||
RUN mkdir -p /usr/share/nginx/html/css
|
RUN mkdir -p /usr/share/nginx/html/css
|
||||||
RUN mkdir -p /usr/share/nginx/html/js
|
RUN mkdir -p /usr/share/nginx/html/js
|
||||||
RUN mkdir -p /usr/share/nginx/html/img
|
RUN mkdir -p /usr/share/nginx/html/img
|
||||||
|
|
||||||
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
|
|
||||||
|
|
||||||
#Localhost certificate challenge: Y#XwrJ#DoZGz2w6x
|
# Localhost certificate challenge: Y#XwrJ#DoZGz2w6x
|
||||||
|
# Cert challenge doesn't matter to be here or not, as ALL production setups should be using their own certificates + reverse proxy: https://shuffler.io/docs/configuration#using-the-nginx-reverse-proxy-for-tls/ssl
|
||||||
|
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
|
||||||
COPY --from=builder /usr/src/app/certs/fullchain.pem /etc/nginx/fullchain.cert.pem
|
COPY --from=builder /usr/src/app/certs/fullchain.pem /etc/nginx/fullchain.cert.pem
|
||||||
COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem
|
COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem
|
||||||
|
|
||||||
# install CONFD
|
# install CONFD
|
||||||
ENV CONFD_VERSION 0.16.0
|
|
||||||
RUN apt-get update && apt-get install -y curl && apt-get clean
|
RUN apt-get update && apt-get install -y curl && apt-get clean
|
||||||
RUN curl -sSL https://github.com/kelseyhightower/confd/releases/download/v${CONFD_VERSION}/confd-${CONFD_VERSION}-linux-amd64 -o /usr/local/bin/confd && \
|
COPY ./confd/templates/nginx.conf /etc/nginx/nginx.conf.tmpl
|
||||||
chmod +x /usr/local/bin/confd
|
|
||||||
COPY ./confd /etc/confd
|
|
||||||
|
|
||||||
|
## OLD CONFD THINGS (not compatible with arm)
|
||||||
|
#ENV CONFD_VERSION 0.16.0
|
||||||
|
#RUN curl -sSL https://github.com/kelseyhightower/confd/releases/download/v${CONFD_VERSION}/confd-${CONFD_VERSION}-linux-amd64 -o /usr/local/bin/confd && \
|
||||||
|
# chmod +x /usr/local/bin/confd
|
||||||
|
#COPY ./confd /etc/confd
|
||||||
# rewrite command & entrypoint with ours
|
# rewrite command & entrypoint with ours
|
||||||
|
|
||||||
COPY ./entrypoint.sh /
|
COPY ./entrypoint.sh /
|
||||||
|
ENV BACKEND_HOSTNAME="shuffle-backend"
|
||||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
## Lalits frontend magic
|
||||||
|
|
||||||
## Localhost Certificate info:
|
## Localhost Certificate info:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location ~ /api/v(1|2) {
|
location ~ /api/v(1|2) {
|
||||||
proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001;
|
proxy_pass http://${BACKEND_HOSTNAME}:5001;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
@@ -113,7 +113,8 @@ http {
|
|||||||
|
|
||||||
# Get the hostname from environment here?
|
# Get the hostname from environment here?
|
||||||
location ~ /api/v(1|2) {
|
location ~ /api/v(1|2) {
|
||||||
proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001;
|
proxy_pass http://${BACKEND_HOSTNAME}:5001;
|
||||||
|
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
# generate configs
|
envsubst '${BACKEND_HOSTNAME}' < /etc/nginx/nginx.conf.tmpl > /etc/nginx/nginx.conf
|
||||||
/usr/local/bin/confd -backend="env" -confdir="/etc/confd" -onetime
|
|
||||||
|
|
||||||
# run main command
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "shuffler",
|
"name": "shuffler",
|
||||||
"homepage": "https://shuffler.io",
|
"homepage": "https://shuffler.io",
|
||||||
"version": "1.4.0",
|
"version": "2.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
||||||
"@codemirror/commands": "^6.2.4",
|
"@codemirror/commands": "^6.2.4",
|
||||||
"@codemirror/lang-python": "^6.1.3",
|
"@codemirror/lang-python": "^6.1.3",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
@@ -13,7 +12,7 @@
|
|||||||
"@metamask/detect-provider": "^1.2.0",
|
"@metamask/detect-provider": "^1.2.0",
|
||||||
"@mui/icons-material": "^5.14.0",
|
"@mui/icons-material": "^5.14.0",
|
||||||
"@mui/material": "^5.14.0",
|
"@mui/material": "^5.14.0",
|
||||||
"@mui/styles": "^5.14.0",
|
"@mui/styles": "^6.1.4",
|
||||||
"@mui/x-data-grid": "^5.17.11",
|
"@mui/x-data-grid": "^5.17.11",
|
||||||
"@mui/x-date-pickers": "^6.11.1",
|
"@mui/x-date-pickers": "^6.11.1",
|
||||||
"@types/algoliasearch": "^3.34.11",
|
"@types/algoliasearch": "^3.34.11",
|
||||||
@@ -21,7 +20,6 @@
|
|||||||
"@uiw/codemirror-theme-vscode": "^4.21.20",
|
"@uiw/codemirror-theme-vscode": "^4.21.20",
|
||||||
"@uiw/codemirror-themes": "^4.21.9",
|
"@uiw/codemirror-themes": "^4.21.9",
|
||||||
"@uiw/react-codemirror": "^4.21.21",
|
"@uiw/react-codemirror": "^4.21.21",
|
||||||
"@use-it/interval": "^0.1.3",
|
|
||||||
"algoliasearch": "^4.8.3",
|
"algoliasearch": "^4.8.3",
|
||||||
"class-transformer": "^0.2.0",
|
"class-transformer": "^0.2.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
@@ -49,7 +47,6 @@
|
|||||||
"i18next-localstorage-backend": "^4.1.0",
|
"i18next-localstorage-backend": "^4.1.0",
|
||||||
"i18next-xhr-backend": "^3.2.2",
|
"i18next-xhr-backend": "^3.2.2",
|
||||||
"import": "0.0.6",
|
"import": "0.0.6",
|
||||||
"interweave": "^11.2.0",
|
|
||||||
"is-plain-obj": "^4.1.0",
|
"is-plain-obj": "^4.1.0",
|
||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
"match-sorter": "^6.3.1",
|
"match-sorter": "^6.3.1",
|
||||||
@@ -58,12 +55,12 @@
|
|||||||
"moment": "~2.29.4",
|
"moment": "~2.29.4",
|
||||||
"mui-chips-input": "^2.1.3",
|
"mui-chips-input": "^2.1.3",
|
||||||
"mui-nested-menu": "^3.2.1",
|
"mui-nested-menu": "^3.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.3.1",
|
||||||
"react-ace": "^10.1.0",
|
"react-ace": "^10.1.0",
|
||||||
"react-alice-carousel": "^2.6.4",
|
"react-alice-carousel": "^2.6.4",
|
||||||
"react-avatar-editor": "^11.1.0",
|
"react-avatar-editor": "^11.1.0",
|
||||||
"react-beforeunload": "^2.2.1",
|
"react-beforeunload": "^2.2.1",
|
||||||
"react-chartjs-2": "^2.11.1",
|
"react-chartjs-2": "^2.11.2",
|
||||||
"react-cookie": "^4.0.1",
|
"react-cookie": "^4.0.1",
|
||||||
"react-cytoscapejs": "^2.0.0",
|
"react-cytoscapejs": "^2.0.0",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
@@ -73,21 +70,18 @@
|
|||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-ga4": "^2.0.0",
|
"react-ga4": "^2.0.0",
|
||||||
"react-hotkeys": "^2.0.0",
|
"react-hotkeys": "^2.0.0",
|
||||||
"react-i18next": "^13.1.2",
|
|
||||||
"react-instantsearch-dom": "^6.28.0",
|
"react-instantsearch-dom": "^6.28.0",
|
||||||
"react-json-pretty": "^2.2.0",
|
"react-json-pretty": "^2.2.0",
|
||||||
"react-json-view": "^1.21.3",
|
|
||||||
"react-json-view-ssr": "^1.19.1",
|
"react-json-view-ssr": "^1.19.1",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-markdown-github": "^3.3.1",
|
|
||||||
"react-powerhooks": "^0.0.7",
|
"react-powerhooks": "^0.0.7",
|
||||||
"react-router": "^6.14.1",
|
"react-router": "^6.14.1",
|
||||||
"react-router-dom": "^6.14.1",
|
"react-router-dom": "^6.14.1",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"react-social-icons": "^5.15.0",
|
"react-social-icons": "^5.15.0",
|
||||||
"react-stripe-elements": "^6.1.2",
|
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"reaviz": "^14.9.7",
|
"reaviz": "^14.9.7",
|
||||||
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"remark-html": "^16.0.1",
|
"remark-html": "^16.0.1",
|
||||||
"remark-images": "^4.0.0",
|
"remark-images": "^4.0.0",
|
||||||
@@ -134,7 +128,6 @@
|
|||||||
"babel-preset-es2015": "^6.24.1",
|
"babel-preset-es2015": "^6.24.1",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"promise-window": "^1.2.1",
|
"promise-window": "^1.2.1",
|
||||||
"react-hot-loader": "^4.13.0",
|
|
||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="24" height="24" fill="#212121" fill-opacity="0.02"/>
|
||||||
|
<path d="M14 4H7.6C7.17565 4 6.76869 4.16857 6.46863 4.46863C6.16857 4.76869 6 5.17565 6 5.6V18.4C6 18.8243 6.16857 19.2313 6.46863 19.5314C6.76869 19.8314 7.17565 20 7.6 20H17.2C17.6243 20 18.0313 19.8314 18.3314 19.5314C18.6314 19.2313 18.8 18.8243 18.8 18.4V8.8L14 4Z" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 4V8.8H18.8" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 598 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5 7.20001H6.6H19.4" stroke="#FD4C62" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M17.7996 7.2V18.4C17.7996 18.8243 17.631 19.2313 17.331 19.5314C17.0309 19.8314 16.624 20 16.1996 20H8.19961C7.77526 20 7.3683 19.8314 7.06824 19.5314C6.76818 19.2313 6.59961 18.8243 6.59961 18.4V7.2M8.99961 7.2V5.6C8.99961 5.17565 9.16818 4.76869 9.46824 4.46863C9.7683 4.16857 10.1753 4 10.5996 4H13.7996C14.224 4 14.6309 4.16857 14.931 4.46863C15.231 4.76869 15.3996 5.17565 15.3996 5.6V7.2" stroke="#FD4C62" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 669 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="16" height="11" viewBox="0 0 16 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.99984 6.83329C8.73622 6.83329 9.33317 6.23634 9.33317 5.49996C9.33317 4.76358 8.73622 4.16663 7.99984 4.16663C7.26346 4.16663 6.6665 4.76358 6.6665 5.49996C6.6665 6.23634 7.26346 6.83329 7.99984 6.83329Z" stroke="#C8C8C8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10.8269 2.67329C11.1988 3.04478 11.4938 3.48593 11.6951 3.97153C11.8964 4.45712 12 4.97763 12 5.50329C12 6.02895 11.8964 6.54946 11.6951 7.03505C11.4938 7.52064 11.1988 7.9618 10.8269 8.33329M5.17354 8.32662C4.80163 7.95513 4.5066 7.51398 4.3053 7.02838C4.104 6.54279 4.00039 6.02228 4.00039 5.49662C4.00039 4.97096 4.104 4.45045 4.3053 3.96486C4.5066 3.47927 4.80163 3.03811 5.17354 2.66662M12.7135 0.786621C13.9633 2.03681 14.6654 3.73219 14.6654 5.49995C14.6654 7.26772 13.9633 8.9631 12.7135 10.2133M3.28687 10.2133C2.03706 8.9631 1.33496 7.26772 1.33496 5.49995C1.33496 3.73219 2.03706 2.03681 3.28687 0.786621" stroke="#C8C8C8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="512" cy="512" r="512" style="fill:#0091e2"/>
|
||||||
|
<path d="M827.3 461.5c-1.6-1.3-16.1-12.2-46.7-12.2-8.1 0-16.2.6-24.2 2.1-5.9-40.7-39.5-60.5-41-61.4l-8.2-4.8-5.4 7.8c-6.8 10.5-11.7 22-14.6 34.2-5.5 23.2-2.2 45 9.6 63.6-14.2 7.9-37.1 9.9-41.7 10H277c-9.9 0-17.9 8-17.9 17.9-.4 33.1 5.2 66 16.5 97.1 13 34.2 32.4 59.3 57.6 74.7 28.2 17.3 74.1 27.2 126.2 27.2 23.5.1 47-2.1 70.1-6.4 32.1-5.9 63-17.1 91.4-33.2 23.4-13.6 44.5-30.8 62.4-51.1 29.9-33.9 47.8-71.7 61.1-105.2h5.3c32.8 0 53-13.1 64.1-24.1 7.4-7 13.2-15.5 16.9-25l2.3-6.9-5.7-4.3zM312 489.9h50.7c2.4 0 4.4-2 4.4-4.4v-45.1c0-2.4-2-4.4-4.4-4.5H312c-2.4 0-4.4 2-4.4 4.4v45.2c0 2.5 2 4.4 4.4 4.4m69.9 0h50.7c2.4 0 4.4-2 4.4-4.4v-45.1c0-2.4-2-4.4-4.4-4.5h-50.7c-2.5 0-4.5 2-4.5 4.5v45.1c0 2.5 2 4.4 4.5 4.4m70.8.1h50.7c2.4 0 4.4-2 4.4-4.4v-45.1c0-2.4-2-4.4-4.4-4.5h-50.7c-2.4 0-4.4 2-4.4 4.4v45.2c0 2.4 2 4.3 4.4 4.4m70.1 0h50.7c2.4 0 4.4-2 4.5-4.4v-45.1c0-2.5-2-4.5-4.5-4.5h-50.7c-2.4 0-4.4 2-4.4 4.4v45.2c0 2.4 1.9 4.4 4.4 4.4m-141-65h50.7c2.4 0 4.4-2 4.4-4.5v-45.1c0-2.4-2-4.4-4.4-4.4h-50.7c-2.5 0-4.4 2-4.5 4.4v45.1c.1 2.5 2.1 4.5 4.5 4.5m70.9 0h50.7c2.4 0 4.4-2 4.4-4.5v-45.1c0-2.4-2-4.4-4.4-4.4h-50.7c-2.4 0-4.4 2-4.4 4.4v45.1c0 2.5 2 4.5 4.4 4.5m70.1 0h50.7c2.5 0 4.4-2 4.5-4.5v-45.1c0-2.5-2-4.4-4.5-4.4h-50.7c-2.4 0-4.4 2-4.4 4.4v45.1c0 2.5 1.9 4.5 4.4 4.5m0-64.9h50.7c2.5 0 4.5-2 4.5-4.5v-45.2c0-2.4-2-4.4-4.5-4.4h-50.7c-2.4 0-4.4 2-4.4 4.4v45.2c0 2.5 1.9 4.5 4.4 4.5M593.4 490h50.7c2.4 0 4.4-2 4.4-4.4v-45.1c0-2.5-2-4.4-4.4-4.5h-50.7c-2.4 0-4.4 2-4.4 4.4v45.2c0 2.4 2 4.4 4.4 4.4" style="fill:#fff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="14" height="17" viewBox="0 0 14 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.75 1H2.5C2.10218 1 1.72064 1.15804 1.43934 1.43934C1.15804 1.72064 1 2.10218 1 2.5V14.5C1 14.8978 1.15804 15.2794 1.43934 15.5607C1.72064 15.842 2.10218 16 2.5 16H11.5C11.8978 16 12.2794 15.842 12.5607 15.5607C12.842 15.2794 13 14.8978 13 14.5V6.25L7.75 1Z" stroke="#C8C8C8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.75 1V6.25H13" stroke="#C8C8C8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 569 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="24" height="24" fill="#212121" fill-opacity="0.02"/>
|
||||||
|
<path d="M8.22559 16.4467L11.7788 20L15.3321 16.4467" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.7793 12.0052V20" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19.6676 17.415C20.4399 16.8719 21.019 16.0968 21.321 15.2023C21.6229 14.3078 21.632 13.3403 21.3468 12.4403C21.0617 11.5402 20.4971 10.7545 19.7352 10.197C18.9732 9.6396 18.0534 9.33948 17.1092 9.34021H15.99C15.7228 8.299 15.2229 7.33196 14.5279 6.5119C13.8329 5.69184 12.961 5.04013 11.9777 4.60583C10.9944 4.17153 9.92534 3.96596 8.85109 4.00459C7.77684 4.04322 6.72535 4.32505 5.77578 4.82886C4.82621 5.33267 4.00331 6.04534 3.36902 6.9132C2.73474 7.78106 2.30559 8.78151 2.11391 9.83922C1.92222 10.8969 1.97297 11.9844 2.26236 13.0196C2.55174 14.0549 3.07221 15.011 3.78459 15.816" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16.1038 4.66848C16.3158 4.45654 16.5674 4.28843 16.8443 4.17373C17.1212 4.05903 17.418 4 17.7177 4C18.0174 4 18.3142 4.05903 18.5911 4.17373C18.868 4.28843 19.1196 4.45654 19.3315 4.66848C19.5435 4.88041 19.7116 5.13201 19.8263 5.40891C19.941 5.68582 20 5.9826 20 6.28232C20 6.58204 19.941 6.87882 19.8263 7.15573C19.7116 7.43263 19.5435 7.68423 19.3315 7.89617L8.43807 18.7896L4 20L5.21038 15.5619L16.1038 4.66848Z" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 596 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="12" cy="12" r="12" transform="matrix(-1 0 0 1 24 0)" fill="#2F2F2F"/>
|
||||||
|
<path d="M8 6L14 12L8 18" stroke="#F1F1F1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 296 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M21 10H17L14 19L8 1L5 10H1" stroke="#F1F1F1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 225 B |
@@ -14,7 +14,9 @@ import HealthPage from "./components/HealthPage.jsx";
|
|||||||
//import Header from "./components/Header.jsx";
|
//import Header from "./components/Header.jsx";
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
import Apps from "./views/Apps";
|
import Apps from "./views/Apps";
|
||||||
|
import Apps2 from "./views/Apps2.jsx";
|
||||||
import AppCreator from "./views/AppCreator";
|
import AppCreator from "./views/AppCreator";
|
||||||
|
import DetectionDashBoard from "./views/DetectionDashboard.jsx";
|
||||||
|
|
||||||
import Welcome from "./views/Welcome.jsx";
|
import Welcome from "./views/Welcome.jsx";
|
||||||
import Dashboard from "./views/Dashboard.jsx";
|
import Dashboard from "./views/Dashboard.jsx";
|
||||||
@@ -22,6 +24,7 @@ import DashboardView from "./views/DashboardViews.jsx";
|
|||||||
import AdminSetup from "./views/AdminSetup";
|
import AdminSetup from "./views/AdminSetup";
|
||||||
import Admin from "./views/Admin";
|
import Admin from "./views/Admin";
|
||||||
import Docs from "./views/Docs.jsx";
|
import Docs from "./views/Docs.jsx";
|
||||||
|
import Usecases2 from "./views/Usecases2.jsx";
|
||||||
//import Introduction from "./views/Introduction";
|
//import Introduction from "./views/Introduction";
|
||||||
import SetAuthentication from "./views/SetAuthentication";
|
import SetAuthentication from "./views/SetAuthentication";
|
||||||
import SetAuthenticationSSO from "./views/SetAuthenticationSSO";
|
import SetAuthenticationSSO from "./views/SetAuthenticationSSO";
|
||||||
@@ -42,11 +45,20 @@ import AlertTemplate from "./components/AlertTemplate";
|
|||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import RuntimeDebugger from "./components/RuntimeDebugger.jsx"
|
import RuntimeDebugger from "./components/RuntimeDebugger.jsx"
|
||||||
|
|
||||||
|
import MFASetUp from './components/MFASetUP.jsx';
|
||||||
|
import ApiExplorerWrapper from './views/ApiExplorerWrapper.jsx';
|
||||||
|
import LeftSideBar from './components/LeftSideBar.jsx';
|
||||||
|
import CodeWorkflow from './views/CodeWorkflow.jsx';
|
||||||
|
import NotFound from './views/404.jsx';
|
||||||
|
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
|
||||||
import Drift from "react-driftjs";
|
import Drift from "react-driftjs";
|
||||||
|
|
||||||
|
import { AppContext } from './context/ContextApi.jsx';
|
||||||
|
import Workflows2 from "./views/Workflows2.jsx";
|
||||||
|
|
||||||
// Production - backend proxy forwarding in nginx
|
// Production - backend proxy forwarding in nginx
|
||||||
var globalUrl = window.location.origin;
|
var globalUrl = window.location.origin;
|
||||||
|
|
||||||
@@ -194,28 +206,38 @@ const App = (message, props) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div style={{ minHeight: 68, maxHeight: 68, }}>
|
|
||||||
<Header
|
{curpath.includes("/workflows") && curpath.includes("/run") ?
|
||||||
billingInfo={{}}
|
<div style={{ height: 60, }} />
|
||||||
|
:
|
||||||
|
isLoggedIn ?
|
||||||
|
<div style={{ position: 'fixed', top: 16, left: 10, zIndex: 100000 }}>
|
||||||
|
<LeftSideBar userdata={userdata} globalUrl={globalUrl} serverside={false} notifications={notifications} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div style={{ minHeight: 68, maxHeight: 68, }}>
|
||||||
|
<Header
|
||||||
|
billingInfo={{}}
|
||||||
|
|
||||||
notifications={notifications}
|
notifications={notifications}
|
||||||
setNotifications={setNotifications}
|
setNotifications={setNotifications}
|
||||||
checkLogin={checkLogin}
|
checkLogin={checkLogin}
|
||||||
cookies={cookies}
|
cookies={cookies}
|
||||||
removeCookie={removeCookie}
|
removeCookie={removeCookie}
|
||||||
isLoaded={isLoaded}
|
isLoaded={isLoaded}
|
||||||
globalUrl={globalUrl}
|
globalUrl={globalUrl}
|
||||||
setIsLoggedIn={setIsLoggedIn}
|
setIsLoggedIn={setIsLoggedIn}
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
userdata={userdata}
|
userdata={userdata}
|
||||||
|
|
||||||
curpath={curpath}
|
curpath={curpath}
|
||||||
serverside={false}
|
serverside={false}
|
||||||
isMobile={false}
|
isMobile={false}
|
||||||
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
<div style={{ height: 60 }} />
|
<div style={{ height: 60 }} />
|
||||||
@@ -375,6 +397,19 @@ const App = (message, props) => {
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/usecases2"
|
||||||
|
element={
|
||||||
|
<Usecases2
|
||||||
|
userdata={userdata}
|
||||||
|
isLoaded={isLoaded}
|
||||||
|
isLoggedIn={isLoggedIn}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -401,6 +436,23 @@ const App = (message, props) => {
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/apps2"
|
||||||
|
element={
|
||||||
|
<Apps2
|
||||||
|
serverside={false}
|
||||||
|
isLoaded={isLoaded}
|
||||||
|
isLoggedIn={isLoggedIn}
|
||||||
|
checkLogin={checkLogin}
|
||||||
|
userdata={userdata}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
surfaceColor={theme.palette.surfaceColor}
|
||||||
|
inputColor={theme.palette.inputColor}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -414,6 +466,12 @@ const App = (message, props) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route exact path="/apis/:appid" element={<ApiExplorerWrapper serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} isMobile={false} selectedApp={undefined} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} />
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/detections/sigma"
|
||||||
|
element={<DetectionDashBoard globalUrl={globalUrl} />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path="/workflows"
|
path="/workflows"
|
||||||
@@ -430,6 +488,23 @@ const App = (message, props) => {
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/workflows2"
|
||||||
|
element={
|
||||||
|
<Workflows2
|
||||||
|
checkLogin={checkLogin}
|
||||||
|
cookies={cookies}
|
||||||
|
removeCookie={removeCookie}
|
||||||
|
isLoaded={isLoaded}
|
||||||
|
isLoggedIn={isLoggedIn}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
cookies={cookies}
|
||||||
|
userdata={userdata}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -463,8 +538,14 @@ const App = (message, props) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route exact path="/workflows/:key/code" element={<CodeWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} />
|
||||||
<Route exact path="/workflows/:key/run" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } />
|
<Route exact path="/workflows/:key/run" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } />
|
||||||
<Route exact path="/workflows/:key/execute" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } />
|
<Route exact path="/workflows/:key/execute" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } />
|
||||||
|
|
||||||
|
<Route exact path="/forms" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} />
|
||||||
|
<Route exact path="/forms/:key/run" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} />
|
||||||
|
<Route exact path="/forms/:key" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} />
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path="/docs/:key"
|
path="/docs/:key"
|
||||||
@@ -520,6 +601,7 @@ const App = (message, props) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route exact path="/login/:key/mfa-setup" element={<MFASetUp setCookie={setCookie} serverside={false} mainColor={theme.palette.backgroundColor} userdata={userdata} stripeKey={undefined} globalUrl={globalUrl} inputColor={theme.palette.inputColor} isLoaded={isLoaded} {...props} />} />
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path="/login_sso"
|
path="/login_sso"
|
||||||
@@ -598,30 +680,40 @@ const App = (message, props) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/*"
|
||||||
|
element={
|
||||||
|
<NotFound />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<AppContext>
|
||||||
<CssBaseline />
|
<ThemeProvider theme={theme}>
|
||||||
<CookiesProvider>
|
<CssBaseline />
|
||||||
<BrowserRouter>
|
<CookiesProvider>
|
||||||
{includedData}
|
<BrowserRouter>
|
||||||
</BrowserRouter>
|
{includedData}
|
||||||
<ToastContainer
|
</BrowserRouter>
|
||||||
position="bottom-center"
|
<ToastContainer
|
||||||
autoClose={5000}
|
position="bottom-center"
|
||||||
hideProgressBar={false}
|
autoClose={5000}
|
||||||
newestOnTop={false}
|
hideProgressBar={false}
|
||||||
closeOnClick
|
newestOnTop={false}
|
||||||
rtl={false}
|
closeOnClick
|
||||||
pauseOnFocusLoss
|
rtl={false}
|
||||||
draggable
|
pauseOnFocusLoss
|
||||||
pauseOnHover
|
draggable
|
||||||
theme="dark"
|
pauseOnHover
|
||||||
/>
|
theme="dark"
|
||||||
</CookiesProvider>
|
/>
|
||||||
</ThemeProvider>
|
</CookiesProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</AppContext>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import edgehandles from "cytoscape-edgehandles";
|
|||||||
|
|
||||||
import cytoscape from "cytoscape";
|
import cytoscape from "cytoscape";
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import { isMobile } from 'react-device-detect';
|
||||||
|
|
||||||
cytoscape.use(edgehandles)
|
cytoscape.use(edgehandles)
|
||||||
|
|
||||||
@@ -52,13 +53,15 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const category = inputcategory.toLowerCase().split(":")[0].trim()
|
const category = inputcategory.toLowerCase().split(":")[0].trim()
|
||||||
|
|
||||||
//console.log("findSpecificApp: ", category, framework)
|
|
||||||
if (category === "edr" || category === "eradication" || category === "edr & av") {
|
if (category === "edr" || category === "eradication" || category === "edr & av") {
|
||||||
if (framework["EDR & AV"] !== undefined && framework["EDR & AV"].name !== undefined) {
|
if (framework["EDR & AV"] !== undefined && framework["EDR & AV"].name !== undefined && framework["EDR & AV"].name !== "") {
|
||||||
return framework["EDR & AV"]
|
return framework["EDR & AV"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["edr"] !== undefined && framework["edr"].name !== undefined && framework["edr"].name !== "") {
|
||||||
|
return framework["edr"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "EDR :default",
|
name: "EDR :default",
|
||||||
large_image: parsedDatatypeImages()["EDR & AV"],
|
large_image: parsedDatatypeImages()["EDR & AV"],
|
||||||
@@ -67,10 +70,14 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
id: "",
|
id: "",
|
||||||
}
|
}
|
||||||
} else if (category === "communication" || category === "comms") {
|
} else if (category === "communication" || category === "comms") {
|
||||||
if (framework["Comms"] !== undefined && framework["Comms"].name !== undefined) {
|
if (framework["Comms"] !== undefined && framework["Comms"].name !== undefined && framework["Comms"].name !== "") {
|
||||||
return framework["Comms"]
|
return framework["Comms"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["communication"] !== undefined && framework["communication"].name !== undefined && framework["communication"].name !== "") {
|
||||||
|
return framework["communication"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "COMMS :default",
|
name: "COMMS :default",
|
||||||
large_image: parsedDatatypeImages()["COMMS"],
|
large_image: parsedDatatypeImages()["COMMS"],
|
||||||
@@ -79,10 +86,14 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
id: "",
|
id: "",
|
||||||
}
|
}
|
||||||
} else if (category === "email") {
|
} else if (category === "email") {
|
||||||
if (framework["Email"] !== undefined && framework["Email"].name !== undefined) {
|
if (framework["Email"] !== undefined && framework["Email"].name !== undefined && framework["Email"].name !== "") {
|
||||||
return framework["Email"]
|
return framework["Email"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["email"] !== undefined && framework["email"].name !== undefined && framework["email"].name !== "") {
|
||||||
|
return framework["email"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "COMMS :default",
|
name: "COMMS :default",
|
||||||
large_image: parsedDatatypeImages()["COMMS"],
|
large_image: parsedDatatypeImages()["COMMS"],
|
||||||
@@ -91,10 +102,14 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
id: "",
|
id: "",
|
||||||
}
|
}
|
||||||
} else if (category === "assets") {
|
} else if (category === "assets") {
|
||||||
if (framework["Assets"] !== undefined && framework["Assets"].name !== undefined) {
|
if (framework["Assets"] !== undefined && framework["Assets"].name !== undefined && framework["Assets"].name !== "") {
|
||||||
return framework["Assets"]
|
return framework["Assets"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["assets"] !== undefined && framework["assets"].name !== undefined && framework["assets"].name !== "") {
|
||||||
|
return framework["assets"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "ASSETS :default",
|
name: "ASSETS :default",
|
||||||
large_image: parsedDatatypeImages()["ASSETS"],
|
large_image: parsedDatatypeImages()["ASSETS"],
|
||||||
@@ -103,10 +118,14 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
id: "",
|
id: "",
|
||||||
}
|
}
|
||||||
} else if (category === "cases") {
|
} else if (category === "cases") {
|
||||||
if (framework["Cases"] !== undefined && framework["Cases"].name !== undefined) {
|
if (framework["Cases"] !== undefined && framework["Cases"].name !== undefined && framework["Cases"].name !== "") {
|
||||||
return framework["Cases"]
|
return framework["Cases"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["cases"] !== undefined && framework["cases"].name !== undefined && framework["cases"].name !== "") {
|
||||||
|
return framework["cases"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "CASES :default",
|
name: "CASES :default",
|
||||||
large_image: parsedDatatypeImages()["CASES"],
|
large_image: parsedDatatypeImages()["CASES"],
|
||||||
@@ -115,10 +134,14 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
id: "",
|
id: "",
|
||||||
}
|
}
|
||||||
} else if (category === "iam") {
|
} else if (category === "iam") {
|
||||||
if (framework["IAM"] !== undefined && framework["IAM"].name !== undefined) {
|
if (framework["IAM"] !== undefined && framework["IAM"].name !== undefined && framework["IAM"].name !== "") {
|
||||||
return framework["IAM"]
|
return framework["IAM"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["iam"] !== undefined && framework["iam"].name !== undefined && framework["iam"].name !== "") {
|
||||||
|
return framework["iam"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "IAM :default",
|
name: "IAM :default",
|
||||||
large_image: parsedDatatypeImages()["IAM"],
|
large_image: parsedDatatypeImages()["IAM"],
|
||||||
@@ -127,10 +150,14 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
id: "",
|
id: "",
|
||||||
}
|
}
|
||||||
} else if (category === "network") {
|
} else if (category === "network") {
|
||||||
if (framework["Network"] !== undefined && framework["Network"].name !== undefined) {
|
if (framework["Network"] !== undefined && framework["Network"].name !== undefined && framework["Network"].name !== "") {
|
||||||
return framework["Network"]
|
return framework["Network"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["network"] !== undefined && framework["network"].name !== undefined && framework["network"].name !== "") {
|
||||||
|
return framework["network"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "Network :default",
|
name: "Network :default",
|
||||||
large_image: parsedDatatypeImages()["NETWORK"],
|
large_image: parsedDatatypeImages()["NETWORK"],
|
||||||
@@ -139,10 +166,14 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
id: "",
|
id: "",
|
||||||
}
|
}
|
||||||
} else if (category === "intel") {
|
} else if (category === "intel") {
|
||||||
if (framework["Intel"] !== undefined && framework["Intel"].name !== undefined) {
|
if (framework["Intel"] !== undefined && framework["Intel"].name !== undefined && framework["Intel"].name !== "") {
|
||||||
return framework["Intel"]
|
return framework["Intel"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["intel"] !== undefined && framework["intel"].name !== undefined && framework["intel"].name !== "") {
|
||||||
|
return framework["intel"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "INTEL :default",
|
name: "INTEL :default",
|
||||||
large_image: parsedDatatypeImages()["INTEL"],
|
large_image: parsedDatatypeImages()["INTEL"],
|
||||||
@@ -151,9 +182,13 @@ export const findSpecificApp = (framework, inputcategory) => {
|
|||||||
id: "",
|
id: "",
|
||||||
}
|
}
|
||||||
} else if (category === "siem") {
|
} else if (category === "siem") {
|
||||||
if (framework["SIEM"] !== undefined && framework["SIEM"].name !== undefined) {
|
if (framework["SIEM"] !== undefined && framework["SIEM"].name !== undefined && framework["SIEM"].name !== "") {
|
||||||
return framework["SIEM"]
|
return framework["SIEM"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framework["siem"] !== undefined && framework["siem"].name !== undefined && framework["siem"].name !== "") {
|
||||||
|
return framework["siem"]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "SIEM :default",
|
name: "SIEM :default",
|
||||||
@@ -1094,7 +1129,7 @@ const AppFramework = (props) => {
|
|||||||
}, [newSelectedApp])
|
}, [newSelectedApp])
|
||||||
|
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
const imgSize = 50;
|
const imgSize = 50;
|
||||||
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
|
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
|
||||||
|
|
||||||
@@ -1937,14 +1972,14 @@ const AppFramework = (props) => {
|
|||||||
{data.name}
|
{data.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<div style={{display: "flex", width: 200, margin: "auto", marginTop: 15, }}>
|
<div style={{display: "flex", width: 200, margin: "auto", marginTop: 15, }}>
|
||||||
<div style={{backgroundColor: theme.palette.inputColor, height: 75, width: 75, borderRadius: theme.palette.borderRadius, border: "1px solid rgba(255,255,255,0.7)", marginRight: 15, position: "relative",}}>
|
<div style={{backgroundColor: theme.palette.inputColor, height: 75, width: 75, borderRadius: theme.palette?.borderRadius, border: "1px solid rgba(255,255,255,0.7)", marginRight: 15, position: "relative",}}>
|
||||||
{parsedLeftImage}
|
{parsedLeftImage}
|
||||||
{parsedLeftText}
|
{parsedLeftText}
|
||||||
</div>
|
</div>
|
||||||
<div style={{backgroundColor: theme.palette.inputColor, maxHeight: 30, maxWidth: 30, height: 30, width: 30, borderRadius: theme.palette.borderRadius, border: "1px solid rgba(255,255,255,0.7)", marginTop: 22, padding: "10px 0px 0px 9px",}}>
|
<div style={{backgroundColor: theme.palette.inputColor, maxHeight: 30, maxWidth: 30, height: 30, width: 30, borderRadius: theme.palette?.borderRadius, border: "1px solid rgba(255,255,255,0.7)", marginTop: 22, padding: "10px 0px 0px 9px",}}>
|
||||||
{svgIcon}
|
{svgIcon}
|
||||||
</div>
|
</div>
|
||||||
<div style={{backgroundColor: theme.palette.inputColor, height: 75, width: 75, borderRadius: theme.palette.borderRadius, border: "1px solid rgba(255,255,255,0.7)", marginLeft: 15, position: "relative",}}>
|
<div style={{backgroundColor: theme.palette.inputColor, height: 75, width: 75, borderRadius: theme.palette?.borderRadius, border: "1px solid rgba(255,255,255,0.7)", marginLeft: 15, position: "relative",}}>
|
||||||
{parsedRightImage}
|
{parsedRightImage}
|
||||||
{parsedRightText}
|
{parsedRightText}
|
||||||
</div>
|
</div>
|
||||||
@@ -2152,7 +2187,7 @@ const AppFramework = (props) => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
Object.getOwnPropertyNames(discoveryData).length > 0 ?
|
Object.getOwnPropertyNames(discoveryData).length > 0 ?
|
||||||
<Paper style={{width: 300, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 25, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
|
<Paper style={{width: 300, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 25, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: isMobile?-50: 50, }}>
|
||||||
{paperTitle.length > 0 ?
|
{paperTitle.length > 0 ?
|
||||||
<span>
|
<span>
|
||||||
<Typography variant="h6" style={{textAlign: "center"}}>
|
<Typography variant="h6" style={{textAlign: "center"}}>
|
||||||
@@ -2321,7 +2356,7 @@ const AppFramework = (props) => {
|
|||||||
elements={elements}
|
elements={elements}
|
||||||
minZoom={0.35}
|
minZoom={0.35}
|
||||||
maxZoom={2.00}
|
maxZoom={2.00}
|
||||||
style={{width: 560*scale, height: 560*scale, backgroundColor: theme.palette.backgroundColor, margin: "auto",}}
|
style={{width: isMobile?null:560*scale, height: 560*scale, backgroundColor: theme.palette.backgroundColor, margin: isMobile?null:"auto",}}
|
||||||
stylesheet={frameworkStyle}
|
stylesheet={frameworkStyle}
|
||||||
boxSelectionEnabled={false}
|
boxSelectionEnabled={false}
|
||||||
panningEnabled={false}
|
panningEnabled={false}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ const AppGrid = (props) => {
|
|||||||
const [formMail, setFormMail] = React.useState("");
|
const [formMail, setFormMail] = React.useState("");
|
||||||
const [message, setMessage] = React.useState("");
|
const [message, setMessage] = React.useState("");
|
||||||
const [formMessage, setFormMessage] = React.useState("");
|
const [formMessage, setFormMessage] = React.useState("");
|
||||||
|
const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]);
|
||||||
|
|
||||||
const buttonStyle = {
|
const buttonStyle = {
|
||||||
borderRadius: 30,
|
borderRadius: 30,
|
||||||
@@ -232,6 +233,11 @@ const AppGrid = (props) => {
|
|||||||
removeQuery("q");
|
removeQuery("q");
|
||||||
refine(event.currentTarget.value);
|
refine(event.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if(event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
limit={5}
|
limit={5}
|
||||||
/>
|
/>
|
||||||
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
||||||
@@ -293,7 +299,7 @@ const AppGrid = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
var baseurl = globalUrl;
|
var baseurl = globalUrl;
|
||||||
fetch(baseurl + "/api/v1/getinfo", {
|
fetch(baseurl + "/api/v1/me", {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -347,7 +353,7 @@ const AppGrid = (props) => {
|
|||||||
if (responseJson.success === false) {
|
if (responseJson.success === false) {
|
||||||
toast.error(responseJson.reason);
|
toast.error(responseJson.reason);
|
||||||
} else {
|
} else {
|
||||||
toast.success(`App ${type}d Successfully!`);
|
//toast.success(`App ${type}d Successfully!`);
|
||||||
if (type === 'activate') {
|
if (type === 'activate') {
|
||||||
setAllActivatedAppIds(prev => [...prev, data.objectID]);
|
setAllActivatedAppIds(prev => [...prev, data.objectID]);
|
||||||
setIsAnyAppActivated(true);
|
setIsAnyAppActivated(true);
|
||||||
@@ -390,7 +396,7 @@ const AppGrid = (props) => {
|
|||||||
{!isLoading ? (
|
{!isLoading ? (
|
||||||
<div>
|
<div>
|
||||||
{hits.length === 0 && searchQuery.length >= 0 && showNoAppFound ? (
|
{hits.length === 0 && searchQuery.length >= 0 && showNoAppFound ? (
|
||||||
<Typography variant="body1" style={{ marginTop: '30%' }}>No App Found</Typography>
|
<Typography variant="body1" style={{ marginTop: '30%' }}>No Apps Found</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Grid item spacing={2} justifyContent="flex-start">
|
<Grid item spacing={2} justifyContent="flex-start">
|
||||||
<div
|
<div
|
||||||
@@ -458,8 +464,17 @@ const AppGrid = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
|
id={`image_${index}`}
|
||||||
alt={data.name}
|
alt={data.name}
|
||||||
src={data.image_url ? data.image_url : "/images/no_image.png"}
|
src={data.image_url ? data.image_url : "/images/no_image.png"}
|
||||||
|
onError={(e) => {
|
||||||
|
// Replace the image with the default image
|
||||||
|
const foundImage = document.getElementById(`image_${index}`)
|
||||||
|
if (foundImage !== undefined && foundImage !== null) {
|
||||||
|
foundImage.src = theme.palette.defaultImage
|
||||||
|
data.image_url = theme.palette.defaultImage
|
||||||
|
}
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
@@ -989,11 +1004,16 @@ const AppGrid = (props) => {
|
|||||||
}}
|
}}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
color="primary"
|
color="primary"
|
||||||
placeholder="Search your Activated or Self-built apps"
|
placeholder="Search your Activated or self-built apps"
|
||||||
id="shuffle_search_field"
|
id="shuffle_search_field"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setSearchQuery(event.currentTarget.value);
|
setSearchQuery(event.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if(event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
limit={5}
|
limit={5}
|
||||||
/>
|
/>
|
||||||
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
||||||
@@ -1541,9 +1561,13 @@ const AppGrid = (props) => {
|
|||||||
<Typography variant="h5" style={{ marginBottom: 30, marginTop: 30, fontWeight: "400", fontSize: 24 }}>
|
<Typography variant="h5" style={{ marginBottom: 30, marginTop: 30, fontWeight: "400", fontSize: 24 }}>
|
||||||
Filter By
|
Filter By
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<FilterUsersAndOrgsAppByCategory selectedCategoryForUsersAndOgsApps={selectedCategoryForUsersAndOgsApps} setselectedCategoryForUsersAndOgsApps={setselectedCategoryForUsersAndOgsApps} />
|
<FilterUsersAndOrgsAppByCategory selectedCategoryForUsersAndOgsApps={selectedCategoryForUsersAndOgsApps} setselectedCategoryForUsersAndOgsApps={setselectedCategoryForUsersAndOgsApps} />
|
||||||
|
|
||||||
<FilterUsersAndOrgsAppByActionLabel selectedTagsForUserAndOrgApps={selectedTagsForUserAndOrgApps} setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps} />
|
<FilterUsersAndOrgsAppByActionLabel selectedTagsForUserAndOrgApps={selectedTagsForUserAndOrgApps} setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps} />
|
||||||
|
|
||||||
<FilterUsersAndOrgsAppByCreatedWith selectedOptionOfCreatedWith={selectedOptionOfCreatedWith} setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith} />
|
<FilterUsersAndOrgsAppByCreatedWith selectedOptionOfCreatedWith={selectedOptionOfCreatedWith} setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith} />
|
||||||
|
|
||||||
<FilterUsersAndOrgsAppCreatedBy />
|
<FilterUsersAndOrgsAppCreatedBy />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1712,6 +1736,10 @@ const AppGrid = (props) => {
|
|||||||
? `/apps/${data.id}`
|
? `/apps/${data.id}`
|
||||||
: `https://shuffler.io/apps/${data.id}`;
|
: `https://shuffler.io/apps/${data.id}`;
|
||||||
|
|
||||||
|
if (data.name === "" && data.id === "") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Zoom
|
<Zoom
|
||||||
key={index}
|
key={index}
|
||||||
@@ -1808,17 +1836,71 @@ const AppGrid = (props) => {
|
|||||||
width: 230,
|
width: 230,
|
||||||
textAlign: 'start',
|
textAlign: 'start',
|
||||||
marginLeft: 8,
|
marginLeft: 8,
|
||||||
color: "rgba(158, 158, 158, 1)"
|
color: "rgba(158, 158, 158, 1)",
|
||||||
|
display: "flex",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data.tags &&
|
<div style={{minWidth: 120, overflow: "hidden", }}>
|
||||||
data.tags.map((tag, tagIndex) => (
|
{data.generated !== true ?
|
||||||
<span key={tagIndex}>
|
<div>
|
||||||
{normalizedString(tag)}
|
{data.tags &&
|
||||||
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
data.tags.slice(0,2).map((tag, tagIndex) => (
|
||||||
</span>
|
<span key={tagIndex}>
|
||||||
))}
|
{normalizedString(tag)}
|
||||||
</div>
|
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
{currTab === 1 && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true ?
|
||||||
|
<Button style={{
|
||||||
|
marginLeft: 15,
|
||||||
|
width: 102,
|
||||||
|
height: 35,
|
||||||
|
borderRadius: 200,
|
||||||
|
backgroundColor: "rgba(73, 73, 73, 1)",
|
||||||
|
color: "rgba(241, 241, 241, 1)",
|
||||||
|
textTransform: "none",
|
||||||
|
}}
|
||||||
|
onClick={(event) => {
|
||||||
|
//deactivatedIndexes.push(index)
|
||||||
|
//setDeactivatedIndexes(deactivatedIndexes)
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
//handleActivateButton(event, data, "deactivate");
|
||||||
|
// FIXME: Put this in a function lol
|
||||||
|
const url = `${globalUrl}/api/v1/apps/${data.id}/deactivate`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
toast.error(responseJson.reason);
|
||||||
|
} else {
|
||||||
|
toast.success("App Deactivated Successfully. Reload UI to see updated changes.")
|
||||||
|
//const updatedIds = allActivatedAppIds.filter(id => id !== data.objectID);
|
||||||
|
//setAllActivatedAppIds(updatedIds);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log("app error: ", error.toString());
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
Deactivate
|
||||||
|
</Button>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* )} */}
|
{/* )} */}
|
||||||
</div>
|
</div>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
@@ -1936,6 +2018,7 @@ const AppGrid = (props) => {
|
|||||||
selectedOptionOfCreatedWith={selectedOptionOfCreatedWith}
|
selectedOptionOfCreatedWith={selectedOptionOfCreatedWith}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<AppTab
|
<AppTab
|
||||||
selectedCategoryForUsersAndOgsApps={selectedCategoryForUsersAndOgsApps}
|
selectedCategoryForUsersAndOgsApps={selectedCategoryForUsersAndOgsApps}
|
||||||
selectedTagsForUserAndOrgApps={selectedTagsForUserAndOrgApps}
|
selectedTagsForUserAndOrgApps={selectedTagsForUserAndOrgApps}
|
||||||
@@ -1944,6 +2027,7 @@ const AppGrid = (props) => {
|
|||||||
setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps}
|
setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps}
|
||||||
setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith}
|
setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<Configure clickAnalytics />
|
<Configure clickAnalytics />
|
||||||
</InstantSearch>
|
</InstantSearch>
|
||||||
|
|||||||
@@ -0,0 +1,759 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Stack,
|
||||||
|
Avatar,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import Search from '@mui/icons-material/Search';
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
||||||
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||||
|
import LaunchIcon from '@mui/icons-material/Launch';
|
||||||
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||||
|
import { CloudDownloadOutlined } from '@mui/icons-material';
|
||||||
|
import { findSpecificApp } from './AppFramework';
|
||||||
|
import theme from "../theme";
|
||||||
|
import YAML from 'yaml';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||||
|
|
||||||
|
const [frameworkData, setFrameworkData] = useState({})
|
||||||
|
const [userdata, setUserdata] = useState({})
|
||||||
|
const [usecases, setUsecases] = useState([])
|
||||||
|
const [workflows, setWorkflows] = useState([])
|
||||||
|
const [prevSubcase, setPrevSubcase] = useState({})
|
||||||
|
const [inputUsecase, setInputUsecase] = useState({})
|
||||||
|
const [latestUsecase, setLatestUsecase] = useState([])
|
||||||
|
const [foundAppUsecase, setFoundAppUsecase] = useState({})
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const parseUsecase = (subcase) => {
|
||||||
|
const srcdata = findSpecificApp(frameworkData, subcase.type)
|
||||||
|
const dstdata = findSpecificApp(frameworkData, subcase.last)
|
||||||
|
|
||||||
|
if (srcdata !== undefined && srcdata !== null) {
|
||||||
|
subcase.srcimg = srcdata.large_image
|
||||||
|
subcase.srcapp = srcdata.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dstdata !== undefined && dstdata !== null) {
|
||||||
|
subcase.dstimg = dstdata.large_image
|
||||||
|
subcase.dstapp = dstdata.name
|
||||||
|
}
|
||||||
|
return subcase
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
var baseurl = globalUrl;
|
||||||
|
fetch(baseurl + "/api/v1/me", {
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(responseJson => {
|
||||||
|
if (responseJson.success) {
|
||||||
|
setUserdata(responseJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log("Failed login check: ", error);
|
||||||
|
});
|
||||||
|
}, [app]);
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const preparedData = {
|
||||||
|
"siem": findSpecificApp({}, "SIEM"),
|
||||||
|
"communication": findSpecificApp({}, "COMMUNICATION"),
|
||||||
|
"assets": findSpecificApp({}, "ASSETS"),
|
||||||
|
"cases": findSpecificApp({}, "CASES"),
|
||||||
|
"network": findSpecificApp({}, "NETWORK"),
|
||||||
|
"intel": findSpecificApp({}, "INTEL"),
|
||||||
|
"edr": findSpecificApp({}, "EDR"),
|
||||||
|
"iam": findSpecificApp({}, "IAM"),
|
||||||
|
"email": findSpecificApp({}, "EMAIL"),
|
||||||
|
}
|
||||||
|
|
||||||
|
setFrameworkData(preparedData)
|
||||||
|
} else {
|
||||||
|
setFrameworkData(responseJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error getting framework: ", error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
|
||||||
|
|
||||||
|
const newUsecases = [...usecases]
|
||||||
|
newUsecases.forEach((category, index) => {
|
||||||
|
category.list.forEach((subcase, subindex) => {
|
||||||
|
getUsecase(subcase, index, subindex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
setLatestUsecase(newUsecases)
|
||||||
|
// Matching workflows with usecases
|
||||||
|
if (responseJson.success !== false) {
|
||||||
|
if (workflows !== undefined && workflows !== null && workflows.length > 0) {
|
||||||
|
var categorydata = responseJson
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
|
||||||
|
category.matches.push({
|
||||||
|
"workflow": workflow.id,
|
||||||
|
"category": subcategory?.name,
|
||||||
|
})
|
||||||
|
|
||||||
|
subcategory.matches.push(workflow)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcategory.matches.length > 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newcategories.push(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newcategories !== undefined && newcategories !== null && newcategories.length > 0) {
|
||||||
|
setUsecases(newcategories)
|
||||||
|
} else {
|
||||||
|
setUsecases(responseJson)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setUsecases(responseJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
//toast("ERROR: " + error.toString());
|
||||||
|
console.log("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) {
|
||||||
|
fetchUsecases()
|
||||||
|
console.log("Status not 200 for workflows :O!: ", response.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
fetchUsecases(responseJson)
|
||||||
|
|
||||||
|
if (responseJson !== undefined) {
|
||||||
|
setWorkflows(responseJson);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
fetchUsecases()
|
||||||
|
//toast(error.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUsecase = (subcase, index, subindex) => {
|
||||||
|
subcase = parseUsecase(subcase)
|
||||||
|
setPrevSubcase(subcase)
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/workflows/usecases/${escape(subcase?.name?.replaceAll(" ", "_"))}`, {
|
||||||
|
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) => {
|
||||||
|
var parsedUsecase = responseJson
|
||||||
|
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
parsedUsecase = subcase
|
||||||
|
} else {
|
||||||
|
parsedUsecase = responseJson
|
||||||
|
|
||||||
|
parsedUsecase.srcimg = subcase.srcimg
|
||||||
|
parsedUsecase.srcapp = subcase.srcapp
|
||||||
|
parsedUsecase.dstimg = subcase.dstimg
|
||||||
|
parsedUsecase.dstapp = subcase.dstapp
|
||||||
|
}
|
||||||
|
// Look for the type of app and fill in img1, srcapp...
|
||||||
|
setInputUsecase(parsedUsecase)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
//toast(error.toString());
|
||||||
|
setInputUsecase(subcase)
|
||||||
|
console.log("Error getting usecase: ", error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAvailableWorkflows()
|
||||||
|
getFramework()
|
||||||
|
}, [app])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const foundCategory = latestUsecase?.find((category) =>
|
||||||
|
category?.list?.some((subcase) => subcase?.srcapp === app?.name || subcase?.dstapp === app?.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
const foundSubcase = foundCategory?.list?.find(
|
||||||
|
(subcase) => subcase?.srcapp === app?.name || subcase?.dstapp === app?.name
|
||||||
|
);
|
||||||
|
|
||||||
|
setFoundAppUsecase(foundSubcase);
|
||||||
|
}, [latestUsecase])
|
||||||
|
|
||||||
|
const downloadApp = (inputdata) => {
|
||||||
|
const id = inputdata.id;
|
||||||
|
|
||||||
|
toast("Downloading..");
|
||||||
|
fetch(globalUrl + "/api/v1/apps/" + id + "/config", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
window.location.pathname = "/apps";
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (!responseJson.success) {
|
||||||
|
toast("Failed to download file");
|
||||||
|
} else {
|
||||||
|
console.log(responseJson);
|
||||||
|
const basedata = atob(responseJson.openapi);
|
||||||
|
console.log("BASE: ", basedata);
|
||||||
|
var inputdata = JSON.parse(basedata);
|
||||||
|
console.log("POST INPUT: ", inputdata);
|
||||||
|
inputdata = JSON.parse(inputdata.body);
|
||||||
|
|
||||||
|
const newpaths = {};
|
||||||
|
if (inputdata["paths"] !== undefined) {
|
||||||
|
Object.keys(inputdata["paths"]).forEach(function (key) {
|
||||||
|
newpaths[key.split("?")[0]] = inputdata.paths[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inputdata.paths = newpaths;
|
||||||
|
console.log("INPUT: ", inputdata);
|
||||||
|
var name = inputdata.info.title;
|
||||||
|
name = name.replace(/ /g, "_", -1);
|
||||||
|
name = name.toLowerCase();
|
||||||
|
|
||||||
|
delete inputdata.id;
|
||||||
|
delete inputdata.editing;
|
||||||
|
|
||||||
|
const data = YAML.stringify(inputdata);
|
||||||
|
var blob = new Blob([data], {
|
||||||
|
type: "application/octet-stream",
|
||||||
|
});
|
||||||
|
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
var link = document.createElement("a");
|
||||||
|
link.setAttribute("href", url);
|
||||||
|
link.setAttribute("download", `${name}.yaml`);
|
||||||
|
var event = document.createEvent("MouseEvents");
|
||||||
|
event.initMouseEvent(
|
||||||
|
"click",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
window,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
link.dispatchEvent(event);
|
||||||
|
//link.parentNode.removeChild(link)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
toast(error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCloud =
|
||||||
|
window.location.host === "localhost:3002" ||
|
||||||
|
window.location.host === "shuffler.io" || window.location.host === "localhost:3000"
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
|
||||||
|
var newAppname = app?.name;
|
||||||
|
if (newAppname === undefined) {
|
||||||
|
newAppname = "Undefined";
|
||||||
|
} else {
|
||||||
|
newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1);
|
||||||
|
newAppname = newAppname?.replaceAll("_", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
var canEditApp = userdata.admin === "true" || userdata?.id === app?.owner || app?.owner === "" || (userdata.admin === "true" && userdata.active_org.id === app?.reference_org) || !app?.generated
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
maxWidth="sm"
|
||||||
|
fullWidth
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
borderRadius: 2,
|
||||||
|
border: "1px solid #494949",
|
||||||
|
minWidth: '440px',
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
backgroundColor: "#212121",
|
||||||
|
'& .MuiDialogContent-root': {
|
||||||
|
backgroundColor: "#212121",
|
||||||
|
},
|
||||||
|
'& .MuiDialogTitle-root': {
|
||||||
|
backgroundColor: "#212121",
|
||||||
|
},
|
||||||
|
'& .MuiTypography-root': {
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
},
|
||||||
|
'& .MuiButton-root': {
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
pb: 2,
|
||||||
|
pt: 2,
|
||||||
|
pl: 3,
|
||||||
|
pr: 2,
|
||||||
|
fontFamily: theme?.typography?.fontFamily
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography component="div" sx={{ fontWeight: 500, color: "#F1F1F1", fontSize: "22px" }}>
|
||||||
|
About {app?.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
onClick={onClose}
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogContent sx={{ py: 3, px: 3 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'space-between', justifyContent: 'space-between' }}>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", flexDirection: "row", gap: 10, fontFamily: theme?.typography?.fontFamily }}>
|
||||||
|
<img
|
||||||
|
alt={app?.name}
|
||||||
|
src={app?.large_image || app?.image_url}
|
||||||
|
style={{
|
||||||
|
borderRadius: 4,
|
||||||
|
maxWidth: 100,
|
||||||
|
minWidth: 100,
|
||||||
|
maxHeight: "100%",
|
||||||
|
display: "block",
|
||||||
|
margin: "0 auto",
|
||||||
|
boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.2)"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", justifyContent: "center", paddingLeft: 8 }}>
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
}}>
|
||||||
|
<Typography variant="h5" component="div" sx={{ fontWeight: 600 }}>
|
||||||
|
{newAppname}
|
||||||
|
</Typography>
|
||||||
|
{
|
||||||
|
isCloud && (
|
||||||
|
<Link
|
||||||
|
to={"/apps/" + (app?.id || app?.objectID)}
|
||||||
|
style={{ textDecoration: "none", color: "#f85a3e", marginTop: "-2px" }}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
style={{
|
||||||
|
color: "#f85a3e",
|
||||||
|
fontSize: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<OpenInNewIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="textSecondary"
|
||||||
|
>
|
||||||
|
{app?.categories ? app.categories.join(", ") : "Communication"}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "row", justifyContent: "center", alignItems: "center", gap: 10 }}>
|
||||||
|
|
||||||
|
{app?.activated &&
|
||||||
|
app?.private_id !== undefined &&
|
||||||
|
app?.private_id?.length > 0 &&
|
||||||
|
app?.generated ? (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
bgcolor: '#494949',
|
||||||
|
'&:hover': { bgcolor: '#494949' },
|
||||||
|
textTransform: 'none',
|
||||||
|
borderRadius: 1,
|
||||||
|
minWidth: '45px',
|
||||||
|
width: '45px',
|
||||||
|
height: '40px',
|
||||||
|
padding: 2,
|
||||||
|
color: "#fff",
|
||||||
|
fontFamily: theme?.typography?.fontFamily
|
||||||
|
}}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
downloadApp(app);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloudDownloadOutlined />
|
||||||
|
</Button>) : null}
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "#494949",
|
||||||
|
'&:hover': { bgcolor: '#494949' },
|
||||||
|
textTransform: 'none',
|
||||||
|
borderRadius: 1,
|
||||||
|
py: 1,
|
||||||
|
px: 3,
|
||||||
|
height: '40px',
|
||||||
|
color: "#fff",
|
||||||
|
fontFamily: theme?.typography?.fontFamily
|
||||||
|
}}
|
||||||
|
startIcon={canEditApp ? <EditIcon /> :
|
||||||
|
(app?.generated && app?.activated && userdata?.id !== app?.owner && isCloud ?
|
||||||
|
<ForkRightIcon /> : null
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (canEditApp) {
|
||||||
|
const editUrl = "/apps/edit/" + (app?.id || app?.objectID);
|
||||||
|
navigate(editUrl)
|
||||||
|
}else{
|
||||||
|
const forkUrl = "/apps/new?id=" + (app?.id || app?.objectID);
|
||||||
|
navigate(forkUrl)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{canEditApp ? "Edit" : "Fork"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
padding: "26px 0px"
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
textAlign: "start",
|
||||||
|
flex: 1,
|
||||||
|
}}>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: 600,
|
||||||
|
mb: 0.3,
|
||||||
|
color: '#fff'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
20
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
fontSize: '14px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Public Workflow
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
flex: 1,
|
||||||
|
textAlign: "start",
|
||||||
|
borderLeft: "1px solid rgba(255, 255, 255, 0.12)",
|
||||||
|
paddingLeft: "10px",
|
||||||
|
height: "100%",
|
||||||
|
}}>
|
||||||
|
<Typography variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
mb: 0.3,
|
||||||
|
color: '#fff'
|
||||||
|
}}>
|
||||||
|
{Array.isArray(app?.actions) ? app.actions.length : app?.actions}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||||
|
Actions
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
borderLeft: "1px solid rgba(255, 255, 255, 0.12)",
|
||||||
|
flex: 1,
|
||||||
|
paddingLeft: "10px",
|
||||||
|
paddingTop: "5px"
|
||||||
|
}}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: "5px", fontFamily: theme?.typography?.fontFamily, fontSize: "14px", fontWeight: 600, color: 'white' }}>
|
||||||
|
{
|
||||||
|
app?.collection ? (
|
||||||
|
<>
|
||||||
|
<CheckCircleIcon sx={{ color: '#4CAF50' }} />
|
||||||
|
<Typography variant="body1" sx={{
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#fff',
|
||||||
|
marginTop: "1px",
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
fontSize: "16px"
|
||||||
|
}}>
|
||||||
|
app.collection
|
||||||
|
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Typography sx={{
|
||||||
|
fontSize: "16px",
|
||||||
|
fontWeight: 500,
|
||||||
|
marginTop: "1px",
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
fontFamily: theme?.typography?.fontFamily
|
||||||
|
}}>
|
||||||
|
No collection yet
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Typography variant="body2" sx={{ color: 'rgba(158, 158, 158, 1)' }}>
|
||||||
|
Part of a collection
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "start",
|
||||||
|
width: "100%"
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
fontSize: "16px",
|
||||||
|
color: "#fff",
|
||||||
|
marginBottom: "16px",
|
||||||
|
fontWeight: 600
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
(foundAppUsecase?.srcapp !== undefined && foundAppUsecase?.dstapp !== undefined) ? (
|
||||||
|
"Connect " + foundAppUsecase?.srcapp?.replaceAll("_", " ") + " to " + foundAppUsecase?.dstapp?.replaceAll("_", " ")
|
||||||
|
) : (
|
||||||
|
"Connect " + app?.name + " to any tool"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box sx={{
|
||||||
|
bgcolor: '#2F2F2F',
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 2,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 3
|
||||||
|
}}>
|
||||||
|
<Stack direction="row" spacing={-1}>
|
||||||
|
{
|
||||||
|
foundAppUsecase === undefined ? (
|
||||||
|
<Avatar sx={{ width: 32, height: 32, bgcolor: 'background.paper', border: 1, borderColor: 'divider' }}>
|
||||||
|
<Search sx={{ color: 'text.primary', zIndex: 10, fontSize: 18 }} />
|
||||||
|
</Avatar>
|
||||||
|
) : (
|
||||||
|
<Avatar
|
||||||
|
src={foundAppUsecase?.srcimg}
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: 1,
|
||||||
|
borderColor: 'divider',
|
||||||
|
zIndex: 10
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
foundAppUsecase === undefined ? (
|
||||||
|
<Avatar sx={{ width: 32, height: 32, bgcolor: 'background.paper', border: 1, borderColor: 'divider' }}>
|
||||||
|
<AddIcon sx={{ color: 'text.primary', zIndex: 10, fontSize: 18 }} />
|
||||||
|
</Avatar>
|
||||||
|
) : (
|
||||||
|
<Avatar
|
||||||
|
src={foundAppUsecase?.dstimg}
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: 1,
|
||||||
|
borderColor: 'divider',
|
||||||
|
zIndex: 10
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Stack>
|
||||||
|
<Typography sx={{ ml: 2, fontSize: "16px", letterSpacing: "0.5px" }}>
|
||||||
|
{foundAppUsecase?.name || "Search for a Usecase"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", justifyContent: "center", fontFamily: theme?.typography?.fontFamily }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
bgcolor: '#FF8544',
|
||||||
|
'&:hover': { bgcolor: '#FF8544' },
|
||||||
|
textTransform: 'none',
|
||||||
|
borderRadius: 1,
|
||||||
|
py: 1,
|
||||||
|
px: 7,
|
||||||
|
fontSize: "14px",
|
||||||
|
letterSpacing: "0.5px",
|
||||||
|
color: "black",
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
minWidth: '200px'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/usecases2")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Find a Usecase
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppModal;
|
||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from "react";
|
|||||||
import theme from '../theme.jsx';
|
import theme from '../theme.jsx';
|
||||||
import ReactGA from 'react-ga4';
|
import ReactGA from 'react-ga4';
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { useNavigate, Link } from 'react-router-dom';
|
||||||
|
import { isMobile } from 'react-device-detect';
|
||||||
import { Search as Searchicon, CloudQueue as CloudQueueicon, Code as Codeicon, Close as Closeicon, Folder as Foldericon, LibraryBooks as LibraryBooksicon, Delete as DeleteIcon, Close as CloseIcon, } from '@mui/icons-material';
|
import { Search as Searchicon, CloudQueue as CloudQueueicon, Code as Codeicon, Close as Closeicon, Folder as Foldericon, LibraryBooks as LibraryBooksicon, Delete as DeleteIcon, Close as CloseIcon, } from '@mui/icons-material';
|
||||||
import aa from 'search-insights'
|
import aa from 'search-insights'
|
||||||
import Deleteicon from '@mui/icons-material/Delete';
|
import Deleteicon from '@mui/icons-material/Delete';
|
||||||
@@ -47,6 +47,8 @@ const AppSearchButtons = (props) => {
|
|||||||
const [newSelectedApp, setNewSelectedApp] = useState(undefined)
|
const [newSelectedApp, setNewSelectedApp] = useState(undefined)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log("UPDATED APP: ", newSelectedApp)
|
||||||
|
|
||||||
if (newSelectedApp !== undefined && setMissing != undefined) {
|
if (newSelectedApp !== undefined && setMissing != undefined) {
|
||||||
const submitAppFramework = {
|
const submitAppFramework = {
|
||||||
"description": newSelectedApp.description,
|
"description": newSelectedApp.description,
|
||||||
@@ -136,8 +138,14 @@ const AppSearchButtons = (props) => {
|
|||||||
|
|
||||||
const icon = foundApp.large_image
|
const icon = foundApp.large_image
|
||||||
var foundAppImage = AppImage
|
var foundAppImage = AppImage
|
||||||
if (foundApp.name !== undefined && foundApp.name !== null && !foundApp.name.includes(":default")) {
|
if (foundApp.name !== undefined && foundApp.name !== null && foundApp.name.length > 0 && !foundApp.name.includes(":default")) {
|
||||||
foundAppImage = foundApp.large_image
|
|
||||||
|
if (AppImage === undefined || AppImage === null || AppImage.length < 10) {
|
||||||
|
foundAppImage = foundApp.large_image
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newapp = findSpecificApp(appFramework, appType)
|
||||||
|
// const { userdata, globalUrl, appFramework, moreButton, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, getAppFramework, } = props
|
||||||
}
|
}
|
||||||
|
|
||||||
let xsValue = 12;
|
let xsValue = 12;
|
||||||
@@ -181,7 +189,7 @@ const AppSearchButtons = (props) => {
|
|||||||
width: 319,
|
width: 319,
|
||||||
height: 395,
|
height: 395,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
marginLeft: 70,
|
marginLeft: isMobile? null:70,
|
||||||
marginTop: 68,
|
marginTop: 68,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
@@ -204,9 +212,14 @@ const AppSearchButtons = (props) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: 10,
|
||||||
|
height: 10,
|
||||||
|
|
||||||
// width: 224,
|
// width: 224,
|
||||||
marginLeft: discoveryData === ("communication") ? 112 : 200,
|
//marginLeft: discoveryData === ("communication") ? 112 : 200,
|
||||||
width: "100%",
|
//width: "100%",
|
||||||
marginBottom: 23,
|
marginBottom: 23,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
background: "rgba(33, 33, 33, 1)",
|
background: "rgba(33, 33, 33, 1)",
|
||||||
@@ -220,6 +233,7 @@ const AppSearchButtons = (props) => {
|
|||||||
<Closeicon style={{ width: 16 }} />
|
<Closeicon style={{ width: 16 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{/*
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Delete app"
|
title="Delete app"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
@@ -259,6 +273,7 @@ const AppSearchButtons = (props) => {
|
|||||||
<DeleteIcon style={{ height: 15, width: 15, }} />
|
<DeleteIcon style={{ height: 15, width: 15, }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
TextField,
|
TextField,
|
||||||
Collapse,
|
Collapse,
|
||||||
|
Fade,
|
||||||
IconButton,
|
IconButton,
|
||||||
Avatar,
|
Avatar,
|
||||||
ButtonBase,
|
ButtonBase,
|
||||||
@@ -41,10 +42,13 @@ const AppSelection = props => {
|
|||||||
userdata,
|
userdata,
|
||||||
globalUrl,
|
globalUrl,
|
||||||
appFramework,
|
appFramework,
|
||||||
|
setAppFramework,
|
||||||
setActiveStep,
|
setActiveStep,
|
||||||
defaultSearch,
|
defaultSearch,
|
||||||
setDefaultSearch,
|
setDefaultSearch,
|
||||||
checkLogin,
|
checkLogin,
|
||||||
|
isAppPage=false
|
||||||
|
|
||||||
} = props;
|
} = props;
|
||||||
const [discoveryData, setDiscoveryData] = React.useState({})
|
const [discoveryData, setDiscoveryData] = React.useState({})
|
||||||
const [selectionOpen, setSelectionOpen] = React.useState(false)
|
const [selectionOpen, setSelectionOpen] = React.useState(false)
|
||||||
@@ -57,9 +61,10 @@ const AppSelection = props => {
|
|||||||
const [moreButton, setMoreButton] = useState(false);
|
const [moreButton, setMoreButton] = useState(false);
|
||||||
|
|
||||||
// const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
|
// const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
|
||||||
|
document.title = "Choose your apps"
|
||||||
const ref = useRef()
|
const ref = useRef()
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (newSelectedApp === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
|
if (newSelectedApp === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
|
||||||
@@ -98,74 +103,75 @@ const AppSelection = props => {
|
|||||||
else if (discoveryData === "IAM") {
|
else if (discoveryData === "IAM") {
|
||||||
appFramework.iam = submitNewApp
|
appFramework.iam = submitNewApp
|
||||||
}
|
}
|
||||||
setFrameworkItem(submitNewApp);
|
|
||||||
setSelectionOpen(false);
|
setFrameworkItem(submitNewApp)
|
||||||
console.log("Selected app changed (effect)");
|
setSelectionOpen(false)
|
||||||
|
|
||||||
|
if (setAppFramework !== undefined) {
|
||||||
|
setAppFramework(appFramework)
|
||||||
|
}
|
||||||
|
GetApps()
|
||||||
}, [newSelectedApp]);
|
}, [newSelectedApp]);
|
||||||
|
|
||||||
|
const reloadAppButtons = (framework) => {
|
||||||
|
var tempApps = []
|
||||||
|
const lastApps = {}
|
||||||
|
let endTypes = ["network", "assets", "iam"]
|
||||||
|
|
||||||
|
if (framework === undefined || framework === null || Object.keys(framework).length === 0) {
|
||||||
|
//window.location.href = "/welcome"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(framework).forEach(([key, value]) => {
|
||||||
|
// Overwrrite email properly
|
||||||
|
if (key.toLowerCase() === "communication") {
|
||||||
|
value["type"] = "email"
|
||||||
|
framework["email"] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.toLowerCase() === "other") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value.type = key;
|
||||||
|
if (endTypes.includes(value.type.toLowerCase())) {
|
||||||
|
lastApps[value.type] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPosted.type === value.type) {
|
||||||
|
value = lastPosted
|
||||||
|
}
|
||||||
|
|
||||||
|
tempApps.push(JSON.parse(JSON.stringify(value)));
|
||||||
|
});
|
||||||
|
|
||||||
|
tempApps.sort((a, b) => {
|
||||||
|
if (a.type.length > b.type.length) {
|
||||||
|
return -1;
|
||||||
|
} else if (a.type.length < b.type.length) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let lastType = lastPosted.type === undefined ? "" : lastPosted.type.toLowerCase()
|
||||||
|
if (endTypes.includes(lastType)) {
|
||||||
|
lastApps[lastPosted.type] = lastPosted
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moreButton) {
|
||||||
|
tempApps.push(JSON.parse(JSON.stringify(lastApps["network"])))
|
||||||
|
tempApps.push(JSON.parse(JSON.stringify(lastApps["assets"])))
|
||||||
|
tempApps.push(JSON.parse(JSON.stringify(lastApps["iam"])))
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppButtons(tempApps)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
var tempApps = []
|
reloadAppButtons(appFramework)
|
||||||
if (tempApps.length === 0) {
|
|
||||||
// Object.entries(appFramework).forEach(([key, value]) => {
|
|
||||||
// value.type = key;
|
|
||||||
// tempApps.push(value);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Define the custom sorting order
|
|
||||||
// const customSortingOrder = ["CASES", "SIEM", "ENDPOINT", "INTEL", "EMAIL"];
|
|
||||||
|
|
||||||
const lastApps = {}
|
|
||||||
let endTypes = ["network", "assets", "iam"]
|
|
||||||
|
|
||||||
if (appFramework === undefined || appFramework === null || Object.keys(appFramework).length === 0) {
|
|
||||||
//window.location.href = "/welcome"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Object.entries(appFramework).forEach(([key, value]) => {
|
|
||||||
if (key.toLowerCase() === "other" || key.toLowerCase() === "communication") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
value.type = key;
|
|
||||||
|
|
||||||
if (endTypes.includes(value.type.toLowerCase())) {
|
|
||||||
lastApps[value.type] = value
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastPosted.type === value.type) {
|
|
||||||
value = lastPosted
|
|
||||||
}
|
|
||||||
|
|
||||||
tempApps.push(JSON.parse(JSON.stringify(value)));
|
|
||||||
});
|
|
||||||
|
|
||||||
tempApps.sort((a, b) => {
|
|
||||||
if (a.type.length > b.type.length) {
|
|
||||||
return -1;
|
|
||||||
} else if (a.type.length < b.type.length) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let lastType = lastPosted.type === undefined ? "" : lastPosted.type.toLowerCase()
|
|
||||||
|
|
||||||
if (endTypes.includes(lastType)) {
|
|
||||||
lastApps[lastPosted.type] = lastPosted
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moreButton) {
|
|
||||||
tempApps.push(JSON.parse(JSON.stringify(lastApps["network"])))
|
|
||||||
tempApps.push(JSON.parse(JSON.stringify(lastApps["assets"])))
|
|
||||||
tempApps.push(JSON.parse(JSON.stringify(lastApps["iam"])))
|
|
||||||
}
|
|
||||||
|
|
||||||
setAppButtons(tempApps)
|
|
||||||
console.log("Updated appButtons: ", appButtons)
|
|
||||||
GetApps()
|
|
||||||
}
|
|
||||||
}, [lastPosted, moreButton])
|
}, [lastPosted, moreButton])
|
||||||
|
|
||||||
if (appFramework === undefined || appFramework === null || Object.keys(appFramework).length === 0) {
|
if (appFramework === undefined || appFramework === null || Object.keys(appFramework).length === 0) {
|
||||||
@@ -174,11 +180,6 @@ const AppSelection = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setFrameworkItem = (data) => {
|
const setFrameworkItem = (data) => {
|
||||||
console.log("Setting framework item: ", data, isCloud)
|
|
||||||
// if (!isCloud) {
|
|
||||||
// activateApp(data.id)
|
|
||||||
// }
|
|
||||||
|
|
||||||
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
|
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -241,31 +242,39 @@ const AppSelection = props => {
|
|||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((response) => {
|
||||||
if (responseJson === null) {
|
return response.json()
|
||||||
console.log("null-response from server")
|
})
|
||||||
const pretend_apps = [{
|
.then((responseJson) => {
|
||||||
"description": "TBD",
|
if (responseJson === null || responseJson === undefined) {
|
||||||
"id": "TBD",
|
console.log("null-response from server")
|
||||||
"large_image": "",
|
const pretend_apps = [{
|
||||||
"name": "TBD",
|
"description": "TBD",
|
||||||
"type": "TBD"
|
"id": "TBD",
|
||||||
}]
|
"large_image": "",
|
||||||
|
"name": "TBD",
|
||||||
|
"type": "TBD"
|
||||||
|
}]
|
||||||
|
|
||||||
setApps(pretend_apps)
|
setApps(pretend_apps)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseJson.success === false) {
|
if (responseJson.success === false) {
|
||||||
console.log("error loading apps: ", responseJson)
|
console.log("error loading apps: ", responseJson)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setApps(responseJson);
|
if (setAppFramework !== undefined) {
|
||||||
})
|
setAppFramework(responseJson)
|
||||||
.catch((error) => {
|
}
|
||||||
console.log("App loading error: " + error.toString());
|
|
||||||
})
|
setApps(responseJson)
|
||||||
|
reloadAppButtons(responseJson)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("App loading error: " + error.toString());
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onNodeSelect = (label) => {
|
const onNodeSelect = (label) => {
|
||||||
@@ -277,8 +286,6 @@ const AppSelection = props => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("NODESELECT: ", label)
|
|
||||||
|
|
||||||
setDiscoveryData(label)
|
setDiscoveryData(label)
|
||||||
setSelectionOpen(true)
|
setSelectionOpen(true)
|
||||||
setDefaultSearch(label.charAt(0).toUpperCase() + (label.substring(1)).toLowerCase())
|
setDefaultSearch(label.charAt(0).toUpperCase() + (label.substring(1)).toLowerCase())
|
||||||
@@ -304,209 +311,256 @@ const AppSelection = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse in={true}>
|
<Fade in={true} timeout={1250}>
|
||||||
<Tooltip
|
<div>
|
||||||
title="Back"
|
{/*
|
||||||
placement="top"
|
<Tooltip
|
||||||
style={{ zIndex: 10011 }}
|
title="Back"
|
||||||
>
|
placement="top"
|
||||||
<IconButton
|
style={{ zIndex: 10011 }}
|
||||||
style={{
|
>
|
||||||
}}
|
<IconButton
|
||||||
onClick={() => {
|
style={{
|
||||||
navigate('/welcome');
|
}}
|
||||||
window.location.reload();
|
onClick={() => {
|
||||||
}}
|
navigate('/welcome');
|
||||||
>
|
window.location.reload();
|
||||||
<ArrowBackIcon style={{ width: 20 }} />
|
}}
|
||||||
<Typography style={{fontSize : 16, marginLeft : 2}}>Back</Typography>
|
>
|
||||||
</IconButton>
|
<ArrowBackIcon style={{ width: 20 }} />
|
||||||
</Tooltip>
|
<Typography style={{fontSize : 16, marginLeft : 2}}>Back</Typography>
|
||||||
<div
|
</IconButton>
|
||||||
style={{
|
</Tooltip>
|
||||||
// minHeight: sizing,
|
*/}
|
||||||
// maxHeight: sizing,
|
<div
|
||||||
marginTop: 10,
|
style={{
|
||||||
width: isMobile ? 350 : 500,
|
// minHeight: sizing,
|
||||||
marginBottom: 25,
|
// maxHeight: sizing,
|
||||||
textAlign: isMobile ? "center" : null,
|
marginTop: 10,
|
||||||
}}
|
width: isMobile ? 350 : 500,
|
||||||
>
|
marginBottom: 25,
|
||||||
{selectionOpen ? (
|
textAlign: isMobile ? "center" : null,
|
||||||
<div
|
}}
|
||||||
style={{
|
>
|
||||||
width: isMobile ? 225 : 319,
|
{selectionOpen ? (
|
||||||
height: 395,
|
<div
|
||||||
flexShrink: 0,
|
style={{
|
||||||
marginLeft: 70,
|
width: isMobile ? 225 : 319,
|
||||||
marginTop: 68,
|
height: 395,
|
||||||
position: "absolute",
|
flexShrink: 0,
|
||||||
zIndex: 100,
|
marginLeft: 70,
|
||||||
borderRadius: 6,
|
marginTop: 68,
|
||||||
border: "1px solid var(--Container-Stroke, #494949)",
|
position: "absolute",
|
||||||
background: "var(--Container, #212121)",
|
zIndex: 100,
|
||||||
boxShadow: "8px 8px 32px 24px rgba(0, 0, 0, 0.16)",
|
borderRadius: 6,
|
||||||
}}
|
border: "1px solid var(--Container-Stroke, #494949)",
|
||||||
>
|
background: "var(--Container, #212121)",
|
||||||
<div style={{ display: "flex" }}>
|
boxShadow: "8px 8px 32px 24px rgba(0, 0, 0, 0.16)",
|
||||||
<div style={{ display: "flex", textAlign: "center", textTransform: "capitalize" }}>
|
}}
|
||||||
<Typography style={{ padding: 16, color: "#FFFFFF", textTransform: "capitalize" }}> {discoveryData} </Typography>
|
>
|
||||||
</div>
|
<div style={{ display: "flex" }}>
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex", textAlign: "center", textTransform: "capitalize" }}>
|
||||||
<Tooltip
|
<Typography style={{ padding: 16, color: "#FFFFFF", textTransform: "capitalize" }}> {discoveryData} </Typography>
|
||||||
title="Close"
|
</div>
|
||||||
placement="top"
|
<div style={{ display: "flex" }}>
|
||||||
style={{ zIndex: 10011 }}
|
<Tooltip
|
||||||
>
|
title="Close"
|
||||||
<IconButton
|
placement="top"
|
||||||
style={{
|
style={{ zIndex: 10011 }}
|
||||||
flex: 1,
|
>
|
||||||
// width: 224,
|
<IconButton
|
||||||
marginLeft: discoveryData === ("communication") ? 112 : 200,
|
style={{
|
||||||
width: "100%",
|
flex: 1,
|
||||||
marginBottom: 23,
|
|
||||||
fontSize: 16,
|
|
||||||
background: "rgba(33, 33, 33, 1)",
|
|
||||||
borderColor: "rgba(33, 33, 33, 1)",
|
|
||||||
borderRadius: 8,
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectionOpen(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon style={{ width: 16 }} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip
|
|
||||||
title="Delete app"
|
|
||||||
placement="bottom"
|
|
||||||
style={{ zIndex: 10011 }}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
style={{ zIndex: 12501, position: "absolute", top: 32, right: 16 }}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setSelectionOpen(false)
|
|
||||||
setDefaultSearch("")
|
|
||||||
const submitDeletedApp = {
|
|
||||||
"description": "",
|
|
||||||
"id": "remove",
|
|
||||||
"name": "",
|
|
||||||
"type": discoveryData
|
|
||||||
}
|
|
||||||
setFrameworkItem(submitDeletedApp)
|
|
||||||
setNewSelectedApp({})
|
|
||||||
setTimeout(() => {
|
|
||||||
setDiscoveryData({})
|
|
||||||
setFrameworkItem(submitDeletedApp)
|
|
||||||
setNewSelectedApp({})
|
|
||||||
}, 1000)
|
|
||||||
//setAppName(discoveryData.cases.name)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DeleteIcon style={{ color: "white", height: 15, width: 15, }} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{ width: "100%", border: "1px #494949 solid" }}
|
|
||||||
/>
|
|
||||||
<AppSearch
|
|
||||||
defaultSearch={defaultSearch}
|
|
||||||
newSelectedApp={newSelectedApp}
|
|
||||||
setNewSelectedApp={setNewSelectedApp}
|
|
||||||
userdata={userdata}
|
|
||||||
// cy={cy}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<Typography
|
|
||||||
variant="h4"
|
|
||||||
style={{
|
|
||||||
marginLeft: 8,
|
|
||||||
marginTop: isMobile ? null : 40,
|
|
||||||
marginRight: 30,
|
|
||||||
marginBottom: 0,
|
|
||||||
}}
|
|
||||||
color="rgba(241, 241, 241, 1)"
|
|
||||||
>
|
|
||||||
Find your apps
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
style={{
|
|
||||||
marginLeft: 8,
|
|
||||||
marginTop: 10,
|
|
||||||
marginRight: 30,
|
|
||||||
marginBottom: 40,
|
|
||||||
}}
|
|
||||||
color="rgba(158, 158, 158, 1)"
|
|
||||||
>
|
|
||||||
Select the apps you work with and we will connect them for you.
|
|
||||||
</Typography>
|
|
||||||
<Grid rowSpacing={1} columnSpacing={2} container >
|
|
||||||
{appButtons.map((appData, index) => {
|
|
||||||
// This is here due to a memory issue with setting apps properly
|
|
||||||
if (appData.id === "remove") {
|
|
||||||
console.log("Removed as appdata is overridden: ", appData)
|
|
||||||
|
|
||||||
appData = {
|
position: "absolute",
|
||||||
"count": 0,
|
top: 0,
|
||||||
"description": "",
|
right: 6,
|
||||||
"id": "",
|
}}
|
||||||
"large_image": "",
|
onClick={() => {
|
||||||
"name": "",
|
setSelectionOpen(false)
|
||||||
"type": appData.type,
|
}}
|
||||||
}
|
>
|
||||||
}
|
<CloseIcon style={{ width: 16 }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
title="Remove selection"
|
||||||
|
placement="bottom"
|
||||||
|
style={{ zIndex: 10011 }}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
style={{ zIndex: 12501, position: "absolute", top: 26, right: 6, }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setSelectionOpen(false)
|
||||||
|
setDefaultSearch("")
|
||||||
|
const submitDeletedApp = {
|
||||||
|
"description": "",
|
||||||
|
"id": "remove",
|
||||||
|
"name": "",
|
||||||
|
"type": discoveryData
|
||||||
|
}
|
||||||
|
|
||||||
const appName = appData.name
|
setFrameworkItem(submitDeletedApp)
|
||||||
const AppImage = appData.large_image
|
setNewSelectedApp({})
|
||||||
const appType = appData.type
|
setTimeout(() => {
|
||||||
|
setDiscoveryData({})
|
||||||
|
setFrameworkItem(submitDeletedApp)
|
||||||
|
setNewSelectedApp({})
|
||||||
|
}, 200)
|
||||||
|
//setAppName(discoveryData.cases.name)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon style={{ color: "white", height: 15, width: 15, }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ width: "100%", border: "1px #494949 solid" }}
|
||||||
|
/>
|
||||||
|
<AppSearch
|
||||||
|
defaultSearch={defaultSearch}
|
||||||
|
newSelectedApp={newSelectedApp}
|
||||||
|
setNewSelectedApp={setNewSelectedApp}
|
||||||
|
userdata={userdata}
|
||||||
|
// cy={cy}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{
|
||||||
|
!isAppPage && (
|
||||||
|
<>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
style={{
|
||||||
|
marginLeft: 8,
|
||||||
|
marginTop: isMobile ? null : 40,
|
||||||
|
marginRight: 30,
|
||||||
|
marginBottom: 0,
|
||||||
|
}}
|
||||||
|
color="rgba(241, 241, 241, 1)"
|
||||||
|
>
|
||||||
|
Find your apps
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
style={{
|
||||||
|
marginLeft: 8,
|
||||||
|
marginTop: 10,
|
||||||
|
marginRight: 30,
|
||||||
|
marginBottom: 40,
|
||||||
|
}}
|
||||||
|
color="rgba(158, 158, 158, 1)"
|
||||||
|
>
|
||||||
|
Select the apps you work with and we will connect them for you.
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
isAppPage && (
|
||||||
|
<div style={{marginBottom: 20}}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "rgba(158, 158, 158, 1)",
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Your organization has no apps yet, select your starting apps here
|
||||||
|
or discover more apps using the <span
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/apps2?tab=all_apps")
|
||||||
|
}}
|
||||||
|
style={{ color: "#FF8444", fontWeight: "medium", fontSize: 16, cursor:"pointer" }}>App Library</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Grid rowSpacing={1} columnSpacing={2} container >
|
||||||
|
{appButtons.map((appData, index) => {
|
||||||
|
// This is here due to a memory issue with setting apps properly
|
||||||
|
if (appData.id === "remove") {
|
||||||
|
appData = {
|
||||||
|
"count": 0,
|
||||||
|
"description": "",
|
||||||
|
"id": "",
|
||||||
|
"large_image": "",
|
||||||
|
"name": "",
|
||||||
|
"type": appData.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<AppSearchButtons
|
|
||||||
appFramework={appFramework}
|
|
||||||
index={index}
|
|
||||||
totalApps={appButtons.length}
|
|
||||||
appName={appName}
|
|
||||||
appType={appType}
|
|
||||||
AppImage={AppImage}
|
|
||||||
defaultSearch={defaultSearch}
|
|
||||||
finishedApps={finishedApps}
|
|
||||||
onNodeSelect={onNodeSelect}
|
|
||||||
discoveryData={discoveryData}
|
|
||||||
setDiscoveryData={setDiscoveryData}
|
|
||||||
setDefaultSearch={setDefaultSearch}
|
|
||||||
apps={apps}
|
|
||||||
setMoreButton={setMoreButton}
|
|
||||||
moreButton={moreButton}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Grid>
|
|
||||||
</div>
|
|
||||||
{!moreButton ? (
|
|
||||||
<div style={{ width: "100%", marginLeft: isMobile ? 80 : 200, marginBottom: 20, textAlign: isMobile ? "center" : null }}>
|
|
||||||
<Link style={{ color: "#FF8444" }} onClick={() => {
|
|
||||||
setMoreButton(true)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
if (appData === undefined || appData === null || appData.name === undefined || appData.name === "") {
|
||||||
navigate("/welcome?tab=2")
|
appData = {
|
||||||
}, 250)
|
"count": 0,
|
||||||
}}
|
"description": "",
|
||||||
>See More Apps</Link>
|
"id": "",
|
||||||
</div>) : ""}
|
"large_image": "",
|
||||||
<div style={{ flexDirection: "row", width: isMobile ? 340 : null, textAlign: isMobile ? "center" : null }}>
|
"name": "",
|
||||||
<Button variant="contained" type="submit" fullWidth style={bottomButtonStyle} onClick={() => {
|
"type": appData.type,
|
||||||
navigate("/welcome?tab=3")
|
}
|
||||||
setActiveStep(2)
|
}
|
||||||
}}>
|
|
||||||
Continue
|
//console.log("APP: ", appData)
|
||||||
</Button>
|
|
||||||
</div>
|
const appName = appData.name
|
||||||
</Collapse>
|
const AppImage = appData.large_image
|
||||||
|
const appType = appData.type
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppSearchButtons
|
||||||
|
appFramework={appFramework}
|
||||||
|
index={index}
|
||||||
|
totalApps={appButtons.length}
|
||||||
|
|
||||||
|
appName={appName}
|
||||||
|
appType={appType}
|
||||||
|
AppImage={AppImage}
|
||||||
|
|
||||||
|
defaultSearch={defaultSearch}
|
||||||
|
finishedApps={finishedApps}
|
||||||
|
onNodeSelect={onNodeSelect}
|
||||||
|
discoveryData={discoveryData}
|
||||||
|
setDiscoveryData={setDiscoveryData}
|
||||||
|
setDefaultSearch={setDefaultSearch}
|
||||||
|
apps={apps}
|
||||||
|
setMoreButton={setMoreButton}
|
||||||
|
moreButton={moreButton}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!isAppPage && (
|
||||||
|
<>
|
||||||
|
{!moreButton ? (
|
||||||
|
<div style={{ width: "100%", marginLeft: isMobile ? 80 : 200, marginBottom: 20, textAlign: isMobile ? "center" : null }}>
|
||||||
|
<Link style={{ color: "#FF8444" }} onClick={() => {
|
||||||
|
setMoreButton(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate("/welcome?tab=2")
|
||||||
|
}, 250)
|
||||||
|
}}
|
||||||
|
>See More Apps</Link>
|
||||||
|
</div>) : ""}
|
||||||
|
|
||||||
|
<div style={{ flexDirection: "row", width: isMobile ? 340 : null, textAlign: isMobile ? "center" : null }}>
|
||||||
|
<Button variant="contained" type="submit" fullWidth style={bottomButtonStyle} onClick={() => {
|
||||||
|
navigate("/usecases2")
|
||||||
|
setActiveStep(2)
|
||||||
|
}}>
|
||||||
|
See usecases
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Fade>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,9 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
|
|||||||
const Appsearch = props => {
|
const Appsearch = props => {
|
||||||
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props
|
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
|
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
|
||||||
const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs
|
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 [formMail, setFormMail] = React.useState("");
|
||||||
const [message, setMessage] = React.useState("");
|
const [message, setMessage] = React.useState("");
|
||||||
const [formMessage, setFormMessage] = React.useState("");
|
const [formMessage, setFormMessage] = React.useState("");
|
||||||
|
|||||||
@@ -45,21 +45,21 @@ const AuthenticationItem = (props) => {
|
|||||||
data.fields = [
|
data.fields = [
|
||||||
{
|
{
|
||||||
key: "url",
|
key: "url",
|
||||||
value: "Secret. Replaced during app execution!",
|
value: "URL Secret. Replaced during runtime",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "client_id",
|
key: "client_id",
|
||||||
value: "Secret. Replaced during app execution!",
|
value: "ClientID Secret. Replaced during runtime.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "client_secret",
|
key: "client_secret",
|
||||||
value: "Secret. Replaced during app execution!",
|
value: "Client Secret. Replaced during runtime.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "scope",
|
key: "scope",
|
||||||
value: "Secret. Replaced during app execution!",
|
value: "Scope Secret. Replaced during runtime.",
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteAuthentication = (data) => {
|
const deleteAuthentication = (data) => {
|
||||||
@@ -152,7 +152,7 @@ const AuthenticationItem = (props) => {
|
|||||||
src={data.app.large_image}
|
src={data.app.large_image}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 50,
|
maxWidth: 50,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
style={{ minWidth: 75, maxWidth: 75 }}
|
style={{ minWidth: 75, maxWidth: 75 }}
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ const AuthenticationData = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -304,7 +304,7 @@ const AuthenticationData = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -344,12 +344,13 @@ const AuthenticationData = (props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAuthenticationModalOpen(false);
|
setAuthenticationModalOpen(false);
|
||||||
}}
|
}}
|
||||||
color="primary"
|
color="secondary"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
style={{ borderRadius: "0px" }}
|
style={{ borderRadius: "0px" }}
|
||||||
|
variant="outlined"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAuthenticationOptions(authenticationOption);
|
setAuthenticationOptions(authenticationOption);
|
||||||
handleSubmitCheck();
|
handleSubmitCheck();
|
||||||
|
|||||||
@@ -104,14 +104,19 @@ const AuthenticationData = (props) => {
|
|||||||
toast("Failed to set app auth: " + responseJson.reason);
|
toast("Failed to set app auth: " + responseJson.reason);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setSubmitSuccessful(true)
|
setSubmitSuccessful(true)
|
||||||
if (getAppAuthentication !== undefined) {
|
if (getAppAuthentication !== undefined) {
|
||||||
getAppAuthentication(true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setAuthenticationModalOpen !== undefined) {
|
if (workflow !== undefined && workflow !== null && workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
|
||||||
setAuthenticationModalOpen(false)
|
getAppAuthentication(true, false, undefined, workflow.org_id)
|
||||||
}
|
} else {
|
||||||
|
getAppAuthentication(true, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setAuthenticationModalOpen !== undefined) {
|
||||||
|
setAuthenticationModalOpen(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -289,7 +294,7 @@ const AuthenticationData = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -323,10 +328,10 @@ const AuthenticationData = (props) => {
|
|||||||
|
|
||||||
const authenticationButtons = <span>
|
const authenticationButtons = <span>
|
||||||
<Button
|
<Button
|
||||||
style={{ borderRadius: theme.palette.borderRadius, marginTop: authFieldsOnly ? 20 : 0 }}
|
style={{ borderRadius: theme.palette?.borderRadius, marginTop: authFieldsOnly ? 20 : 0 }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAuthenticationOptions(authenticationOption);
|
setAuthenticationOptions(authenticationOption)
|
||||||
handleSubmitCheck();
|
handleSubmitCheck()
|
||||||
}}
|
}}
|
||||||
variant={"contained"}
|
variant={"contained"}
|
||||||
disabled={submitSuccessful}
|
disabled={submitSuccessful}
|
||||||
@@ -432,7 +437,7 @@ const AuthenticationData = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useContext, memo, useMemo } from 'react';
|
||||||
|
|
||||||
import theme from '../theme.jsx';
|
import theme from '../theme.jsx';
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
@@ -37,13 +37,14 @@ import {
|
|||||||
} from 'reaviz';
|
} from 'reaviz';
|
||||||
|
|
||||||
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
|
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
|
||||||
|
import { Context } from '../context/ContextApi.jsx';
|
||||||
|
|
||||||
const LineChartWrapper = ({keys, inputname, height, width}) => {
|
const LineChartWrapper = ({keys, inputname, height, width}) => {
|
||||||
const [hovered, setHovered] = useState("");
|
const [hovered, setHovered] = useState("");
|
||||||
const inputdata = keys.data === undefined ? keys : keys.data
|
const inputdata = keys.data === undefined ? keys : keys.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: 30, marginTop: 15, backgroundColor: theme.palette.platformColor, overflow: "hidden", }}>
|
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, padding: 30, marginTop: 15, backgroundColor: theme.palette.platformColor, overflow: "hidden", }}>
|
||||||
<Typography variant="h6" style={{marginBotton: 15, }}>
|
<Typography variant="h6" style={{marginBotton: 15, }}>
|
||||||
{inputname}
|
{inputname}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -82,8 +83,8 @@ const AppStats = (defaultprops) => {
|
|||||||
const [workflows, setWorkflows] = useState(inputWorkflows === undefined ? [] : inputWorkflows)
|
const [workflows, setWorkflows] = useState(inputWorkflows === undefined ? [] : inputWorkflows)
|
||||||
const [resultRows, setResultRows] = useState([])
|
const [resultRows, setResultRows] = useState([])
|
||||||
const [resultLoading, setResultLoading] = useState(true)
|
const [resultLoading, setResultLoading] = useState(true)
|
||||||
|
|
||||||
const includedExecutions = selectedOrganization.sync_features.app_executions !== undefined ? selectedOrganization.sync_features.app_executions.limit : 0
|
const includedExecutions = selectedOrganization?.sync_features?.app_executions !== undefined ? selectedOrganization?.sync_features?.app_executions?.limit : 0
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workflows === undefined || workflows === null || workflows.length === 0) {
|
if (workflows === undefined || workflows === null || workflows.length === 0) {
|
||||||
@@ -92,7 +93,6 @@ const AppStats = (defaultprops) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getWorkflowStats = async (workflow, startTime, endTime) => {
|
const getWorkflowStats = async (workflow, startTime, endTime) => {
|
||||||
if (!userdata.support) {
|
if (!userdata.support) {
|
||||||
return workflow
|
return workflow
|
||||||
@@ -134,6 +134,9 @@ const AppStats = (defaultprops) => {
|
|||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log("Error getting workflow stats: " + error);
|
||||||
|
return workflow
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
@@ -159,8 +162,6 @@ const AppStats = (defaultprops) => {
|
|||||||
|
|
||||||
const loadWorkflowStats = (foundWorkflows, startTime, endTime) => {
|
const loadWorkflowStats = (foundWorkflows, startTime, endTime) => {
|
||||||
if (!userdata.support) {
|
if (!userdata.support) {
|
||||||
console.log("Not support")
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,8 +486,13 @@ const AppStats = (defaultprops) => {
|
|||||||
setApprunCosts(appcostRuns)
|
setApprunCosts(appcostRuns)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStats = () => {
|
const getStats = (orgid) => {
|
||||||
fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`, {
|
|
||||||
|
if (orgid === undefined || orgid === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/orgs/${orgid}/stats`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -516,8 +522,10 @@ const AppStats = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getStats()
|
if(selectedOrganization?.id?.length > 0) {
|
||||||
}, [])
|
getStats(selectedOrganization.id)
|
||||||
|
}
|
||||||
|
}, [selectedOrganization])
|
||||||
|
|
||||||
const paperStyle = {
|
const paperStyle = {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
@@ -636,7 +644,7 @@ const AppStats = (defaultprops) => {
|
|||||||
|
|
||||||
const data = (
|
const data = (
|
||||||
<div className="content" style={{width: "100%", margin: "auto", }}>
|
<div className="content" style={{width: "100%", margin: "auto", }}>
|
||||||
<Typography variant="body1" style={{margin: "auto", marginLeft: 10, marginBottom: 20, }} color="textSecondary">
|
<Typography style={{margin: "auto", marginLeft: 10, marginBottom: 20, fontSize: 16}} color="textSecondary">
|
||||||
All shown statistics are gathered from <a
|
All shown statistics are gathered from <a
|
||||||
href={`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`}
|
href={`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -719,6 +727,40 @@ const AppStats = (defaultprops) => {
|
|||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
|
{clickedFromOrgTab? (
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs} style={{ flex: 1 }}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
|
<div style={{ flex: 1, maxWidth: "200px", }}>
|
||||||
|
<DateTimePicker
|
||||||
|
sx={{
|
||||||
|
marginTop: 1,
|
||||||
|
marginLeft: 1,
|
||||||
|
}}
|
||||||
|
ampm={false}
|
||||||
|
label="Search from"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value={startTime}
|
||||||
|
onChange={handleStartTimeChange}
|
||||||
|
renderInput={(params) => <TextField {...params} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1, maxWidth: "200px",}}>
|
||||||
|
<DateTimePicker
|
||||||
|
sx={{
|
||||||
|
marginTop: 1,
|
||||||
|
marginLeft: 1,
|
||||||
|
}}
|
||||||
|
ampm={false}
|
||||||
|
label="Search until"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value={endTime}
|
||||||
|
onChange={handleEndTimeChange}
|
||||||
|
renderInput={(params) => <TextField {...params} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LocalizationProvider>
|
||||||
|
):(
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs} style={{flex: 1, }}>
|
<LocalizationProvider dateAdapter={AdapterDayjs} style={{flex: 1, }}>
|
||||||
<div style={{display: "flex", flexDirection: "column", }}>
|
<div style={{display: "flex", flexDirection: "column", }}>
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
@@ -751,6 +793,8 @@ const AppStats = (defaultprops) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useContext } from "react";
|
||||||
import ReactGA from 'react-ga4';
|
import ReactGA from 'react-ga4';
|
||||||
import theme from "../theme.jsx";
|
import theme from "../theme.jsx";
|
||||||
import { ToastContainer, toast } from "react-toastify"
|
import { ToastContainer, toast } from "react-toastify"
|
||||||
|
|
||||||
|
import {
|
||||||
|
CheckCircle as CheckCircleIcon,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
Divider,
|
Divider,
|
||||||
Button,
|
Button,
|
||||||
|
Tooltip,
|
||||||
Grid,
|
Grid,
|
||||||
Card,
|
Card,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import {
|
||||||
|
red,
|
||||||
|
green,
|
||||||
|
} from "../views/AngularWorkflow.jsx"
|
||||||
|
import { Context } from "../context/ContextApi.jsx";
|
||||||
|
|
||||||
//import { useAlert
|
//import { useAlert
|
||||||
|
|
||||||
const Branding = (props) => {
|
const Branding = (props) => {
|
||||||
@@ -20,7 +31,8 @@ const Branding = (props) => {
|
|||||||
const [publishingInfo, setPublishingInfo] = useState("");
|
const [publishingInfo, setPublishingInfo] = useState("");
|
||||||
const [publishRequirements, setPublishRequirements] = useState([])
|
const [publishRequirements, setPublishRequirements] = useState([])
|
||||||
|
|
||||||
|
const { leftSideBarOpenByClick } = useContext(Context)
|
||||||
|
|
||||||
const handleEditOrg = (joinStatus) => {
|
const handleEditOrg = (joinStatus) => {
|
||||||
const data = {
|
const data = {
|
||||||
"org_id": selectedOrganization.id,
|
"org_id": selectedOrganization.id,
|
||||||
@@ -45,7 +57,7 @@ const Branding = (props) => {
|
|||||||
toast("Failed updating org: ", responseJson.reason);
|
toast("Failed updating org: ", responseJson.reason);
|
||||||
} else {
|
} else {
|
||||||
if (joinStatus == "join") {
|
if (joinStatus == "join") {
|
||||||
setPublishingInfo("Your organization is now part of the Creator Incentive Program. You can now create and publish content to your organization's page. You can also create a creator account to manage your organization's content.")
|
setPublishingInfo("Your organization is now part of the Partner Program. You can now create, publish and manage content for your organization's public page.")
|
||||||
} else {
|
} else {
|
||||||
setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.")
|
setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.")
|
||||||
}
|
}
|
||||||
@@ -70,7 +82,15 @@ const Branding = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isOrganizationReady = () => {
|
const isOrganizationReady = () => {
|
||||||
console.log("Is organization ready?")
|
|
||||||
|
// Check if it's a suborg
|
||||||
|
if (selectedOrganization.creator_org !== "") {
|
||||||
|
const comment = "Child orgs can't become creators"
|
||||||
|
if (!publishRequirements.includes(comment)) {
|
||||||
|
setPublishRequirements([...publishRequirements, comment])
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// A simple checklist to ensure the button shows up properly
|
// A simple checklist to ensure the button shows up properly
|
||||||
if (selectedOrganization.name === selectedOrganization.org) {
|
if (selectedOrganization.name === selectedOrganization.org) {
|
||||||
@@ -82,15 +102,6 @@ const Branding = (props) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a suborg
|
|
||||||
if (selectedOrganization.creator_org !== "") {
|
|
||||||
const comment = "Child orgs can't become creators"
|
|
||||||
if (!publishRequirements.includes(comment)) {
|
|
||||||
setPublishRequirements([...publishRequirements, comment])
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedOrganization.large_image === "" || selectedOrganization.large_image === theme.palette.defaultImage) {
|
if (selectedOrganization.large_image === "" || selectedOrganization.large_image === theme.palette.defaultImage) {
|
||||||
const comment = "Add a logo for your organization"
|
const comment = "Add a logo for your organization"
|
||||||
if (!publishRequirements.includes(comment)) {
|
if (!publishRequirements.includes(comment)) {
|
||||||
@@ -102,38 +113,89 @@ const Branding = (props) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPublished = selectedOrganization.creator_id === ""
|
||||||
|
const leadinfo = selectedOrganization.lead_info === undefined || selectedOrganization.lead_info === null || selectedOrganization.lead_info === "" ? "" : JSON.stringify(selectedOrganization.lead_info)
|
||||||
|
const isPartner = leadinfo.includes("partner")
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: clickedFromOrgTab? 1030: "auto", padding: 27, height: "auto", backgroundColor: '#212121', borderRadius: '16px', }}>
|
<div style={{ width: clickedFromOrgTab? "100%": "auto", height: "100%", minHeight: 1100, boxSizing: 'border-box', transition: "width 0.3s ease", padding: "27px 10px 19px 27px", height: "auto", backgroundColor: '#212121', borderRadius: '16px', }}>
|
||||||
<h2 style={{marginTop: clickedFromOrgTab ?0:null,}}>
|
<div style={{height: 843, overflowY: "auto",}}>
|
||||||
Branding
|
<div style={{width: "100%", overflowX: 'hidden', }}>
|
||||||
</h2>
|
<Typography style={{fontSize: 24, fontWeight: "bold", marginTop: clickedFromOrgTab ?0:null,}}>
|
||||||
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
|
Partner Status & Branding
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 10, marginBottom: 10, fontSize: 16 }}>
|
||||||
You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more.
|
You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" color="textSecondary" style={{display: 'flex', marginTop: 20, marginBottom: 10 }}>
|
||||||
|
{isPublished ? <CheckCircleIcon style={{color: red, }} /> : <CheckCircleIcon style={{color: green, }} />}
|
||||||
|
<span style={{marginLeft: 10, color: isPublished ? red : green, fontSize: 16 }}>{isPublished ? "Not Published" : "Published"}</span>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<a href="https://shuffler.io/partners" target="_blank" style={{ textDecoration: "none", }}>
|
||||||
|
<Typography variant="body1" color="textSecondary" style={{display: 'flex', marginTop: 20, marginBottom: 10 }}>
|
||||||
|
{!isPartner ? <CheckCircleIcon style={{color: red, }} /> : <CheckCircleIcon style={{color: green, }} />}
|
||||||
|
<Tooltip title="Official Partner Program (manual verification)" placement="top" arrow>
|
||||||
|
<span style={{marginLeft: 10, color: !isPartner ? red : green, fontSize: 16}}>{!isPartner? "Not Officially Partnered" : "Officially Partnered"}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Typography>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{!isPublished ? (
|
||||||
|
<a
|
||||||
|
href={`/partners/${selectedOrganization.creator_id}/edit`}
|
||||||
|
target="_blank"
|
||||||
|
style={{ textDecoration: "none" }} // Optional: remove underline
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 10,
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: 16
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Modify Public Partner Details
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
disabled
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 10,
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: 16
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Modify Public Partner Details
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<Divider style={{marginTop: 50, marginBottom: 50, }} />
|
<Divider style={{marginTop: 50, marginBottom: 50, }} />
|
||||||
<h2>
|
<Typography style={{fontSize: 24, fontWeight: "bold"}}>
|
||||||
Creator Incentive Program
|
Partner Program
|
||||||
</h2>
|
</Typography>
|
||||||
<div style={{ display: "flex", width: 900, }}>
|
<div style={{ display: "flex", width: 900, marginTop: 10}}>
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
<Typography variant="body1" color="textSecondary">
|
<Typography variant="body1" color="textSecondary" style={{fontSize: 16}}>
|
||||||
By changing publishing settings, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Terms of Service</a>, and acknowledge that your organization's non-sensitive data will be added as a <a target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}} href="https://shuffler.io/creators">creator account</a>. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization is reversible.<div/>Support: <a href="mailto:support@shuffler.io"target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>support@shuffler.io</a>
|
By changing publishing settings, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Terms of Service</a>, and acknowledge that your organization's non-sensitive data will be added as a <a target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}} href="https://shuffler.io/creators">creator account</a>. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization IS reversible.<div/>Support: <a href="mailto:support@shuffler.io"target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>support@shuffler.io</a>
|
||||||
</Typography>
|
</Typography>
|
||||||
{selectedOrganization.creator_id == "" ?
|
{selectedOrganization.creator_id == "" ?
|
||||||
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
|
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
|
||||||
|
|
||||||
</Typography>
|
</Typography>
|
||||||
:
|
:
|
||||||
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
|
null
|
||||||
|
|
||||||
<a href={`/creators/${selectedOrganization.creator_id}`} target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Modify your creator organization</a>
|
|
||||||
</Typography>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style={{ height: 40, marginTop: 10, width: 300, }}
|
style={{ height: 40, marginTop: 10, width: 300, textTransform: 'none', fontSize: 18, backgroundColor: "#ff8544", color: "#1a1a1a" }}
|
||||||
variant={selectedOrganization.creator_id == "" ? "contained" : "outlined"}
|
variant={selectedOrganization.creator_id == "" ? "contained" : "outlined"}
|
||||||
color={selectedOrganization.creator_id == "" ? "primary" : "secondary"}
|
color={selectedOrganization.creator_id == "" ? "primary" : "secondary"}
|
||||||
disabled={!isOrganizationReady()}
|
disabled={!isOrganizationReady()}
|
||||||
@@ -141,7 +203,7 @@ const Branding = (props) => {
|
|||||||
handleChangePublishing();
|
handleChangePublishing();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedOrganization.creator_id == "" ? "Join" : "Leave"} Creators
|
{selectedOrganization.creator_id == "" ? "Join" : "Leave"} Partner Program
|
||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "white", }}>
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "white", }}>
|
||||||
@@ -159,6 +221,8 @@ const Branding = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useContext, memo } from "react";
|
||||||
import theme from "../theme.jsx";
|
import theme from "../theme.jsx";
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import ReactJson from "react-json-view";
|
import ReactJson from "react-json-view-ssr";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Typography,
|
Typography,
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
|
Skeleton,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -47,6 +48,7 @@ import {
|
|||||||
VisibilityOff as VisibilityOffIcon,
|
VisibilityOff as VisibilityOffIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { validateJson, } from "../views/Workflows.jsx";
|
import { validateJson, } from "../views/Workflows.jsx";
|
||||||
|
import { Context } from "../context/ContextApi.jsx";
|
||||||
|
|
||||||
const scrollStyle1 = {
|
const scrollStyle1 = {
|
||||||
height: 100,
|
height: 100,
|
||||||
@@ -65,7 +67,7 @@ const scrollStyle2 = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const CacheView = (props) => {
|
const CacheView = memo((props) => {
|
||||||
const { globalUrl, userdata, serverside, orgId, isSelectedDataStore } = props;
|
const { globalUrl, userdata, serverside, orgId, isSelectedDataStore } = props;
|
||||||
const [orgCache, setOrgCache] = React.useState("");
|
const [orgCache, setOrgCache] = React.useState("");
|
||||||
const [listCache, setListCache] = React.useState([]);
|
const [listCache, setListCache] = React.useState([]);
|
||||||
@@ -78,11 +80,13 @@ const CacheView = (props) => {
|
|||||||
const [cacheCursor, setCacheCursor] = React.useState("");
|
const [cacheCursor, setCacheCursor] = React.useState("");
|
||||||
const [dataValue, setDataValue] = React.useState({});
|
const [dataValue, setDataValue] = React.useState({});
|
||||||
const [editCache, setEditCache] = React.useState(false);
|
const [editCache, setEditCache] = React.useState(false);
|
||||||
|
const [cachedLoaded, setCachedLoaded] = React.useState(false);
|
||||||
const [show, setShow] = useState({});
|
const [show, setShow] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listOrgCache(orgId);
|
if(orgId?.length >0){
|
||||||
}, []);
|
listOrgCache(orgId);
|
||||||
|
}
|
||||||
|
}, [orgId]);
|
||||||
|
|
||||||
const listOrgCache = (orgId) => {
|
const listOrgCache = (orgId) => {
|
||||||
fetch(globalUrl + `/api/v1/orgs/${orgId}/list_cache`, {
|
fetch(globalUrl + `/api/v1/orgs/${orgId}/list_cache`, {
|
||||||
@@ -104,6 +108,7 @@ const CacheView = (props) => {
|
|||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson.success === true) {
|
if (responseJson.success === true) {
|
||||||
setListCache(responseJson.keys);
|
setListCache(responseJson.keys);
|
||||||
|
setCachedLoaded(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseJson.cursor !== undefined && responseJson.cursor !== null && responseJson.cursor !== "") {
|
if (responseJson.cursor !== undefined && responseJson.cursor !== null && responseJson.cursor !== "") {
|
||||||
@@ -232,6 +237,34 @@ const CacheView = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleReactJsonClipboard = (copy) => {
|
||||||
|
const elementName = "copy_element_shuffle";
|
||||||
|
let copyText = document.getElementById(elementName);
|
||||||
|
|
||||||
|
if (copyText) {
|
||||||
|
if (copy.namespace && copy.name && copy.src) {
|
||||||
|
copy = copy.src;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clipboard = navigator.clipboard;
|
||||||
|
if (!clipboard) {
|
||||||
|
toast("Can only copy over HTTPS (port 3443)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stringified = JSON.stringify(copy);
|
||||||
|
if (stringified.startsWith('"') && stringified.endsWith('"')) {
|
||||||
|
stringified = stringified.slice(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(stringified);
|
||||||
|
toast("Copied value to clipboard, NOT json path.");
|
||||||
|
} else {
|
||||||
|
console.log("Failed to copy from " + elementName + ": ", copyText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const modalView = (
|
const modalView = (
|
||||||
// console.log("key:", dataValue.key),
|
// console.log("key:", dataValue.key),
|
||||||
//console.log("value:",dataValue.value),
|
//console.log("value:",dataValue.value),
|
||||||
@@ -241,11 +274,23 @@ const CacheView = (props) => {
|
|||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
style: {
|
sx: {
|
||||||
backgroundColor: theme.palette.surfaceColor,
|
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
||||||
color: "white",
|
border: theme?.palette?.DialogStyle?.border,
|
||||||
minWidth: "800px",
|
minWidth: "800px",
|
||||||
minHeight: "320px",
|
minHeight: "320px",
|
||||||
|
fontFamily: theme?.typography?.fontFamily,
|
||||||
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||||
|
zIndex: 1000,
|
||||||
|
'& .MuiDialogContent-root': {
|
||||||
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||||
|
},
|
||||||
|
'& .MuiDialogTitle-root': {
|
||||||
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||||
|
},
|
||||||
|
'& .MuiDialogActions-root': {
|
||||||
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -254,7 +299,7 @@ const CacheView = (props) => {
|
|||||||
{ editCache ? "Edit Cache" : "Add Cache" }
|
{ editCache ? "Edit Cache" : "Add Cache" }
|
||||||
</span>
|
</span>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<div style={{ paddingLeft: "30px", paddingRight: '30px' }}>
|
<div style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: "#212121", }}>
|
||||||
Key
|
Key
|
||||||
<TextField
|
<TextField
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -278,7 +323,7 @@ const CacheView = (props) => {
|
|||||||
onChange={(e) => setKey(e.target.value)}
|
onChange={(e) => setKey(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ paddingLeft: 30, paddingRight: 30 }}>
|
<div style={{ paddingLeft: 30, paddingRight: 30, backgroundColor: "#212121" }}>
|
||||||
<div style={{display: "flex", }}>
|
<div style={{display: "flex", }}>
|
||||||
<Typography style={{marginTop: 25, marginBottom: 0, flex: 20, }}>
|
<Typography style={{marginTop: 25, marginBottom: 0, flex: 20, }}>
|
||||||
Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON)
|
Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON)
|
||||||
@@ -320,7 +365,7 @@ const CacheView = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<DialogActions style={{ paddingLeft: "30px", paddingRight: '30px' }}>
|
<DialogActions style={{ paddingLeft: "30px", paddingRight: '30px' }}>
|
||||||
<Button
|
<Button
|
||||||
style={{ borderRadius: "0px" }}
|
style={{ borderRadius: "2px", fontSize: 16, color: "#ff8544", textTransform:"none" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false)
|
setModalOpen(false)
|
||||||
setValue("")
|
setValue("")
|
||||||
@@ -332,7 +377,7 @@ const CacheView = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
style={{ borderRadius: "0px" }}
|
style={{ borderRadius: "2px", backgroundColor: "#ff8544",color: "#1a1a1a", textTransform:"none" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
{editCache ? editOrgCache(orgId) : addOrgCache(orgId)}
|
{editCache ? editOrgCache(orgId) : addOrgCache(orgId)}
|
||||||
|
|
||||||
@@ -348,10 +393,11 @@ const CacheView = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div style={{paddingBottom: isSelectedDataStore?null:250, minHeight: 1000, boxSizing: "border-box", width: isSelectedDataStore? "100%" :null, transition: "width 0.3s ease", padding:isSelectedDataStore?"27px 10px 27px 27px":null, height: isSelectedDataStore?"100%":null, color: isSelectedDataStore?'#ffffff':null, backgroundColor: isSelectedDataStore?'#212121':null, borderTopRightRadius: isSelectedDataStore?'8px':null, borderBottomRightRadius: isSelectedDataStore?'8px':null, borderLeft: "1px solid #494949" }}>
|
||||||
<div style={{paddingBottom: isSelectedDataStore?null:250, width: isSelectedDataStore?1030:null, padding:isSelectedDataStore?27:null, height: isSelectedDataStore?"auto":null, color: isSelectedDataStore?'#ffffff':null, backgroundColor: isSelectedDataStore?'#212121':null, borderRadius: isSelectedDataStore?'16px':null, }}>
|
|
||||||
{modalView}
|
{modalView}
|
||||||
<div style={{ marginTop: isSelectedDataStore?null:20, marginBottom: 20 }}>
|
<div style={{height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}>
|
||||||
|
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}>
|
||||||
|
<div style={{ marginTop: isSelectedDataStore?null:20, marginBottom: 20 }}>
|
||||||
<h2 style={{ display: isSelectedDataStore?null: "inline" }}>Shuffle Datastore</h2>
|
<h2 style={{ display: isSelectedDataStore?null: "inline" }}>Shuffle Datastore</h2>
|
||||||
<span style={{ marginLeft: isSelectedDataStore?null:25, color:isSelectedDataStore?"#9E9E9E":null}}>
|
<span style={{ marginLeft: isSelectedDataStore?null:25, color:isSelectedDataStore?"#9E9E9E":null}}>
|
||||||
Datastore is a permanent key-value database for storing data that can be used cross-workflow. <br/>You can store anything from lists of IPs to complex configurations.
|
Datastore is a permanent key-value database for storing data that can be used cross-workflow. <br/>You can store anything from lists of IPs to complex configurations.
|
||||||
@@ -366,7 +412,7 @@ const CacheView = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
style={{backgroundColor: isSelectedDataStore?'rgba(255, 132, 68, 0.2)':null, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null, color:isSelectedDataStore?"#FF8444":null, borderRadius:isSelectedDataStore?200:null, width:isSelectedDataStore?162:null, height:isSelectedDataStore?40:null}}
|
style={{backgroundColor: isSelectedDataStore? "#ff8544":null, fontSize: 16, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null, color:isSelectedDataStore?"#1a1a1a":null, borderRadius:isSelectedDataStore?8:null, width:isSelectedDataStore?162:null, height:isSelectedDataStore?40:null}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() =>{
|
onClick={() =>{
|
||||||
@@ -379,7 +425,7 @@ const CacheView = (props) => {
|
|||||||
Add Cache
|
Add Cache
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
style={{ marginLeft: 5, marginRight: 15, backgroundColor: isSelectedDataStore?"#2F2F2F":null, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null,borderRadius:isSelectedDataStore?200:null, width:isSelectedDataStore?81:null, height:isSelectedDataStore?40:null, }}
|
style={{ marginLeft: 5, marginRight: 15, backgroundColor: isSelectedDataStore?"#2F2F2F":null, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null,borderRadius:isSelectedDataStore?8:null, width:isSelectedDataStore?81:null, height:isSelectedDataStore?40:null, }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => listOrgCache(orgId)}
|
onClick={() => listOrgCache(orgId)}
|
||||||
@@ -392,28 +438,79 @@ const CacheView = (props) => {
|
|||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
}}
|
}}
|
||||||
/>}
|
/>}
|
||||||
<List style={{borderRadius: isSelectedDataStore?8:null, border:isSelectedDataStore?"1px solid #494949":null, marginTop:isSelectedDataStore?24:null}}>
|
<div
|
||||||
<ListItem style={{width: isSelectedDataStore?"100%":null, borderBottom:isSelectedDataStore?"1px solid #494949":null}}>
|
style={{
|
||||||
<ListItemText
|
borderRadius: 8,
|
||||||
primary="Key"
|
marginTop: 24,
|
||||||
style={{ minWidth: isSelectedDataStore?200:250, maxWidth: isSelectedDataStore?200:250, }}
|
border: "1px solid #494949",
|
||||||
/>
|
width: "100%",
|
||||||
<ListItemText
|
overflowX: "auto",
|
||||||
primary="Value"
|
paddingBottom: 0,
|
||||||
style={{ minWidth: isSelectedDataStore?300:400, maxWidth: isSelectedDataStore?300:400, overflowX: "auto", overflowY: "hidden", }}
|
}}
|
||||||
/>
|
>
|
||||||
<ListItemText
|
<List
|
||||||
primary="Actions"
|
style={{
|
||||||
style={{ minWidth: 150, maxWidth: 150, marginLeft: isSelectedDataStore?80:null,}}
|
borderRadius: 8,
|
||||||
/>
|
paddingBottom: 0,
|
||||||
<ListItemText
|
tableLayout: "auto",
|
||||||
style={{textAlign:isSelectedDataStore?"center":null}}
|
display: "table",
|
||||||
primary="Updated"
|
width: '100%',
|
||||||
/>
|
minWidth: 800,
|
||||||
|
overflowX: "auto",
|
||||||
|
}}>
|
||||||
|
<ListItem style={{width: isSelectedDataStore?"100%":null, borderBottom:isSelectedDataStore?"1px solid #494949":null, display: "table-row"}}>
|
||||||
|
{["Key", "Value", "Actions", "Updated"].map((header, index) => (
|
||||||
|
<ListItemText
|
||||||
|
key={index}
|
||||||
|
primary={header}
|
||||||
|
style={{
|
||||||
|
display: "table-cell",
|
||||||
|
padding: "0px 8px 8px 8px",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
borderBottom: "1px solid #494949"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{listCache === undefined || listCache === null
|
{cachedLoaded === false
|
||||||
? null
|
? [...Array(6)].map((_, rowIndex) => (
|
||||||
: listCache.map((data, index) => {
|
<ListItem
|
||||||
|
key={rowIndex}
|
||||||
|
style={{
|
||||||
|
display: "table-row",
|
||||||
|
backgroundColor: "#212121",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Array(4)
|
||||||
|
.fill()
|
||||||
|
.map((_, colIndex) => (
|
||||||
|
<ListItemText
|
||||||
|
key={colIndex}
|
||||||
|
style={{
|
||||||
|
display: "table-cell",
|
||||||
|
padding: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
variant="text"
|
||||||
|
animation="wave"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#1a1a1a",
|
||||||
|
height: "20px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemText>
|
||||||
|
))}
|
||||||
|
</ListItem>
|
||||||
|
))
|
||||||
|
: listCache?.length === 0 ? (
|
||||||
|
<Typography style={{ textAlign: "center", marginTop: 20, marginBottom: 20, minWidth: 1000, }}>
|
||||||
|
No Keys Found
|
||||||
|
</Typography>
|
||||||
|
): listCache?.map((data, index) => {
|
||||||
var bgColor = isSelectedDataStore? "#212121":"#27292d";
|
var bgColor = isSelectedDataStore? "#212121":"#27292d";
|
||||||
if (index % 2 === 0) {
|
if (index % 2 === 0) {
|
||||||
bgColor = isSelectedDataStore? "#1A1A1A":"#1f2023";
|
bgColor = isSelectedDataStore? "#1A1A1A":"#1f2023";
|
||||||
@@ -421,49 +518,61 @@ const CacheView = (props) => {
|
|||||||
|
|
||||||
const validate = validateJson(data.value);
|
const validate = validateJson(data.value);
|
||||||
return (
|
return (
|
||||||
<ListItem key={index} style={{ backgroundColor: bgColor, maxHeight: 300, overflow: "auto", }}>
|
<ListItem key={index} style={{display:'table-row', backgroundColor: bgColor, maxHeight: 300, overflow: "auto", borderBottomLeftRadius: listCache?.length - 1 === index ? 8 : 0, borderBottomRightRadius: listCache?.length - 1 === index ? 8 : 0}}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 200,
|
display: "table-cell",
|
||||||
minWidth: 200,
|
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
padding: 8,
|
||||||
|
verticalAlign: "middle",
|
||||||
}}
|
}}
|
||||||
primary={data.key}
|
primary={data.key}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{
|
style={{
|
||||||
minWidth: 300,
|
display: "table-cell",
|
||||||
maxWidth: 300,
|
overflowY: "auto",
|
||||||
// height:200,
|
overflowX: "auto",
|
||||||
overflowX: "hidden",
|
border: "1px solid rgba(255,255,255,0.7)",
|
||||||
|
borderRadius: 6,
|
||||||
|
backgroundColor: "#151515",
|
||||||
|
maxHeight: 300,
|
||||||
|
verticalAlign: "middle",
|
||||||
}}
|
}}
|
||||||
primary={validate.valid ?
|
primary={validate.valid ?
|
||||||
<ReactJson
|
<ReactJson
|
||||||
src={validate.result}
|
src={validate.result}
|
||||||
theme={theme.palette.jsonTheme}
|
theme={theme.palette.jsonTheme}
|
||||||
style={theme.palette.reactJsonStyle}
|
style={{
|
||||||
collapsed={true}
|
padding: 5,
|
||||||
enableClipboard={(copy) => {
|
maxHeight: 300,
|
||||||
//handleReactJsonClipboard(copy);
|
overflowY: "auto",
|
||||||
}}
|
}}
|
||||||
displayDataTypes={false}
|
collapsed={true}
|
||||||
onSelect={(select) => {
|
enableClipboard={(copy) => {
|
||||||
//HandleJsonCopy(showResult, select, data.action.label);
|
// handleReactJsonClipboard(copy);
|
||||||
//console.log("SELECTED!: ", select);
|
}}
|
||||||
}}
|
collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
|
||||||
name={"value"}
|
iconStyle={theme.palette.jsonIconStyle}
|
||||||
/>
|
displayDataTypes={false}
|
||||||
|
onSelect={(select) => {
|
||||||
|
// HandleJsonCopy(showResult, select, data.action.label);
|
||||||
|
console.log("SELECTED!: ", select);
|
||||||
|
}}
|
||||||
|
name={"value"}
|
||||||
|
/>
|
||||||
:
|
:
|
||||||
data.value
|
data.value
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 200,
|
display: "table-cell",
|
||||||
minWidth: 200,
|
verticalAlign: "middle",
|
||||||
marginLeft: 50,
|
padding: 8
|
||||||
}}
|
}}
|
||||||
primary=<span style={{ display: "inline" }}>
|
primary={(
|
||||||
|
<span style={{ display: "inline" }}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Edit item"
|
title="Edit item"
|
||||||
style={{}}
|
style={{}}
|
||||||
@@ -482,9 +591,7 @@ const CacheView = (props) => {
|
|||||||
setModalOpen(true)
|
setModalOpen(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditIcon
|
<img src="/icons/editIcon.svg" alt="edit" />
|
||||||
style={{ color: "white" }}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -508,7 +615,6 @@ const CacheView = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={"Delete item"}
|
title={"Delete item"}
|
||||||
style={{ marginLeft: 25, }}
|
|
||||||
aria-label={"Delete"}
|
aria-label={"Delete"}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
@@ -519,18 +625,18 @@ const CacheView = (props) => {
|
|||||||
//deleteFile(orgId);
|
//deleteFile(orgId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon
|
<img src="/icons/deleteIcon.svg" alt="delete" />
|
||||||
style={{ color: "white" }}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 225,
|
display: "table-cell",
|
||||||
minWidth: 225,
|
verticalAlign: "middle",
|
||||||
|
padding: 8
|
||||||
}}
|
}}
|
||||||
primary={new Date(data.edited * 1000).toISOString()}
|
primary={new Date(data.edited * 1000).toISOString()}
|
||||||
/>
|
/>
|
||||||
@@ -538,8 +644,11 @@ const CacheView = (props) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
export default CacheView;
|
|
||||||
|
export default memo(CacheView);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
|
|||||||
import { useInterval } from "react-powerhooks";
|
import { useInterval } from "react-powerhooks";
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import theme from "../theme.jsx";
|
import theme from "../theme.jsx";
|
||||||
|
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
@@ -79,7 +80,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (requiredActions.length === 0) {
|
if (requiredActions.length === 0) {
|
||||||
if (setConfigurationFinished !== undefined) {
|
if (setConfigurationFinished !== undefined) {
|
||||||
setConfigurationFinished(true)
|
setConfigurationFinished(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [requiredActions])
|
}, [requiredActions])
|
||||||
@@ -141,17 +142,18 @@ const ConfigureWorkflow = (props) => {
|
|||||||
|
|
||||||
// Where is this from?
|
// Where is this from?
|
||||||
if (workflow === undefined || workflow === null || workflow.id === undefined) {
|
if (workflow === undefined || workflow === null || workflow.id === undefined) {
|
||||||
return null;
|
//console.log("Workflow is undefined or null: ", workflow)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apps === undefined || apps === null) {
|
if (apps === undefined || apps === null) {
|
||||||
console.log("Apps is undefined or null: ", apps)
|
//console.log("Apps is undefined or null: ", apps)
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appAuthentication === undefined || appAuthentication === null) {
|
if (appAuthentication === undefined || appAuthentication === null) {
|
||||||
console.log("App authentication is undefined or null: ", appAuthentication)
|
//console.log("App authentication is undefined or null: ", appAuthentication)
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getApp = (actionId, appId) => {
|
const getApp = (actionId, appId) => {
|
||||||
@@ -310,7 +312,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.authentication_id === "" && app.authentication.required === true && action.parameters !== undefined && action.parameters !== null) {
|
if (action?.authentication_id === "" && app?.authentication?.required === true && action.parameters !== undefined && action.parameters !== null) {
|
||||||
// Check if configuration is filled or not
|
// Check if configuration is filled or not
|
||||||
var filled = true;
|
var filled = true;
|
||||||
for (let [key,keyval] in Object.entries(action.parameters)) {
|
for (let [key,keyval] in Object.entries(action.parameters)) {
|
||||||
@@ -322,7 +324,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.authentication.type === "oauth2" || app.authentication.type === "oauth2-app") {
|
if (app?.authentication?.type === "oauth2" || app?.authentication?.type === "oauth2-app") {
|
||||||
filled = false
|
filled = false
|
||||||
|
|
||||||
action.auth_type = "oauth2"
|
action.auth_type = "oauth2"
|
||||||
@@ -425,10 +427,8 @@ const ConfigureWorkflow = (props) => {
|
|||||||
trigger.index = key;
|
trigger.index = key;
|
||||||
|
|
||||||
if (trigger.trigger_type === "WEBHOOK") {
|
if (trigger.trigger_type === "WEBHOOK") {
|
||||||
console.log("Found webhook: ", trigger)
|
|
||||||
|
|
||||||
if (trigger.app_association !== undefined && trigger.app_association.name !== null && trigger.app_association.name !== "") {
|
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 findapp = trigger.app_association.name.toLowerCase()
|
||||||
const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp)
|
const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp)
|
||||||
|
|
||||||
@@ -449,9 +449,6 @@ const ConfigureWorkflow = (props) => {
|
|||||||
|
|
||||||
newactions[foundindex].show_steps = true
|
newactions[foundindex].show_steps = true
|
||||||
|
|
||||||
console.log("CHANGED ACTION: ", newactions[foundindex])
|
|
||||||
//console.log("Index: ", newactions[foundindex])
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -601,7 +598,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: <InputAdornment position="end"></InputAdornment>,
|
endAdornment: <InputAdornment position="end"></InputAdornment>,
|
||||||
@@ -796,15 +793,13 @@ const ConfigureWorkflow = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " ");
|
parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " ");
|
||||||
|
|
||||||
console.log("AUTH Action: ", action)
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
style={{padding: 0, display: "flex", flexDirection: "column", }}
|
style={{padding: 0, display: "flex", flexDirection: "column", }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
border: filled ? `1px solid ${theme.palette.green}` : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, width: "100%", padding: 12, cursor: "pointer",
|
border: filled ? `1px solid ${theme.palette.green}` : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, width: "100%", padding: 12, cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
id="app-config"
|
id="app-config"
|
||||||
>
|
>
|
||||||
@@ -853,7 +848,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
{opened ?
|
{opened ?
|
||||||
<div style={{padding: 12, }}>
|
<div style={{padding: 12, }}>
|
||||||
|
|
||||||
{action.app.authentication.type === "oauth2-app" || action.app.authentication.type === "oauth2" || action.auth_type === "oauth2" ?
|
{action.app?.authentication?.type === "oauth2-app" || action.app?.authentication?.type === "oauth2" || action.auth_type === "oauth2" ?
|
||||||
<div>
|
<div>
|
||||||
<AuthenticationOauth2
|
<AuthenticationOauth2
|
||||||
selectedApp={action.app}
|
selectedApp={action.app}
|
||||||
@@ -950,7 +945,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{action.app.authentication.type !== "oauth2-app" && action.app.authentication.type !== "oauth2" ?
|
{action.app?.authentication?.type !== "oauth2-app" && action.app?.authentication?.type !== "oauth2" ?
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -1001,7 +996,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
|
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
|
||||||
color: action.auth_done ? "#686a6c" : "#ffffff",
|
color: action.auth_done ? "#686a6c" : "#ffffff",
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
minWidth: 350,
|
minWidth: 350,
|
||||||
maxHeight: 50,
|
maxHeight: 50,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
@@ -1041,7 +1036,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={action.app_name}
|
alt={action.app_name}
|
||||||
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
|
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette?.borderRadius, }}
|
||||||
src={action.large_image}
|
src={action.large_image}
|
||||||
/>
|
/>
|
||||||
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
|
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
|
||||||
@@ -1061,7 +1056,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
|
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
|
||||||
color: action.auth_done ? "#686a6c" : "#ffffff",
|
color: action.auth_done ? "#686a6c" : "#ffffff",
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
minWidth: 350,
|
minWidth: 350,
|
||||||
maxHeight: 50,
|
maxHeight: 50,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
@@ -1092,7 +1087,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={action.app_name}
|
alt={action.app_name}
|
||||||
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
|
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette?.borderRadius, }}
|
||||||
src={action.large_image}
|
src={action.large_image}
|
||||||
/>
|
/>
|
||||||
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
|
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
|
||||||
@@ -1112,7 +1107,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
|
backgroundColor: action.auth_done ? theme.palette.surfaceColor : theme.palette.inputColor,
|
||||||
color: action.auth_done ? "#686a6c" : "#ffffff",
|
color: action.auth_done ? "#686a6c" : "#ffffff",
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
minWidth: 350,
|
minWidth: 350,
|
||||||
maxHeight: 50,
|
maxHeight: 50,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
@@ -1127,7 +1122,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={action.app_name}
|
alt={action.app_name}
|
||||||
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
|
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette?.borderRadius, }}
|
||||||
src={action.large_image}
|
src={action.large_image}
|
||||||
/>
|
/>
|
||||||
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
|
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
|
||||||
@@ -1279,7 +1274,7 @@ const ConfigureWorkflow = (props) => {
|
|||||||
const [finishCount, setFinishCount] = useState(0)
|
const [finishCount, setFinishCount] = useState(0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{backgroundColor: hovered ? theme.palette.inputColor : "inherit", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, cursor: "pointer", }}
|
<div style={{backgroundColor: hovered ? theme.palette.inputColor : "inherit", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, cursor: "pointer", }}
|
||||||
>
|
>
|
||||||
<div style={{display: "flex", marginLeft: 15, marginTop: 15, marginBottom: 15, }}
|
<div style={{display: "flex", marginLeft: 15, marginTop: 15, marginBottom: 15, }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1321,7 +1316,6 @@ const ConfigureWorkflow = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (step.type === "authenticate") {
|
if (step.type === "authenticate") {
|
||||||
console.log("AUTH STEP: ", step)
|
|
||||||
if (data.must_authenticate === true ) {
|
if (data.must_authenticate === true ) {
|
||||||
filled = false
|
filled = false
|
||||||
} else {
|
} else {
|
||||||
@@ -1393,9 +1387,23 @@ const ConfigureWorkflow = (props) => {
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div style={{marginTop: 10, }} />
|
||||||
|
|
||||||
|
{/*
|
||||||
|
<WorkflowValidationTimeline
|
||||||
|
workflow={workflow}
|
||||||
|
|
||||||
|
apps={apps}
|
||||||
|
|
||||||
|
getParents={undefined}
|
||||||
|
execution={undefined}
|
||||||
|
/>
|
||||||
|
<div style={{marginBottom: 10, }} />
|
||||||
|
*/}
|
||||||
|
|
||||||
{requiredActions.length > 0 ? (
|
{requiredActions.length > 0 ? (
|
||||||
<span>
|
<span>
|
||||||
<Typography variant="body2" style={{}}>
|
<Typography variant="body2" color="textSecondary">
|
||||||
Please configure the following steps to help us complete your workflow. This can also be done later.
|
Please configure the following steps to help us complete your workflow. This can also be done later.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ const CreatorGrid = props => {
|
|||||||
removeQuery("q")
|
removeQuery("q")
|
||||||
refine(event.currentTarget.value)
|
refine(event.currentTarget.value)
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if(event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
||||||
</form>
|
</form>
|
||||||
@@ -147,6 +152,7 @@ const CreatorGrid = props => {
|
|||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
alignContent: "space-between",
|
alignContent: "space-between",
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
|
padding:"0px 180px",
|
||||||
}
|
}
|
||||||
|
|
||||||
const Hits = ({ hits }) => {
|
const Hits = ({ hits }) => {
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Bar } from 'react-chartjs-2';
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
export const LoadStats = (globalUrl, cachekey) => {
|
||||||
|
if (globalUrl === undefined) {
|
||||||
|
console.log("Error: Global URL is undefined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachekey === undefined) {
|
||||||
|
console.log("Error: Cachekey is undefined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var basedata = {
|
||||||
|
"key": cachekey,
|
||||||
|
"total": 0,
|
||||||
|
"available_keys": [],
|
||||||
|
"labels": [],
|
||||||
|
"datasets": [
|
||||||
|
{
|
||||||
|
"label": "",
|
||||||
|
"data": [],
|
||||||
|
"backgroundColor": [],
|
||||||
|
"barThickness": 15,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
//const url = `${globalUrl}/api/v1/stats/app_executions_test2`
|
||||||
|
//cachekey = cachekey.replace(" ", "_", -1)
|
||||||
|
const url = `${globalUrl}/api/v1/stats/${cachekey}`
|
||||||
|
return fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.json()
|
||||||
|
}).then((respJson) => {
|
||||||
|
const selectedIndex = 0
|
||||||
|
|
||||||
|
if (respJson.success === true) {
|
||||||
|
for (let entryKey in respJson.entries) {
|
||||||
|
const entry = respJson.entries[entryKey]
|
||||||
|
basedata.labels.push(entry.date)
|
||||||
|
|
||||||
|
basedata.datasets[0].data.push(entry.value)
|
||||||
|
basedata.datasets[0].backgroundColor.push(entry.value > 0 ? "rgba(255,255,255,0.4)" : "red")
|
||||||
|
}
|
||||||
|
|
||||||
|
basedata.available_keys = respJson.available_keys
|
||||||
|
basedata.total = respJson.total
|
||||||
|
|
||||||
|
return basedata
|
||||||
|
} else {
|
||||||
|
console.log("Failed to get stats")
|
||||||
|
return basedata
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast("Failed to get stats")
|
||||||
|
return basedata
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const DashboardBarchart = (props) => {
|
||||||
|
const { timelineData, title, height, } = props;
|
||||||
|
var inputHeight = 15
|
||||||
|
if (height !== undefined && height !== null) {
|
||||||
|
inputHeight = height
|
||||||
|
}
|
||||||
|
|
||||||
|
const barOptions = {
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
enabled: true, // Ensure tooltips are enabled
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
top: 0, // Adjust the top padding as needed
|
||||||
|
bottom: -10, // Adjust the bottom padding as needed
|
||||||
|
left: 0, // Adjust the left padding as needed
|
||||||
|
right: 0, // Adjust the right padding as needed
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: false,
|
||||||
|
},
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
beginAtZero: false,
|
||||||
|
}],
|
||||||
|
xAxes: [{
|
||||||
|
ticks: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
beginAtZero: false,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
callbacks: {
|
||||||
|
label: function (tooltipItem, data) {
|
||||||
|
const label = data.labels[tooltipItem.index]
|
||||||
|
return label.split('\n')[0]
|
||||||
|
},
|
||||||
|
afterLabel: function (tooltipItem, data) {
|
||||||
|
const amount = tooltipItem.value === undefined || tooltipItem.value === null ? 0 : tooltipItem.value
|
||||||
|
return `Amount: ${amount}`
|
||||||
|
},
|
||||||
|
title: function () {
|
||||||
|
return title === undefined ? '' : title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Bar
|
||||||
|
data={timelineData}
|
||||||
|
options={barOptions}
|
||||||
|
height={inputHeight}
|
||||||
|
getElementAtEvent={(elements) => {
|
||||||
|
if (elements && elements.length > 0) {
|
||||||
|
//toast("Click event")
|
||||||
|
console.log("Clicked: ", elements)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashboardBarchart;
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Box,
|
||||||
|
TextField,
|
||||||
|
Switch,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
Paper,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import theme from '../theme.jsx';
|
||||||
|
import DetectionRuleCard from "../components/DetectionRuleCard.jsx";
|
||||||
|
|
||||||
|
const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isTenzirActive) => {
|
||||||
|
|
||||||
|
if (!isTenzirActive) {
|
||||||
|
toast("connect to siem first for global enable/disable to work");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = folderDisabled ? "enable_folder" : "disable_folder";
|
||||||
|
const url = `${globalUrl}/api/v1/detections/${action}`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "PUT",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === true) {
|
||||||
|
if (action === "enable_folder") setFolderDisabled(false);
|
||||||
|
else setFolderDisabled(true);
|
||||||
|
} else {
|
||||||
|
//toast(`failed to disable rule`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error in ${action} the rule: `, error);
|
||||||
|
toast(`An error occurred while ${action} the rule`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const Detection = (props) => {
|
||||||
|
const { globalUrl, ruleInfo, folderDisabled, setFolderDisabled, isTenzirActive } = props;
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleConnectClick = () => {
|
||||||
|
if (!isTenzirActive) {
|
||||||
|
setLoading(true);
|
||||||
|
const url = `${globalUrl}/api/v1/detections/siem/connect`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
window.location.reload();
|
||||||
|
}, 15000);
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
toast("Failed to connect to SIEM");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(`Error in connecting to SIEM: `, error);
|
||||||
|
toast("An error occurred while connecting to SIEM");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Already connected to SIEM");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredRules = ruleInfo?.filter((rule) =>
|
||||||
|
rule.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
rule.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Paper
|
||||||
|
style={{
|
||||||
|
marginTop: 50,
|
||||||
|
width: "100%",
|
||||||
|
padding: 50,
|
||||||
|
backgroundColor: theme.palette.backgroundColor,
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" component="div">
|
||||||
|
Sigma Detection Rules
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleConnectClick}
|
||||||
|
disabled={loading} // Disable the button while loading
|
||||||
|
color={isTenzirActive ? "primary" : "secondary"}
|
||||||
|
style={{ }}
|
||||||
|
>
|
||||||
|
{loading ? <CircularProgress size={24} /> : isTenzirActive ? "Connected to siem" : "Connect to siem"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
label="Search rules"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
{/* <Button
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => uploadRef.current.click()}
|
||||||
|
>
|
||||||
|
<PublishIcon /> Upload sigma file
|
||||||
|
</Button>
|
||||||
|
<input
|
||||||
|
hidden
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
ref={uploadRef}
|
||||||
|
onChange={(event) => {
|
||||||
|
uploadFiles(event.target.files);
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Typography variant="body2" sx={{ mr: 1 }}>
|
||||||
|
Global disable/enable
|
||||||
|
</Typography>
|
||||||
|
<Switch
|
||||||
|
checked={!folderDisabled}
|
||||||
|
onChange={() =>
|
||||||
|
handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isTenzirActive)
|
||||||
|
}
|
||||||
|
disabled={!isTenzirActive}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "500px",
|
||||||
|
width: "100%",
|
||||||
|
overflowY: "auto",
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{filteredRules?.length > 0 ?
|
||||||
|
filteredRules.map((card) => {
|
||||||
|
console.log("RULE CARD: ", card);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DetectionRuleCard
|
||||||
|
key={card.file_id}
|
||||||
|
ruleName={card.title}
|
||||||
|
description={card.description}
|
||||||
|
file_id={card.file_id}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
folderDisabled={folderDisabled}
|
||||||
|
isTenzirActive={isTenzirActive}
|
||||||
|
{...card}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: null }
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Detection;
|
||||||
@@ -0,0 +1,455 @@
|
|||||||
|
import React, { useState, useEffect, } from "react";
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Box,
|
||||||
|
TextField,
|
||||||
|
Switch,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
Paper,
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import {
|
||||||
|
OpenInNew as OpenInNewIcon,
|
||||||
|
FmdGood as FmdGoodIcon,
|
||||||
|
} from "@mui/icons-material"
|
||||||
|
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import theme from '../theme.jsx';
|
||||||
|
import DetectionRuleCard from "../components/DetectionRuleCard.jsx";
|
||||||
|
import {
|
||||||
|
green,
|
||||||
|
red,
|
||||||
|
grey,
|
||||||
|
} from "../views/AngularWorkflow.jsx"
|
||||||
|
|
||||||
|
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
|
||||||
|
|
||||||
|
const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isDetectionActive) => {
|
||||||
|
if (!isDetectionActive) {
|
||||||
|
toast.warn("Connect to siem first for global enable/disable to work");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = folderDisabled ? "enable_folder" : "disable_folder";
|
||||||
|
const url = `${globalUrl}/api/v1/detections/${action}`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "PUT",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === true) {
|
||||||
|
if (action === "enable_folder") setFolderDisabled(false);
|
||||||
|
else setFolderDisabled(true);
|
||||||
|
} else {
|
||||||
|
//toast(`failed to disable rule`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error in ${action} the rule: `, error);
|
||||||
|
toast(`An error occurred while ${action} the rule`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetectionExplorer = (props) => {
|
||||||
|
const { globalUrl, userdata, ruleInfo, folderDisabled, setFolderDisabled, detectionInfo, importDetectionFromUrl, rulesLoading, isDetectionActive, setIsDetectionActive, ruleMapping, setRuleMapping, } = props;
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const [workflow, setWorkflow] = useState({})
|
||||||
|
const [detectionWorkflowId, setDetectionWorkflowId] = useState("")
|
||||||
|
const [isDetectionValid, setIsDetectionValid] = useState(false)
|
||||||
|
const [availableDetection, setAvailableDetection] = React.useState([]);
|
||||||
|
const [environmentList, setEnvironmentList] = React.useState([])
|
||||||
|
|
||||||
|
const loadUsecases = () => {
|
||||||
|
const url = `${globalUrl}/api/v1/workflows/usecases`
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseJson.length == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var usecaseCategory in responseJson) {
|
||||||
|
const category = responseJson[usecaseCategory]
|
||||||
|
if (!category.name.toLowerCase().includes("respond") && !category.name.toLowerCase().includes("response")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
setAvailableDetection(category.list)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error in loading usecases: `, error);
|
||||||
|
//toast(`An error occurred while loading usecases`);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadWorkflow = (workflowId) => {
|
||||||
|
const url = `${globalUrl}/api/v1/workflows/${workflowId}`
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson.id === workflowId) {
|
||||||
|
setWorkflow(responseJson)
|
||||||
|
} else {
|
||||||
|
toast(`Failed to load workflow ${workflowId}`);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error in loading workflow ${workflowId}: `, error);
|
||||||
|
toast(`An error occurred while loading workflow ${workflowId}`);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConnectClick = () => {
|
||||||
|
if (detectionWorkflowId !== "") {
|
||||||
|
// FIXME: Show the Usecase UI for how to fix the workflow(s)
|
||||||
|
// Instead loading full workflow and showing it directly? Hmm
|
||||||
|
//toast.warn("Please reload the UI to load the detection status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDetectionActive) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detectionInfo.category === undefined || detectionInfo.category === null) {
|
||||||
|
toast.warn("Detection category not found. Please try again or contact support@shuffler.io if you think this is a bug.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
const url = `${globalUrl}/api/v1/detections/${detectionInfo?.category}/connect`
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === true) {
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
if (setIsDetectionActive !== undefined) {
|
||||||
|
setIsDetectionActive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseJson.workflow_id !== undefined && responseJson.workflow_id !== null) {
|
||||||
|
setDetectionWorkflowId(responseJson.workflow_id)
|
||||||
|
|
||||||
|
loadWorkflow(responseJson.workflow_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseJson.workflow_valid !== undefined && responseJson.workflow_valid !== null) {
|
||||||
|
setIsDetectionValid(responseJson.workflow_valid)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (responseJson.reason !== undefined && responseJson.reason !== null) {
|
||||||
|
toast(responseJson.reason)
|
||||||
|
} else {
|
||||||
|
if (responseJson.workflow_id === "" && responseJson.workflow_valid === false) {
|
||||||
|
toast.info(`Sent job to generate a Detection Workflow and enable ${detectionInfo?.category}. Please wait a minute and reload this UI.`);
|
||||||
|
} else {
|
||||||
|
toast.error(`Failed to connect to ${detectionInfo?.category}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseJson.action !== undefined && responseJson.actio !== null && responseJson.action.length > 0) {
|
||||||
|
//if (responseJson.action === "environment_create") {
|
||||||
|
// navigate("/admin?tab=environments")
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(`Error in connecting to ${detectionInfo?.category}: `, error);
|
||||||
|
toast.error(`An error occurred while connecting to ${detectionInfo?.category}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadEnvironments = () => {
|
||||||
|
const url = `${globalUrl}/api/v1/getenvironments`
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseJson.length == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnvironmentList(responseJson)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error in loading environments: `, error);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadUsecases()
|
||||||
|
loadEnvironments()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (detectionInfo === undefined || detectionInfo === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detectionInfo.category === undefined || detectionInfo.category === null || detectionInfo.category === "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConnectClick()
|
||||||
|
}, [detectionInfo])
|
||||||
|
|
||||||
|
const filteredRules = ruleInfo === "default" ? [] : ruleInfo?.filter((rule) =>
|
||||||
|
rule.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
rule.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
)
|
||||||
|
|
||||||
|
const lakeNodes = environmentList !== undefined && environmentList !== null ? environmentList.filter((env) => env?.data_lake?.enabled === true).length : 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Paper
|
||||||
|
style={{
|
||||||
|
marginTop: 50,
|
||||||
|
width: "100%",
|
||||||
|
padding: 50,
|
||||||
|
backgroundColor: theme.palette.backgroundColor,
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" component="div">
|
||||||
|
{detectionInfo?.title} {filteredRules === undefined || filteredRules === null ? null : `(${filteredRules?.length} rules)`}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div style={{display: "flex", }}>
|
||||||
|
{workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0 ?
|
||||||
|
<div style={{display: "flex", }}>
|
||||||
|
<div style={{minWidth: 400, maxWidth: 400, }}>
|
||||||
|
<WorkflowValidationTimeline
|
||||||
|
originalWorkflow={workflow}
|
||||||
|
|
||||||
|
apps={[]}
|
||||||
|
getParents={undefined}
|
||||||
|
execution={undefined}
|
||||||
|
|
||||||
|
workflow={workflow}
|
||||||
|
|
||||||
|
showHoverColor={true}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
userdata={userdata}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
window.open(`/workflows/${workflow.id}`, "_blank")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<OpenInNewIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
handleConnectClick()
|
||||||
|
}}
|
||||||
|
disabled={loading} // Disable the button while loading
|
||||||
|
style={{
|
||||||
|
// Red = workflow exists, validation is false
|
||||||
|
// Green = workflow exists, validation is true
|
||||||
|
// Grey = workflow does not exist
|
||||||
|
backgroundColor: detectionWorkflowId === "" ? grey : isDetectionValid ? green : red,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? <CircularProgress size={24} /> :
|
||||||
|
detectionWorkflowId === "" ? `Connect to ${detectionInfo?.category}` :
|
||||||
|
isDetectionValid ? `Connected to ${detectionInfo?.category}` : `Fix ${detectionInfo?.category} connection`}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
{detectionInfo?.category === "SIGMA" || detectionInfo?.category === "SIEM" ?
|
||||||
|
<Tooltip title={`You have ${lakeNodes} available Data Lake node(s)`}>
|
||||||
|
<a href="/admin?tab=environments" style={{textDecoration: "none", color: "inherit", }} target="_blank" rel="noreferrer">
|
||||||
|
<FmdGoodIcon style={{marginLeft: 15, marginTop: 5, color: lakeNodes > 0 ? green : red}} />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
{filteredRules?.length > 0 ?
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
minHeight: 50,
|
||||||
|
maxHeight: 50,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
label="Search rules"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Typography variant="body2" sx={{ mr: 1 }}>
|
||||||
|
Global disable/enable
|
||||||
|
</Typography>
|
||||||
|
<Switch
|
||||||
|
checked={!folderDisabled}
|
||||||
|
onChange={() =>
|
||||||
|
handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isDetectionActive)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
: null}
|
||||||
|
<Divider />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "500px",
|
||||||
|
width: "100%",
|
||||||
|
overflowY: "auto",
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
{filteredRules?.length > 0 ?
|
||||||
|
|
||||||
|
ruleMapping !== undefined && ruleMapping !== null && ruleMapping.value !== undefined && ruleMapping.value !== null ?
|
||||||
|
filteredRules.map((rule, index) => {
|
||||||
|
return (
|
||||||
|
<div style={{marginTop: 5, }}>
|
||||||
|
<DetectionRuleCard
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
key={index}
|
||||||
|
ruleName={rule.file_name}
|
||||||
|
description={rule.description}
|
||||||
|
|
||||||
|
file_id={rule.file_id}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
folderDisabled={folderDisabled}
|
||||||
|
isDetectionActive={isDetectionActive}
|
||||||
|
|
||||||
|
ruleMapping={ruleMapping}
|
||||||
|
setRuleMapping={setRuleMapping}
|
||||||
|
|
||||||
|
availableDetection={availableDetection}
|
||||||
|
{...rule}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
:
|
||||||
|
<div style={{textAlign: "center", }}>
|
||||||
|
{rulesLoading === true ?
|
||||||
|
<Container style={{ display: "flex", justifyContent: "center", alignItems: "center", marginTop: 25, }}>
|
||||||
|
<div>
|
||||||
|
<CircularProgress />
|
||||||
|
<Typography variant="h6" style={{ marginTop: 20 }}>Downloading rules, please wait...</Typography>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
<Typography variant="h6" color="textSecondary" style={{marginTop: 50, }}>
|
||||||
|
No rules loaded yet
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
style={{marginTop: 20, }}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (importDetectionFromUrl !== undefined) {
|
||||||
|
importDetectionFromUrl(true, detectionInfo.download_repo)
|
||||||
|
} else {
|
||||||
|
toast("Import function not found. Please contact support@shuffler.io")
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Load Default Rules
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetectionExplorer;
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
import React, { useState, useEffect, } from "react";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
Switch,
|
||||||
|
Tooltip,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
Divider,
|
||||||
|
FormLabel,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import DashboardBarchart, { LoadStats } from '../components/DashboardBarchart.jsx';
|
||||||
|
import {
|
||||||
|
Edit as EditIcon,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
|
||||||
|
import theme from '../theme.jsx';
|
||||||
|
|
||||||
|
|
||||||
|
const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, isTenzirActive, availableDetection, ruleMapping, setRuleMapping, ...otherProps }) => {
|
||||||
|
const [openCodeEditor, setOpenCodeEditor] = React.useState(false);
|
||||||
|
const [fileData, setFileData] = React.useState("");
|
||||||
|
const [isEnabled, setIsEnabled] = React.useState(otherProps.is_enabled);
|
||||||
|
const [filteredBarchart, setFilteredBarchart] = React.useState(null)
|
||||||
|
|
||||||
|
const [responseValue, setResponseValue] = React.useState("No response action")
|
||||||
|
const isCloud = ["localhost:3002", "shuffler.io"].includes(window.location.host);
|
||||||
|
|
||||||
|
console.log("Rulemapping: ", ruleMapping)
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
//const url = `${globalUrl}/api/v1/stats/app_executions_test2`
|
||||||
|
//const resp = LoadStats(globalUrl, ruleName)
|
||||||
|
//const resp = LoadStats(globalUrl, "app_executions_test2")
|
||||||
|
const resp = LoadStats(globalUrl, "app_executions_cloud")
|
||||||
|
resp.then((data) => {
|
||||||
|
if (data === undefined) {
|
||||||
|
setFilteredBarchart([])
|
||||||
|
} else {
|
||||||
|
setFilteredBarchart(data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (ruleMapping !== undefined && ruleMapping !== null && ruleMapping.value !== undefined && ruleMapping.value !== null) {
|
||||||
|
console.log("FIX MAPPING FROM ruleMapping.value: ", ruleMapping)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
console.log("Response Value: ", responseValue)
|
||||||
|
|
||||||
|
const handleSwitchChange = (event) => {
|
||||||
|
if (folderDisabled) {
|
||||||
|
toast.warn("Enable the directory to enable individual rules");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTenzirActive) {
|
||||||
|
toast.warn("Connect to the siem first to enable/disable the rule");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newIsEnabled = event.target.checked;
|
||||||
|
toggleRule(file_id, !newIsEnabled, globalUrl, () => {
|
||||||
|
setIsEnabled(newIsEnabled);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const UpdateText = (text) => {
|
||||||
|
fetch(`${globalUrl}/api/v1/files/${file_id}/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();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
toast("Successfully updated rule");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast("Error updating file: " + error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
minHeight: 100,
|
||||||
|
marginBottom: 10,
|
||||||
|
paddingBottom: 0,
|
||||||
|
}}>
|
||||||
|
<CardContent
|
||||||
|
style={{
|
||||||
|
padding: "10px 30px 0px 30px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6">{ruleName.replaceAll("_", " ")} ({filteredBarchart === null || filteredBarchart.total === undefined ? 0 : filteredBarchart.total})</Typography>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
MenuProps={{
|
||||||
|
disableScrollLock: true,
|
||||||
|
}}
|
||||||
|
labelId="Response Action"
|
||||||
|
value={responseValue}
|
||||||
|
SelectDisplayProps={{
|
||||||
|
style: {
|
||||||
|
color: "rgba(255,255,255,0.4)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
onChange={(e) => {
|
||||||
|
toast("Changing response: " + e.target.value)
|
||||||
|
console.log("Target: ", e.target.value)
|
||||||
|
|
||||||
|
setResponseValue(e.target.value)
|
||||||
|
|
||||||
|
// FIXME: Handle:
|
||||||
|
// 1. Get the current cache for the detection
|
||||||
|
// 2. Create a new mapping for Detection -> Response
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
height: 40,
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
value="No response action"
|
||||||
|
>
|
||||||
|
<em>No selected response</em>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{availableDetection === undefined || availableDetection === null ? null : availableDetection.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
overflowX: "auto",
|
||||||
|
}}
|
||||||
|
value={data.name}
|
||||||
|
>
|
||||||
|
{data.name}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
|
||||||
|
<Tooltip title="Edit Rule" placement="top">
|
||||||
|
<IconButton onClick={() => openEditBar(file_id, setOpenCodeEditor, setFileData, globalUrl)}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={isEnabled && !folderDisabled ? "Disable Rule" : "Enable Rule"} placement="top">
|
||||||
|
<Switch
|
||||||
|
checked={isEnabled && !folderDisabled}
|
||||||
|
onChange={handleSwitchChange}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
overflow: 'visible',
|
||||||
|
zIndex: 10,
|
||||||
|
//border: "1px solid rgba(255,255,255,0.3)",
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
marginTop: 5,
|
||||||
|
|
||||||
|
minHeight: 40,
|
||||||
|
maxHeight: 40,
|
||||||
|
}}>
|
||||||
|
{filteredBarchart === null ? null :
|
||||||
|
<DashboardBarchart
|
||||||
|
timelineData={filteredBarchart}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
<Typography variant="body2" style={{ marginTop: '2%' }}>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<ShuffleCodeEditor
|
||||||
|
isCloud={isCloud}
|
||||||
|
expansionModalOpen={openCodeEditor}
|
||||||
|
setExpansionModalOpen={setOpenCodeEditor}
|
||||||
|
setcodedata={setFileData}
|
||||||
|
codedata={fileData}
|
||||||
|
isFileEditor={true}
|
||||||
|
key={fileData} // https://reactjs.org/docs/reconciliation.html#recursing-on-children
|
||||||
|
runUpdateText={UpdateText}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleRule = (fileId, isCurrentlyEnabled, globalUrl, callback) => {
|
||||||
|
const action = isCurrentlyEnabled ? "disable" : "enable";
|
||||||
|
const url = `${globalUrl}/api/v1/detections/${fileId}/${action}_rule`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "PUT",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === false) {
|
||||||
|
toast(`Failed to ${action} the rule`);
|
||||||
|
} else {
|
||||||
|
toast(`Rule ${action}d successfully`);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error in ${action}ing the rule: `, error);
|
||||||
|
toast(`An error occurred while ${action}ing the rule`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditBar = (file_id, setOpenCodeEditor, setFileData, globalUrl) => {
|
||||||
|
getFileContent(file_id, setFileData, globalUrl)
|
||||||
|
|
||||||
|
setOpenCodeEditor(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileContent = (file_id, setFileData, globalUrl) => {
|
||||||
|
setFileData("");
|
||||||
|
fetch(globalUrl + "/api/v1/files/" + file_id + "/content", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for file :O!");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then((respdata) => {
|
||||||
|
if (respdata.length === 0) {
|
||||||
|
toast("Failed getting file. Is it deleted?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return respdata
|
||||||
|
})
|
||||||
|
.then((responseData) => {
|
||||||
|
|
||||||
|
setFileData(responseData);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast(error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default RuleCard;
|
||||||
@@ -75,7 +75,12 @@ const DiscordChat = props => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
value={currentRefinement}
|
value={currentRefinement}
|
||||||
onChange={(event) => refine(event.currentTarget.value)}
|
onChange={(event) => refine(event.currentTarget.value)}
|
||||||
placeholder="Search Discord Chats"
|
onKeyDown={(event) => {
|
||||||
|
if(event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="Search Discord Chats..."
|
||||||
style={{ backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%", }}
|
style={{ backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%", }}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -143,7 +148,7 @@ const DiscordChat = props => {
|
|||||||
const CustomHits = connectHits(Hits);
|
const CustomHits = connectHits(Hits);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: "100%", textAlign:"center", position: "relative", height: "100%", }}>
|
<div style={{textAlign:"center", position: "relative", height: "100%", padding:"0px 240px" }}>
|
||||||
<InstantSearch searchClient={searchClient} indexName="discord_chat">
|
<InstantSearch searchClient={searchClient} indexName="discord_chat">
|
||||||
<div style={{ maxWidth: 450, margin: "auto", marginTop: 15, marginBottom: 5, }}>
|
<div style={{ maxWidth: 450, margin: "auto", marginTop: 15, marginBottom: 5, }}>
|
||||||
<CustomSearchBox />
|
<CustomSearchBox />
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ const DocsGrid = props => {
|
|||||||
removeQuery("q")
|
removeQuery("q")
|
||||||
refine(event.currentTarget.value)
|
refine(event.currentTarget.value)
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if(event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
limit={5}
|
limit={5}
|
||||||
/>
|
/>
|
||||||
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
||||||
@@ -274,7 +279,7 @@ const DocsGrid = props => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
*/}
|
*/}
|
||||||
<div style={{width: "100%", position: "relative", height: "100%",}}>
|
<div style={{width: "100%", position: "relative", height: "100%", padding: "0px 180px"}}>
|
||||||
<InstantSearch searchClient={searchClient} indexName="documentation">
|
<InstantSearch searchClient={searchClient} indexName="documentation">
|
||||||
<div style={{maxWidth: 450, margin: "auto", marginTop: 15, marginBottom: 15, }}>
|
<div style={{maxWidth: 450, margin: "auto", marginTop: 15, marginBottom: 15, }}>
|
||||||
<CustomSearchBox />
|
<CustomSearchBox />
|
||||||
|
|||||||
@@ -0,0 +1,546 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { Box, Typography, IconButton, CircularProgress, Tooltip } from '@mui/material';
|
||||||
|
import { CheckCircle, Error, ArrowBack, Close, Cached as CachedIcon, Pause as PauseIcon } from '@mui/icons-material';
|
||||||
|
import theme from '../theme.jsx';
|
||||||
|
import ReactJson from "react-json-view-ssr";
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { validateJson } from "../views/Workflows.jsx";
|
||||||
|
// import HandleJsonCopy from "./ShuffleCodeEditor1";
|
||||||
|
|
||||||
|
const STATUS_CONFIG = {
|
||||||
|
EXECUTING: {
|
||||||
|
color: '#64B5F6',
|
||||||
|
icon: () => <CircularProgress size={16} thickness={4} sx={{ color: '#64B5F6' }} />,
|
||||||
|
label: 'Executing'
|
||||||
|
},
|
||||||
|
SUCCESS: {
|
||||||
|
color: '#4CAF50',
|
||||||
|
icon: () => <CheckCircle sx={{ color: '#4CAF50', fontSize: 16 }} />,
|
||||||
|
label: 'Success'
|
||||||
|
},
|
||||||
|
FINISHED: {
|
||||||
|
color: '#4CAF50',
|
||||||
|
icon: () => <CheckCircle sx={{ color: '#4CAF50', fontSize: 16 }} />,
|
||||||
|
label: 'Finished'
|
||||||
|
},
|
||||||
|
ABORTED: {
|
||||||
|
color: '#F44336',
|
||||||
|
icon: () => <Error sx={{ color: '#F44336', fontSize: 16 }} />,
|
||||||
|
label: 'Aborted'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let to_be_copied = ""
|
||||||
|
|
||||||
|
const handleReactJsonClipboard = (copy) => {
|
||||||
|
toast("Copied JSON path to clipboard, NOT Path")
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const HandleJsonCopy = (base, copy, base_node_name) => {
|
||||||
|
if (typeof copy.name === "string") {
|
||||||
|
copy.name = copy.name.replaceAll(" ", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
//lol
|
||||||
|
if (typeof base === 'object' || typeof base === 'dict') {
|
||||||
|
base = JSON.stringify(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base_node_name === "execution_argument" || base_node_name === "Execution Argument") {
|
||||||
|
base_node_name = "exec"
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("COPY: ", base_node_name, copy);
|
||||||
|
|
||||||
|
//var newitem = JSON.parse(base);
|
||||||
|
var newitem = validateJson(base).result
|
||||||
|
to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_");
|
||||||
|
for (let copykey in copy.namespace) {
|
||||||
|
if (copy.namespace[copykey].includes("Results for")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newitem !== undefined && newitem !== null) {
|
||||||
|
newitem = newitem[copy.namespace[copykey]];
|
||||||
|
if (!isNaN(copy.namespace[copykey])) {
|
||||||
|
to_be_copied += ".#";
|
||||||
|
} else {
|
||||||
|
to_be_copied += "." + copy.namespace[copykey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newitem !== undefined && newitem !== null) {
|
||||||
|
newitem = newitem[copy.name];
|
||||||
|
if (!isNaN(copy.name)) {
|
||||||
|
to_be_copied += ".#";
|
||||||
|
} else {
|
||||||
|
to_be_copied += "." + copy.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to_be_copied.replaceAll(" ", "_");
|
||||||
|
const elementName = "copy_element_shuffle";
|
||||||
|
var copyText = document.getElementById(elementName);
|
||||||
|
if (copyText !== null && copyText !== undefined) {
|
||||||
|
console.log("NAVIGATOR: ", navigator);
|
||||||
|
const clipboard = navigator.clipboard;
|
||||||
|
if (clipboard === undefined) {
|
||||||
|
toast("Can only copy over HTTPS (port 3443)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(to_be_copied);
|
||||||
|
copyText.select();
|
||||||
|
copyText.setSelectionRange(0, 99999); /* For mobile devices *
|
||||||
|
|
||||||
|
/* Copy the text inside the text field */
|
||||||
|
document.execCommand("copy");
|
||||||
|
toast("Copied JSON path to clipboard.")
|
||||||
|
console.log("COPYING!");
|
||||||
|
} else {
|
||||||
|
console.log("Couldn't find element ", elementName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExecuteWorkflow = async (executionData, globalUrl) => {
|
||||||
|
try {
|
||||||
|
const workflowData = executionData.workflow;
|
||||||
|
|
||||||
|
// Execute workflow with original parameters
|
||||||
|
await fetch(`${globalUrl}/api/v1/workflows/${workflowData.id}/execute`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: workflowData
|
||||||
|
}).then(response => {
|
||||||
|
window.location.href = `/workflows/${workflowData.id}/code?execution_id=` + response.json().execution_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error re-executing workflow:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExecutionsList = ({ executions, onSelectExecution, activeExecutionId }) => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{executions.map((execution) => {
|
||||||
|
const status = STATUS_CONFIG[execution.status] || STATUS_CONFIG.ABORTED;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={execution.execution_id}
|
||||||
|
onClick={() => onSelectExecution(execution)}
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
py: 1,
|
||||||
|
px: 2,
|
||||||
|
borderBottom: '1px solid #2A2A2A',
|
||||||
|
backgroundColor: activeExecutionId === execution.execution_id ?
|
||||||
|
'rgba(255,255,255,0.05)' : 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.05)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{status.icon()}
|
||||||
|
|
||||||
|
<Box sx={{ ml: 2, flex: 1, overflow: 'hidden' }}>
|
||||||
|
<Typography variant="body2" sx={{
|
||||||
|
color: '#E0E0E0',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}}>
|
||||||
|
{new Date(execution.started_at * 1000).toLocaleString()}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" sx={{
|
||||||
|
color: status.color
|
||||||
|
}}>
|
||||||
|
{status.label}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExecutionDetail = ({ execution: initialExecution, onBack, globalUrl, onExecutionUpdate, selectedAction, executeWorkflow }) => {
|
||||||
|
const [execution, setExecution] = useState(initialExecution);
|
||||||
|
const [status, setStatus] = useState(STATUS_CONFIG[execution.status] || STATUS_CONFIG.EXECUTING);
|
||||||
|
const [validResult, setValidResult] = useState("{}")
|
||||||
|
|
||||||
|
const abortExecution = async () => {
|
||||||
|
try {
|
||||||
|
await fetch(`${globalUrl}/api/v1/workflows/${execution.workflow.id}/executions/${execution.execution_id}/abort`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
const updatedExecution = {
|
||||||
|
...execution,
|
||||||
|
status: "ABORTED",
|
||||||
|
};
|
||||||
|
setExecution(updatedExecution);
|
||||||
|
onExecutionUpdate(updatedExecution);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Abort error:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setStatus(STATUS_CONFIG[execution.status] || STATUS_CONFIG.EXECUTING);
|
||||||
|
}, [execution]);
|
||||||
|
|
||||||
|
const pollExecutionStatus = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${globalUrl}/api/v1/streams/results`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({
|
||||||
|
execution_id: execution.execution_id,
|
||||||
|
authorization: execution.authorization,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const currentStatus = data.results?.[0]?.status || 'EXECUTING';
|
||||||
|
|
||||||
|
const updatedExecution = {
|
||||||
|
...execution,
|
||||||
|
...data,
|
||||||
|
status: currentStatus
|
||||||
|
};
|
||||||
|
|
||||||
|
setExecution(updatedExecution);
|
||||||
|
onExecutionUpdate(updatedExecution);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Polling error:', error);
|
||||||
|
}
|
||||||
|
}, [execution, globalUrl, onExecutionUpdate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let pollTimeout;
|
||||||
|
if (execution.status === 'EXECUTING') {
|
||||||
|
pollTimeout = setTimeout(() => pollExecutionStatus(), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (execution?.results?.length === 1) {
|
||||||
|
setValidResult(JSON.parse(execution?.results[0]?.result || "{}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => clearTimeout(pollTimeout);
|
||||||
|
}, [execution.status, pollExecutionStatus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
p: 2,
|
||||||
|
borderBottom: '1px solid #454545'
|
||||||
|
}}>
|
||||||
|
<IconButton onClick={onBack} size="small" sx={{ mr: 2 }}>
|
||||||
|
<ArrowBack fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="subtitle2" sx={{ color: '#E0E0E0', mr: 2 }}>
|
||||||
|
Execution Details
|
||||||
|
</Typography>
|
||||||
|
{status.icon()}
|
||||||
|
<Typography variant="body2" sx={{ ml: 1, color: status.color }}>
|
||||||
|
{status.label}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ ml: 'auto' }}>
|
||||||
|
{execution.status === "EXECUTING" && (
|
||||||
|
<Tooltip title="Abort workflow" placement="top">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={abortExecution}
|
||||||
|
sx={{ color: theme.palette.error.main }}
|
||||||
|
>
|
||||||
|
<PauseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tooltip title="Rerun workflow (uses same startnode as the original)" placement="top">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
ExecuteWorkflow(
|
||||||
|
execution,
|
||||||
|
globalUrl
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
sx={{ color: theme.palette.primary.main }}
|
||||||
|
>
|
||||||
|
<CachedIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
||||||
|
<Box sx={{ mb: 3 }}>
|
||||||
|
<Typography variant="caption" sx={{ color: '#888', display: 'block', mb: 1 }}>
|
||||||
|
Started at
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
{new Date(execution.started_at * 1000).toLocaleString()}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ mb: 3 }}>
|
||||||
|
<Typography variant="caption" sx={{ color: '#888', display: 'block', mb: 1 }}>
|
||||||
|
Execution ID
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{
|
||||||
|
backgroundColor: '#2A2A2A',
|
||||||
|
p: 1,
|
||||||
|
borderRadius: 1,
|
||||||
|
fontFamily: 'monospace'
|
||||||
|
}}>
|
||||||
|
{execution.execution_id}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="caption" sx={{ color: '#888', display: 'block', mb: 1 }}>
|
||||||
|
Result
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{
|
||||||
|
backgroundColor: '#2A2A2A',
|
||||||
|
borderRadius: 1,
|
||||||
|
overflow: 'auto',
|
||||||
|
maxHeight: '200px'
|
||||||
|
}}>
|
||||||
|
<pre style={{
|
||||||
|
margin: 0,
|
||||||
|
padding: '1rem',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: status.color,
|
||||||
|
fontFamily: "'JetBrains Mono', monospace"
|
||||||
|
}}>
|
||||||
|
{execution?.status === 'EXECUTING' ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', p: 2 }}>
|
||||||
|
<CircularProgress size={24} />
|
||||||
|
<Typography sx={{ ml: 2, color: '#888' }}>Executing...</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
execution?.results?.length === 1 ?
|
||||||
|
<ReactJson
|
||||||
|
src={validResult}
|
||||||
|
theme={theme.palette.jsonTheme}
|
||||||
|
style={{
|
||||||
|
borderRadius: 5,
|
||||||
|
border: `2px solid ${theme.palette.inputColor}`,
|
||||||
|
padding: 10,
|
||||||
|
maxHeight: 450,
|
||||||
|
minheight: 450,
|
||||||
|
overflow: "auto",
|
||||||
|
minWidth: 450,
|
||||||
|
maxWidth: "100%",
|
||||||
|
zIndex: 1200
|
||||||
|
}}
|
||||||
|
enableClipboard={(copy) => {
|
||||||
|
handleReactJsonClipboard(copy);
|
||||||
|
}}
|
||||||
|
collapsed={false}
|
||||||
|
displayDataTypes={false}
|
||||||
|
onSelect={(select) => {
|
||||||
|
var basename = "exec"
|
||||||
|
if (selectedAction !== undefined && selectedAction !== null && Object.keys(selectedAction).length !== 0) {
|
||||||
|
basename = selectedAction.label.toLowerCase().replaceAll(" ", "_")
|
||||||
|
}
|
||||||
|
HandleJsonCopy(validResult, select, basename)
|
||||||
|
}}
|
||||||
|
name={"JSON autocompletion"}
|
||||||
|
/> :
|
||||||
|
<ReactJson
|
||||||
|
src={{}}
|
||||||
|
theme={theme.palette.jsonTheme}
|
||||||
|
style={{
|
||||||
|
borderRadius: 5,
|
||||||
|
border: `2px solid ${theme.palette.inputColor}`,
|
||||||
|
padding: 10,
|
||||||
|
maxHeight: 450,
|
||||||
|
minheight: 450,
|
||||||
|
overflow: "auto",
|
||||||
|
minWidth: 450,
|
||||||
|
maxWidth: "100%",
|
||||||
|
zIndex: 1200
|
||||||
|
}}
|
||||||
|
collapsed={false}
|
||||||
|
enableClipboard={(copy) => { }}
|
||||||
|
displayDataTypes={false}
|
||||||
|
name={"JSON autocompletion"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</pre>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const ExecutionPanel = ({
|
||||||
|
workflow,
|
||||||
|
globalUrl,
|
||||||
|
onClose,
|
||||||
|
currentExecution,
|
||||||
|
mainAction
|
||||||
|
}) => {
|
||||||
|
const [executions, setExecutions] = useState([]);
|
||||||
|
const [selectedExecution, setSelectedExecution] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const handleExecutionUpdate = useCallback((updatedExecution) => {
|
||||||
|
setExecutions(prevExecutions => {
|
||||||
|
const updatedExecutions = [...prevExecutions];
|
||||||
|
const index = updatedExecutions.findIndex(
|
||||||
|
e => e.execution_id === updatedExecution.execution_id
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
updatedExecutions[index] = updatedExecution;
|
||||||
|
}
|
||||||
|
return updatedExecutions;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchExecutions = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${globalUrl}/api/v2/workflows/${workflow.id}/executions`, {
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setExecutions(data.executions);
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const executionId = urlParams.get('execution_id');
|
||||||
|
if (executionId) {
|
||||||
|
const execution = data.executions.find(e => e.execution_id === executionId);
|
||||||
|
if (execution) {
|
||||||
|
setSelectedExecution(execution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch executions:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [workflow.id, globalUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchExecutions();
|
||||||
|
}, [fetchExecutions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentExecution?.execution_id) {
|
||||||
|
setExecutions(prev => {
|
||||||
|
const existingIndex = prev.findIndex(e => e.execution_id === currentExecution.execution_id);
|
||||||
|
if (existingIndex === -1) {
|
||||||
|
return [currentExecution, ...prev];
|
||||||
|
}
|
||||||
|
const updated = [...prev];
|
||||||
|
updated[existingIndex] = currentExecution;
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
setSelectedExecution(currentExecution);
|
||||||
|
}
|
||||||
|
}, [currentExecution]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
height: "fit-content",
|
||||||
|
backgroundColor: '#1E1E1E',
|
||||||
|
borderTop: '1px solid #454545',
|
||||||
|
overflow: 'hidden',
|
||||||
|
color: '#CCCCCC',
|
||||||
|
fontFamily: "'JetBrains Mono', monospace",
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading && !executions.length ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
||||||
|
<CircularProgress size={24} />
|
||||||
|
</Box>
|
||||||
|
) : selectedExecution ? (
|
||||||
|
<ExecutionDetail
|
||||||
|
execution={selectedExecution}
|
||||||
|
onBack={() => {
|
||||||
|
setSelectedExecution(null);
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.delete('execution_id');
|
||||||
|
window.history.pushState({}, '', url);
|
||||||
|
fetchExecutions();
|
||||||
|
}}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
selecteAction={mainAction}
|
||||||
|
onExecutionUpdate={handleExecutionUpdate}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Box sx={{
|
||||||
|
p: 2,
|
||||||
|
borderBottom: '1px solid #454545',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
|
<Typography variant="subtitle2" sx={{ color: '#E0E0E0' }}>
|
||||||
|
Execution History
|
||||||
|
</Typography>
|
||||||
|
<IconButton size="small" onClick={onClose} sx={{ color: '#888' }}>
|
||||||
|
<Close fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ flex: 1, overflow: 'auto' }}>
|
||||||
|
<ExecutionsList
|
||||||
|
executions={executions}
|
||||||
|
onSelectExecution={(execution) => {
|
||||||
|
setSelectedExecution(execution);
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.set('execution_id', execution.execution_id);
|
||||||
|
window.history.pushState({}, '', url);
|
||||||
|
}}
|
||||||
|
activeExecutionId={currentExecution?.execution_id}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default ExecutionPanel;
|
||||||
@@ -227,7 +227,7 @@ const ExploreWorkflow = (props) => {
|
|||||||
<ArrowBackIosNewIcon />
|
<ArrowBackIosNewIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div style={{ minWidth: 554, maxWidth: 554, borderRadius: theme.palette.borderRadius, }}>
|
<div style={{ minWidth: 554, maxWidth: 554, borderRadius: theme.palette?.borderRadius, }}>
|
||||||
<AliceCarousel
|
<AliceCarousel
|
||||||
style={{ backgroundColor: theme.palette.surfaceColor, minHeight: 750, maxHeight: 750, }}
|
style={{ backgroundColor: theme.palette.surfaceColor, minHeight: 750, maxHeight: 750, }}
|
||||||
items={formattedCarousel}
|
items={formattedCarousel}
|
||||||
@@ -320,7 +320,7 @@ const ExploreWorkflow = (props) => {
|
|||||||
|
|
||||||
<div style={{ marginTop: 0, }}>
|
<div style={{ marginTop: 0, }}>
|
||||||
<div className="thumbs" style={{ display: "flex" }}>
|
<div className="thumbs" style={{ display: "flex" }}>
|
||||||
<div style={{ minWidth: isMobile ? 300 : 554, maxWidth: isMobile ? 300 : 554, borderRadius: theme.palette.borderRadius, }}>
|
<div style={{ minWidth: isMobile ? 300 : 554, maxWidth: isMobile ? 300 : 554, borderRadius: theme.palette?.borderRadius, }}>
|
||||||
<Grid item xs={11} style={{}}>
|
<Grid item xs={11} style={{}}>
|
||||||
{suggestedUsecases.length === 0 && usecasesSet ?
|
{suggestedUsecases.length === 0 && usecasesSet ?
|
||||||
<Typography variant="h6" style={{ marginTop: 30, marginBottom: 50, }} color="rgba(158, 158, 158, 1)">
|
<Typography variant="h6" style={{ marginTop: 30, marginBottom: 50, }} color="rgba(158, 158, 158, 1)">
|
||||||
|
|||||||
@@ -0,0 +1,687 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
import theme from '../theme.jsx';
|
||||||
|
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
|
||||||
|
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
Chip,
|
||||||
|
TextField,
|
||||||
|
CircularProgress,
|
||||||
|
} from "@mui/material"
|
||||||
|
|
||||||
|
import {
|
||||||
|
CheckCircleOutline as CheckCircleOutlineIcon,
|
||||||
|
ErrorOutline as ErrorOutlineIcon,
|
||||||
|
} from "@mui/icons-material"
|
||||||
|
|
||||||
|
import {
|
||||||
|
green,
|
||||||
|
red,
|
||||||
|
} from "../views/AngularWorkflow.jsx"
|
||||||
|
|
||||||
|
const FixWorkflowValidationErrors = (props) => {
|
||||||
|
const { globalUrl, workflow, setWorkflow, setUpdateParent, } = props;
|
||||||
|
|
||||||
|
const [appsLoading, setAppsLoading] = useState(false)
|
||||||
|
const [apps, setApps] = useState([])
|
||||||
|
const [appAuth, setAppAuth] = useState([])
|
||||||
|
const [_, setUpdate] = useState(0)
|
||||||
|
|
||||||
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "migration.shuffler.io";
|
||||||
|
|
||||||
|
if (workflow === undefined || workflow === null) {
|
||||||
|
console.error("Workflow is undefined")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.validation === undefined || workflow.validation === null) {
|
||||||
|
console.error("Workflow validation is undefined")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.validation.valid === true) {
|
||||||
|
console.error("Workflow is valid - nothing to do for errors")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setWorkflow === undefined || setWorkflow === null) {
|
||||||
|
console.error("No setWorkflow")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchApps = () => {
|
||||||
|
if (appsLoading === true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppsLoading(true)
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/apps`
|
||||||
|
fetch(url,{
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include"
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
setAppsLoading(false)
|
||||||
|
if (data.success === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setApps(data)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
setAppsLoading(false)
|
||||||
|
console.error("Error: ", error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the workflow as well
|
||||||
|
const saveWorkflow = (workflow) => {
|
||||||
|
if (workflow.id === undefined || workflow.id === null) {
|
||||||
|
toast("Workflow ID is missing during save. Please try again")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/workflows/${workflow.id}`
|
||||||
|
fetch(url, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(workflow),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success === false) {
|
||||||
|
toast("Failed to save workflow")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
toast("Failed to save workflow: " + error.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchAuthentication = (reset, updateAction, closeMenu, action_id) => {
|
||||||
|
if (appsLoading === true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/apps/authentication`
|
||||||
|
fetch(url,{
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include"
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const authlist = data.data
|
||||||
|
setAppAuth(authlist)
|
||||||
|
if (updateAction === true) {
|
||||||
|
console.log("Updating action: ", action_id)
|
||||||
|
|
||||||
|
// Find the action in the workflow and set auth for it
|
||||||
|
var foundActionIndex = -1
|
||||||
|
for (var i = 0; i < workflow.actions.length; i++) {
|
||||||
|
if (workflow.actions[i].id === action_id) {
|
||||||
|
foundActionIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundActionIndex === -1) {
|
||||||
|
console.error("Failed to find action in workflow")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const appId = workflow.actions[foundActionIndex].app_id
|
||||||
|
var lastauth = -1
|
||||||
|
for (var authKey in authlist) {
|
||||||
|
if (authlist[authKey].app_id !== appId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authlist[authKey].created > lastauth) {
|
||||||
|
lastauth = authlist[authKey].created
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("FOUND AUTH: ", authlist[authKey])
|
||||||
|
workflow.actions[foundActionIndex].authentication_id = authlist[authKey].id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (setWorkflow !== undefined) {
|
||||||
|
setWorkflow(workflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Auth loading error: ", error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apps !== undefined && apps !== null && apps.length === 0 && appsLoading === false) {
|
||||||
|
fetchApps()
|
||||||
|
fetchAuthentication()
|
||||||
|
}
|
||||||
|
|
||||||
|
const setSelectedAction = (action) => {
|
||||||
|
if (workflow === undefined || workflow === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.actions === undefined || workflow.actions === null || workflow.actions.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setWorkflow === undefined || setWorkflow === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < workflow.actions.length; i++) {
|
||||||
|
if (workflow.actions[i].id === action.id) {
|
||||||
|
workflow.actions[i] = action
|
||||||
|
|
||||||
|
// Update any action with the same app_id to have same auth
|
||||||
|
for (var j = 0; j < workflow.actions.length; j++) {
|
||||||
|
if (workflow.actions[j].app_id === action.app_id) {
|
||||||
|
workflow.actions[j].authentication_id = action.authentication_id
|
||||||
|
workflow.actions[j].selectedAuthentication = action.selectedAuthentication
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkflow(workflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ErrorItem = (props) => {
|
||||||
|
const { apps, error, index } = props
|
||||||
|
|
||||||
|
const [validating, setValidating] = useState(false)
|
||||||
|
const [actionRunInfo, setActionRunInfo] = useState({})
|
||||||
|
if (error === undefined || error === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apps === undefined || apps === null || apps.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateApp = (app, action) => {
|
||||||
|
if (validating) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setValidating(true)
|
||||||
|
|
||||||
|
// FIXME: Run execution:
|
||||||
|
// 1. Should set app authentication validation
|
||||||
|
if (isCloud) {
|
||||||
|
action.environment = "Cloud"
|
||||||
|
} else {
|
||||||
|
action.environment = "Shuffle"
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
setExecutionResult({
|
||||||
|
valid: false,
|
||||||
|
result: baseResult,
|
||||||
|
})
|
||||||
|
setExecuting(true);
|
||||||
|
*/
|
||||||
|
|
||||||
|
setActionRunInfo({})
|
||||||
|
const url = `${globalUrl}/api/v1/apps/${app.id}/run?validation=true`
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(action),
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for stream results :O!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
setValidating(false)
|
||||||
|
|
||||||
|
setActionRunInfo(responseJson)
|
||||||
|
|
||||||
|
//console.log("RESPONSE: ", responseJson)
|
||||||
|
if (
|
||||||
|
responseJson.success === true &&
|
||||||
|
responseJson.result !== null &&
|
||||||
|
responseJson.result !== undefined &&
|
||||||
|
responseJson.result.length > 0
|
||||||
|
) {
|
||||||
|
//toast("
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast("Execution error: " + error.toString());
|
||||||
|
setValidating(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var authReturn = null
|
||||||
|
var validationReturn = null
|
||||||
|
var foundApp = {
|
||||||
|
"name": "",
|
||||||
|
"id": "",
|
||||||
|
}
|
||||||
|
var foundAction = {
|
||||||
|
"name": "",
|
||||||
|
"label": "",
|
||||||
|
"id": "",
|
||||||
|
"app_id": "",
|
||||||
|
"app_name": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedImage = null
|
||||||
|
var resolveButton = null
|
||||||
|
if (error.app_id !== undefined && error.app_id !== null) {
|
||||||
|
for (var i = 0; i < apps.length; i++) {
|
||||||
|
if (apps[i].id === error.app_id) {
|
||||||
|
foundApp = apps[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundApp) {
|
||||||
|
toast("Couldn't find relevant app. Is it activated?")
|
||||||
|
return "Failed to find app"
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedImage =
|
||||||
|
<img
|
||||||
|
src={foundApp.large_image}
|
||||||
|
style={{
|
||||||
|
width: 25,
|
||||||
|
height: 25,
|
||||||
|
marginRight: 10,
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
border: `1px solid ${theme.palette.borderColor}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.action_id !== undefined && error.action_id !== null && workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
|
||||||
|
for (var i = 0; i < workflow.actions.length; i++) {
|
||||||
|
if (workflow.actions[i].id === error.action_id) {
|
||||||
|
foundAction = workflow.actions[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationIcon = Object.getOwnPropertyNames(actionRunInfo).length === 0 ? null :
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<Typography variant="body1">
|
||||||
|
{actionRunInfo.result}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
{actionRunInfo.validation.valid === true ?
|
||||||
|
<CheckCircleOutlineIcon style={{color: green, marginRight: 10, }} />
|
||||||
|
:
|
||||||
|
<ErrorOutlineIcon style={{color: red, marginRight: 10, }} />
|
||||||
|
}
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
const authenticationType = foundApp.authentication
|
||||||
|
if (error.type === "configuration" || error.type === "authentication") {
|
||||||
|
// FIXME: Check the error
|
||||||
|
if (appAuth === undefined || appAuth === null) {
|
||||||
|
return "Loading auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
var relevantAuthentication = []
|
||||||
|
var foundAuth = {}
|
||||||
|
for (var key in appAuth) {
|
||||||
|
if (appAuth[key].app.id !== error.app_id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
foundAuth = appAuth[key]
|
||||||
|
relevantAuthentication.push(appAuth[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundAction.selectedAuthentication === undefined || foundAction.selectedAuthentication === null) {
|
||||||
|
foundAction.selectedAuthentication = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("FOUNDACTION: ", foundAction, foundAuth)
|
||||||
|
|
||||||
|
var authFound = false
|
||||||
|
if (foundAuth.id !== undefined && foundAuth.id !== null && foundAuth.id.length > 0) {
|
||||||
|
var authGroups = []
|
||||||
|
// Choose from a dropdown
|
||||||
|
authReturn = <Select
|
||||||
|
MenuProps={{
|
||||||
|
disableScrollLock: true,
|
||||||
|
}}
|
||||||
|
labelId="select-app-auth"
|
||||||
|
value={
|
||||||
|
foundAction.authentication_id === "authgroups" ? "authgroups" :
|
||||||
|
Object.getOwnPropertyNames(foundAction.selectedAuthentication).length === 0
|
||||||
|
? "No selection"
|
||||||
|
: foundAction.selectedAuthentication
|
||||||
|
}
|
||||||
|
SelectDisplayProps={{
|
||||||
|
style: {
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value === "No selection") {
|
||||||
|
foundAction.selectedAuthentication = {};
|
||||||
|
foundAction.authentication_id = "";
|
||||||
|
|
||||||
|
for (let [key,keyval] in Object.entries(foundAction.parameters)) {
|
||||||
|
if (foundAction.parameters[key].configuration === false) {
|
||||||
|
//console.log("FIELDSKIP: ", foundAction.parameters[key].name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundAction.parameters[key].name === "url" && authenticationType?.type === "oauth2-app" && foundAction.parameters[key].value.includes("http")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundAction.parameters[key].example !== undefined && foundAction.parameters[key].example !== null && foundAction.parameters[key].example !== "") {
|
||||||
|
if (foundAction.parameters[key].example.toLowerCase().includes("apik") || foundAction.parameters[key].example.toLowerCase().includes("key") || foundAction.parameters[key].example.toLowerCase().includes("pass") || foundAction.parameters[key].example.toLowerCase().includes("****")) {
|
||||||
|
foundAction.parameters[key].value = ""
|
||||||
|
} else {
|
||||||
|
foundAction.parameters[key].value = foundAction.parameters[key].example
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
foundAction.parameters[key].value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedAction(foundAction)
|
||||||
|
setUpdate(Math.random());
|
||||||
|
|
||||||
|
} else if (e.target.value === "authgroups") {
|
||||||
|
if (authGroups !== undefined && authGroups !== null && authGroups.length === 0) {
|
||||||
|
toast("No auth groups created. Opening window to create one")
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.open("/admin?tab=app_auth", "_blank")
|
||||||
|
}, 2500)
|
||||||
|
} else {
|
||||||
|
foundAction.selectedAuthentication = {};
|
||||||
|
foundAction.authentication_id = "authgroups"
|
||||||
|
|
||||||
|
for (let [key,keyval] in Object.entries(foundAction.parameters)) {
|
||||||
|
//console.log(foundAction.parameters[key])
|
||||||
|
if (foundAction.parameters[key].configuration) {
|
||||||
|
|
||||||
|
if (foundAction.parameters[key].name === "url" && authenticationType?.type === "oauth2-app") {
|
||||||
|
} else {
|
||||||
|
foundAction.parameters[key].value = "authgroup controlled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedAction(foundAction)
|
||||||
|
setUpdate(Math.random())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foundAction.selectedAuthentication = e.target.value
|
||||||
|
foundAction.authentication_id = e.target.value.id
|
||||||
|
|
||||||
|
setSelectedAction(foundAction)
|
||||||
|
setUpdate(Math.random())
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
height: 40,
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
value="No selection"
|
||||||
|
>
|
||||||
|
{selectedImage}
|
||||||
|
<em>No selection</em>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{relevantAuthentication.map((data) => {
|
||||||
|
if (data.last_modified === true) {
|
||||||
|
//console.log("LAST MODIFIED: ", data.label)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundAction.authentication_id === data.id) {
|
||||||
|
authFound = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={data.id}
|
||||||
|
disabled={data.id === foundAction.authentication_id}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
overflowX: "auto",
|
||||||
|
}}
|
||||||
|
value={data}
|
||||||
|
>
|
||||||
|
{selectedImage}
|
||||||
|
|
||||||
|
{data.label}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/*
|
||||||
|
<Divider style={{marginTop: 10, marginBottom: 10, }}/>
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
disabled
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
value="authgroups"
|
||||||
|
>
|
||||||
|
<em>Auth Groups</em>
|
||||||
|
</MenuItem>
|
||||||
|
*/}
|
||||||
|
|
||||||
|
</Select>
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Validate the CURRENT authentication that has been chosen?
|
||||||
|
if (foundApp.authentication === undefined || foundApp.authentication === null) {
|
||||||
|
toast("Authentication error: No authentication found")
|
||||||
|
authReturn = "Failed to find auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authReturn === null && foundApp.authentication.type === "oauth2" || foundApp.authentication.type === "oauth2-app") {
|
||||||
|
authReturn =
|
||||||
|
<AuthenticationOauth2
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
authenticationType={foundApp.authentication}
|
||||||
|
selectedAction={foundAction}
|
||||||
|
|
||||||
|
selectedApp={foundApp}
|
||||||
|
getAppAuthentication={fetchAuthentication}
|
||||||
|
isCloud={true}
|
||||||
|
authButtonOnly={true}
|
||||||
|
/>
|
||||||
|
} else if (authReturn === null) {
|
||||||
|
authReturn = "Other auth - Not implemented"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
validationReturn = authReturn === null || foundAuth.id === undefined || foundAuth.id === null || foundAuth.id.length === 0 || !authFound ? null :
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
style={{
|
||||||
|
height: 35,
|
||||||
|
justifyContent: !validating ? "flex-start" : "center",
|
||||||
|
textTransform: "none",
|
||||||
|
fontSize: 18,
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
toast("Validating app")
|
||||||
|
validateApp(foundApp, foundAction)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{validationIcon}
|
||||||
|
{validating ?
|
||||||
|
<CircularProgress
|
||||||
|
color="secondary"
|
||||||
|
style={{width: 30, height: 30, }}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<span>
|
||||||
|
{selectedImage}
|
||||||
|
Validate {foundApp.name.replace("_", " ", -1)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
//resolveButton = !(Object.getOwnPropertyNames(actionRunInfo).length === 0 || actionRunInfo.validation.valid === true) ? null :
|
||||||
|
resolveButton =
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
style={{
|
||||||
|
height: 35,
|
||||||
|
textTransform: "none",
|
||||||
|
backgroundColor: green,
|
||||||
|
color: "black",
|
||||||
|
borderRadius: 50,
|
||||||
|
width: 200,
|
||||||
|
margin: "auto",
|
||||||
|
marginTop: 35,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
toast("Resolving error")
|
||||||
|
console.log("Error: ", error)
|
||||||
|
console.log("Errors: ", workflow.validation)
|
||||||
|
|
||||||
|
// Remove the error from the validation list
|
||||||
|
var newErrors = []
|
||||||
|
for (var workflowErrorKey in workflow.validation.errors) {
|
||||||
|
if (workflow.validation.errors[workflowErrorKey].error !== error.error) {
|
||||||
|
newErrors.push(workflow.validation.errors[workflowErrorKey])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
workflow.validation.errors = newErrors
|
||||||
|
if (workflow.validation.errors.length === 0) {
|
||||||
|
workflow.validation.valid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets it in the parent
|
||||||
|
setWorkflow(workflow)
|
||||||
|
if (setUpdateParent !== undefined) {
|
||||||
|
setUpdateParent(Math.random())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves the actual workflow with the update(s)
|
||||||
|
saveWorkflow(workflow)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Resolve
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{authReturn}
|
||||||
|
<div style={{marginTop: 5, }} />
|
||||||
|
{validationReturn}
|
||||||
|
|
||||||
|
{resolveButton}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Workflow validation: ", workflow.validation)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{workflow.errors !== undefined && workflow.errors !== null ?
|
||||||
|
<div>
|
||||||
|
General errors: {workflow.errors.length}
|
||||||
|
{workflow.errors.map((error, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
- {error}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
<Divider style={{marginTop: 15, marginBottom: 15, }}/>
|
||||||
|
|
||||||
|
|
||||||
|
{workflow.validation.errors !== undefined && workflow.validation.errors !== null ?
|
||||||
|
<div>
|
||||||
|
Validation errors: {workflow.validation.errors.length}
|
||||||
|
{workflow.validation.errors.map((error, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ErrorItem
|
||||||
|
apps={apps}
|
||||||
|
error={error}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
<Divider style={{marginTop: 15, marginBottom: 15, }} />
|
||||||
|
Apps loaded: {apps.length}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FixWorkflowValidationErrors
|
||||||
@@ -50,8 +50,6 @@ const hoverOutColor = "#e8eaf6"
|
|||||||
|
|
||||||
const Header = props => {
|
const Header = props => {
|
||||||
const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, homePage, userdata, serverside, } = props;
|
const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, homePage, userdata, serverside, } = props;
|
||||||
//const theme = useTheme();
|
|
||||||
//const alert = useAlert()
|
|
||||||
|
|
||||||
|
|
||||||
const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
|
const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
|
||||||
@@ -829,7 +827,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
backgroundColor: theme.palette.surfaceColor,
|
backgroundColor: theme.palette.surfaceColor,
|
||||||
marginRight: 15,
|
marginRight: 15,
|
||||||
color: "white",
|
color: "white",
|
||||||
@@ -904,7 +902,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
|
|||||||
|
|
||||||
<Tooltip color="primary" title={parsedTitle} placement="left">
|
<Tooltip color="primary" title={parsedTitle} placement="left">
|
||||||
<div style={{display: "flex"}}>
|
<div style={{display: "flex"}}>
|
||||||
{isCloud?<Typography variant="body2" style={{borderRadius: theme.palette.borderRadius, float: "left", margin: "0 0 0 0", marginRight: 25, }}>{regiontag}</Typography>:null} {image} <span style={{marginLeft: 8}}>{data.name}</span>
|
{isCloud?<Typography variant="body2" style={{borderRadius: theme.palette?.borderRadius, float: "left", margin: "0 0 0 0", marginRight: 25, }}>{regiontag}</Typography>:null} {image} <span style={{marginLeft: 8}}>{data.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@@ -935,7 +933,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
|
|||||||
null
|
null
|
||||||
:
|
:
|
||||||
<Tooltip title={`Amount of executions left: ${userdata.app_execution_usage} / ${userdata.app_execution_limit}. When the limit is reached, you can still use Shuffle normally, but your Workflow triggers may stop working. Reach out to support@shuffler.io to extend this limit.`}>
|
<Tooltip title={`Amount of executions left: ${userdata.app_execution_usage} / ${userdata.app_execution_limit}. When the limit is reached, you can still use Shuffle normally, but your Workflow triggers may stop working. Reach out to support@shuffler.io to extend this limit.`}>
|
||||||
<div style={{maxHeight: 30, minHeight: 30, padding: 8, textAlign: "center", cursor: "pointer", borderRadius: theme.palette.borderRadius, marginRight: 10, marginTop: 12, backgroundColor: theme.palette.surfaceColor, minWidth: 60, maxWidth: 60, border: userdata.app_execution_usage/userdata.app_execution_limit >= 0.9 ? "#f86a3e" : null, }} onClick={() => {
|
<div style={{maxHeight: 30, minHeight: 30, padding: 8, textAlign: "center", cursor: "pointer", borderRadius: theme.palette?.borderRadius, marginRight: 10, marginTop: 12, backgroundColor: theme.palette.surfaceColor, minWidth: 60, maxWidth: 60, border: userdata.app_execution_usage/userdata.app_execution_limit >= 0.9 ? "#f86a3e" : null, }} onClick={() => {
|
||||||
console.log(userdata.appe_execution_usage/userdata.app_execution_limit)
|
console.log(userdata.appe_execution_usage/userdata.app_execution_limit)
|
||||||
if (window.drift !== undefined) {
|
if (window.drift !== undefined) {
|
||||||
window.drift.api.startInteraction({ interactionId: 326905 })
|
window.drift.api.startInteraction({ interactionId: 326905 })
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import {
|
|||||||
import HealthBarChart from '../components/HealthBarChart.jsx';
|
import HealthBarChart from '../components/HealthBarChart.jsx';
|
||||||
|
|
||||||
const HealthPage = (props) => {
|
const HealthPage = (props) => {
|
||||||
const { userdata } = props;
|
const { globalUrl, userdata } = props;
|
||||||
const [healthData, setHealthData] = useState(null);
|
const [healthData, setHealthData] = useState(null);
|
||||||
const [selectedRange, setSelectedRange] = useState('30d');
|
const [selectedRange, setSelectedRange] = useState('30d');
|
||||||
const [filteredData, setFilteredData] = useState([]);
|
const [filteredData, setFilteredData] = useState([]);
|
||||||
const [averageUptime, setAverageUptime] = useState(0);
|
const [averageUptime, setAverageUptime] = useState(0);
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||||
const globalUrl = `https://shuffler.io`
|
//const globalUrl = `https://shuffler.io`
|
||||||
|
|
||||||
console.log("HEALTHPAGE 1")
|
console.log("HEALTHPAGE 1")
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react";
|
|||||||
import ReactGA from 'react-ga4';
|
import ReactGA from 'react-ga4';
|
||||||
|
|
||||||
import theme from "../theme.jsx";
|
import theme from "../theme.jsx";
|
||||||
import { useTheme } from "@mui/styles";
|
|
||||||
import countries from "../components/Countries.jsx";
|
import countries from "../components/Countries.jsx";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -88,7 +87,7 @@ const LicencePopup = (props) => {
|
|||||||
|
|
||||||
const paperStyle = {
|
const paperStyle = {
|
||||||
padding: 20,
|
padding: 20,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +193,7 @@ const LicencePopup = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
style={{ borderRadius: theme.palette.borderRadius, }}
|
style={{ borderRadius: theme.palette?.borderRadius, }}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<div style={{}}>
|
<div style={{}}>
|
||||||
@@ -320,7 +319,7 @@ const LicencePopup = (props) => {
|
|||||||
margin: "auto",
|
margin: "auto",
|
||||||
width: 100,
|
width: 100,
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
// borderRadius: theme.palette.borderRadius,
|
// borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
: null}
|
: null}
|
||||||
@@ -390,7 +389,7 @@ const LicencePopup = (props) => {
|
|||||||
value={feature.split("Worker License: ")[1]}
|
value={feature.split("Worker License: ")[1]}
|
||||||
style={{
|
style={{
|
||||||
// backgroundColor: theme.palette.inputColor,
|
// backgroundColor: theme.palette.inputColor,
|
||||||
// borderRadius: theme.palette.borderRadius,
|
// borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
id={fieldId}
|
id={fieldId}
|
||||||
onClick={() => { }}
|
onClick={() => { }}
|
||||||
@@ -515,16 +514,16 @@ const LicencePopup = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("New variant: ", shuffleVariant)
|
console.log("New variant: ", shuffleVariant)
|
||||||
|
|
||||||
if (shuffleVariant === 1) {
|
if (shuffleVariant === 1) {
|
||||||
setCalculatedCost("$600")
|
setCalculatedCost("$960")
|
||||||
setSelectedValue(8)
|
setSelectedValue(8)
|
||||||
} else {
|
} else {
|
||||||
setCalculatedCost("$540")
|
setCalculatedCost("$960")
|
||||||
setSelectedValue(300)
|
setSelectedValue(300)
|
||||||
}
|
}
|
||||||
}, [shuffleVariant])
|
}, [shuffleVariant])
|
||||||
|
|
||||||
if (typeof window === 'undefined' || window.location === undefined) {
|
if (typeof window === 'undefined' || window.location === undefined) {
|
||||||
return null
|
return null
|
||||||
@@ -680,7 +679,7 @@ const LicencePopup = (props) => {
|
|||||||
color: "white",
|
color: "white",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Priceitem: ", shuffleVariant)
|
||||||
const isLoggedInHandler = () => {
|
const isLoggedInHandler = () => {
|
||||||
if (calculatedCost === payasyougo) {
|
if (calculatedCost === payasyougo) {
|
||||||
handlePayasyougo(props.userdata)
|
handlePayasyougo(props.userdata)
|
||||||
@@ -690,7 +689,7 @@ const LicencePopup = (props) => {
|
|||||||
const priceItem = window.location.origin === "https://shuffler.io" ?
|
const priceItem = window.location.origin === "https://shuffler.io" ?
|
||||||
shuffleVariant === 0 ? "app_executions" : "cores"
|
shuffleVariant === 0 ? "app_executions" : "cores"
|
||||||
:
|
:
|
||||||
shuffleVariant === 0 ? "price_1MROFrDzMUgUjxHShcSxgHO1" : "price_1NXjQqDzMUgUjxHSg690R4FP"
|
shuffleVariant === 0 ? "price_1PZPSSEJjT17t98NLJoTMYja" : "price_1PZPQuEJjT17t98N3yORUtd9"
|
||||||
|
|
||||||
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
|
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
|
||||||
const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
|
const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
|
||||||
@@ -824,7 +823,7 @@ const LicencePopup = (props) => {
|
|||||||
{errorMessage.length > 0 ? <Typography variant="h4">Error: {errorMessage}</Typography> : null}
|
{errorMessage.length > 0 ? <Typography variant="h4">Error: {errorMessage}</Typography> : null}
|
||||||
<Card style={{
|
<Card style={{
|
||||||
padding: 20,
|
padding: 20,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
border: "1px solid #f85a3e",
|
border: "1px solid #f85a3e",
|
||||||
}}>
|
}}>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Paper, Typography, Box, CircularProgress, TextField, Button } from "@mui/material";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
const MFASetup = ({ isLoaded, globalUrl, setCookie }) => {
|
||||||
|
const [image2FA, setImage2FA] = useState("");
|
||||||
|
const [secret2FA, setSecret2FA] = useState("");
|
||||||
|
const [mfaCode, setMfaCode] = useState("");
|
||||||
|
const [code, setCode] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleGet2FACode();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoaded) {
|
||||||
|
const code = window.location.pathname.split("/")[2];
|
||||||
|
setMfaCode(code);
|
||||||
|
}
|
||||||
|
}, [isLoaded]);
|
||||||
|
|
||||||
|
const handleGet2FACode = () => {
|
||||||
|
if (mfaCode === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/users/${mfaCode}/get2fa`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 404) {
|
||||||
|
toast("User not found. Redirecting to login page in 3 seconds...");
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.pathname = "/login";
|
||||||
|
return;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for apps :O!");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
setImage2FA(responseJson.reason);
|
||||||
|
setSecret2FA(responseJson.extra);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast(error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mfaCode) {
|
||||||
|
handleGet2FACode();
|
||||||
|
}
|
||||||
|
}, [mfaCode]);
|
||||||
|
|
||||||
|
const handleVerify2FA = (mfaCode, code, changeMFAActive) => {
|
||||||
|
const data = {
|
||||||
|
code: code,
|
||||||
|
changeMFAActive: changeMFAActive,
|
||||||
|
};
|
||||||
|
|
||||||
|
toast("Verifying 2fa code. Please wait...");
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/users/${mfaCode}/set2fa`, {
|
||||||
|
mode: "cors",
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
crossDomain: true,
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 500) {
|
||||||
|
toast("Wrong code sent. Please try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
toast.success("Successfully setup 2fa. Redirecting in 3 seconds...");
|
||||||
|
for (var key in responseJson["cookies"]) {
|
||||||
|
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmpView = new URLSearchParams(window.location.search).get("view");
|
||||||
|
if (tmpView !== undefined && tmpView !== null) {
|
||||||
|
var newUrl = `/${tmpView}`;
|
||||||
|
if (tmpView.startsWith("/")) {
|
||||||
|
newUrl = `${tmpView}`;
|
||||||
|
}
|
||||||
|
window.location.pathname = newUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseJson.tutorials !== undefined && responseJson.tutorials !== null) {
|
||||||
|
const welcome = responseJson.tutorials.find((element) => element.name === "welcome");
|
||||||
|
if (welcome === undefined || welcome === null) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.pathname = "/welcome";
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.pathname = "/workflows";
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
toast("Failed to setup 2fa. Please try again.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ paddingTop: 50, margin: "0px auto", width: "500px" }}>
|
||||||
|
<Paper elevation={3} style={{ padding: "30px", backgroundColor: "#212121" }}>
|
||||||
|
<Typography variant="h5" style={{ color: "white", marginBottom: 10, textAlign: "center" }}>
|
||||||
|
Multi-Factor Authentication Setup
|
||||||
|
</Typography>
|
||||||
|
<div style={{ marginTop: 15 }}>
|
||||||
|
<QRCodeSection secret2FA={secret2FA} image2FA={image2FA} />
|
||||||
|
</div>
|
||||||
|
<Typography variant="body1" style={{ color: "white", marginTop: 15 }}>
|
||||||
|
Enter the code from your authenticator app below.
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="code"
|
||||||
|
label="Code"
|
||||||
|
name="code"
|
||||||
|
autoComplete="code"
|
||||||
|
autoFocus
|
||||||
|
onChange={(e) => setCode(e.target.value)}
|
||||||
|
onKeyPress={(e) => {
|
||||||
|
if (e.key === "Enter" && code !== null && code !== "" && code.length === 6) {
|
||||||
|
handleVerify2FA(mfaCode, code, true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => handleVerify2FA(mfaCode, code, true)}
|
||||||
|
style={{
|
||||||
|
backgroundColor: code === null || code === "" || code.length !== 6 ? "gray" : "#f86743",
|
||||||
|
marginTop: 10,
|
||||||
|
color: "#fff",
|
||||||
|
cursor: code === null || code === "" || code.length !== 6 ? "" : "pointer",
|
||||||
|
}}
|
||||||
|
disabled={code === null || code === "" || code.length !== 6}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</Paper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const QRCodeSection = ({ secret2FA, image2FA }) => {
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", height: "100%", width: "100%" }}>
|
||||||
|
{secret2FA && image2FA ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<Typography variant="body2" style={{ color: "white", textAlign: "justify", marginBottom: 10, fontSize: 16 }}>
|
||||||
|
Scan the image below with the two-factor authentication app on your phone. If you can’t use a QR code, use the code {secret2FA} instead.
|
||||||
|
</Typography>
|
||||||
|
<img alt="2FA QR code" src={image2FA} style={{ maxHeight: 200, maxWidth: 200, }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<CircularProgress style={{ margin: "15px auto" }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MFASetup;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, {useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
import { useTheme } from '@mui/styles';
|
|
||||||
import {isMobile} from "react-device-detect";
|
import {isMobile} from "react-device-detect";
|
||||||
import ReactGA from 'react-ga4';
|
import ReactGA from 'react-ga4';
|
||||||
|
import theme from '../theme.jsx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TextField,
|
TextField,
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
const Newsletter = (props) => {
|
const Newsletter = (props) => {
|
||||||
const { globalUrl, } = props;
|
const { globalUrl, } = props;
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [msg, setMsg] = useState("");
|
const [msg, setMsg] = useState("");
|
||||||
const [buttonActive, setButtonActive] = useState(true);
|
const [buttonActive, setButtonActive] = useState(true);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
autoAuth,
|
autoAuth,
|
||||||
authButtonOnly,
|
authButtonOnly,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
|
org_id,
|
||||||
setFinalized,
|
setFinalized,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -148,16 +148,14 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
navigate(`/login?view=${window.location.pathname}&message=Log in to authenticate this app`)
|
navigate(`/login?view=${window.location.pathname}&message=Log in to authenticate this app`)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Should automatically click the auto-auth button?: ", autoAuth)
|
|
||||||
if (autoAuth === true && selectedApp !== undefined) {
|
if (autoAuth === true && selectedApp !== undefined) {
|
||||||
startOauth2Request()
|
startOauth2Request()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (selectedApp.authentication === undefined) {
|
if (selectedApp.authentication === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const startOauth2Request = (admin_consent) => {
|
const startOauth2Request = (admin_consent) => {
|
||||||
// Admin consent also means to add refresh tokens
|
// Admin consent also means to add refresh tokens
|
||||||
@@ -167,7 +165,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
//console.log("APP: ", selectedApp)
|
//console.log("APP: ", selectedApp)
|
||||||
if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
|
if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
|
||||||
handleOauth2Request(
|
handleOauth2Request(
|
||||||
"efe4c3fe-84a1-4821-a84f-23a6cfe8e72d",
|
"fd55c175-aa30-4fa6-b303-09a29fb3f750",
|
||||||
"",
|
"",
|
||||||
"https://graph.microsoft.com",
|
"https://graph.microsoft.com",
|
||||||
["Mail.ReadWrite", "Mail.Send", "offline_access"],
|
["Mail.ReadWrite", "Mail.Send", "offline_access"],
|
||||||
@@ -299,12 +297,11 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
|
|
||||||
const handleOauth2Request = (client_id, client_secret, oauth_url, scopes, admin_consent, prompt, skipScopeReplace) => {
|
const handleOauth2Request = (client_id, client_secret, oauth_url, scopes, admin_consent, prompt, skipScopeReplace) => {
|
||||||
|
|
||||||
console.log("SKIP SCOPE: ", skipScopeReplace)
|
|
||||||
if (skipScopeReplace === false || skipScopeReplace === undefined) {
|
if (skipScopeReplace === false || skipScopeReplace === undefined) {
|
||||||
|
|
||||||
console.log("Selected scopes: ", selectedScopes)
|
console.log("Selected scopes: ", selectedScopes)
|
||||||
if (selectedScopes !== undefined && selectedScopes !== null && selectedScopes.length > 0) {
|
if (selectedScopes !== undefined && selectedScopes !== null && selectedScopes.length > 0) {
|
||||||
toast("Using your scopes instead of the default ones")
|
//toast("Using your scopes instead of the default ones")
|
||||||
scopes = selectedScopes
|
scopes = selectedScopes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,6 +450,8 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
if (orgId !== undefined && orgId !== null && orgId.length > 0) {
|
if (orgId !== undefined && orgId !== null && orgId.length > 0) {
|
||||||
console.log("Adding org_id from user side")
|
console.log("Adding org_id from user side")
|
||||||
state += `%26org_id%3d${orgId}`;
|
state += `%26org_id%3d${orgId}`;
|
||||||
|
}else{
|
||||||
|
state += `%26org_id%3d${org_id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oauth_url !== undefined && oauth_url !== null && oauth_url.length > 0) {
|
if (oauth_url !== undefined && oauth_url !== null && oauth_url.length > 0) {
|
||||||
@@ -461,42 +460,56 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (authenticationType.refresh_uri !== undefined && authenticationType.refresh_uri !== null && authenticationType.refresh_uri.length > 0) {
|
||||||
authenticationType.refresh_uri !== undefined &&
|
state += `%26refresh_uri%3d${authenticationType.refresh_uri}`
|
||||||
authenticationType.refresh_uri !== null &&
|
|
||||||
authenticationType.refresh_uri.length > 0
|
|
||||||
) {
|
|
||||||
state += `%26refresh_uri%3d${authenticationType.refresh_uri}`;
|
|
||||||
} else {
|
} else {
|
||||||
state += `%26refresh_uri%3d${authentication_url}`;
|
state += `%26refresh_uri%3d${authentication_url}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// No prompt forcing
|
if (workflow?.org_id !== undefined && workflow?.org_id !== null && workflow?.org_id.length > 0) {
|
||||||
//var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`;
|
state += `%26org_id%3d${workflow.org_id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Should this be =consent?
|
||||||
var defaultPrompt = "login"
|
var defaultPrompt = "login"
|
||||||
if (prompt !== undefined && prompt !== null && prompt.length > 0) {
|
if (prompt !== undefined && prompt !== null && prompt.length > 0) {
|
||||||
defaultPrompt = prompt
|
defaultPrompt = prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`;
|
var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`;
|
||||||
|
if (admin_consent === true) {
|
||||||
|
console.log("Running Oauth2 WITH admin consent")
|
||||||
|
//url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`;
|
||||||
|
url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url !== undefined && url !== null && url.length > 0) {
|
||||||
|
if (url.toLowerCase().includes("{tenant")) {
|
||||||
|
// Check location of {tenant, then find the next } and replace with 'common'. Make sure next } is AFTER {tenant
|
||||||
|
try {
|
||||||
|
const tenantIndex = url.toLowerCase().indexOf("{tenant")
|
||||||
|
const substring = url.substring(tenantIndex)
|
||||||
|
const nextBracket = substring.indexOf("}")
|
||||||
|
const newUrl = url.substring(0, tenantIndex) + "common" + url.substring(tenantIndex + nextBracket + 1)
|
||||||
|
url = newUrl
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to replace {tenant} with common: ", e)
|
||||||
|
}
|
||||||
|
|
||||||
if (admin_consent === true) {
|
|
||||||
console.log("Running Oauth2 WITH admin consent")
|
|
||||||
//url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`;
|
|
||||||
url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Force new consent
|
/*
|
||||||
|
console.log("OAUTH2 URL: ", url)
|
||||||
|
return
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// 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`;
|
//const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`;
|
||||||
|
|
||||||
// Admin consent
|
// Admin consent
|
||||||
//const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent`
|
//const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent`
|
||||||
|
|
||||||
// &resource=https%3A%2F%2Fgraph.microsoft.com&
|
|
||||||
|
|
||||||
// FIXME: Awful, but works for prototyping
|
|
||||||
// How can we get a callback properly realtime?
|
|
||||||
// How can we properly try-catch without breaks on error?
|
|
||||||
try {
|
try {
|
||||||
var newwin = window.open(url, "", "width=582,height=700");
|
var newwin = window.open(url, "", "width=582,height=700");
|
||||||
//console.log(newwin)
|
//console.log(newwin)
|
||||||
@@ -511,7 +524,12 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
//alert('"Secure Payment" window closed!');
|
//alert('"Secure Payment" window closed!');
|
||||||
|
|
||||||
if (getAppAuthentication !== undefined) {
|
if (getAppAuthentication !== undefined) {
|
||||||
getAppAuthentication(true, true, true);
|
// This should be orgId, not action Id as to load auth properly
|
||||||
|
if (workflow !== undefined && workflow !== null && workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
|
||||||
|
getAppAuthentication(true, true, true, workflow.org_id)
|
||||||
|
} else {
|
||||||
|
getAppAuthentication(true, true, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toast("Authentication successful!")
|
toast("Authentication successful!")
|
||||||
@@ -525,7 +543,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
setFinalized(true)
|
setFinalized(true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Not closed")
|
//console.log("Not closed")
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
//do {
|
//do {
|
||||||
@@ -678,7 +696,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
backgroundColor: "#ffffff",
|
backgroundColor: "#ffffff",
|
||||||
color: "#2f2f2f",
|
color: "#2f2f2f",
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
maxWidth: 300,
|
maxWidth: 300,
|
||||||
maxHeight: 50,
|
maxHeight: 50,
|
||||||
@@ -703,7 +721,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
<span style={{display: "flex"}}>
|
<span style={{display: "flex"}}>
|
||||||
<img
|
<img
|
||||||
alt={selectedAction.app_name}
|
alt={selectedAction.app_name}
|
||||||
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
|
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette?.borderRadius, }}
|
||||||
src={selectedAction.large_image}
|
src={selectedAction.large_image}
|
||||||
/>
|
/>
|
||||||
<Typography style={{ margin: 0, marginLeft: 10, marginTop: 5,}} variant="body1">
|
<Typography style={{ margin: 0, marginLeft: 10, marginTop: 5,}} variant="body1">
|
||||||
@@ -726,7 +744,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<span style={{}}>
|
<span style={{}}>
|
||||||
Oauth2 requires a client ID and secret to authenticate, defined in the remote system. {authenticationType.type === "oauth2-app" ? null : <span>Your redirect URL is <b>{window.location.origin}/set_authentication</b> - </span>}
|
Oauth2 requires a client ID and secret to authenticate, defined in the remote system. <span>Your redirect URL is <b>{window.location.origin}/set_authentication</b> - </span>
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="norefferer"
|
rel="norefferer"
|
||||||
@@ -739,7 +757,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
<div />
|
<div />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{isCloud && registeredApps.includes(selectedApp.name.toLowerCase()) ?
|
{isCloud && registeredApps?.includes(selectedApp?.name?.replaceAll(" ", "_").toLowerCase()) ?
|
||||||
<span>
|
<span>
|
||||||
<span style={{display: "flex"}}>
|
<span style={{display: "flex"}}>
|
||||||
{autoAuthButton}
|
{autoAuthButton}
|
||||||
@@ -785,7 +803,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
: null}
|
: null}
|
||||||
{/*<TextField
|
{/*<TextField
|
||||||
style={{backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius,}}
|
style={{backgroundColor: theme.palette.inputColor, borderRadius: theme.palette?.borderRadius,}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style:{
|
style:{
|
||||||
},
|
},
|
||||||
@@ -817,13 +835,14 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
setOauthUrl(data.value);
|
setOauthUrl(data.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultValue = data.name === "url" && authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0 && (authenticationType.authorizationUrl === undefined || authenticationType.authorizationUrl === null || authenticationType.authorizationUrl.length === 0) && authenticationType.type === "oauth2-app" ? authenticationType.token_uri : data.value === undefined || data.value === null ? "" : data.value
|
const isNormalOauth = authenticationType.redirect_uri !== undefined && authenticationType.redirect_uri !== null && authenticationType.redirect_uri.length > 0
|
||||||
|
|
||||||
|
const defaultValue = !isNormalOauth && data.name === "url" && authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0 && (authenticationType.authorizationUrl === undefined || authenticationType.authorizationUrl === null || authenticationType.authorizationUrl.length === 0) && authenticationType.type === "oauth2-app" ? authenticationType.token_uri : data.value === undefined || data.value === null ? "" : data.value
|
||||||
|
|
||||||
const fieldname = data.name === "url" && authenticationType.grant_type !== undefined && authenticationType.grant_type !== null && authenticationType.grant_type.length > 0 && authenticationType.type === "oauth2-app" ? "Token URL" : data.name
|
const fieldname = !isNormalOauth && data.name === "url" && authenticationType.grant_type !== undefined && authenticationType.grant_type !== null && authenticationType.grant_type.length > 0 && authenticationType.type === "oauth2-app" ? "Token URL" : data.name
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index} style={{ marginTop: authenticationType.type === "oauth2-app" ? 10 : 0, }}>
|
<div key={index} style={{ marginTop: !isNormalOauth && authenticationType.type === "oauth2-app" ? 10 : 0, }}>
|
||||||
<LockOpenIcon style={{ marginRight: 10 }} />
|
<LockOpenIcon style={{ marginRight: 10 }} />
|
||||||
|
|
||||||
<b>{fieldname}</b>
|
<b>{fieldname}</b>
|
||||||
@@ -875,7 +894,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -906,7 +925,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -924,7 +943,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -946,7 +965,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -964,7 +983,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -997,7 +1016,6 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
color: "white",
|
color: "white",
|
||||||
padding: 5,
|
padding: 5,
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
maxWidth: 300,
|
|
||||||
}}
|
}}
|
||||||
onChange={(e, value) => {
|
onChange={(e, value) => {
|
||||||
//handleScopeChange(e)
|
//handleScopeChange(e)
|
||||||
@@ -1044,7 +1062,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
style={{
|
style={{
|
||||||
marginBottom: 40,
|
marginBottom: 40,
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
disabled={
|
disabled={
|
||||||
clientSecret.length === 0 || clientId.length === 0 || buttonClicked || (allscopes.length !== 0 && selectedScopes.length === 0)
|
clientSecret.length === 0 || clientId.length === 0 || buttonClicked || (allscopes.length !== 0 && selectedScopes.length === 0)
|
||||||
@@ -1075,7 +1093,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
disabled={clientSecret.length === 0 || clientId.length === 0}
|
disabled={clientSecret.length === 0 || clientId.length === 0}
|
||||||
variant="text"
|
variant="text"
|
||||||
|
|||||||
@@ -110,8 +110,30 @@ const OrgHeader = (props) => {
|
|||||||
orgDescription,
|
orgDescription,
|
||||||
selectedOrganization.id,
|
selectedOrganization.id,
|
||||||
selectedOrganization.image,
|
selectedOrganization.image,
|
||||||
{},
|
{
|
||||||
{},
|
app_download_repo: selectedOrganization?.defaults?.app_download_repo,
|
||||||
|
app_download_branch: selectedOrganization?.defaults?.app_download_branch,
|
||||||
|
workflow_download_repo: selectedOrganization?.defaults?.workflow_download_repo,
|
||||||
|
workflow_download_branch: selectedOrganization?.defaults?.workflow_download_branch,
|
||||||
|
notification_workflow: selectedOrganization?.defaults?.notification_workflow,
|
||||||
|
documentation_reference: selectedOrganization?.defaults?.documentation_reference,
|
||||||
|
workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo,
|
||||||
|
workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch,
|
||||||
|
workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username,
|
||||||
|
workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token,
|
||||||
|
newsletter: selectedOrganization?.defaults?.newsletter,
|
||||||
|
weekly_recommendations: selectedOrganization?.defaults?.weekly_recommendations,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint,
|
||||||
|
sso_certificate: selectedOrganization?.sso_config?.sso_certificate,
|
||||||
|
client_id: selectedOrganization?.sso_config?.client_id,
|
||||||
|
client_secret: selectedOrganization?.sso_config?.client_secret,
|
||||||
|
openid_authorization: selectedOrganization?.sso_config?.openid_authorization,
|
||||||
|
openid_token: selectedOrganization?.sso_config?.openid_token,
|
||||||
|
SSORequired: selectedOrganization?.sso_config?.SSORequired,
|
||||||
|
auto_provision: selectedOrganization?.sso_config?.auto_provision,
|
||||||
|
},
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, {useState, useEffect, useLayoutEffect} from 'react';
|
|||||||
|
|
||||||
import Draggable from "react-draggable";
|
import Draggable from "react-draggable";
|
||||||
import {
|
import {
|
||||||
Paper
|
Paper
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
const PaperComponent = (props) => {
|
const PaperComponent = (props) => {
|
||||||
@@ -11,7 +11,9 @@ const PaperComponent = (props) => {
|
|||||||
handle="#draggable-dialog-title"
|
handle="#draggable-dialog-title"
|
||||||
cancel={'[class*="MuiDialogContent-root"]'}
|
cancel={'[class*="MuiDialogContent-root"]'}
|
||||||
>
|
>
|
||||||
<Paper {...props} />
|
<Paper
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
</Draggable>
|
</Draggable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useContext, memo } from "react";
|
||||||
|
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import theme from "../theme.jsx";
|
import theme from "../theme.jsx";
|
||||||
@@ -13,22 +13,26 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
Chip,
|
Chip,
|
||||||
Switch,
|
Switch,
|
||||||
|
Skeleton,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { Context } from "../context/ContextApi.jsx";
|
||||||
|
|
||||||
import { useNavigate, Link } from "react-router-dom";
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
import Priority from "../components/Priority.jsx";
|
import Priority from "../components/Priority.jsx";
|
||||||
|
import { constrainMatrix } from "reaviz";
|
||||||
//import { useAlert
|
//import { useAlert
|
||||||
|
|
||||||
const Priorities = (props) => {
|
const Priorities = memo((props) => {
|
||||||
const { globalUrl, userdata,clickedFromOrgTab, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props;
|
const { globalUrl, userdata,clickedFromOrgTab, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props;
|
||||||
|
|
||||||
const [showDismissed, setShowDismissed] = React.useState(false);
|
const [showDismissed, setShowDismissed] = React.useState(false);
|
||||||
const [showRead, setShowRead] = React.useState(false);
|
const [showRead, setShowRead] = React.useState(false);
|
||||||
const [appFramework, setAppFramework] = React.useState({});
|
const [appFramework, setAppFramework] = React.useState({});
|
||||||
|
|
||||||
const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT");
|
const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT");
|
||||||
const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT");
|
const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT");
|
||||||
|
const [highlightKMS, setHighlightKMS] = React.useState(false)
|
||||||
|
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getFramework()
|
getFramework()
|
||||||
|
|
||||||
@@ -36,6 +40,12 @@ const Priorities = (props) => {
|
|||||||
const urlParams = new URLSearchParams(window.location.search)
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
const workflow = urlParams.get("workflow")
|
const workflow = urlParams.get("workflow")
|
||||||
const execution_id = urlParams.get("execution_id")
|
const execution_id = urlParams.get("execution_id")
|
||||||
|
const kms = urlParams.get("kms")
|
||||||
|
|
||||||
|
if (kms !== null && kms !== undefined && kms.length > 0 && kms === "true") {
|
||||||
|
toast.info("KMS-related notifications are highlighted.")
|
||||||
|
setHighlightKMS(true)
|
||||||
|
}
|
||||||
|
|
||||||
if (execution_id !== null) {
|
if (execution_id !== null) {
|
||||||
setSelectedExecutionId(execution_id)
|
setSelectedExecutionId(execution_id)
|
||||||
@@ -209,176 +219,16 @@ const Priorities = (props) => {
|
|||||||
const notificationWidth = "100%"
|
const notificationWidth = "100%"
|
||||||
const imagesize = 22
|
const imagesize = 22
|
||||||
const boxColor = "#86c142"
|
const boxColor = "#86c142"
|
||||||
const NotificationItem = (props) => {
|
|
||||||
const {data} = props
|
|
||||||
|
|
||||||
var image = "";
|
|
||||||
var orgName = "";
|
|
||||||
var orgId = "";
|
|
||||||
|
|
||||||
|
|
||||||
const highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow)
|
|
||||||
|
|
||||||
if (userdata.orgs !== undefined) {
|
|
||||||
const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]);
|
|
||||||
if (foundOrg !== undefined && foundOrg !== null) {
|
|
||||||
//position: "absolute", bottom: 5, right: -5,
|
|
||||||
const imageStyle = {
|
|
||||||
width: imagesize,
|
|
||||||
height: imagesize,
|
|
||||||
pointerEvents: "none",
|
|
||||||
marginLeft:
|
|
||||||
data.creator_org !== undefined && data.creator_org.length > 0
|
|
||||||
? 20
|
|
||||||
: 0,
|
|
||||||
borderRadius: 10,
|
|
||||||
border:
|
|
||||||
foundOrg.id === userdata.active_org.id
|
|
||||||
? `3px solid ${boxColor}`
|
|
||||||
: null,
|
|
||||||
cursor: "pointer",
|
|
||||||
marginRight: 10,
|
|
||||||
};
|
|
||||||
|
|
||||||
image =
|
|
||||||
foundOrg.image === "" ? (
|
|
||||||
<img
|
|
||||||
alt={foundOrg.name}
|
|
||||||
src={theme.palette.defaultImage}
|
|
||||||
style={imageStyle}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
alt={foundOrg.name}
|
|
||||||
src={foundOrg.image}
|
|
||||||
style={imageStyle}
|
|
||||||
onClick={() => {}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
orgName = foundOrg.name;
|
|
||||||
orgId = foundOrg.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.palette.platformColor,
|
|
||||||
width: clickedFromOrgTab ? null :notificationWidth,
|
|
||||||
padding: 30,
|
|
||||||
borderBottom: "1px solid rgba(255,255,255,0.4)",
|
|
||||||
marginBottom: 20,
|
|
||||||
border: highlighted ? "2px solid #f85a3e" : null,
|
|
||||||
borderRadius: theme.palette.borderRadius,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{display: "flex", }}>
|
|
||||||
{data.amount === 1 && data.read === false ?
|
|
||||||
<Chip
|
|
||||||
label={"First seen"}
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
style={{marginRight: 15, height: 25, }}
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
{data.ignored === true ?
|
|
||||||
<Chip
|
|
||||||
label={"Disabled"}
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
style={{marginRight: 15, height: 25, }}
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
{data.read === false ?
|
|
||||||
<Chip
|
|
||||||
label={"Unread"}
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
style={{marginRight: 15, height: 25, }}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
<Chip
|
|
||||||
label={"Read"}
|
|
||||||
variant="outlined"
|
|
||||||
color="secondary"
|
|
||||||
style={{marginRight: 15, height: 25, }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<Typography variant="body1" color="textPrimary">
|
|
||||||
{data.title}
|
|
||||||
</Typography >
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
|
||||||
<img alt={data.title} src={data.image} style={{height: 100, width: 100, }} />
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
<Typography variant="body2" color="textSecondary" style={{marginTop: 10, maxHeight: 200, overflowX: "hidden", overflowY: "auto", }}>
|
|
||||||
{data.description}
|
|
||||||
</Typography >
|
|
||||||
<div style={{ display: "flex" }}>
|
|
||||||
<ButtonGroup>
|
|
||||||
<Button
|
|
||||||
color="secondary"
|
|
||||||
variant="outlined"
|
|
||||||
style={{ marginTop: 15 }}
|
|
||||||
disabled={data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0}
|
|
||||||
onClick={() => {
|
|
||||||
window.open(data.reference_url, "_blank")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Explore
|
|
||||||
</Button>
|
|
||||||
{data.read === false ? (
|
|
||||||
<Button
|
|
||||||
color="secondary"
|
|
||||||
variant="outlined"
|
|
||||||
style={{ marginTop: 15 }}
|
|
||||||
onClick={() => {
|
|
||||||
dismissNotification(data.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Dismiss
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
<Tooltip title="Disabling a notification makes it so similar notifications to this one will NOT be re-opened. It will NOT forward notifications to your notification workflow, but WILL still keep counting." placement="top">
|
|
||||||
<Button
|
|
||||||
color="secondary"
|
|
||||||
variant={data.ignored === true ? "contained" : "outlined"}
|
|
||||||
style={{ marginTop: 15, }}
|
|
||||||
onClick={() => {
|
|
||||||
if (data.ignored === true) {
|
|
||||||
dismissNotification(data.id, false)
|
|
||||||
} else {
|
|
||||||
dismissNotification(data.id, true)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{data.ignored === true ? "Re-enable" : "Disable"}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<Typography variant="body2" color="textSecondary" style={{marginLeft: 20, marginTop: 20, }}>
|
|
||||||
<b>First seen</b>: {new Date(data.created_at * 1000).toISOString().slice(0, 19)}
|
|
||||||
</Typography >
|
|
||||||
<Typography variant="body2" color="textSecondary" style={{marginLeft: 20, marginTop: 20, }}>
|
|
||||||
<b>Last seen</b>: {new Date(data.updated_at * 1000).toISOString().slice(0, 19)}
|
|
||||||
</Typography >
|
|
||||||
<Typography variant="body2" color="textSecondary" style={{marginLeft: 20, marginTop: 20, }}>
|
|
||||||
<b>Times seen</b>: {data.amount}
|
|
||||||
</Typography >
|
|
||||||
</div>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{width: clickedFromOrgTab ? 1030:1000, padding: clickedFromOrgTab ? 27:null, height: clickedFromOrgTab ? "auto":null, backgroundColor: clickedFromOrgTab ? '#212121':null, borderRadius: clickedFromOrgTab ? '16px':null, }}>
|
<div style={{width: "100%", height: "100%", boxSizing: 'border-box', transition: 'width 0.3s ease', padding: clickedFromOrgTab ? "27px 10px 19px 27px":null, height: clickedFromOrgTab ? "auto":null, minHeight: 843, backgroundColor: clickedFromOrgTab ? '#212121':null, borderRadius: clickedFromOrgTab ? '16px':null, }}>
|
||||||
<h2 style={{ display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, marginTop: clickedFromOrgTab?40:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications</h2>
|
<div style={{ maxHeight: 1700, overflowY: "auto", width: '100%', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}>
|
||||||
<span style={{ marginLeft: clickedFromOrgTab?null:25, color: clickedFromOrgTab?"#9E9E9E":null, }}>
|
<div style={{maxWidth: "calc(100% - 20px)"}}>
|
||||||
|
<Typography style={{ fontSize: 24, fontWeight: 'bold', display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications ({
|
||||||
|
notifications?.filter((notification) => showRead === true || notification.read === false).length
|
||||||
|
})</Typography>
|
||||||
|
|
||||||
|
<span style={{ fontSize: 16, marginLeft: clickedFromOrgTab?null:25, color: clickedFromOrgTab?"#9E9E9E":null, }}>
|
||||||
Notifications help you find potential problems with your workflows and apps.
|
Notifications help you find potential problems with your workflows and apps.
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -411,24 +261,11 @@ const Priorities = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{notifications === null || notifications === undefined || notifications.length === 0 ? null :
|
<NotificationComponent notifications={notifications} showRead={showRead} selectedExecutionId={selectedExecutionId} selectedWorkflow={selectedWorkflow} highlightKMS={highlightKMS} userdata={userdata} imagesize={imagesize} boxColor={boxColor} clickedFromOrgTab={clickedFromOrgTab} notificationWidth={notificationWidth} dismissNotification={dismissNotification}/>
|
||||||
<div>
|
|
||||||
{notifications.map((notification, index) => {
|
|
||||||
if (showRead === false && notification.read === true) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NotificationItem data={notification} key={index} />
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{clickedFromOrgTab? null : <Divider style={{marginTop: 50, marginBottom: 50, }} />}
|
{clickedFromOrgTab? null : <Divider style={{marginTop: 50, marginBottom: 50, }} />}
|
||||||
|
<h2 style={{ display: clickedFromOrgTab ? null:"inline", marginBottom: clickedFromOrgTab ? 8:null, marginTop: clickedFromOrgTab ? 30 :null, color: clickedFromOrgTab ? "#ffffff" : null }}>Suggestions</h2>
|
||||||
<h2 style={{ display: clickedFromOrgTab ? null:"inline", marginBottom: clickedFromOrgTab ? 8:null, marginTop: clickedFromOrgTab ?0:null, color: clickedFromOrgTab ? "#ffffff" : null }}>Suggestions</h2>
|
<span style={{ fontSize: 16, color: clickedFromOrgTab ?"#9E9E9E":null,marginLeft: clickedFromOrgTab ?null:25 }}>
|
||||||
<span style={{ color: clickedFromOrgTab ?"#9E9E9E":null,marginLeft: clickedFromOrgTab ?null:25 }}>
|
|
||||||
Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company. <br/>These range from simple configurations in Shuffle to Usecases you may have missed.
|
Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company. <br/>These range from simple configurations in Shuffle to Usecases you may have missed.
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -470,9 +307,232 @@ const Priorities = (props) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default Priorities;
|
export default Priorities;
|
||||||
|
|
||||||
|
|
||||||
|
const NotificationItem = memo((props) => {
|
||||||
|
const {data, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification} = props
|
||||||
|
|
||||||
|
var image = "";
|
||||||
|
var orgName = "";
|
||||||
|
var orgId = "";
|
||||||
|
|
||||||
|
|
||||||
|
var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow)
|
||||||
|
|
||||||
|
if (!highlighted && highlightKMS) {
|
||||||
|
if (data.title !== undefined && data.title !== null && data.title.toLowerCase().includes("kms")) {
|
||||||
|
highlighted = true
|
||||||
|
} else if (data.description !== undefined && data.description !== null && data.description.toLowerCase().includes("kms")) {
|
||||||
|
highlighted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userdata.orgs !== undefined) {
|
||||||
|
const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]);
|
||||||
|
if (foundOrg !== undefined && foundOrg !== null) {
|
||||||
|
//position: "absolute", bottom: 5, right: -5,
|
||||||
|
const imageStyle = {
|
||||||
|
width: imagesize,
|
||||||
|
height: imagesize,
|
||||||
|
pointerEvents: "none",
|
||||||
|
marginLeft:
|
||||||
|
data.creator_org !== undefined && data.creator_org.length > 0
|
||||||
|
? 20
|
||||||
|
: 0,
|
||||||
|
borderRadius: 10,
|
||||||
|
border:
|
||||||
|
foundOrg.id === userdata.active_org.id
|
||||||
|
? `3px solid ${boxColor}`
|
||||||
|
: null,
|
||||||
|
cursor: "pointer",
|
||||||
|
marginRight: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
image =
|
||||||
|
foundOrg.image === "" ? (
|
||||||
|
<img
|
||||||
|
alt={foundOrg.name}
|
||||||
|
src={theme.palette.defaultImage}
|
||||||
|
style={imageStyle}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
alt={foundOrg.name}
|
||||||
|
src={foundOrg.image}
|
||||||
|
style={imageStyle}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
orgName = foundOrg.name;
|
||||||
|
orgId = foundOrg.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor.backgroundColor,
|
||||||
|
width: clickedFromOrgTab ? null :notificationWidth,
|
||||||
|
padding: 30,
|
||||||
|
borderBottom: "1px solid rgba(255,255,255,0.4)",
|
||||||
|
marginBottom: 20,
|
||||||
|
border: highlighted ? "2px solid #f85a3e" : null,
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{display: "flex", }}>
|
||||||
|
{data.amount === 1 && data.read === false ?
|
||||||
|
<Chip
|
||||||
|
label={"First seen"}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
style={{marginRight: 15, height: 25, }}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
{data.ignored === true ?
|
||||||
|
<Chip
|
||||||
|
label={"Disabled"}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
style={{marginRight: 15, height: 25, }}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
{data.read === false ?
|
||||||
|
<Chip
|
||||||
|
label={"Unread"}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
style={{marginRight: 15, height: 25, }}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<Chip
|
||||||
|
label={"Read"}
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
style={{marginRight: 15, height: 25, }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<Typography variant="body1" color="textPrimary" style={{ wordWrap: "break-word", overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||||
|
{data.title}
|
||||||
|
</Typography >
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
||||||
|
<img alt={data.title} src={data.image} style={{height: 100, width: 100, }} />
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, maxHeight: 200, overflowX: "hidden", overflowY: "auto", wordWrap: "break-word" }}>
|
||||||
|
{data.description}
|
||||||
|
</Typography >
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
variant="outlined"
|
||||||
|
style={{ marginTop: 15 }}
|
||||||
|
disabled={data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(data.reference_url, "_blank")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Explore
|
||||||
|
</Button>
|
||||||
|
{data.read === false ? (
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
variant="outlined"
|
||||||
|
style={{ marginTop: 15 }}
|
||||||
|
onClick={() => {
|
||||||
|
dismissNotification(data.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
<Tooltip title="Disabling a notification makes it so similar notifications to this one will NOT be re-opened. It will NOT forward notifications to your notification workflow, but WILL still keep counting." placement="top">
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
variant={data.ignored === true ? "contained" : "outlined"}
|
||||||
|
style={{ marginTop: 15, }}
|
||||||
|
onClick={() => {
|
||||||
|
if (data.ignored === true) {
|
||||||
|
dismissNotification(data.id, false)
|
||||||
|
} else {
|
||||||
|
dismissNotification(data.id, true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.ignored === true ? "Re-enable" : "Disable"}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="textSecondary" style={{ marginLeft: 20, marginTop: 20, wordWrap: "break-word", overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||||
|
<b>First seen</b>: {new Date(data.created_at * 1000).toISOString().slice(0, 19)}
|
||||||
|
</Typography >
|
||||||
|
|
||||||
|
<Typography variant="body2" color="textSecondary" style={{ marginLeft: 20, marginTop: 20, wordWrap: "break-word", overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||||
|
<b>Last seen</b>: {new Date(data.updated_at * 1000).toISOString().slice(0, 19)}
|
||||||
|
</Typography >
|
||||||
|
|
||||||
|
<Typography variant="body2" color="textSecondary" style={{ marginLeft: 20, marginTop: 20, wordWrap: "break-word", overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||||
|
<b>Times seen</b>: {data.amount}
|
||||||
|
</Typography >
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const NotificationComponent = memo(({notifications, showRead, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification}) => {
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{notifications === null || notifications === undefined || notifications?.length === 0 ? (
|
||||||
|
null
|
||||||
|
) :
|
||||||
|
<div>
|
||||||
|
{notifications?.map((notification, index) => {
|
||||||
|
if (showRead === false && notification.read === true) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NotificationItem data={notification} key={index} selectedExecutionId={selectedExecutionId} selectedWorkflow={selectedWorkflow} highlightKMS={highlightKMS} userdata={userdata} imagesize={imagesize} boxColor={boxColor} clickedFromOrgTab={clickedFromOrgTab} notificationWidth={notificationWidth} dismissNotification={dismissNotification} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// const PaddingWrapper = memo(({children, clickedFromOrgTab}) => {
|
||||||
|
|
||||||
|
// const { leftSideBarOpenByClick } = useContext(Context)
|
||||||
|
|
||||||
|
// return(
|
||||||
|
// <div style={{width: leftSideBarOpenByClick ? 950 : 1030,transition: 'width 0.3s ease', padding: clickedFromOrgTab ? "27px 10px 19px 27px":null, height: clickedFromOrgTab ? "auto":null, minHeight: 843, backgroundColor: clickedFromOrgTab ? '#212121':null, borderRadius: clickedFromOrgTab ? '16px':null, }}>
|
||||||
|
// {children}
|
||||||
|
// </div>
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const Wrapper = memo(({children, clickedFromOrgTab}) => {
|
||||||
|
|
||||||
|
// return(
|
||||||
|
// <PaddingWrapper clickedFromOrgTab={clickedFromOrgTab}>
|
||||||
|
// {children}
|
||||||
|
// </PaddingWrapper>
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
|||||||
@@ -24,9 +24,16 @@ import {
|
|||||||
const Priority = (props) => {
|
const Priority = (props) => {
|
||||||
const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props;
|
const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props;
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
|
if (window.location.pathname === "/workflows") {
|
||||||
|
const hidePriorities = localStorage.getItem("hidePriorities", "true")
|
||||||
|
if (hidePriorities === "true") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var realignedSrc = false
|
var realignedSrc = false
|
||||||
var realignedDst = false
|
var realignedDst = false
|
||||||
let newdescription = priority.description
|
let newdescription = priority.description
|
||||||
@@ -114,7 +121,7 @@ const Priority = (props) => {
|
|||||||
const srcSize = realignedSrc ? 35 : 30
|
const srcSize = realignedSrc ? 35 : 30
|
||||||
const dstSize = realignedDst ? 35 : 30
|
const dstSize = realignedDst ? 35 : 30
|
||||||
return (
|
return (
|
||||||
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : clickedFromOrgTab ?null:"1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: clickedFromOrgTab ? 24:15, textAlign: "center", minHeight: isCloud ? 70 : 100, maxHeight: isCloud ? 70 : 100, textAlign: "left", backgroundColor: clickedFromOrgTab ? "#1A1A1A": theme.palette.surfaceColor, display: "flex", }}>
|
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : clickedFromOrgTab ?null:"1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, marginTop: 10, marginBottom: 10, padding: clickedFromOrgTab ? 24:15, textAlign: "center", minHeight: isCloud ? 70 : 100, maxHeight: isCloud ? 70 : 100, textAlign: "left", backgroundColor: clickedFromOrgTab ? "#1A1A1A": theme.palette.surfaceColor, display: "flex", }}>
|
||||||
<div style={{flex: 2, overflow: "hidden",}}>
|
<div style={{flex: 2, overflow: "hidden",}}>
|
||||||
<span style={{display: "flex", }}>
|
<span style={{display: "flex", }}>
|
||||||
{priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null}
|
{priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null}
|
||||||
@@ -124,7 +131,7 @@ const Priority = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
{priority.type === "usecase" && priority.description.includes("&") ?
|
{priority.type === "usecase" && priority.description.includes("&") ?
|
||||||
<span style={{display: "flex", marginTop: 10, }}>
|
<span style={{display: "flex", marginTop: 10, }}>
|
||||||
<img src={newdescription.split("&")[1]} alt={priority.name} style={{height: srcSize, width: srcSize, marginRight: realignedSrc ? isCloud ? 0 : -10 : 10, borderRadius: theme.palette.borderRadius-3, marginTop: realignedSrc ? 5 : 0 }} />
|
<img src={newdescription.split("&")[1]} alt={priority.name} style={{height: srcSize, width: srcSize, marginRight: realignedSrc ? isCloud ? 0 : -10 : 10, borderRadius: theme.palette?.borderRadius-3, marginTop: realignedSrc ? 5 : 0 }} />
|
||||||
<Typography variant="body2" color="textSecondary" style={{marginTop: 3, }}>
|
<Typography variant="body2" color="textSecondary" style={{marginTop: 3, }}>
|
||||||
{newdescription.split("&")[0]}
|
{newdescription.split("&")[0]}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -132,7 +139,7 @@ const Priority = (props) => {
|
|||||||
{newdescription.split("&").length > 3 ?
|
{newdescription.split("&").length > 3 ?
|
||||||
<span style={{display: "flex", }}>
|
<span style={{display: "flex", }}>
|
||||||
<ArrowForwardIcon style={{marginLeft: 15, marginRight: 15, }}/>
|
<ArrowForwardIcon style={{marginLeft: 15, marginRight: 15, }}/>
|
||||||
<img src={newdescription.split("&")[3]} alt={priority.name+"2"} style={{height: dstSize, width: dstSize, marginRight: realignedDst ? -5 : 10, borderRadius: theme.palette.borderRadius-3, marginTop: realignedDst ? 5 : 0 }} />
|
<img src={newdescription.split("&")[3]} alt={priority.name+"2"} style={{height: dstSize, width: dstSize, marginRight: realignedDst ? -5 : 10, borderRadius: theme.palette?.borderRadius-3, marginTop: realignedDst ? 5 : 0 }} />
|
||||||
<Typography variant="body2" color="textSecondary" style={{marginTop: 3}}>
|
<Typography variant="body2" color="textSecondary" style={{marginTop: 3}}>
|
||||||
{newdescription.split("&")[2]}
|
{newdescription.split("&")[2]}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -176,6 +183,20 @@ const Priority = (props) => {
|
|||||||
<Button style={{borderRadius: 25, fontSize:16, boxShadow: clickedFromOrgTab ? "none":null,textTransform: clickedFromOrgTab ? 'capitalize':null, width: 100, height: 50, marginTop: 8, }} variant="text" color="secondary" onClick={() => {
|
<Button style={{borderRadius: 25, fontSize:16, boxShadow: clickedFromOrgTab ? "none":null,textTransform: clickedFromOrgTab ? 'capitalize':null, width: 100, height: 50, marginTop: 8, }} variant="text" color="secondary" onClick={() => {
|
||||||
// dismiss -> get envs
|
// dismiss -> get envs
|
||||||
changeRecommendation(priority, "dismiss")
|
changeRecommendation(priority, "dismiss")
|
||||||
|
|
||||||
|
// Check window location if it's /workflows
|
||||||
|
if (window.location.pathname === "/workflows") {
|
||||||
|
// Set local storage to hide priorities for now
|
||||||
|
localStorage.setItem("hidePriorities", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCloud) {
|
||||||
|
ReactGA.event({
|
||||||
|
category: "recommendation",
|
||||||
|
action: `dismiss_${priority.name}`,
|
||||||
|
label: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
}}>
|
}}>
|
||||||
Dismiss
|
Dismiss
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mui/material"
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
import theme from "../theme.jsx";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Lock as LockIcon,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
|
// onclickHandler = function override from parent onclick
|
||||||
|
const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflowId, }) => {
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [hovered, setHovered] = React.useState(false)
|
||||||
|
if (workflow === undefined || workflow === null) {
|
||||||
|
console.log("No workflow")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note for @Lalit:
|
||||||
|
*
|
||||||
|
* When you want to make a list of something that is complex,
|
||||||
|
* make a component. This way, you can easily manage
|
||||||
|
* the logic, and we can actually reuse it. This component is used
|
||||||
|
* multiple places, so do make sure to not break it randomly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const expandLeftNav = leftNavOpen === true || leftNavOpen === undefined ? true : false
|
||||||
|
|
||||||
|
// Check if workflow.input_markdown has an image in it
|
||||||
|
// If it does, show it as the main thing
|
||||||
|
var relevantImageUrl = ""
|
||||||
|
if (workflow?.form_control?.input_markdown !== undefined && workflow?.form_control?.input_markdown !== null && workflow?.form_control?.input_markdown !== "") {
|
||||||
|
// Look for <img> tag or  markdown
|
||||||
|
// html > markdown
|
||||||
|
const imgTag = workflow?.form_control?.input_markdown.match(/<img[^>]+>/g)
|
||||||
|
|
||||||
|
if (imgTag !== null) {
|
||||||
|
const src = imgTag[0].match(/src="([^"]+)"/)
|
||||||
|
if (src !== null) {
|
||||||
|
relevantImageUrl = src[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const markdownTag = workflow?.form_control?.input_markdown.match(/!\[.*\]\(.*\)/g)
|
||||||
|
|
||||||
|
if (markdownTag !== null) {
|
||||||
|
const src = markdownTag[0].match(/\(([^)]+)\)/)
|
||||||
|
if (src !== null) {
|
||||||
|
relevantImageUrl = src[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
>
|
||||||
|
<Link to={`/workflows/` + workflow?.id} style={{textDecoration: "none"}}>
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
if (onclickHandler !== undefined) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
onclickHandler()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
textTransform: "none",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
textAlign: "left",
|
||||||
|
opacity: expandLeftNav ? 1 : 0,
|
||||||
|
transition: "opacity 0.1s",
|
||||||
|
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
backgroundColor: hovered || currentWorkflowId === workflow.id ? "#1f1f1f" : "transparent",
|
||||||
|
}}
|
||||||
|
disableRipple
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
marginRight: "auto",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
{relevantImageUrl !== undefined && relevantImageUrl !== null && relevantImageUrl !== "" ?
|
||||||
|
<Avatar
|
||||||
|
alt={workflow?.name}
|
||||||
|
src={relevantImageUrl}
|
||||||
|
style={{ width: 24, height: 24, marginRight: 5, }}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
workflow?.apps?.slice(0, 2).map((data, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
marginLeft: index === 1 ? -8 : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
alt={data.app_name}
|
||||||
|
src={
|
||||||
|
data.large_image
|
||||||
|
? data.large_image
|
||||||
|
: "/images/no_image.png"
|
||||||
|
}
|
||||||
|
style={{ width: 24, height: 24 }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
color: "#CDCDCD",
|
||||||
|
fontSize: 16,
|
||||||
|
marginLeft: 8,
|
||||||
|
maxWidth: 180,
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{workflow?.name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{onclickHandler !== undefined && workflow.sharing !== "form" ?
|
||||||
|
<Tooltip title="Private Org Form" placement="right">
|
||||||
|
<LockIcon style={{height: 15, width: 15, color: "grey", position: "absolute", left: -17, }}/>
|
||||||
|
</Tooltip>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecentWorkflow
|
||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
PlayArrow as PlayArrowIcon,
|
PlayArrow as PlayArrowIcon,
|
||||||
Insights as InsightsIcon,
|
Insights as InsightsIcon,
|
||||||
Replay as ReplayIcon,
|
Replay as ReplayIcon,
|
||||||
|
EditNote as EditNoteIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'
|
import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'
|
||||||
@@ -76,6 +77,10 @@ const RuntimeDebugger = (props) => {
|
|||||||
{"id": "", "name": "All Workflows",}
|
{"id": "", "name": "All Workflows",}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if (document != undefined) {
|
||||||
|
document.title = "Workflow Run Debugger"
|
||||||
|
}
|
||||||
|
|
||||||
// Shitty workflow search on purpose :)
|
// Shitty workflow search on purpose :)
|
||||||
const handleWorkflowUsageCount = (workflows) => {
|
const handleWorkflowUsageCount = (workflows) => {
|
||||||
if (workflows === undefined || workflows === null || workflows.length === 0) {
|
if (workflows === undefined || workflows === null || workflows.length === 0) {
|
||||||
@@ -219,7 +224,7 @@ const RuntimeDebugger = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const getAvailableWorkflows = () => {
|
const getAvailableWorkflows = (workflowId) => {
|
||||||
fetch(globalUrl + "/api/v1/workflows", {
|
fetch(globalUrl + "/api/v1/workflows", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -240,6 +245,15 @@ const RuntimeDebugger = (props) => {
|
|||||||
var foundWorkflows = [{"id": "", "name": "All Workflows",}]
|
var foundWorkflows = [{"id": "", "name": "All Workflows",}]
|
||||||
foundWorkflows.push(...responseJson)
|
foundWorkflows.push(...responseJson)
|
||||||
setWorkflows(foundWorkflows)
|
setWorkflows(foundWorkflows)
|
||||||
|
|
||||||
|
if (workflowId !== undefined && workflowId !== null && workflowId !== "" && workflowId.length === 36) {
|
||||||
|
for (var key in responseJson) {
|
||||||
|
if (responseJson[key].id === workflowId) {
|
||||||
|
setWorkflow(responseJson[key])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -248,7 +262,6 @@ const RuntimeDebugger = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getAvailableWorkflows()
|
|
||||||
|
|
||||||
// Find workflow_id in url query
|
// Find workflow_id in url query
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
@@ -265,6 +278,8 @@ const RuntimeDebugger = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAvailableWorkflows(workflowId)
|
||||||
|
|
||||||
const foundStatus = urlParams.get('status');
|
const foundStatus = urlParams.get('status');
|
||||||
if (foundStatus !== undefined && foundStatus !== null && foundStatus !== "") {
|
if (foundStatus !== undefined && foundStatus !== null && foundStatus !== "") {
|
||||||
setStatus(foundStatus)
|
setStatus(foundStatus)
|
||||||
@@ -314,15 +329,17 @@ const RuntimeDebugger = (props) => {
|
|||||||
|
|
||||||
var source = params.row.execution_source
|
var source = params.row.execution_source
|
||||||
if (source === "schedule") {
|
if (source === "schedule") {
|
||||||
foundSource = <img src={alltriggers[1].large_image} alt="schedule" style={{borderRadius: theme.palette.borderRadius, height: imageSize, width: imageSize, }} />
|
foundSource = <img src={alltriggers[1].large_image} alt="schedule" style={{borderRadius: theme.palette?.borderRadius, height: imageSize, width: imageSize, }} />
|
||||||
} else if (source === "webhook") {
|
} else if (source === "webhook") {
|
||||||
foundSource = <img src={alltriggers[0].large_image} alt="webhook" style={{borderRadius: theme.palette.borderRadius, height: imageSize, width: imageSize, }} />
|
foundSource = <img src={alltriggers[0].large_image} alt="webhook" style={{borderRadius: theme.palette?.borderRadius, height: imageSize, width: imageSize, }} />
|
||||||
} else if (source === "subflow" || source.length === 36) {
|
} else if (source === "subflow" || source.length === 36) {
|
||||||
foundSource = <img src={alltriggers[4].large_image} alt="subflow" style={{borderRadius: theme.palette.borderRadius, height: imageSize, width: imageSize, }} />
|
foundSource = <img src={alltriggers[3].large_image} alt="subflow" style={{borderRadius: theme.palette?.borderRadius, height: imageSize, width: imageSize, }} />
|
||||||
source = "subflow"
|
source = "subflow"
|
||||||
} else if (source === "rerun" || source.length === 36) {
|
} else if (source === "rerun" || source.length === 36) {
|
||||||
foundSource = <ReplayIcon style={{color: theme.palette.primary.secondary, height: imageSize, width: imageSize, }} />
|
foundSource = <ReplayIcon style={{color: theme.palette.primary.secondary, height: imageSize, width: imageSize, }} />
|
||||||
source = "rerun of a previous run"
|
source = "rerun of a previous run"
|
||||||
|
} else if (source === "form") {
|
||||||
|
foundSource = <EditNoteIcon style={{color: theme.palette.primary.secondary, height: imageSize, width: imageSize, }} />
|
||||||
} else {
|
} else {
|
||||||
source = "manual"
|
source = "manual"
|
||||||
}
|
}
|
||||||
@@ -888,13 +905,13 @@ const RuntimeDebugger = (props) => {
|
|||||||
{userdata.support === true ?
|
{userdata.support === true ?
|
||||||
<Button
|
<Button
|
||||||
variant={ignoreOrg ? "contained" : "outlined"}
|
variant={ignoreOrg ? "contained" : "outlined"}
|
||||||
color="primary"
|
color="secondary"
|
||||||
style={{maxHeight: 40, marginTop: 25, }}
|
style={{marginLeft: 100, maxHeight: 40, marginTop: 25, }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIgnoreOrg(!ignoreOrg)
|
setIgnoreOrg(!ignoreOrg)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ignoreOrg ? "Ignoring Org" : "Ignore Org"}
|
{ignoreOrg ? "Ignoring Org" : "Ignore Org (Support Only)"}
|
||||||
</Button>
|
</Button>
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
@@ -942,11 +959,8 @@ const RuntimeDebugger = (props) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(option) => {
|
getOptionLabel={(option) => {
|
||||||
if (
|
if (option === undefined || option === null ||
|
||||||
option === undefined ||
|
option.name === undefined || option.name === null
|
||||||
option === null ||
|
|
||||||
option.name === undefined ||
|
|
||||||
option.name === null
|
|
||||||
) {
|
) {
|
||||||
return "No Workflow Selected";
|
return "No Workflow Selected";
|
||||||
}
|
}
|
||||||
@@ -961,7 +975,7 @@ const RuntimeDebugger = (props) => {
|
|||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
height: 50,
|
height: 50,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
}}
|
}}
|
||||||
@@ -995,13 +1009,13 @@ const RuntimeDebugger = (props) => {
|
|||||||
<Tooltip arrow placement="left" title={
|
<Tooltip arrow placement="left" title={
|
||||||
<span style={{}}>
|
<span style={{}}>
|
||||||
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
||||||
<img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
|
<img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette?.borderRadius, }} />
|
||||||
: null}
|
: null}
|
||||||
<Typography>
|
<Typography>
|
||||||
Choose {data.name}
|
Choose {data.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</span>
|
</span>
|
||||||
} placement="bottom">
|
}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
@@ -1023,7 +1037,7 @@ const RuntimeDebugger = (props) => {
|
|||||||
<TextField
|
<TextField
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
}}
|
}}
|
||||||
{...params}
|
{...params}
|
||||||
label="Workflow"
|
label="Workflow"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef, useContext } from 'react';
|
||||||
|
|
||||||
import theme from '../theme.jsx';
|
import theme from '../theme.jsx';
|
||||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
@@ -33,6 +33,8 @@ import {
|
|||||||
AvatarGroup,
|
AvatarGroup,
|
||||||
} from "@mui/material"
|
} from "@mui/material"
|
||||||
|
|
||||||
|
import { Context } from '../context/ContextApi.jsx';
|
||||||
|
|
||||||
import { Search as SearchIcon, Close as CloseIcon, Folder as FolderIcon, Code as CodeIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material'
|
import { Search as SearchIcon, Close as CloseIcon, Folder as FolderIcon, Code as CodeIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material'
|
||||||
|
|
||||||
import algoliasearch from 'algoliasearch/lite';
|
import algoliasearch from 'algoliasearch/lite';
|
||||||
@@ -47,8 +49,9 @@ const chipStyle = {
|
|||||||
|
|
||||||
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
|
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
|
||||||
const SearchData = props => {
|
const SearchData = props => {
|
||||||
const { serverside, globalUrl, userdata, setModalOpen, modalOpen } = props
|
const { serverside, globalUrl, userdata } = props
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
const { searchBarModalOpen, setSearchBarModalOpen } = useContext(Context);
|
||||||
const borderRadius = 3
|
const borderRadius = 3
|
||||||
const node = useRef()
|
const node = useRef()
|
||||||
const [searchOpen, setSearchOpen] = useState(false)
|
const [searchOpen, setSearchOpen] = useState(false)
|
||||||
@@ -56,8 +59,8 @@ const SearchData = props => {
|
|||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
const handleLinkClick = () => {
|
const handleLinkClick = () => {
|
||||||
if (modalOpen) {
|
if (searchBarModalOpen) {
|
||||||
setModalOpen(false); // Assuming setModalOpen is defined correctly
|
setSearchBarModalOpen(false); // Assuming setModalOpen is defined correctly
|
||||||
} else {
|
} else {
|
||||||
console.log("Condition not met, staying on the same page");
|
console.log("Condition not met, staying on the same page");
|
||||||
}
|
}
|
||||||
@@ -71,7 +74,7 @@ const SearchData = props => {
|
|||||||
// return null
|
// return null
|
||||||
//}
|
//}
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
// if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) {
|
// if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) {
|
||||||
// setModalOpen(false)
|
// setModalOpen(false)
|
||||||
// }
|
// }
|
||||||
@@ -88,14 +91,14 @@ const SearchData = props => {
|
|||||||
|
|
||||||
const textFieldRef = useRef(null);
|
const textFieldRef = useRef(null);
|
||||||
const keyPressHandler = (e) => {
|
const keyPressHandler = (e) => {
|
||||||
if (e.which === 13) {
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
// navigate(`/search?q=${currentRefinement}`, { state: value, replace: true });
|
// navigate(`/search?q=${currentRefinement}`, { state: value, replace: true });
|
||||||
// setModalOpen(false);
|
// setModalOpen(false);
|
||||||
const trimmedValue = inputValue.trim();
|
const trimmedValue = inputValue.trim();
|
||||||
if (trimmedValue !== '') {
|
if (trimmedValue !== '') {
|
||||||
e.preventDefault();
|
|
||||||
navigate(`/search?q=${trimmedValue}`, { state: trimmedValue, replace: true });
|
navigate(`/search?q=${trimmedValue}`, { state: trimmedValue, replace: true });
|
||||||
setModalOpen(false);
|
setSearchBarModalOpen(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -249,7 +252,7 @@ const SearchData = props => {
|
|||||||
<Link key={hit.objectID} to={parsedUrl} rel="noopener noreferrer" style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
|
<Link key={hit.objectID} to={parsedUrl} rel="noopener noreferrer" style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
|
||||||
//console.log("CLICK")
|
//console.log("CLICK")
|
||||||
setSearchOpen(true)
|
setSearchOpen(true)
|
||||||
setModalOpen(false)
|
setSearchBarModalOpen(false)
|
||||||
aa('init', {
|
aa('init', {
|
||||||
appId: searchClient.appId,
|
appId: searchClient.appId,
|
||||||
apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"]
|
apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"]
|
||||||
@@ -498,7 +501,7 @@ const SearchData = props => {
|
|||||||
return (
|
return (
|
||||||
<Link key={hit.objectID} to={parsedUrl} style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
|
<Link key={hit.objectID} to={parsedUrl} style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
|
||||||
setSearchOpen(true)
|
setSearchOpen(true)
|
||||||
setModalOpen(false)
|
setSearchBarModalOpen(false)
|
||||||
aa('init', {
|
aa('init', {
|
||||||
appId: searchClient.appId,
|
appId: searchClient.appId,
|
||||||
apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"]
|
apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"]
|
||||||
@@ -547,8 +550,6 @@ const SearchData = props => {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
console.log("OBJECT CHANGE: ", hit.objectID)
|
|
||||||
|
|
||||||
// This does nothing rofl
|
// This does nothing rofl
|
||||||
if (userdata.active_apps === undefined || userdata.active_apps === null) {
|
if (userdata.active_apps === undefined || userdata.active_apps === null) {
|
||||||
activateApp(hit.name, hit.objectID, "activate")
|
activateApp(hit.name, hit.objectID, "activate")
|
||||||
@@ -702,7 +703,7 @@ const SearchData = props => {
|
|||||||
|
|
||||||
console.log("CLICK")
|
console.log("CLICK")
|
||||||
setSearchOpen(true)
|
setSearchOpen(true)
|
||||||
setModalOpen(false)
|
setSearchBarModalOpen(false)
|
||||||
}}>
|
}}>
|
||||||
<ListItem key={hit.objectID} style={innerlistitemStyle} onMouseOver={() => {
|
<ListItem key={hit.objectID} style={innerlistitemStyle} onMouseOver={() => {
|
||||||
setMouseHoverIndex(index)
|
setMouseHoverIndex(index)
|
||||||
@@ -873,10 +874,11 @@ const SearchData = props => {
|
|||||||
</List>
|
</List>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid style={{ textAlign: "end", width: "100%", textTransform: 'capitalize', }}>
|
<Grid style={{ textAlign: "end", width: "100%", textTransform: 'capitalize', }}>
|
||||||
<Button style={{ textAlign: "center", textTransform: 'capitalize' }}
|
<Link to="/search" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||||
onClick={() => { window.location = "/search"; }} >
|
<Button style={{ textAlign: "center", textTransform: 'capitalize' }}>
|
||||||
See More
|
See More
|
||||||
</Button>
|
</Button>
|
||||||
|
</Link>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : null
|
) : null
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef, useContext } from 'react';
|
||||||
|
|
||||||
import theme from '../theme.jsx';
|
import theme from '../theme.jsx';
|
||||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
import SearchBox from "../components/SearchData.jsx";
|
import SearchBox from "../components/SearchData.jsx";
|
||||||
|
|
||||||
|
import { Context } from '../context/ContextApi.jsx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Chip,
|
Chip,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -45,20 +47,22 @@ const chipStyle = {
|
|||||||
const SearchField = props => {
|
const SearchField = props => {
|
||||||
const { serverside, userdata, isMobile, isLoaded, globalUrl, isHeader, isLoggedIn, small, rounded } = props
|
const { serverside, userdata, isMobile, isLoaded, globalUrl, isHeader, isLoggedIn, small, rounded } = props
|
||||||
|
|
||||||
|
const {searchBarModalOpen, setSearchBarModalOpen} = useContext(Context);
|
||||||
|
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
const borderRadius = 3
|
const borderRadius = 3
|
||||||
const node = useRef()
|
const node = useRef()
|
||||||
const [searchOpen, setSearchOpen] = useState(false)
|
const [searchOpen, setSearchOpen] = useState(false)
|
||||||
const [modalOpen, setModalOpen] = React.useState(false);
|
// const [modalOpen, setModalOpen] = React.useState(false);
|
||||||
const [oldPath, setOldPath] = useState("")
|
const [oldPath, setOldPath] = useState("")
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Mousetrap.bind(['command+k', 'ctrl+k'], () => {
|
Mousetrap.bind(['command+k', 'ctrl+k'], () => {
|
||||||
setModalOpen(true);
|
setSearchBarModalOpen(true);
|
||||||
return false; // Prevent the default action
|
return false; // Prevent the default action
|
||||||
});
|
});
|
||||||
Mousetrap.bind(['esc'], () => {
|
Mousetrap.bind(['esc'], () => {
|
||||||
setModalOpen(false);
|
setSearchBarModalOpen(false);
|
||||||
return false; // Prevent the default action
|
return false; // Prevent the default action
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -72,9 +76,9 @@ const SearchField = props => {
|
|||||||
// console.log("key:", dataValue.key),
|
// console.log("key:", dataValue.key),
|
||||||
//console.log("value:",dataValue.value),
|
//console.log("value:",dataValue.value),
|
||||||
<Dialog
|
<Dialog
|
||||||
open={modalOpen}
|
open={searchBarModalOpen}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setModalOpen(false);
|
setSearchBarModalOpen(false);
|
||||||
}}
|
}}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -92,12 +96,12 @@ const SearchField = props => {
|
|||||||
{isHeader ? <div style={{ display: "flex"}}>
|
{isHeader ? <div style={{ display: "flex"}}>
|
||||||
<DialogTitle style={{ marginTop: 15, marginLeft: 5, color: "var(--Paragraph-text, #C8C8C8)" }} >Search for Docs, Apps, Workflows and more</DialogTitle>
|
<DialogTitle style={{ marginTop: 15, marginLeft: 5, color: "var(--Paragraph-text, #C8C8C8)" }} >Search for Docs, Apps, Workflows and more</DialogTitle>
|
||||||
<Button color="secondary" fullWidth style={{ marginLeft:180, }} onClick={() => {
|
<Button color="secondary" fullWidth style={{ marginLeft:180, }} onClick={() => {
|
||||||
setModalOpen(false);
|
setSearchBarModalOpen(false);
|
||||||
}}><CloseIcon /></Button>
|
}}><CloseIcon /></Button>
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
<DialogContent className='dialog-content' style={{}}>
|
<DialogContent className='dialog-content' style={{}}>
|
||||||
<SearchBox globalUrl={globalUrl} setModalOpen={setModalOpen} modalOpen={modalOpen} serverside={serverside} userdata={userdata} />
|
<SearchBox globalUrl={globalUrl} serverside={serverside} userdata={userdata} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<Divider style={{overflow: "hidden"}}/>
|
<Divider style={{overflow: "hidden"}}/>
|
||||||
<span style={{display:"flex", width:"100%", height:30}}>
|
<span style={{display:"flex", width:"100%", height:30}}>
|
||||||
@@ -124,10 +128,10 @@ const SearchField = props => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: "auto", marginLeft: !isLoggedIn ? 0 : 130 }}>
|
<div style={{ marginTop: "auto", marginLeft: !isLoggedIn ? 0: "auto", marginRight: !isLoggedIn ? 0 : "auto", width: !isLoggedIn ? "auto" : 410, }}>
|
||||||
{modalView}
|
{modalView}
|
||||||
<TextField
|
<TextField
|
||||||
style={{ backgroundColor: "#212121", height: 48, borderRadius: rounded === true ? 25 : theme.palette.borderRadius, minWidth: fieldWidth, maxWidth: fieldWidth, }}
|
style={{ backgroundColor: "#212121", height: 48, borderRadius: rounded === true ? 25 : theme.palette?.borderRadius, minWidth: fieldWidth, maxWidth: fieldWidth, }}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
color: "white",
|
color: "white",
|
||||||
@@ -155,7 +159,7 @@ const SearchField = props => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
placeholder="Search Apps, Workflows, Docs..."
|
placeholder="Search Apps, Workflows, Docs..."
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
setModalOpen(true)
|
setSearchBarModalOpen(true)
|
||||||
}}
|
}}
|
||||||
limit={5}
|
limit={5}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -40,11 +40,12 @@ import {
|
|||||||
|
|
||||||
Close as CloseIcon,
|
Close as CloseIcon,
|
||||||
DragIndicator as DragIndicatorIcon,
|
DragIndicator as DragIndicatorIcon,
|
||||||
|
RestartAlt as RestartAltIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
|
|
||||||
import { validateJson } from "../views/Workflows.jsx";
|
import { validateJson } from "../views/Workflows.jsx";
|
||||||
import ReactJson from "react-json-view";
|
import ReactJson from "react-json-view-ssr";
|
||||||
import PaperComponent from "../components/PaperComponent.jsx";
|
import PaperComponent from "../components/PaperComponent.jsx";
|
||||||
|
|
||||||
import { padding, textAlign } from '@mui/system';
|
import { padding, textAlign } from '@mui/system';
|
||||||
@@ -56,6 +57,7 @@ import { tags as t } from '@lezer/highlight';
|
|||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
import ace from "ace-builds";
|
import ace from "ace-builds";
|
||||||
import 'ace-builds/src-noconflict/mode-python';
|
import 'ace-builds/src-noconflict/mode-python';
|
||||||
|
import 'ace-builds/src-noconflict/mode-json';
|
||||||
//import 'ace-builds/src-noconflict/theme-twilight';
|
//import 'ace-builds/src-noconflict/theme-twilight';
|
||||||
//import 'ace-builds/src-noconflict/theme-solarized_dark';
|
//import 'ace-builds/src-noconflict/theme-solarized_dark';
|
||||||
import 'ace-builds/src-noconflict/theme-gruvbox';
|
import 'ace-builds/src-noconflict/theme-gruvbox';
|
||||||
@@ -105,12 +107,16 @@ const CodeEditor = (props) => {
|
|||||||
selectedAction ,
|
selectedAction ,
|
||||||
workflowExecutions,
|
workflowExecutions,
|
||||||
getParents,
|
getParents,
|
||||||
|
activeDialog,
|
||||||
|
setActiveDialog,
|
||||||
fieldname,
|
fieldname,
|
||||||
|
contentLoading,
|
||||||
|
editorData,
|
||||||
|
|
||||||
|
setAiQueryModalOpen,
|
||||||
|
fullScreenMode
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
|
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
|
||||||
|
|
||||||
//const { setContainer } = useCodeMirror({
|
//const { setContainer } = useCodeMirror({
|
||||||
@@ -163,6 +169,11 @@ const CodeEditor = (props) => {
|
|||||||
setMenuPosition(null);
|
setMenuPosition(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
highlight_variables(localcodedata)
|
||||||
|
expectedOutput(localcodedata)
|
||||||
|
}, [localcodedata])
|
||||||
|
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -627,8 +638,8 @@ const CodeEditor = (props) => {
|
|||||||
newMarkers.push({
|
newMarkers.push({
|
||||||
startRow: i,
|
startRow: i,
|
||||||
startCol: startCh,
|
startCol: startCh,
|
||||||
endRow: i+1,
|
endRow: i,
|
||||||
endCol: endCh+1,
|
endCol: endCh,
|
||||||
className: correctVariable ? "good-marker" : "bad-marker",
|
className: correctVariable ? "good-marker" : "bad-marker",
|
||||||
type: "text",
|
type: "text",
|
||||||
})
|
})
|
||||||
@@ -811,11 +822,7 @@ const CodeEditor = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleItemClick = (values) => {
|
const handleItemClick = (values) => {
|
||||||
if (
|
if (values === undefined || values === null || values.length === 0) {
|
||||||
values === undefined ||
|
|
||||||
values === null ||
|
|
||||||
values.length === 0
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,7 +837,11 @@ const CodeEditor = (props) => {
|
|||||||
toComplete += values[key].autocomplete;
|
toComplete += values[key].autocomplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
setlocalcodedata(localcodedata+toComplete)
|
|
||||||
|
handleClick({
|
||||||
|
"value": toComplete
|
||||||
|
})
|
||||||
|
//setlocalcodedata(localcodedata+toComplete)
|
||||||
setMenuPosition(null)
|
setMenuPosition(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,10 +850,37 @@ const CodeEditor = (props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.value.includes("{%") && !item.value.includes("{{")) {
|
// Injects it in the right spot instead of random
|
||||||
setlocalcodedata(localcodedata+" | "+item.value+" }}")
|
var edited = false
|
||||||
} else {
|
if (currentCharacter !== undefined && currentCharacter !== null && currentCharacter !== -1 && currentLine !== undefined && currentLine !== null && currentLine !== -1) {
|
||||||
setlocalcodedata(localcodedata+item.value)
|
// Input at the right spot
|
||||||
|
var codedatasplit = localcodedata.split('\n')
|
||||||
|
if (codedatasplit.length > currentLine) {
|
||||||
|
var currentLineData = codedatasplit[currentLine]
|
||||||
|
|
||||||
|
// Remove newlines from item.value
|
||||||
|
if (item.value.includes("% python %")) {
|
||||||
|
item.value = item.value.replaceAll("\n", ";")
|
||||||
|
item.value = item.value.replaceAll("python %};", "python %}")
|
||||||
|
} else {
|
||||||
|
item.value = item.value.replaceAll("\n", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLineData = currentLineData.slice(0, currentCharacter) + item.value + currentLineData.slice(currentCharacter)
|
||||||
|
codedatasplit[currentLine] = currentLineData
|
||||||
|
|
||||||
|
setlocalcodedata(codedatasplit.join('\n'))
|
||||||
|
|
||||||
|
edited = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edited === false) {
|
||||||
|
if (!item.value.includes("{%") && !item.value.includes("{{")) {
|
||||||
|
setlocalcodedata(localcodedata+" | "+item.value+" }}")
|
||||||
|
} else {
|
||||||
|
setlocalcodedata(localcodedata+item.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setAnchorEl(null)
|
setAnchorEl(null)
|
||||||
@@ -858,8 +896,8 @@ const CodeEditor = (props) => {
|
|||||||
// Shuffle Tools 1.2.0 (in most cases?)
|
// Shuffle Tools 1.2.0 (in most cases?)
|
||||||
const appid = toolsAppId !== undefined && toolsAppId !== null && toolsAppId.length > 0 ? toolsAppId : "3e2bdf9d5069fe3f4746c29d68785a6a"
|
const appid = toolsAppId !== undefined && toolsAppId !== null && toolsAppId.length > 0 ? toolsAppId : "3e2bdf9d5069fe3f4746c29d68785a6a"
|
||||||
|
|
||||||
const actionname = selectedAction.name === "execute_python" && !inputdata.replaceAll(" ", "").includes("{%python%}") ? "execute_python" : "repeat_back_to_me"
|
const actionname = selectedAction.name === "execute_python" && !inputdata.replaceAll(" ", "").includes("{%python%}") ? "execute_python" : selectedAction.name === "execute_bash" ? "execute_bash" : "repeat_back_to_me"
|
||||||
const params = actionname === "execute_python" ? [{"name": "code", "value":inputdata}] : [{"name":"call", "value": inputdata}]
|
const params = actionname === "execute_python" ? [{"name": "code", "value":inputdata}] : actionname === "execute_bash" ? [{"name": "code", "value":inputdata}, {"name": "shuffle_input", "value": "", }] : [{"name":"call", "value": inputdata}]
|
||||||
|
|
||||||
const actiondata = {"description":"Repeats the call parameter","id":"","name":actionname,"label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id": appid,"tags":null,"authentication":[],"tested":false,"parameters": params, "execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}}
|
const actiondata = {"description":"Repeats the call parameter","id":"","name":actionname,"label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id": appid,"tags":null,"authentication":[],"tested":false,"parameters": params, "execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}}
|
||||||
|
|
||||||
@@ -946,23 +984,77 @@ const CodeEditor = (props) => {
|
|||||||
|
|
||||||
|
|
||||||
// Define a custom completer for the Ace Editor
|
// Define a custom completer for the Ace Editor
|
||||||
const customVariables = availableVariables
|
|
||||||
const customCompleter = {
|
const customCompleter = {
|
||||||
getCompletions: function(editor, session, pos, prefix, callback) {
|
getCompletions: function(editor, session, pos, prefix, callback) {
|
||||||
callback(null, customVariables.map((variable) => ({
|
console.log("CUSTOM COMPLETER: ", prefix)
|
||||||
caption: variable,
|
|
||||||
value: variable,
|
callback(null, availableVariables.map((variable) => {
|
||||||
meta: 'custom',
|
console.log("CUSTOM VAR: ", variable)
|
||||||
})));
|
|
||||||
|
return ({
|
||||||
|
caption: variable,
|
||||||
|
value: variable,
|
||||||
|
meta: 'custom',
|
||||||
|
})
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fullScreenMode) {
|
||||||
|
return (
|
||||||
|
<AceEditor
|
||||||
|
mode="python"
|
||||||
|
theme="gruvbox"
|
||||||
|
value={localcodedata}
|
||||||
|
onChange={(value, editor) => {
|
||||||
|
// setlocalcodedata(value)
|
||||||
|
// expectedOutput(value)
|
||||||
|
// highlight_variables(value,editor)
|
||||||
|
setlocalcodedata(value)
|
||||||
|
setcodedata(value)
|
||||||
|
}}
|
||||||
|
name="python-editor"
|
||||||
|
fontSize={14}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
showPrintMargin={false}
|
||||||
|
showGutter={true}
|
||||||
|
markers={markers}
|
||||||
|
highlightActiveLine={false}
|
||||||
|
|
||||||
|
enableBasicAutocompletion={true}
|
||||||
|
completers={[customCompleter]}
|
||||||
|
|
||||||
|
style={{
|
||||||
|
wordBreak: "break-word",
|
||||||
|
marginTop: 0,
|
||||||
|
paddingBottom: 10,
|
||||||
|
overflowY: "auto",
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
wordWrap: "break-word",
|
||||||
|
backgroundColor: "rgba(40,40,40,1)",
|
||||||
|
zIndex: activeDialog === "codeeditor" ? 1200 : 1100,
|
||||||
|
}}
|
||||||
|
|
||||||
|
setOptions={{
|
||||||
|
enableBasicAutocompletion: true,
|
||||||
|
enableLiveAutocompletion: true,
|
||||||
|
enableSnippets: true,
|
||||||
|
showLineNumbers: true,
|
||||||
|
tabSize: 4,
|
||||||
|
fontFamily: "'JetBrains Mono', Consolas, monospace",
|
||||||
|
useSoftTabs: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
aria-labelledby="draggable-code-modal"
|
aria-labelledby="draggable-dialog-title"
|
||||||
disableBackdropClick={true}
|
// disableBackdropClick={true}
|
||||||
disableEnforceFocus={true}
|
disableEnforceFocus={true}
|
||||||
//style={{ pointerEvents: "none" }}
|
style={{ pointerEvents: "none", zIndex: activeDialog === "codeeditor" ? 1200 : 1100}}
|
||||||
hideBackdrop={true}
|
hideBackdrop={true}
|
||||||
open={expansionModalOpen}
|
open={expansionModalOpen}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@@ -977,8 +1069,14 @@ const CodeEditor = (props) => {
|
|||||||
}}
|
}}
|
||||||
PaperComponent={PaperComponent}
|
PaperComponent={PaperComponent}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
|
onClick: () => {
|
||||||
|
if (setActiveDialog !== undefined) {
|
||||||
|
setActiveDialog("codeeditor")
|
||||||
|
}
|
||||||
|
},
|
||||||
style: {
|
style: {
|
||||||
zIndex: 12501,
|
// zIndex: 12501,
|
||||||
|
pointerEvents: "auto",
|
||||||
color: "white",
|
color: "white",
|
||||||
minWidth: isMobile ? "100%" : isFileEditor ? 650 : "80%",
|
minWidth: isMobile ? "100%" : isFileEditor ? 650 : "80%",
|
||||||
maxWidth: isMobile ? "100%" : isFileEditor ? 650 : 1100,
|
maxWidth: isMobile ? "100%" : isFileEditor ? 650 : 1100,
|
||||||
@@ -986,9 +1084,22 @@ const CodeEditor = (props) => {
|
|||||||
maxHeight: isMobile ? "100%" : 700,
|
maxHeight: isMobile ? "100%" : 700,
|
||||||
border: theme.palette.defaultBorder,
|
border: theme.palette.defaultBorder,
|
||||||
padding: isMobile ? "25px 10px 25px 10px" : 25,
|
padding: isMobile ? "25px 10px 25px 10px" : 25,
|
||||||
|
zoom: 0.8,
|
||||||
|
backgroundColor: "black",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{contentLoading === true ?
|
||||||
|
<Tooltip
|
||||||
|
color="primary"
|
||||||
|
title={`The File content is loading. Please wait a moment.`}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<CircularProgress style={{position: "absolute", right: 106, top: 6, }}/>
|
||||||
|
</Tooltip>
|
||||||
|
: null}
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
color="primary"
|
color="primary"
|
||||||
title={`Move window`}
|
title={`Move window`}
|
||||||
@@ -1071,10 +1182,15 @@ const CodeEditor = (props) => {
|
|||||||
*/}
|
*/}
|
||||||
{ isFileEditor ? null :
|
{ isFileEditor ? null :
|
||||||
<div style={{display: "flex", maxHeight: 40, }}>
|
<div style={{display: "flex", maxHeight: 40, }}>
|
||||||
{selectedAction.name === "execute_python" ?
|
{selectedAction?.name === "execute_python" ?
|
||||||
<Typography variant="body1" style={{marginTop: 5, }}>
|
<Typography variant="body1" style={{marginTop: 5, }}>
|
||||||
Run Python Code
|
Run Python Code
|
||||||
</Typography>
|
</Typography>
|
||||||
|
:
|
||||||
|
selectedAction.name === "execute_bash" ?
|
||||||
|
<Typography variant="body1" style={{marginTop: 5, }}>
|
||||||
|
Run Bash Code
|
||||||
|
</Typography>
|
||||||
:
|
:
|
||||||
<div style={{display: "flex", }}>
|
<div style={{display: "flex", }}>
|
||||||
<Button
|
<Button
|
||||||
@@ -1236,7 +1352,7 @@ const CodeEditor = (props) => {
|
|||||||
maxHeight: 650,
|
maxHeight: 650,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{actionlist.map((innerdata) => {
|
{actionlist?.map((innerdata) => {
|
||||||
const icon =
|
const icon =
|
||||||
innerdata.type === "action" ? (
|
innerdata.type === "action" ? (
|
||||||
<AppsIcon style={{ marginRight: 10 }} />
|
<AppsIcon style={{ marginRight: 10 }} />
|
||||||
@@ -1321,6 +1437,8 @@ const CodeEditor = (props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("CLICKED: ", innerdata);
|
console.log("CLICKED: ", innerdata);
|
||||||
console.log(innerdata.example)
|
console.log(innerdata.example)
|
||||||
|
|
||||||
|
//const handleClick = (item) => {
|
||||||
handleItemClick([innerdata]);
|
handleItemClick([innerdata]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1465,14 +1583,37 @@ const CodeEditor = (props) => {
|
|||||||
width: 50,
|
width: 50,
|
||||||
marginLeft: 100,
|
marginLeft: 100,
|
||||||
}}
|
}}
|
||||||
|
disabled={editorData === undefined || editorData.example === undefined || editorData.example === null || editorData.example.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
setlocalcodedata(editorData.example)
|
||||||
|
}}
|
||||||
|
color="secondary"
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
title={"Reset to example body"}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<RestartAltIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
style={{
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
marginLeft: 0,
|
||||||
|
}}
|
||||||
disabled={isAiLoading}
|
disabled={isAiLoading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
autoFormat(localcodedata)
|
if (setAiQueryModalOpen !== undefined) {
|
||||||
|
setAiQueryModalOpen(true)
|
||||||
|
} else {
|
||||||
|
autoFormat(localcodedata)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
color="primary"
|
color="primary"
|
||||||
title={"Auto format data"}
|
title={"Format with AI"}
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
{isAiLoading ?
|
{isAiLoading ?
|
||||||
@@ -1487,7 +1628,7 @@ const CodeEditor = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
// minHeight: 548,
|
// minHeight: 548,
|
||||||
@@ -1495,8 +1636,10 @@ const CodeEditor = (props) => {
|
|||||||
}}>
|
}}>
|
||||||
{(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor ? (
|
{(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor ? (
|
||||||
<AceEditor
|
<AceEditor
|
||||||
|
id="shuffle-codeeditor"
|
||||||
|
name="shuffle-codeeditor"
|
||||||
value={localcodedata}
|
value={localcodedata}
|
||||||
mode={selectedAction === undefined ? "" : selectedAction.name === "execute_python" ? "python" : ""}
|
mode={selectedAction === undefined ? "json" : selectedAction.name === "execute_python" ? "python" : selectedAction.name === "execute_bash" ? "bash" : "json"}
|
||||||
theme="gruvbox"
|
theme="gruvbox"
|
||||||
height={isFileEditor ? 450 : 550}
|
height={isFileEditor ? 450 : 550}
|
||||||
width={isFileEditor ? 650 : "100%"}
|
width={isFileEditor ? 650 : "100%"}
|
||||||
@@ -1515,17 +1658,16 @@ const CodeEditor = (props) => {
|
|||||||
whiteSpace: "pre-wrap",
|
whiteSpace: "pre-wrap",
|
||||||
wordWrap: "break-word",
|
wordWrap: "break-word",
|
||||||
backgroundColor: "rgba(40,40,40,1)",
|
backgroundColor: "rgba(40,40,40,1)",
|
||||||
|
zIndex: activeDialog === "codeeditor" ? 1200 : 1100,
|
||||||
}}
|
}}
|
||||||
onLoad={(editor) => {
|
onLoad={(editor) => {
|
||||||
highlight_variables(localcodedata)
|
highlight_variables(localcodedata)
|
||||||
}}
|
}}
|
||||||
onCursorChange={(cursorPosition, editor, value) => {
|
onCursorChange={(cursorPosition, editor, value) => {
|
||||||
setCurrentCharacter(cursorPosition.column)
|
setCurrentCharacter(cursorPosition.cursor.column)
|
||||||
setCurrentLine(cursorPosition.row)
|
setCurrentLine(cursorPosition.cursor.row)
|
||||||
findIndex(cursorPosition.row, cursorPosition.column)
|
findIndex(cursorPosition.row, cursorPosition.column)
|
||||||
|
|
||||||
//highlight_variables(localcodedata)
|
|
||||||
//console.log("VALUE CURSOR: ", value)
|
|
||||||
}}
|
}}
|
||||||
onChange={(value, editor) => {
|
onChange={(value, editor) => {
|
||||||
// setlocalcodedata(value)
|
// setlocalcodedata(value)
|
||||||
@@ -1559,7 +1701,7 @@ const CodeEditor = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isFileEditor ? null :
|
{isFileEditor ? null :
|
||||||
<div style={{flex: 1, marginLeft: 5, borderLeft: "1px solid rgba(255,255,255,0.3)", paddingLeft: 5, }}>
|
<div style={{flex: 1, marginLeft: 5, borderLeft: "1px solid rgba(255,255,255,0.3)", paddingLeft: 5, overflow: "hidden", }}>
|
||||||
<div>
|
<div>
|
||||||
{isMobile ? null :
|
{isMobile ? null :
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
@@ -1567,11 +1709,13 @@ const CodeEditor = (props) => {
|
|||||||
paddingLeft: 10,
|
paddingLeft: 10,
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
cursor: "move"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<span style={{color: "white"}}>
|
<span style={{color: "white"}}>
|
||||||
Expected Output
|
|
||||||
|
{selectedAction === undefined ? "" : selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ? "Code to run" : `Expected Output for '${selectedAction.name}'`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1588,7 +1732,7 @@ const CodeEditor = (props) => {
|
|||||||
border: `1px solid ${theme.palette.primary.main}`,
|
border: `1px solid ${theme.palette.primary.main}`,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 24,
|
top: 24,
|
||||||
right: 65,
|
right: 100,
|
||||||
maxHeight: 35,
|
maxHeight: 35,
|
||||||
minWidth: 70,
|
minWidth: 70,
|
||||||
}}
|
}}
|
||||||
@@ -1599,7 +1743,7 @@ const CodeEditor = (props) => {
|
|||||||
{executing ?
|
{executing ?
|
||||||
<CircularProgress style={{height: 18, width: 18, }} />
|
<CircularProgress style={{height: 18, width: 18, }} />
|
||||||
:
|
:
|
||||||
<span>Try it <PlayArrowIcon style={{height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} /> </span>
|
<span>{selectedAction === undefined ? "" : selectedAction.name === "execute_python" ? "Run Python Code" : selectedAction.name === "execute_bash" ? "Run Bash" : "Try it"}<PlayArrowIcon style={{height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} /> </span>
|
||||||
}
|
}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -1618,6 +1762,7 @@ const CodeEditor = (props) => {
|
|||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
minWidth: 450,
|
minWidth: 450,
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
|
zIndex: activeDialog === "codeeditor" ? 1200 : 1100,
|
||||||
}}
|
}}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
enableClipboard={(copy) => {
|
enableClipboard={(copy) => {
|
||||||
@@ -1645,11 +1790,12 @@ const CodeEditor = (props) => {
|
|||||||
padding: 10,
|
padding: 10,
|
||||||
marginTop: -2,
|
marginTop: -2,
|
||||||
border: `2px solid ${theme.palette.inputColor}`,
|
border: `2px solid ${theme.palette.inputColor}`,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
maxHeight: 450,
|
maxHeight: 450,
|
||||||
minHeight: 450,
|
minHeight: 450,
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
wordWrap: "anywhere",
|
wordWrap: "anywhere",
|
||||||
|
zIndex: activeDialog === "codeeditor" ? 1200 : 1100,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{expOutput}
|
{expOutput}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ const SuggestedWorkflows = (props) => {
|
|||||||
placement="top"
|
placement="top"
|
||||||
style={{ zIndex: 10011 }}
|
style={{ zIndex: 10011 }}
|
||||||
>
|
>
|
||||||
<div key={index} style={{cursor: finished ? "auto" : "pointer", marginTop: 10, padding: 10, borderRadius: theme.palette.borderRadius, border: `1px solid ${bordercolor}`, display: "flex", backgroundColor: hovering === true ? theme.palette.inputColor : theme.palette.surfaceColor, }} onMouseOver={() => {
|
<div key={index} style={{cursor: finished ? "auto" : "pointer", marginTop: 10, padding: 10, borderRadius: theme.palette?.borderRadius, border: `1px solid ${bordercolor}`, display: "flex", backgroundColor: hovering === true ? theme.palette.inputColor : theme.palette.surfaceColor, }} onMouseOver={() => {
|
||||||
setHovering(true)
|
setHovering(true)
|
||||||
}} onMouseOut={() => {
|
}} onMouseOut={() => {
|
||||||
setHovering(false)
|
setHovering(false)
|
||||||
@@ -140,7 +140,7 @@ const SuggestedWorkflows = (props) => {
|
|||||||
|
|
||||||
//<Paper style={{width: 275, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
|
//<Paper style={{width: 275, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
|
||||||
return (
|
return (
|
||||||
<Paper style={{margin: "auto", position: "relative", backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette.borderRadius, zIndex: foundZindex, border: "1px solid rgba(255,255,255,0.2)", top: 100, left: 85,}}>
|
<Paper style={{margin: "auto", position: "relative", backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius, zIndex: foundZindex, border: "1px solid rgba(255,255,255,0.2)", top: 100, left: 85,}}>
|
||||||
<Dialog
|
<Dialog
|
||||||
open={usecaseSearch.length > 0 && usecaseSearchType.length > 0}
|
open={usecaseSearch.length > 0 && usecaseSearchType.length > 0}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@@ -193,7 +193,7 @@ const SuggestedWorkflows = (props) => {
|
|||||||
apps={apps}
|
apps={apps}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<div style={{minWidth: 250, maxWidth: 250, padding: 15, borderRadius: theme.palette.borderRadius, position: "relative", }}>
|
<div style={{minWidth: 250, maxWidth: 250, padding: 15, borderRadius: theme.palette?.borderRadius, position: "relative", }}>
|
||||||
<Typography variant="body1" style={{textAlign: "center"}}>
|
<Typography variant="body1" style={{textAlign: "center"}}>
|
||||||
Suggested Workflows ({finishedUsecases.length}/{usecaseSuggestions.length})
|
Suggested Workflows ({finishedUsecases.length}/{usecaseSuggestions.length})
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ const UsecaseSearch = (props) => {
|
|||||||
const [selectedAction, setSelectedAction] = React.useState({});
|
const [selectedAction, setSelectedAction] = React.useState({});
|
||||||
const [firstRequest, setFirstRequest] = React.useState(true);
|
const [firstRequest, setFirstRequest] = React.useState(true);
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
//const alert = useAlert()
|
//const alert = useAlert()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -594,7 +594,7 @@ const UsecaseSearch = (props) => {
|
|||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
border: "2px solid rgba(255,255,255,0.6)",
|
border: "2px solid rgba(255,255,255,0.6)",
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
maxHeight: 30,
|
maxHeight: 30,
|
||||||
maxWidth: 30,
|
maxWidth: 30,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
@@ -616,7 +616,7 @@ const UsecaseSearch = (props) => {
|
|||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
border: "2px solid rgba(255,255,255,0.6)",
|
border: "2px solid rgba(255,255,255,0.6)",
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette?.borderRadius,
|
||||||
maxWidth: 30,
|
maxWidth: 30,
|
||||||
maxHeight: 30,
|
maxHeight: 30,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
@@ -1266,9 +1266,11 @@ const UsecaseSearch = (props) => {
|
|||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson.success === false) {
|
if (responseJson.success === false) {
|
||||||
var msgString = "Failed to activate the app"
|
var msgString = "Failed to activate the app"
|
||||||
|
|
||||||
if (responseJson.reason !== undefined) {
|
if (responseJson.reason !== undefined) {
|
||||||
msgString += ": " + responseJson.reason
|
msgString += ": " + responseJson.reason
|
||||||
}
|
}
|
||||||
|
|
||||||
toast(msgString)
|
toast(msgString)
|
||||||
} else {
|
} else {
|
||||||
//toast("App activated for your organization! Refresh the page to use the app.")
|
//toast("App activated for your organization! Refresh the page to use the app.")
|
||||||
@@ -1388,7 +1390,7 @@ const UsecaseSearch = (props) => {
|
|||||||
<Typography variant="body1" style={{color: "rgba(255,255,255,0.5)", marginRight: 20, marginTop: 13, }}>
|
<Typography variant="body1" style={{color: "rgba(255,255,255,0.5)", marginRight: 20, marginTop: 13, }}>
|
||||||
{startText}
|
{startText}
|
||||||
</Typography>
|
</Typography>
|
||||||
<div style={{border: `1px solid ${borderColor}`, backgroundColor: theme.palette.surfaceColor, width: miditem === true ? "65%" : "85%", marginLeft: miditem === true ? 125 : 0, borderRadius: expanded ? theme.palette.borderRadius : 50, maxHeight: expanded || hasError ? 500 : 50, minHeight: 50, }}>
|
<div style={{border: `1px solid ${borderColor}`, backgroundColor: theme.palette.surfaceColor, width: miditem === true ? "65%" : "85%", marginLeft: miditem === true ? 125 : 0, borderRadius: expanded ? theme.palette?.borderRadius : 50, maxHeight: expanded || hasError ? 500 : 50, minHeight: 50, }}>
|
||||||
|
|
||||||
{selectionOpen === true ?
|
{selectionOpen === true ?
|
||||||
<AppsearchPopout
|
<AppsearchPopout
|
||||||
@@ -1560,7 +1562,7 @@ const UsecaseSearch = (props) => {
|
|||||||
// <b>{defaultSearch}: {allusecases[usecaseIndex].name}</b>
|
// <b>{defaultSearch}: {allusecases[usecaseIndex].name}</b>
|
||||||
//console.log("UseCase: ", usecases)
|
//console.log("UseCase: ", usecases)
|
||||||
return (
|
return (
|
||||||
<div style={{maxWidth: "100%", minWidth: "100%", border: "1px solid rgba(255,255,255,0)", borderRadius: theme.palette.borderRadius,}}>
|
<div style={{maxWidth: "100%", minWidth: "100%", border: "1px solid rgba(255,255,255,0)", borderRadius: theme.palette?.borderRadius,}}>
|
||||||
{configureWorkflowModal}
|
{configureWorkflowModal}
|
||||||
{authenticationModal}
|
{authenticationModal}
|
||||||
{showTitle !== false && defaultSearch !== undefined ?
|
{showTitle !== false && defaultSearch !== undefined ?
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const WelcomeForm = (props) => {
|
|||||||
discoveryWrapper,
|
discoveryWrapper,
|
||||||
setDiscoveryWrapper,
|
setDiscoveryWrapper,
|
||||||
appFramework,
|
appFramework,
|
||||||
|
setAppFramework,
|
||||||
getFramework,
|
getFramework,
|
||||||
activeStep,
|
activeStep,
|
||||||
setActiveStep,
|
setActiveStep,
|
||||||
@@ -160,7 +161,7 @@ const WelcomeForm = (props) => {
|
|||||||
const [clickdiff, setclickdiff] = useState(0);
|
const [clickdiff, setclickdiff] = useState(0);
|
||||||
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
|
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
//const alert = useAlert();
|
//const alert = useAlert();
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
@@ -435,20 +436,6 @@ const WelcomeForm = (props) => {
|
|||||||
setSkipped(newSkipped);
|
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 handleReset = () => {
|
const handleReset = () => {
|
||||||
setActiveStep(0);
|
setActiveStep(0);
|
||||||
@@ -645,6 +632,7 @@ const WelcomeForm = (props) => {
|
|||||||
globalUrl={globalUrl}
|
globalUrl={globalUrl}
|
||||||
userdata={userdata}
|
userdata={userdata}
|
||||||
appFramework={appFramework}
|
appFramework={appFramework}
|
||||||
|
setAppFramework={setAppFramework}
|
||||||
setActiveStep={setActiveStep}
|
setActiveStep={setActiveStep}
|
||||||
defaultSearch={defaultSearch}
|
defaultSearch={defaultSearch}
|
||||||
setDefaultSearch={setDefaultSearch}
|
setDefaultSearch={setDefaultSearch}
|
||||||
@@ -660,7 +648,7 @@ const WelcomeForm = (props) => {
|
|||||||
|
|
||||||
<div style={{ marginTop: 0, }}>
|
<div style={{ marginTop: 0, }}>
|
||||||
<div className="thumbs" style={{ display: "flex" }}>
|
<div className="thumbs" style={{ display: "flex" }}>
|
||||||
<div style={{ minWidth: isMobile ? 300 : 554, maxWidth: isMobile ? 300 : 554, borderRadius: theme.palette.borderRadius, }}>
|
<div style={{ minWidth: isMobile ? 300 : 554, maxWidth: isMobile ? 300 : 554, borderRadius: theme.palette?.borderRadius, }}>
|
||||||
<ExploreWorkflow
|
<ExploreWorkflow
|
||||||
globalUrl={globalUrl}
|
globalUrl={globalUrl}
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
|
|||||||
const AppGrid = props => {
|
const AppGrid = props => {
|
||||||
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props
|
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
|
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
|
||||||
const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs
|
const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs
|
||||||
//const [apps, setApps] = React.useState([]);
|
//const [apps, setApps] = React.useState([]);
|
||||||
@@ -204,6 +204,9 @@ const AppGrid = props => {
|
|||||||
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
|
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style:{
|
style:{
|
||||||
|
color: "white",
|
||||||
|
fontSize: "1em",
|
||||||
|
height: 50,
|
||||||
},
|
},
|
||||||
startAdornment: (
|
startAdornment: (
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
@@ -221,6 +224,11 @@ const AppGrid = props => {
|
|||||||
removeQuery("q")
|
removeQuery("q")
|
||||||
refine(event.currentTarget.value)
|
refine(event.currentTarget.value)
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if(event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
limit={5}
|
limit={5}
|
||||||
/>
|
/>
|
||||||
: null}
|
: null}
|
||||||
@@ -233,6 +241,8 @@ const AppGrid = props => {
|
|||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
alignContent: "space-between",
|
alignContent: "space-between",
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
|
padding: "0px 180px",
|
||||||
|
width:"auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
var workflowDelay = -50
|
var workflowDelay = -50
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const WorkflowTemplatePopup = (props) => {
|
|||||||
|
|
||||||
const [requestSent, setRequestSent] = React.useState(false)
|
const [requestSent, setRequestSent] = React.useState(false)
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modalOpen !== true) {
|
if (modalOpen !== true) {
|
||||||
@@ -585,7 +585,7 @@ const WorkflowTemplatePopup = (props) => {
|
|||||||
{/*errorMessage === "" && configurationFinished === true && workflow.id !== undefined && workflowLoading === false ?
|
{/*errorMessage === "" && configurationFinished === true && workflow.id !== undefined && workflowLoading === false ?
|
||||||
<Tooltip title="Click to explore the workflow" placement="top">
|
<Tooltip title="Click to explore the workflow" placement="top">
|
||||||
<span
|
<span
|
||||||
style={{position: "fixed", display: "flex", right: "10%", top: "20%", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: "15px 30px 15px 30px", backgroundColor: theme.palette.platformColor, cursor: "pointer", }}
|
style={{position: "fixed", display: "flex", right: "10%", top: "20%", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, padding: "15px 30px 15px 30px", backgroundColor: theme.palette.platformColor, cursor: "pointer", }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Open in new tab
|
// Open in new tab
|
||||||
window.open("/workflows/" + workflow.id, "_blank")
|
window.open("/workflows/" + workflow.id, "_blank")
|
||||||
|
|||||||
@@ -0,0 +1,901 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
import theme from '../theme.jsx';
|
||||||
|
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
|
import AppSearchButtons from "../components/AppSearchButtons.jsx";
|
||||||
|
import { isMobile } from "react-device-detect";
|
||||||
|
import RenderCytoscape from "../components/RenderCytoscape.jsx";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Drawer,
|
||||||
|
CircularProgress,
|
||||||
|
Fade,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Check as CheckIcon,
|
||||||
|
TrendingFlat as TrendingFlatIcon,
|
||||||
|
Close as CloseIcon,
|
||||||
|
East as EastIcon,
|
||||||
|
Interests as InterestsIcon,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
|
import {
|
||||||
|
green,
|
||||||
|
yellow,
|
||||||
|
red,
|
||||||
|
grey,
|
||||||
|
} from "../views/AngularWorkflow.jsx"
|
||||||
|
|
||||||
|
import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup.jsx";
|
||||||
|
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
|
||||||
|
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx";
|
||||||
|
import FixWorkflowValidationErrors from "../components/FixWorkflowValidationErrors.jsx";
|
||||||
|
|
||||||
|
const WorkflowTemplatePopup = (props) => {
|
||||||
|
const {
|
||||||
|
userdata, appFramework, globalUrl, img1, srcapp, img2, dstapp, title, description, visualOnly, apps, isLoggedIn, isHomePage, getAppFramework, showTryit, shownColor, workflowBuilt, usecaseDetails,
|
||||||
|
|
||||||
|
isModalOpenDefault,
|
||||||
|
setIsClicked,
|
||||||
|
inputWorkflowId,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [isActive, setIsActive] = useState(workflowBuilt === true);
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const [modalOpen, setModalOpen] = useState(isModalOpenDefault === true ? true : false)
|
||||||
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
const [workflowLoading, setWorkflowLoading] = useState(false)
|
||||||
|
const [showLoginButton, setShowLoginButton] = useState(false);
|
||||||
|
const [appAuthentication, setAppAuthentication] = React.useState(undefined);
|
||||||
|
const [missingSource, setMissingSource] = React.useState(undefined)
|
||||||
|
const [missingDestination, setMissingDestination] = React.useState(undefined);
|
||||||
|
const [configurationFinished, setConfigurationFinished] = React.useState(false)
|
||||||
|
const [appSetupDone, setAppSetupDone] = React.useState(false)
|
||||||
|
|
||||||
|
const [requestSent, setRequestSent] = React.useState(false)
|
||||||
|
const [showTryitOut, setShowTryitout] = React.useState(showTryit === true ? true : false)
|
||||||
|
|
||||||
|
const [loadingWorkflow, setLoadingWorkflow] = React.useState(false)
|
||||||
|
const [workflow, setWorkflow] = useState({});
|
||||||
|
const [_, setUpdate] = useState(0)
|
||||||
|
|
||||||
|
const fetchWorkflow = (id) => {
|
||||||
|
if (id === undefined || id === null || id === "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadingWorkflow === true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingWorkflow(true)
|
||||||
|
const url = `${globalUrl}/api/v1/workflows/${id}`
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
setLoadingWorkflow(false)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for framework!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
console.log("Error in workflow loading for ID ", id)
|
||||||
|
} else {
|
||||||
|
setWorkflow(responseJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("err in framework: ", error.toString());
|
||||||
|
setLoadingWorkflow(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputWorkflowId !== undefined && inputWorkflowId !== null && inputWorkflowId !== "" && workflow.id !== inputWorkflowId) {
|
||||||
|
fetchWorkflow(inputWorkflowId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||||
|
let navigate = useNavigate();
|
||||||
|
useEffect(() => {
|
||||||
|
if (modalOpen !== true) {
|
||||||
|
if (workflowLoading === true) {
|
||||||
|
setWorkflowLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log("Modal is not open, so we are not doing anything.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflowLoading !== true) {
|
||||||
|
//console.log("Workflow loading is false, so we can try to get the workflow.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("DEBUG: Skipped direct generation without Try it for now.")
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (!srcapp.includes(":default") && !dstapp.includes(":default")) {
|
||||||
|
if (appSetupDone === false && setAppSetupDone !== undefined) {
|
||||||
|
setAppSetupDone(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
getGeneratedWorkflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingSource !== undefined && missingDestination !== undefined) {
|
||||||
|
if (appSetupDone === false && setAppSetupDone !== undefined) {
|
||||||
|
setAppSetupDone(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getAppFramework !== undefined) {
|
||||||
|
setTimeout(() => {
|
||||||
|
getAppFramework()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}, [modalOpen, missingSource, missingDestination])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
//console.log("IN USEEFFECT FOR CONFIG: ", configurationFinished)
|
||||||
|
if (configurationFinished === true && workflow.id !== undefined && workflow.id !== null && workflow.id !== "") {
|
||||||
|
//toast.success("Generation Successful")
|
||||||
|
|
||||||
|
/*
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate("/workflows/" + workflow.id)
|
||||||
|
}, 2000)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}, [configurationFinished, workflow])
|
||||||
|
|
||||||
|
const imageSize = 32
|
||||||
|
const defaultBorder = "1px solid rgba(255,255,255,0.6)"
|
||||||
|
const imagestyleWrapper = {
|
||||||
|
height: imageSize,
|
||||||
|
width: imageSize,
|
||||||
|
borderRadius: imageSize,
|
||||||
|
border: isHomePage ? null : defaultBorder,
|
||||||
|
overflow: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
const imagestyleWrapperDefault = {
|
||||||
|
height: imageSize,
|
||||||
|
width: imageSize,
|
||||||
|
borderRadius: imageSize,
|
||||||
|
border: isHomePage ? null : defaultBorder,
|
||||||
|
overflow: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
const imagestyle = {
|
||||||
|
height: imageSize,
|
||||||
|
width: imageSize,
|
||||||
|
borderRadius: imageSize,
|
||||||
|
//border: isHomePage ? null : defaultBorder,
|
||||||
|
overflow: "hidden",
|
||||||
|
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
const imagestyleDefault = {
|
||||||
|
display: "block",
|
||||||
|
marginLeft: 9,
|
||||||
|
marginTop: 9,
|
||||||
|
height: imageSize,
|
||||||
|
width: "auto",
|
||||||
|
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modalOpen === false && (title === undefined || title === null || title === "")) {
|
||||||
|
if (setIsClicked !== undefined) {
|
||||||
|
setIsClicked(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("No title for workflow template popup!");
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const loadAppAuth = () => {
|
||||||
|
// Check if it exists, and has keys
|
||||||
|
//
|
||||||
|
if (userdata === undefined || userdata === null || Object.keys(userdata).length === 0) {
|
||||||
|
setErrorMessage("You need to be logged in to try the pre-built Workflow Templates.")
|
||||||
|
setShowLoginButton(true)
|
||||||
|
|
||||||
|
// Send the user to the login screen after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
// Make it cancel if the state modalOpen changes
|
||||||
|
if (modalOpen === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate("/login?view=" + window.location.pathname + window.location.search)
|
||||||
|
}, 4500)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/apps/authentication`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for setting app auth :O!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (!responseJson.success) {
|
||||||
|
toast("Failed to get app auth: " + responseJson.reason);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newauth = [];
|
||||||
|
for (let authkey in responseJson.data) {
|
||||||
|
if (responseJson.data[authkey].defined === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newauth.push(responseJson.data[authkey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppAuthentication(newauth);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
//toast(error.toString());
|
||||||
|
console.log("New auth error: ", error.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can create and set workflows
|
||||||
|
const reloadWorkflow = (workflow_id) => {
|
||||||
|
|
||||||
|
const new_url = `${globalUrl}/api/v1/workflows/${workflow_id}`
|
||||||
|
return fetch(new_url, {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
//setSubmitLoading(false);
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
if (responseJson.reason !== undefined) {
|
||||||
|
toast("Error setting workflow: ", responseJson.reason)
|
||||||
|
} else {
|
||||||
|
toast("Error setting workflow.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
} else if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id !== "") {
|
||||||
|
setWorkflow(responseJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseJson;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast("Failed reloading configured workflow: ", error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Can create and set workflows
|
||||||
|
const saveWorkflow = (workflowdata) => {
|
||||||
|
|
||||||
|
const new_url = `${globalUrl}/api/v1/workflows?set_auth=true`
|
||||||
|
return fetch(new_url, {
|
||||||
|
method: "POST",
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
//setSubmitLoading(false);
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
if (responseJson.reason !== undefined) {
|
||||||
|
toast("Error setting workflow: ", responseJson.reason)
|
||||||
|
} else {
|
||||||
|
toast("Error setting workflow.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case it got a new id, this is to make sure it loads with the correct config
|
||||||
|
if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id !== "") {
|
||||||
|
reloadWorkflow(responseJson.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseJson;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast("Failed generating workflow: ", error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getGeneratedWorkflow = () => {
|
||||||
|
// POST
|
||||||
|
// https://shuffler.io/api/v1/workflows/merge
|
||||||
|
// destination: {app_id: "b9c2feaf99b6309dabaeaa8518c61d3d", app_name: "Servicenow_API", app_version: "",…}
|
||||||
|
// id: ""
|
||||||
|
// middle:[]
|
||||||
|
// name: "Email analysis"
|
||||||
|
// source:{app_id: "accdaaf2eeba6a6ed43b2efc0112032d", app_name
|
||||||
|
if (requestSent === true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("SRCAPP: ", srcapp, "DSTAPP: ", dstapp)
|
||||||
|
if (srcapp === undefined || srcapp === null) {
|
||||||
|
srcapp = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((srcapp !== undefined && srcapp !== null && srcapp.includes(":default")) || (dstapp !== undefined && dstapp !== null && dstapp.includes(":default"))) {
|
||||||
|
toast("You need to select both a source and destination app before generating this workflow.")
|
||||||
|
|
||||||
|
if (srcapp !== undefined && srcapp !== null && srcapp.includes(":default")) {
|
||||||
|
setMissingSource({
|
||||||
|
"type": srcapp.split(":")[0],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dstapp !== undefined && dstapp !== null && dstapp.includes(":default")) {
|
||||||
|
setMissingDestination({
|
||||||
|
"type": dstapp.split(":")[0],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkflowLoading(true)
|
||||||
|
|
||||||
|
const newsrcapp = srcapp
|
||||||
|
const newdstapp = dstapp
|
||||||
|
|
||||||
|
const mergedata = {
|
||||||
|
name: title,
|
||||||
|
id: "",
|
||||||
|
source: {
|
||||||
|
app_name: newsrcapp,
|
||||||
|
},
|
||||||
|
middle: [],
|
||||||
|
destination: {
|
||||||
|
app_name: newdstapp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
setRequestSent(true)
|
||||||
|
const url = isCloud ? `${globalUrl}/api/v1/workflows/merge` : `https://shuffler.io/api/v1/workflows/merge`
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(mergedata),
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
//console.log("Status not 200 for framework!");
|
||||||
|
setRequestSent(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkflowLoading(false)
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id !== "" && responseJson.name !== undefined && responseJson.name !== null && responseJson.name !== "") {
|
||||||
|
console.log("Success in workflow template (prebuilt): ", responseJson);
|
||||||
|
setWorkflow(responseJson)
|
||||||
|
|
||||||
|
// Sets it in the database properly
|
||||||
|
saveWorkflow(responseJson)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
//console.log("Error in workflow template: ", responseJson.error);
|
||||||
|
setRequestSent(false)
|
||||||
|
|
||||||
|
const defaultMessage = "Error: Failed to generate workflow the workflow - the Shuffle team has been notified. Contact support@shuffler.io if you want manual help building this usecase until the AI system is handled."
|
||||||
|
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason !== "") {
|
||||||
|
setErrorMessage(defaultMessage + "\n\n" + responseJson.reason)
|
||||||
|
} else {
|
||||||
|
setErrorMessage(defaultMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsActive(true)
|
||||||
|
//setTimeout(() => {
|
||||||
|
// setModalOpen(false)
|
||||||
|
//}, 5000)
|
||||||
|
} else {
|
||||||
|
console.log("Success in workflow template: ", responseJson);
|
||||||
|
setIsActive(true)
|
||||||
|
if (responseJson.workflow_id === "") {
|
||||||
|
console.log("Failed to build workflow for these tools. Closing in 3 seconds.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchWorkflow(responseJson.workflow_id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("err in framework: ", error.toString());
|
||||||
|
setRequestSent(false)
|
||||||
|
setWorkflowLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modalOpen === true && !srcapp?.includes(":default") && !dstapp?.includes(":default")) {
|
||||||
|
if (appSetupDone === false && setAppSetupDone !== undefined) {
|
||||||
|
setAppSetupDone(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No autoruns anymore without clicking "Try it"
|
||||||
|
if (workflow.id === undefined && workflowLoading === false && errorMessage === "") {
|
||||||
|
//getGeneratedWorkflow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFinished = () => {
|
||||||
|
// Look for configuration fields being done in the current modal
|
||||||
|
// 1. Start by finding the modal
|
||||||
|
const template = document.getElementById("workflow-template")
|
||||||
|
if (template === null || template == undefined) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find item in template with id app-config
|
||||||
|
const appconfig = template.getElementsByClassName("app-config")
|
||||||
|
if (appconfig === null || appconfig == undefined) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalView = () => {
|
||||||
|
if (modalOpen === false) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const divHeight = 500
|
||||||
|
const divWidth = 500
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
anchor={"right"}
|
||||||
|
open={modalOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setModalOpen(false);
|
||||||
|
|
||||||
|
if (setIsClicked !== undefined) {
|
||||||
|
setIsClicked(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: "black",
|
||||||
|
color: "white",
|
||||||
|
minWidth: isHomePage ? null : isMobile ? 300 : 850,
|
||||||
|
maxWidth: isHomePage ? null : isMobile ? 300 : 850,
|
||||||
|
paddingTop: isMobile ? null : 75,
|
||||||
|
itemAlign: "center",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
style={{
|
||||||
|
zIndex: 5000,
|
||||||
|
position: "absolute",
|
||||||
|
top: 14,
|
||||||
|
right: 14,
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setModalOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
<DialogContent style={{marginTop: 0, marginLeft: isHomePage ? null : isMobile ? null : 75, maxWidth: 470, }}>
|
||||||
|
<Typography variant="h4" style={{ fontSize: isMobile ? 20 : null}}>
|
||||||
|
<b>Configure Workflow</b>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{title === undefined || title === null || title === "" ? null :
|
||||||
|
<span>
|
||||||
|
<Typography variant="body2" color="textSecondary" style={{marginTop: 25, }}>
|
||||||
|
Selected Workflow:
|
||||||
|
</Typography>
|
||||||
|
<div style={{marginBottom: 0, }} id="workflow-template">
|
||||||
|
<WorkflowTemplatePopup2
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
img1={img1}
|
||||||
|
srcapp={srcapp}
|
||||||
|
img2={img2}
|
||||||
|
dstapp={dstapp}
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
visualOnly={true}
|
||||||
|
|
||||||
|
workflowBuilt={workflowBuilt}
|
||||||
|
shownColor={shownColor}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div style={{marginTop: 15, }}>
|
||||||
|
{/* Fix the timeline when errors are fixed.. how? */}
|
||||||
|
<WorkflowValidationTimeline
|
||||||
|
workflow={workflow}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FixWorkflowValidationErrors
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
workflow={workflow}
|
||||||
|
setWorkflow={setWorkflow}
|
||||||
|
|
||||||
|
setUpdateParent={setUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{workflowLoading === true ?
|
||||||
|
<div style={{marginTop: 75, textAlign: "center", }}>
|
||||||
|
<Typography variant="h4"> Generating the Workflow...
|
||||||
|
</Typography>
|
||||||
|
<CircularProgress style={{marginLeft: 0, marginTop: 25, }}/>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
{usecaseDetails === undefined ? null :
|
||||||
|
<Typography variant="h6" style={{marginTop: 75, }}>
|
||||||
|
{usecaseDetails?.description}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
<Typography variant="h6" style={{marginTop: 75, }}>
|
||||||
|
{errorMessage !== "" ? errorMessage : ""}
|
||||||
|
</Typography>
|
||||||
|
{showLoginButton ?
|
||||||
|
<Link to="/register?message=Please login to create workflows&view=usecases"
|
||||||
|
style={{
|
||||||
|
textDecoration: 'none',
|
||||||
|
marginBottom: 50,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
fontSize: 18,
|
||||||
|
color: "rgba(255, 132, 68, 1)",
|
||||||
|
marginTop: 32,
|
||||||
|
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
||||||
|
fontWeight: 550,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
<EastIcon style={{ marginTop: 3, marginLeft: 7 }} />
|
||||||
|
</Typography>
|
||||||
|
</Link>
|
||||||
|
:
|
||||||
|
!showTryitOut && !isActive ?
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
style={{
|
||||||
|
textTransform: "none",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
//setWorkflowLoading(true)
|
||||||
|
getGeneratedWorkflow()
|
||||||
|
loadAppAuth()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Try this usecase <TrendingFlatIcon style={{ }} />
|
||||||
|
</Button>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{!isLoggedIn ? null :
|
||||||
|
<div>
|
||||||
|
{(appSetupDone === false && missingSource !== undefined || missingDestination !== undefined) ?
|
||||||
|
<Typography variant="body1" style={{marginTop: 75, marginBottom: 10, }}>
|
||||||
|
{"Find relevant Apps for this Usecase"}
|
||||||
|
</Typography>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{(missingSource !== undefined) ?
|
||||||
|
<div style={{}}>
|
||||||
|
<AppSearchButtons
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
appFramework={appFramework}
|
||||||
|
|
||||||
|
appType={missingSource.type}
|
||||||
|
AppImage={missingSource.image}
|
||||||
|
|
||||||
|
setMissing={setMissingSource}
|
||||||
|
|
||||||
|
getAppFramework={getAppFramework}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{(missingDestination !== undefined) ?
|
||||||
|
<div style={{}}>
|
||||||
|
<AppSearchButtons
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
appFramework={appFramework}
|
||||||
|
|
||||||
|
appType={missingDestination.type}
|
||||||
|
AppImage={missingDestination.image}
|
||||||
|
|
||||||
|
setMissing={setMissingDestination}
|
||||||
|
|
||||||
|
getAppFramework={getAppFramework}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<ConfigureWorkflow
|
||||||
|
userdata={userdata}
|
||||||
|
theme={theme}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
appAuthentication={appAuthentication}
|
||||||
|
setAppAuthentication={setAppAuthentication}
|
||||||
|
|
||||||
|
workflow={workflow}
|
||||||
|
apps={apps}
|
||||||
|
|
||||||
|
setConfigurationFinished={setConfigurationFinished}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isModalOpenDefault === true) {
|
||||||
|
return <ModalView />
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedTitle = title !== undefined && title !== null ? title : ""
|
||||||
|
const maxlength = 50
|
||||||
|
if (title !== undefined && title !== null && title.length > maxlength) {
|
||||||
|
parsedTitle = title.substring(0, maxlength) + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTitle = parsedTitle.replaceAll("_", " ")
|
||||||
|
|
||||||
|
const parsedDescription = description !== undefined && description !== null ? description.replaceAll("_", " ") : ""
|
||||||
|
|
||||||
|
const boxHeight = 104
|
||||||
|
const highlightColor = shownColor !== undefined && shownColor !== null && shownColor !== "" ? shownColor : "#f85a3e"
|
||||||
|
|
||||||
|
var hasInterest = false
|
||||||
|
if (userdata.interests !== undefined && userdata.interests !== null && userdata.interests.length > 0) {
|
||||||
|
const comparisonTitle = title === undefined || title === null ? "" : title.trim().toLowerCase().replaceAll(" ", "_")
|
||||||
|
for (var interestkey in userdata.interests) {
|
||||||
|
if (userdata.interests[interestkey].name === undefined || userdata.interests[interestkey].name === null || userdata.interests[interestkey].name === "") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modalOpen) {
|
||||||
|
console.log("COMPARE: ", userdata.interests[interestkey].name.trim().toLowerCase().replaceAll(" ", "_"), comparisonTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userdata.interests[interestkey].name.trim().toLowerCase().replaceAll(" ", "_") === comparisonTitle) {
|
||||||
|
if (modalOpen) {
|
||||||
|
console.log("FOUND: ", comparisonTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasInterest = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const borderStyle = isHomePage ? null : isHovered && isActive ? errorMessage !== "" ? "1px solid red" : `2px solid ${theme.palette.green}` : isHovered ? `1px solid ${highlightColor}` : "1px solid rgba(33, 33, 33, 1)"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", height: boxHeight, borderRadius: theme.palette?.borderRadius, justifyContent: isMobile ? null : "center" }}
|
||||||
|
>
|
||||||
|
<ModalView />
|
||||||
|
|
||||||
|
<div
|
||||||
|
// variant={isActive === 1 ? "contained" : "outlined"}
|
||||||
|
color="secondary"
|
||||||
|
disabled={visualOnly === true}
|
||||||
|
style={{
|
||||||
|
width: isHomePage? isMobile ? null : "100%" : "99%",
|
||||||
|
borderRadius: 8,
|
||||||
|
textTransform: "none",
|
||||||
|
backgroundColor: isHomePage ? null : theme.palette.inputColor,
|
||||||
|
border: borderStyle,
|
||||||
|
cursor: isActive ? errorMessage !== "" ? "not-allowed" : "pointer" : "pointer",
|
||||||
|
position: "relative",
|
||||||
|
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
setIsHovered(true)
|
||||||
|
|
||||||
|
setShowTryitout(true)
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
setIsHovered(false)
|
||||||
|
|
||||||
|
if (showTryit !== true) {
|
||||||
|
setShowTryitout(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (visualOnly === true) {
|
||||||
|
console.log("Not showing more than visuals.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
loadAppAuth()
|
||||||
|
setModalOpen(true)
|
||||||
|
} else if (isLoggedIn && errorMessage !== "") {
|
||||||
|
toast.error("Already failed to generate a workflow for this usecase. Please try again later or contact support@shuffler.io.")
|
||||||
|
|
||||||
|
setModalOpen(true)
|
||||||
|
} else if (isActive) {
|
||||||
|
// toast.success("Workflow already generated. Please try another workflow template!")
|
||||||
|
|
||||||
|
// FIXME: Remove these?
|
||||||
|
loadAppAuth()
|
||||||
|
setModalOpen(true)
|
||||||
|
//getGeneratedWorkflow()
|
||||||
|
} else {
|
||||||
|
setModalOpen(true)
|
||||||
|
//setWorkflowLoading(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
<div style={{display: "flex", }}>
|
||||||
|
{shownColor !== undefined && shownColor !== null && shownColor !== "" ?
|
||||||
|
<div style={{position: "absolute", left: 0, height: boxHeight-2, width: 4, backgroundColor: shownColor, borderTopLeftRadius: 8, borderBottomLeftRadius: 8, }} />
|
||||||
|
: null}
|
||||||
|
|
||||||
|
<div style={{ display: "flex", itemAlign: "left", textAlign: "left", }}>
|
||||||
|
<div style={{display: "flex", flex: 1, marginLeft: 25, marginTop: showTryitOut && !isActive ? 14 : 30, }}>
|
||||||
|
<div style={{zIndex: 51}}>
|
||||||
|
{img1 !== undefined && img1 !== "" && srcapp !== undefined && srcapp !== "" ?
|
||||||
|
<Tooltip title={srcapp.replaceAll(":default", "").replaceAll("_", " ").replaceAll(" API", "")} placement="top">
|
||||||
|
<div style={srcapp !== undefined && srcapp.includes(":default") ? imagestyleWrapperDefault : imagestyleWrapper}>
|
||||||
|
<img src={img1} style={srcapp !== undefined && srcapp.includes(":default") ? imagestyleDefault : imagestyle} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
:
|
||||||
|
<div style={{width: 50, }} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{img2 !== undefined && img2 !== "" && dstapp !== undefined && dstapp !== "" ?
|
||||||
|
<Tooltip title={dstapp.replaceAll(":default", "").replaceAll("_", " ").replaceAll(" API", "")} placement="top">
|
||||||
|
<div style={{display: "flex", position: "relative", left: -10, }}>
|
||||||
|
<div style={dstapp !== undefined && dstapp.includes(":default") ? imagestyleWrapperDefault : imagestyleWrapper}>
|
||||||
|
<img src={img2} style={dstapp !== undefined && dstapp.includes(":default") ? imagestyleDefault : imagestyle} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
:
|
||||||
|
<div style={{width: 0, }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: 20, overflow: "hidden", maxHeight: 30, marginTop: showTryitOut && !isActive ? 8 : 23, }}>
|
||||||
|
<Typography variant="body1" style={{ marginTop: parsedDescription.length === 0 ? 10 : 0, fontSize: isMobile ? 13 : 16, fontWeight: isHomePage ? 600 : null, textTransform: 'capitalize', color: isHomePage ? "var(--White-text, #F1F1F1)" : "rgba(241, 241, 241, 1)"}} >
|
||||||
|
<b>{parsedTitle}</b>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{isActive === true && errorMessage === "" ?
|
||||||
|
<Tooltip title="You already have workflows that are based on this usecase" placement="top">
|
||||||
|
<CheckIcon color="primary" sx={{ borderRadius: 4 }} style={{ position: "absolute", color: theme.palette.green, top: 10, right: 10, }} />
|
||||||
|
</Tooltip>
|
||||||
|
: ""}
|
||||||
|
|
||||||
|
{!isActive && hasInterest === true ?
|
||||||
|
<Tooltip title="Your team has shown interest in this usecase previously." placement="top">
|
||||||
|
<InterestsIcon color="primary" sx={{ borderRadius: 4 }} style={{ position: "absolute", color: "rgba(254, 204, 0, 0.5)", top: 10, right: 10, }} />
|
||||||
|
</Tooltip>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{showTryitOut && !isActive ?
|
||||||
|
<Fade in={showTryitOut} timeout={300}>
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
style={{
|
||||||
|
textTransform: "none",
|
||||||
|
marginTop: 8,
|
||||||
|
marginLeft: 15,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
//setWorkflowLoading(true)
|
||||||
|
getGeneratedWorkflow()
|
||||||
|
loadAppAuth()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Try it out <TrendingFlatIcon style={{ }} />
|
||||||
|
</Button>
|
||||||
|
</Fade>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkflowTemplatePopup
|
||||||
@@ -0,0 +1,694 @@
|
|||||||
|
import React, { useState, } from "react";
|
||||||
|
import { makeStyles, createStyles } from "@mui/styles";
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
Chip,
|
||||||
|
Typography,
|
||||||
|
IconButton,
|
||||||
|
|
||||||
|
Avatar,
|
||||||
|
AvatarGroup,
|
||||||
|
} from "@mui/material"
|
||||||
|
|
||||||
|
import {
|
||||||
|
ErrorOutline as ErrorOutlineIcon,
|
||||||
|
} from "@mui/icons-material"
|
||||||
|
|
||||||
|
import {
|
||||||
|
green,
|
||||||
|
yellow,
|
||||||
|
red,
|
||||||
|
grey,
|
||||||
|
} from "../views/AngularWorkflow.jsx"
|
||||||
|
|
||||||
|
import WorkflowTemplatePopup2 from "../components/WorkflowTemplatePopup2.jsx"
|
||||||
|
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||||
|
import theme from "../theme.jsx";
|
||||||
|
const itemHeight = 24
|
||||||
|
|
||||||
|
export const getParentNodes = (workflow, action) => {
|
||||||
|
if (action === undefined || action === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.actions === undefined || workflow.actions === null) {
|
||||||
|
workflow.actions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.triggers === undefined || workflow.triggers === null) {
|
||||||
|
workflow.triggers = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.branches === undefined || workflow.branches === null) {
|
||||||
|
workflow.branches = []
|
||||||
|
}
|
||||||
|
|
||||||
|
var allkeys = [action.id];
|
||||||
|
var handled = [];
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
// maxiter = max amount of parent nodes to loop
|
||||||
|
// also handles breaks if there are issues
|
||||||
|
var iterations = 0;
|
||||||
|
var maxiter = 10;
|
||||||
|
while (true) {
|
||||||
|
for (let parentkey in allkeys) {
|
||||||
|
if (allkeys[parentkey] === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentnode = workflow.actions.find((element) => element.id === allkeys[parentkey])
|
||||||
|
if (currentnode === undefined) {
|
||||||
|
currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey])
|
||||||
|
|
||||||
|
if (currentnode === undefined) {
|
||||||
|
//console.log("Could not find parent node for: ", allkeys[parentkey])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled.includes(currentnode.id)) {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
handled.push(currentnode.id);
|
||||||
|
results.push(currentnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the name / label here too?
|
||||||
|
if (currentnode.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This part is only handling first level,
|
||||||
|
// but needs to recurse
|
||||||
|
var incomingEdges = []
|
||||||
|
for (var branchkey in workflow.branches) {
|
||||||
|
const branch = workflow.branches[branchkey]
|
||||||
|
if (branch.destination_id !== currentnode.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go up in the levels
|
||||||
|
const parents = getParentNodes(workflow, {
|
||||||
|
id: branch.source_id,
|
||||||
|
})
|
||||||
|
if (parents.length > 0) {
|
||||||
|
incomingEdges = incomingEdges.concat(parents)
|
||||||
|
}
|
||||||
|
|
||||||
|
incomingEdges.push(branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < incomingEdges.length; i++) {
|
||||||
|
var tmp = incomingEdges[i];
|
||||||
|
if (tmp.decorator === true) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allkeys.includes(tmp.source_id)) {
|
||||||
|
allkeys.push(tmp.source_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.length === allkeys.length || iterations === maxiter) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
iterations += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove on the end as we don't want to remove everything
|
||||||
|
results = results.filter((data) => data.id !== action.id)
|
||||||
|
results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input" || data.app_name === "shuffle-subflow")
|
||||||
|
results.push({ label: "Execution Argument", type: "INTERNAL" })
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkflowValidationTimeline = (props) => {
|
||||||
|
const { globalUrl, userdata, workflow, originalWorkflow, apps, getParents, execution, showHoverColor, } = props
|
||||||
|
|
||||||
|
const [hovering, setHovering] = useState(false)
|
||||||
|
const [decidedColor, setDecidedColor] = useState(grey)
|
||||||
|
const [isClicked, setIsClicked] = useState(false)
|
||||||
|
|
||||||
|
const showMiddle = false
|
||||||
|
if (workflow === undefined || workflow === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.validation === undefined || workflow.validation === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.actions === undefined || workflow.actions === null) {
|
||||||
|
workflow.actions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.triggers === undefined || workflow.triggers === null) {
|
||||||
|
workflow.triggers = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.branches === undefined || workflow.branches === null) {
|
||||||
|
workflow.branches = []
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = []
|
||||||
|
if (execution !== undefined) {
|
||||||
|
results = execution.results
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Find startnode
|
||||||
|
// 2. Map childnodes from it
|
||||||
|
var startnodeId = workflow.start
|
||||||
|
|
||||||
|
if (execution !== undefined && execution !== null) {
|
||||||
|
startnodeId = execution.start
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find parent of startnodeId and if it's a webhook
|
||||||
|
var relevantactions = []
|
||||||
|
for (var key in workflow.branches) {
|
||||||
|
const branch = workflow.branches[key]
|
||||||
|
if (branch.destination_id !== startnodeId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var triggerkey in workflow.triggers) {
|
||||||
|
const trigger = workflow.triggers[triggerkey]
|
||||||
|
if (trigger.trigger_type !== "WEBHOOK") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trigger.id === branch.source_id) {
|
||||||
|
trigger.order = -1
|
||||||
|
relevantactions.push(trigger)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in workflow.actions) {
|
||||||
|
const action = workflow.actions[key]
|
||||||
|
if (action.id === startnodeId) {
|
||||||
|
action.order = 0
|
||||||
|
relevantactions.push(action)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var parents = []
|
||||||
|
if (getParents !== undefined) {
|
||||||
|
parents = getParents(action)
|
||||||
|
} else {
|
||||||
|
parents = getParentNodes(workflow, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.app_name === "Integration Framework" || action.app_name === "Integration") {
|
||||||
|
for (var paramkey in action.parameters) {
|
||||||
|
const param = action.parameters[paramkey]
|
||||||
|
if (param.name === "app_name") {
|
||||||
|
action.app_name = param.value.charAt(0).toUpperCase() + param.value.slice(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//const parents = getParentNodes(workflow, action)
|
||||||
|
//console.log("PARENTS", key, parents)
|
||||||
|
if (parents !== undefined && parents !== null && parents.length > 0) {
|
||||||
|
const parentfound = parents.find((element) => element.id === startnodeId)
|
||||||
|
if (parentfound !== undefined) {
|
||||||
|
|
||||||
|
// FIXME: add order here based on how many steps away from the startnode
|
||||||
|
// This just has the parent count
|
||||||
|
action.order = parents.length
|
||||||
|
|
||||||
|
relevantactions.push(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getParents === undefined) {
|
||||||
|
var newactions = []
|
||||||
|
for (var key in workflow.triggers) {
|
||||||
|
const trigger = workflow.triggers[key]
|
||||||
|
if (trigger.trigger_type !== "SUBFLOW" && trigger.trigger_type !== "USERINPUT") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var branchkey in workflow.branches) {
|
||||||
|
const branch = workflow.branches[branchkey]
|
||||||
|
|
||||||
|
// Checking for OUTBOUND branches from it.
|
||||||
|
// This will mean it's NOT the last node and is easy to visualize
|
||||||
|
if (branch.source_id !== trigger.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger.order = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Just in case (:
|
||||||
|
if (workflow.actions.find((element) => element.id === trigger.id) === undefined) {
|
||||||
|
newactions.push(trigger)
|
||||||
|
//workflow.actions.push(trigger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relevantactions.push(...newactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relevantactions.length <= 1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort according to how many parents a node has. MAY be wrong~
|
||||||
|
relevantactions.sort((a, b) => {
|
||||||
|
if (a.order === undefined) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.order === undefined) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.order - b.order
|
||||||
|
})
|
||||||
|
|
||||||
|
// FIXME: Add other relevant items as well from subflows (?)
|
||||||
|
var nodecolor = grey
|
||||||
|
var branchcolor = grey
|
||||||
|
var skipped = false
|
||||||
|
|
||||||
|
var previousTools = false
|
||||||
|
var scheduleNotStarted = false
|
||||||
|
|
||||||
|
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.validation_ran === false) {
|
||||||
|
console.log("Validation didn't run. Why?")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null && workflow.validation.errors.length > 0) {
|
||||||
|
var newErrors = []
|
||||||
|
for (var key in workflow.validation.errors) {
|
||||||
|
const error = workflow.validation.errors[key]
|
||||||
|
if (error.type === "SCHEDULE") {
|
||||||
|
scheduleNotStarted = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newErrors.push(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
workflow.validation.errors = newErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use this variable to control visualization
|
||||||
|
//const showMiddle = false
|
||||||
|
// border: workflow.validation.valid ? `2px solid ${green}` : "1px solid rgba(255,255,255,0.4)",
|
||||||
|
var middleError = ""
|
||||||
|
var startBranchColor = ""
|
||||||
|
var middleBranchColor = ""
|
||||||
|
|
||||||
|
|
||||||
|
const showHoverForClick = showHoverColor === true ? true : false
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "10px 5px 10px 5px",
|
||||||
|
borderRadius: theme.palette?.borderRadius,
|
||||||
|
|
||||||
|
border: hovering === true && showHoverForClick === true ? `1px solid ${decidedColor}` : "1px solid rgba(255,255,255,0.0)",
|
||||||
|
cursor: hovering === true && showHoverForClick === true ? "pointer" : "default",
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
if (isClicked === false) {
|
||||||
|
setHovering(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
if (isClicked === false) {
|
||||||
|
setHovering(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (showHoverForClick === true) {
|
||||||
|
setIsClicked(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
{isClicked === false ? null :
|
||||||
|
<WorkflowTemplatePopup2
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
userdata={userdata}
|
||||||
|
|
||||||
|
isModalOpenDefault={isClicked}
|
||||||
|
workflowBuilt={true}
|
||||||
|
setIsClicked={setIsClicked}
|
||||||
|
inputWorkflowId={workflow.id}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||||
|
|
||||||
|
{scheduleNotStarted === true ?
|
||||||
|
null
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{relevantactions.map((action, index) => {
|
||||||
|
action.result = {}
|
||||||
|
if (results !== undefined) {
|
||||||
|
const foundResult = results.find((element) => element.action.id === action.id)
|
||||||
|
if (foundResult !== undefined) {
|
||||||
|
action.result = foundResult
|
||||||
|
|
||||||
|
action.status = foundResult.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastitem = index === relevantactions.length - 1
|
||||||
|
if (!lastitem) {
|
||||||
|
if (action.app_name === "Shuffle Tools") {
|
||||||
|
if (action.status === "SUCCESS") {
|
||||||
|
branchcolor = red
|
||||||
|
|
||||||
|
// Check action.result for the actual status
|
||||||
|
const validate = validateJson(action.result.result)
|
||||||
|
if (validate.valid) {
|
||||||
|
if (validate.result.success === true) {
|
||||||
|
nodecolor = green
|
||||||
|
branchcolor = green
|
||||||
|
} else {
|
||||||
|
nodecolor = grey
|
||||||
|
branchcolor = grey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else if (action.status === "SKIPPED") {
|
||||||
|
branchcolor = grey
|
||||||
|
} else {
|
||||||
|
// FIXME: How do we handle this?
|
||||||
|
if (action.status === undefined) {
|
||||||
|
nodecolor = grey
|
||||||
|
branchcolor = grey
|
||||||
|
} else {
|
||||||
|
nodecolor = red
|
||||||
|
branchcolor = red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousTools = true
|
||||||
|
|
||||||
|
if (startnodeId !== action.id) {
|
||||||
|
//return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (action.status === "SUCCESS") {
|
||||||
|
nodecolor = green
|
||||||
|
} else if (action.status === "SKIPPED") {
|
||||||
|
nodecolor = grey
|
||||||
|
} else {
|
||||||
|
if (action.status === undefined) {
|
||||||
|
nodecolor = green
|
||||||
|
} else {
|
||||||
|
nodecolor = red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodecolor = grey
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.status === "SKIPPED") {
|
||||||
|
skipped = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var image = ""
|
||||||
|
if (action.large_image !== undefined && action.large_image !== null && action.large_image !== "") {
|
||||||
|
image = action.large_image
|
||||||
|
} else {
|
||||||
|
if (originalWorkflow !== undefined) {
|
||||||
|
for (var key in originalWorkflow.actions) {
|
||||||
|
if (originalWorkflow.actions[key].id === action.id) {
|
||||||
|
image = originalWorkflow.actions[key].large_image
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image === "") {
|
||||||
|
for (var key in originalWorkflow.triggers) {
|
||||||
|
if (originalWorkflow.triggers[key].id === action.id) {
|
||||||
|
image = originalWorkflow.triggers[key].large_image
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var founderror = ""
|
||||||
|
//console.log("WORKFLOW VALIDATION: ", workflow.validation)
|
||||||
|
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null) {
|
||||||
|
const foundError = workflow.validation.errors.find((element) => element.action_id === action.id)
|
||||||
|
if (foundError !== undefined) {
|
||||||
|
founderror = foundError.error
|
||||||
|
nodecolor = red
|
||||||
|
branchcolor = red
|
||||||
|
} else {
|
||||||
|
//console.log("NO ERROR: ", action.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var appgroup = []
|
||||||
|
if (action.app_name === "shuffle-subflow") {
|
||||||
|
if (action.status === "SUCCESS") {
|
||||||
|
nodecolor = green
|
||||||
|
branchcolor = green
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflow.validation.subflow_apps !== undefined && workflow.validation.subflow_apps !== null && workflow.validation.subflow_apps.length > 0) {
|
||||||
|
nodecolor = red
|
||||||
|
branchcolor = red
|
||||||
|
|
||||||
|
for (var subflowkey in workflow.validation.subflow_apps) {
|
||||||
|
const subflowApp = workflow.validation.subflow_apps[subflowkey]
|
||||||
|
founderror += "- " + subflowApp.error+"\n"
|
||||||
|
|
||||||
|
if (subflowApp.error === action.id) {
|
||||||
|
appgroup.push(subflowApp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!showMiddle && relevantactions.length > 2 && index > 0 && index === relevantactions.length - 2) {
|
||||||
|
if (founderror.length > 0) {
|
||||||
|
middleError += founderror+"\n"
|
||||||
|
|
||||||
|
middleBranchColor = branchcolor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === relevantactions.length-2 && relevantactions.length > 2) {
|
||||||
|
|
||||||
|
const selectedIcon = middleError.length > 0 ?
|
||||||
|
<Tooltip title={
|
||||||
|
<Typography variant="body1" style={{margin: 5, whiteSpace: "pre-line", }}>
|
||||||
|
{middleError}
|
||||||
|
</Typography>
|
||||||
|
}>
|
||||||
|
<IconButton style={{width: 30, height: 30, backgroundColor: "rgba(255,255,255,0.0)", borderRadius: 30, marginTop: 2, }}>
|
||||||
|
<ErrorOutlineIcon style={{color: "red", }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
: null
|
||||||
|
|
||||||
|
return selectedIcon
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns for anything non-middle
|
||||||
|
if (relevantactions.length > 2 && index >= 1 && index < relevantactions.length - 2) {
|
||||||
|
if (founderror.length > 0) {
|
||||||
|
middleError += founderror+"\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipped && !lastitem) {
|
||||||
|
nodecolor = grey
|
||||||
|
branchcolor = grey
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousTools) {
|
||||||
|
previousTools = false
|
||||||
|
} else {
|
||||||
|
branchcolor = nodecolor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.trigger_type === "WEBHOOK") {
|
||||||
|
nodecolor = green
|
||||||
|
branchcolor = green
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3
|
||||||
|
if (nodecolor === green) {
|
||||||
|
branchcolor = green
|
||||||
|
} else if (nodecolor === yellow) {
|
||||||
|
branchcolor = yellow
|
||||||
|
} else if (nodecolor === red) {
|
||||||
|
branchcolor = red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
startBranchColor = branchcolor
|
||||||
|
} else if (index !== 0 && index !== relevantactions.length - 1) {
|
||||||
|
// FIXME: This doesn't work yet
|
||||||
|
middleBranchColor = branchcolor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastitem) {
|
||||||
|
if (middleError.length === 0) {
|
||||||
|
branchcolor = startBranchColor
|
||||||
|
} else {
|
||||||
|
//branchcolor = middleBranchColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (founderror === "") {
|
||||||
|
nodecolor = green
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This could mean the workflow hasn't ran yet
|
||||||
|
if (workflow.validation.valid === false && (workflow.validation.errors === undefined || workflow.validation.errors === null || workflow.validation.errors.length == 0) && (workflow.validation.subflow_apps === undefined || workflow.validation.subflow_apps === null || workflow.validation.subflow_apps.length == 0)) {
|
||||||
|
nodecolor = grey
|
||||||
|
branchcolor = grey
|
||||||
|
}
|
||||||
|
|
||||||
|
const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : ""
|
||||||
|
const appname = action.app_name.replaceAll('_', ' ').slice(0, 16)
|
||||||
|
|
||||||
|
const chipBackground = nodecolor === green ? "rgba(2,203,112, 0.2)" : nodecolor === grey ? "#494949": "rgba(245,52,52,0.8)"
|
||||||
|
const chipColor = nodecolor === green ? "#02cb70" : nodecolor === grey ? "#CDCDCD" : "white"
|
||||||
|
|
||||||
|
//const ballcolor = lastitem ? nodecolor : branchcolor
|
||||||
|
const ballcolor = branchcolor
|
||||||
|
const ballsize = 8
|
||||||
|
const topMargin = 20
|
||||||
|
|
||||||
|
const chipStyle = {
|
||||||
|
height: 40,
|
||||||
|
minWidth: 125,
|
||||||
|
maxWidth: 125,
|
||||||
|
borderRadius: 50,
|
||||||
|
color: chipColor,
|
||||||
|
backgroundColor: chipBackground,
|
||||||
|
|
||||||
|
//border: `2px solid ${chipBackground}`,
|
||||||
|
//backgroundColor: "rgba(0,0,0,0.0)",
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image === "") {
|
||||||
|
console.log("MISSING IMAGE: ", appname, image, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decidedColor === grey && nodecolor === green) {
|
||||||
|
setDecidedColor(red)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decidedColor !== red && nodecolor === red) {
|
||||||
|
setDecidedColor(red)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{display: "flex", flex: flex, justifyContent: "right", }}>
|
||||||
|
{lastitem ?
|
||||||
|
<Tooltip title={branchTooltip}>
|
||||||
|
<div style={{display: "flex", width: "100%", position: "relative",}}>
|
||||||
|
<div style={{marginLeft: 0, marginRight: 0, marginTop: topMargin, height: 3, width: "100%", backgroundColor: branchcolor, }} />
|
||||||
|
<div style={{position: "absolute", right: -3, top: topMargin-2.5, backgroundColor: ballcolor, width: ballsize, height: ballsize, borderRadius: 10, }}/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{appgroup.length > 0 ?
|
||||||
|
<AvatarGroup max={4} style={{height: itemHeight+8, }}>
|
||||||
|
{appgroup.map((subflowApp, subflowIndex) => {
|
||||||
|
var appimage = ""
|
||||||
|
if (apps !== undefined && apps !== null && apps.length > 0) {
|
||||||
|
for (var key in apps) {
|
||||||
|
const app = apps[key]
|
||||||
|
if (app.name === subflowApp.app_name) {
|
||||||
|
appimage = apps[key].large_image
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={`App: ${subflowApp.app_name} - subflow app`}>
|
||||||
|
<Avatar
|
||||||
|
style={{
|
||||||
|
border: `4px solid ${nodecolor}`,
|
||||||
|
}}
|
||||||
|
src={appimage}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</AvatarGroup>
|
||||||
|
:
|
||||||
|
<Tooltip title={
|
||||||
|
<Typography variant="body1" style={{margin: 5, color: "white", }}>
|
||||||
|
{founderror.length > 0 ? founderror : `App: ${appname} - Action: ${action.label}`}
|
||||||
|
</Typography>
|
||||||
|
} placement="top">
|
||||||
|
|
||||||
|
<Chip label={`${appname}`} style={chipStyle} icon={
|
||||||
|
<Avatar
|
||||||
|
variant="round"
|
||||||
|
sx={{ backgroundColor: nodecolor, width: itemHeight, height: itemHeight, borderRadius: 50, border: `1px solid ${nodecolor}`,}}
|
||||||
|
>
|
||||||
|
{image !== "" ?
|
||||||
|
<img
|
||||||
|
src={image}
|
||||||
|
style={{
|
||||||
|
width: itemHeight,
|
||||||
|
height: itemHeight,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
</Avatar>
|
||||||
|
} />
|
||||||
|
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
|
||||||
|
{lastitem ? null :
|
||||||
|
<Tooltip title={branchTooltip}>
|
||||||
|
<div style={{display: "flex", width: "100%", position: "relative",}}>
|
||||||
|
<div style={{position: "absolute", left: -3, top: topMargin-2.5, backgroundColor: ballcolor, color: ballcolor, width: ballsize, height: ballsize, borderRadius: 10, }}/>
|
||||||
|
<div style={{marginLeft: 0, marginRight: 0, marginTop: 20, height: 3, width: "100%", backgroundColor: branchcolor, }} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkflowValidationTimeline
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { createContext, useState, useEffect } from 'react';
|
||||||
|
export const Context = createContext();
|
||||||
|
|
||||||
|
export const AppContext =(props) => {
|
||||||
|
|
||||||
|
// Left side bar global states
|
||||||
|
const [searchBarModalOpen, setSearchBarModalOpen] = useState(false);
|
||||||
|
const [leftSideBarOpenByClick, setLeftSideBarOpenByClick] = useState(false);
|
||||||
|
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||||
|
|
||||||
|
|
||||||
|
//Calculate window width
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setWindowWidth(window?.innerWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={{
|
||||||
|
searchBarModalOpen,
|
||||||
|
setSearchBarModalOpen,
|
||||||
|
leftSideBarOpenByClick,
|
||||||
|
setLeftSideBarOpenByClick,
|
||||||
|
windowWidth
|
||||||
|
}}>
|
||||||
|
{props.children}
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -13,6 +13,30 @@ const data = [
|
|||||||
return elementname
|
return elementname
|
||||||
},
|
},
|
||||||
"text-valign": "center",
|
"text-valign": "center",
|
||||||
|
"text-margin-x": function(element) {
|
||||||
|
// Attempt at bottom-positioning
|
||||||
|
// Required text-valign: bottom
|
||||||
|
// FIXME: Disabled for now.
|
||||||
|
return "15px"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const name = element.data("label")
|
||||||
|
console.log("Name: ", name)
|
||||||
|
if (name === null || name === undefined || name == "" || document=== undefined || document === null) {
|
||||||
|
return "0px"
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d')
|
||||||
|
|
||||||
|
context.font = '18px Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif'
|
||||||
|
|
||||||
|
const textWidth = context.measureText(name).width
|
||||||
|
return textWidth + "px"
|
||||||
|
//return -1*(textWidth) + "px"
|
||||||
|
},
|
||||||
|
|
||||||
"font-family": "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif",
|
"font-family": "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif",
|
||||||
"font-weight": "lighter",
|
"font-weight": "lighter",
|
||||||
"font-size": "18px",
|
"font-size": "18px",
|
||||||
@@ -23,7 +47,6 @@ const data = [
|
|||||||
padding: "10px",
|
padding: "10px",
|
||||||
margin: "5px",
|
margin: "5px",
|
||||||
"border-width": "1px",
|
"border-width": "1px",
|
||||||
"text-margin-x": "10px",
|
|
||||||
"z-index": 5001,
|
"z-index": 5001,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -35,7 +58,7 @@ const data = [
|
|||||||
"curve-style": "unbundled-bezier",
|
"curve-style": "unbundled-bezier",
|
||||||
label: "data(label)",
|
label: "data(label)",
|
||||||
"text-margin-y": "-15px",
|
"text-margin-y": "-15px",
|
||||||
width: "5px",
|
width: "3px",
|
||||||
color: "white",
|
color: "white",
|
||||||
"line-fill": "linear-gradient",
|
"line-fill": "linear-gradient",
|
||||||
"line-gradient-stop-positions": ["0.0", "100"],
|
"line-gradient-stop-positions": ["0.0", "100"],
|
||||||
@@ -119,10 +142,10 @@ const data = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: `node[app_name="Shuffle Tools"]`,
|
selector: `node[app_name="Shuffle Tools"], node[app_name="email"], node[app_name="http"]`,
|
||||||
css: {
|
css: {
|
||||||
width: "30px",
|
width: "35px",
|
||||||
height: "30px",
|
height: "35px",
|
||||||
"z-index": 5000,
|
"z-index": 5000,
|
||||||
"font-size": "0px",
|
"font-size": "0px",
|
||||||
"background-width": "75%",
|
"background-width": "75%",
|
||||||
@@ -258,7 +281,9 @@ const data = [
|
|||||||
{
|
{
|
||||||
selector: "node[?isStartNode]",
|
selector: "node[?isStartNode]",
|
||||||
css: {
|
css: {
|
||||||
shape: "ellipse",
|
shape: function(element) {
|
||||||
|
return "ellipse"
|
||||||
|
},
|
||||||
"border-color": "#80deea",
|
"border-color": "#80deea",
|
||||||
width: "80px",
|
width: "80px",
|
||||||
height: "80px",
|
height: "80px",
|
||||||
@@ -270,8 +295,8 @@ const data = [
|
|||||||
{
|
{
|
||||||
selector: "node[!is_valid]",
|
selector: "node[!is_valid]",
|
||||||
css: {
|
css: {
|
||||||
"border-color": "red",
|
"border-color": "#f53434",
|
||||||
"border-width": "10px",
|
"border-width": "5px",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -357,7 +382,7 @@ const data = [
|
|||||||
css: {
|
css: {
|
||||||
"background-color": "#f85a3e",
|
"background-color": "#f85a3e",
|
||||||
"border-color": "#f85a3e",
|
"border-color": "#f85a3e",
|
||||||
"border-width": "12px",
|
"border-width": "7px",
|
||||||
"transition-property": "border-width",
|
"transition-property": "border-width",
|
||||||
"transition-duration": "0.25s",
|
"transition-duration": "0.25s",
|
||||||
label: "data(label)",
|
label: "data(label)",
|
||||||
@@ -462,13 +487,66 @@ const data = [
|
|||||||
"font-size": "0px",
|
"font-size": "0px",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: "node:selected",
|
selector: "node:selected",
|
||||||
css: {
|
css: {
|
||||||
"border-color": "#f86a3e",
|
"border-color": "#f86a3e",
|
||||||
"border-width": "7px",
|
"border-width": "7px",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: `node[buttonType="condition-drag"]`,
|
||||||
|
css: {
|
||||||
|
"width": "5px",
|
||||||
|
"height": "5px",
|
||||||
|
"background-color": "#f85a3e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: `node[name="switch"]`,
|
||||||
|
css: {
|
||||||
|
label: function(element) {
|
||||||
|
// Load from the actual element
|
||||||
|
var nodeheight = 400
|
||||||
|
var conditions = [{
|
||||||
|
"name": "Condition 1",
|
||||||
|
"check": "X equals Y",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Condition 2",
|
||||||
|
"check": "X2 equals Y2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Condition 3",
|
||||||
|
"check": "X3 equals Y3",
|
||||||
|
}]
|
||||||
|
|
||||||
|
conditions.push({
|
||||||
|
"name": "Else",
|
||||||
|
"check": "If all else fails",
|
||||||
|
})
|
||||||
|
|
||||||
|
const newlines = nodeheight / conditions.length
|
||||||
|
console.log("Newlines: ", newlines)
|
||||||
|
|
||||||
|
const label = conditions.map((condition) => {
|
||||||
|
return `${condition.name}\n\n\n`
|
||||||
|
}).join("\n")
|
||||||
|
|
||||||
|
return label
|
||||||
|
},
|
||||||
|
color: "white",
|
||||||
|
"border-color": "#f85a3e",
|
||||||
|
"background-color": "#1f1f1f",
|
||||||
|
"font-size": "19px",
|
||||||
|
"text-margin-x": "-110px",
|
||||||
|
"text-wrap": "wrap",
|
||||||
|
shape: "roundrectangle",
|
||||||
|
width: "100",
|
||||||
|
height: "300",
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
//{
|
//{
|
||||||
|
|||||||
@@ -18,11 +18,9 @@ code {
|
|||||||
margin-right: -13px;
|
margin-right: -13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc:hover {
|
table th, table td {
|
||||||
background-color: gray;
|
border: 1px solid;
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .cm-string{
|
/* .cm-string{
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { createTheme, adaptV4Theme } from "@mui/material/styles";
|
import { createTheme, adaptV4Theme } from "@mui/material/styles";
|
||||||
|
|
||||||
//const theme = createTheme({
|
|
||||||
const theme = createTheme(adaptV4Theme({
|
const theme = createTheme(adaptV4Theme({
|
||||||
palette: {
|
palette: {
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
main: "#F86743",
|
main: "#FF8544",
|
||||||
primary: {
|
primary: {
|
||||||
main: "#F86743",
|
main: "#FF8544",
|
||||||
contrastText: "#ffffff",
|
contrastText: "#ffffff",
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#e8eaf6",
|
main: "rgba(255,255,255,0.7)",
|
||||||
contrastText: "#000000",
|
contrastText: "#000000",
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
secondary: "rgba(255,255,255,0.7)",
|
secondary: "rgba(255,255,255,0.7)",
|
||||||
},
|
},
|
||||||
type: "dark",
|
type: "dark",
|
||||||
inputColor: "rgba(39,41,45,1)",
|
|
||||||
//inputColor: "#383B40",
|
//inputColor: "#383B40",
|
||||||
|
|
||||||
|
inputColor: "rgba(39,41,45,1)",
|
||||||
surfaceColor: "#27292d",
|
surfaceColor: "#27292d",
|
||||||
platformColor: "#1c1c1d",
|
//platformColor: "#1c1c1d",
|
||||||
|
platformColor: "#212121",
|
||||||
backgroundColor: "#1a1a1a",
|
backgroundColor: "#1a1a1a",
|
||||||
|
|
||||||
green: "#5cc879",
|
green: "#5cc879",
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
defaultBorder: "1px solid rgba(255,255,255,0.3)",
|
defaultBorder: "1px solid rgba(255,255,255,0.3)",
|
||||||
@@ -44,10 +46,20 @@ const theme = createTheme(adaptV4Theme({
|
|||||||
overflowX: "auto",
|
overflowX: "auto",
|
||||||
},
|
},
|
||||||
textFieldStyle: {
|
textFieldStyle: {
|
||||||
backgroundColor: "#383B40",
|
backgroundColor: "#212121",
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
DialogStyle: {
|
||||||
|
backgroundColor: "#212121",
|
||||||
|
borderRadius: 2,
|
||||||
|
boxShadow: "0px 0px 10px 0px rgba(0,0,0,0.75)",
|
||||||
|
border: "1px solid #494949",
|
||||||
},
|
},
|
||||||
innerTextfieldStyle: {
|
innerTextfieldStyle: {
|
||||||
|
height: 40,
|
||||||
|
fontSize: 16,
|
||||||
|
backgroundColor: "#212121",
|
||||||
// Removed since upgrading to mui 18
|
// Removed since upgrading to mui 18
|
||||||
//color: "white",
|
//color: "white",
|
||||||
//minHeight: 50,
|
//minHeight: 50,
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Button, Typography } from '@mui/material';
|
||||||
|
import theme from "../theme.jsx";
|
||||||
|
|
||||||
|
const NotFound = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const buttonStyle = {
|
||||||
|
borderRadius: 25,
|
||||||
|
height: 50,
|
||||||
|
fontSize: 18,
|
||||||
|
width: "100%",
|
||||||
|
marginBottom: "10px",
|
||||||
|
};
|
||||||
|
|
||||||
|
const primaryButtonStyle = {
|
||||||
|
...buttonStyle,
|
||||||
|
background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
|
||||||
|
color: "white",
|
||||||
|
'&:hover': {
|
||||||
|
background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
|
||||||
|
opacity: 0.9,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondaryButtonStyle = {
|
||||||
|
...buttonStyle,
|
||||||
|
background: "#383B40",
|
||||||
|
border: "1px solid #494949",
|
||||||
|
color: "white",
|
||||||
|
'&:hover': {
|
||||||
|
background: "#434649",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{paddingTop: 100, }} className="min-h-screen bg-[#1A1A1A] flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "532px",
|
||||||
|
background: "#212121",
|
||||||
|
borderRadius: "8px",
|
||||||
|
padding: "40px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
justifySelf: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/logos/orange_logo.svg"
|
||||||
|
alt="Shuffle Logo"
|
||||||
|
style={{
|
||||||
|
height: 44,
|
||||||
|
width: 44,
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="h1"
|
||||||
|
style={{
|
||||||
|
fontSize: "72px",
|
||||||
|
fontWeight: 900,
|
||||||
|
background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
|
||||||
|
WebkitBackgroundClip: "text",
|
||||||
|
WebkitTextFillColor: "transparent",
|
||||||
|
marginBottom: "10px",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
404
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
style={{
|
||||||
|
color: "white",
|
||||||
|
marginBottom: "8px",
|
||||||
|
fontWeight: 600,
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Page Not Found
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
style={{
|
||||||
|
color: "#9E9E9E",
|
||||||
|
marginBottom: "32px",
|
||||||
|
fontSize: "16px",
|
||||||
|
textAlign: "center",
|
||||||
|
maxWidth: "400px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Our code doggo couldn't find the page you were looking for.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<iframe
|
||||||
|
src="https://giphy.com/embed/FY8c5SKwiNf1EtZKGs"
|
||||||
|
width="125"
|
||||||
|
height="165"
|
||||||
|
frameBorder="0"
|
||||||
|
className="giphy-embed"
|
||||||
|
allowFullScreen
|
||||||
|
style={{
|
||||||
|
marginBottom: 32,
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ width: "100%", maxWidth: "400px" }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => navigate('/docs')}
|
||||||
|
sx={primaryButtonStyle}
|
||||||
|
>
|
||||||
|
Documentation
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
sx={secondaryButtonStyle}
|
||||||
|
>
|
||||||
|
Return Home
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotFound;
|
||||||