Merge branch 'nightly' into dependabot/go_modules/functions/onprem/worker/golang.org/x/crypto-0.31.0
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- nightly
|
||||
paths:
|
||||
- "**"
|
||||
- "!.github/**"
|
||||
@@ -20,19 +20,19 @@ jobs:
|
||||
include:
|
||||
- app: frontend
|
||||
path: frontend
|
||||
version: 2.0.0
|
||||
version: nightly
|
||||
experimental: true
|
||||
- app: backend
|
||||
path: backend
|
||||
version: 2.0.0
|
||||
version: nightly
|
||||
experimental: true
|
||||
- app: orborus
|
||||
path: functions/onprem/orborus
|
||||
version: 2.0.0
|
||||
version: nightly
|
||||
experimental: true
|
||||
- app: worker
|
||||
path: functions/onprem/worker
|
||||
version: 2.0.0
|
||||
version: nightly
|
||||
experimental: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Helm Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
branches:
|
||||
- main
|
||||
- nightly
|
||||
push:
|
||||
branches:
|
||||
- nightly
|
||||
paths:
|
||||
- "functions/kubernetes/charts/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
|
||||
sudo apt-get install apt-transport-https -y --no-install-recommends
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install helm -y --no-install-recommends
|
||||
|
||||
- name: Set versions
|
||||
id: set_versions
|
||||
run: |
|
||||
if [[ ${{ github.event_name }} == 'release' ]]; then
|
||||
CHART_VERSION="${{ github.event.release.tag_name }}"
|
||||
APP_VERSION="${{ github.event.release.tag_name }}"
|
||||
else
|
||||
CHART_VERSION="0.0.0-nightly-untagged-latest"
|
||||
APP_VERSION="nightly"
|
||||
fi
|
||||
|
||||
echo "CHART_VERSION set to ${CHART_VERSION}. Validating..."
|
||||
|
||||
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
||||
SEMVER_REGEX="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||
|
||||
if echo "${CHART_VERSION}" | grep -Pq "${SEMVER_REGEX}"; then
|
||||
echo "${CHART_VERSION} is a valid SemVer string";
|
||||
else
|
||||
echo "${CHART_VERSION} is an invalid SemVer string";
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
echo "CHART_VERSION=${CHART_VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update helm dependencies
|
||||
run: helm dependency update ./functions/kubernetes/charts/shuffle
|
||||
|
||||
- name: Package Helm chart
|
||||
run: helm package ./functions/kubernetes/charts/shuffle --version "${CHART_VERSION}" --app-version="${APP_VERSION}" --destination ./functions/kubernetes/charts
|
||||
|
||||
- name: Login to OCI registry (ghcr.io)
|
||||
run: helm registry login ghcr.io --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push helm chart
|
||||
run: helm push ./functions/kubernetes/charts/shuffle-*.tgz oci://ghcr.io/shuffle/shuffle/charts
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
workflows: ["dockerbuild"]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -19,7 +20,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up opensearch directory
|
||||
run: mkdir shuffle-database && chmod -R 777 shuffle-database
|
||||
run: chmod -R 777 shuffle-database
|
||||
|
||||
- name: Build the stack
|
||||
run: docker-compose up -d
|
||||
|
||||
@@ -15,20 +15,20 @@ RUN wget -O /app_sdk/app_base.py https://raw.githubusercontent.com/Shuffle/app_s
|
||||
ADD ./app_gen /app_gen
|
||||
|
||||
RUN go get -v
|
||||
RUN go mod tidy
|
||||
RUN go clean -modcache
|
||||
#RUN go mod tidy
|
||||
#RUN go clean -modcache
|
||||
|
||||
# From November 2022, CGO is enabled due to packages
|
||||
# that we use requiring it. This is a temporary fix
|
||||
# and makes us HAVE to install libc compatibility packages farther down.
|
||||
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o webapp .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webapp .
|
||||
|
||||
# Certificate build - gets required certs
|
||||
FROM alpine:latest as certs
|
||||
RUN apk --update add ca-certificates
|
||||
RUN apk add --update ca-certificates
|
||||
|
||||
# Sets up the final image
|
||||
FROM alpine:3.17.0
|
||||
FROM alpine:3.21.2
|
||||
|
||||
# FIXME: Install cgo because CGO_ENABLED=1 during build
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
@@ -289,7 +289,7 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin
|
||||
// Handles pulling of the same image if applicable
|
||||
// This fixes some issues with older versions of Docker which can't build
|
||||
// on their own ( <17.05 )
|
||||
pullOptions := types.ImagePullOptions{}
|
||||
pullOptions := image.PullOptions{}
|
||||
downloaded := false
|
||||
for _, image := range tags {
|
||||
// Is this ok? Not sure. Tags shouldn't be controlled here prolly.
|
||||
@@ -555,7 +555,7 @@ func imageCheckBuilder(images []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
allImages, err := client.ImageList(ctx, types.ImageListOptions{
|
||||
allImages, err := client.ImageList(ctx, image.ListOptions{
|
||||
All: true,
|
||||
})
|
||||
|
||||
@@ -631,7 +631,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
images, err := dockercli.ImageList(ctx, types.ImageListOptions{
|
||||
images, err := dockercli.ImageList(ctx, image.ListOptions{
|
||||
All: true,
|
||||
})
|
||||
|
||||
@@ -665,7 +665,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
pullOptions := types.ImagePullOptions{}
|
||||
pullOptions := image.PullOptions{}
|
||||
if len(img.ID) == 0 {
|
||||
_, err := dockercli.ImagePull(context.Background(), version.Name, pullOptions)
|
||||
if err == nil {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
module shuffle
|
||||
|
||||
go 1.22.0
|
||||
go 1.22.2
|
||||
|
||||
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
||||
|
||||
toolchain go1.22.2
|
||||
|
||||
require (
|
||||
cloud.google.com/go/datastore v1.15.0
|
||||
cloud.google.com/go/storage v1.40.0
|
||||
github.com/basgys/goxml2json v1.1.0
|
||||
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
|
||||
github.com/docker/docker v26.1.5+incompatible
|
||||
github.com/docker/docker v27.5.0+incompatible
|
||||
github.com/frikky/kin-openapi v0.42.0
|
||||
github.com/fsouza/go-dockerclient v1.11.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
@@ -20,10 +18,10 @@ require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shuffle/shuffle-shared v0.6.94
|
||||
golang.org/x/crypto v0.31.0
|
||||
github.com/shuffle/shuffle-shared v0.8.3
|
||||
golang.org/x/crypto v0.32.0
|
||||
google.golang.org/api v0.176.1
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/grpc v1.68.1
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.30.2
|
||||
@@ -35,10 +33,11 @@ require (
|
||||
cloud.google.com/go v0.112.1 // indirect
|
||||
cloud.google.com/go/auth v0.3.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.7 // indirect
|
||||
cloud.google.com/go/scheduler v1.10.6 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||
@@ -53,14 +52,14 @@ require (
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/frikky/schemaless v0.0.13 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
@@ -87,13 +86,14 @@ require (
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opensearch-project/opensearch-go v1.1.0 // indirect
|
||||
github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
@@ -110,26 +110,27 @@ require (
|
||||
github.com/src-d/gcfg v1.4.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.19.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
|
||||
google.golang.org/protobuf v1.35.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
@@ -17,6 +17,8 @@ cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbf
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.15.0 h1:0P9WcsQeTWjuD1H14JIY7XQscIPQ4Laje8ti96IC5vg=
|
||||
cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8=
|
||||
@@ -24,6 +26,8 @@ cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
|
||||
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/scheduler v1.10.6 h1:5U8iXLoQ03qOB+ZXlAecU7fiE33+u3QiM9nh4cd0eTE=
|
||||
cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw=
|
||||
@@ -35,6 +39,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krn
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
@@ -108,8 +114,12 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g=
|
||||
github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
|
||||
github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug=
|
||||
@@ -150,6 +160,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
@@ -283,8 +295,12 @@ github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -302,6 +318,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M=
|
||||
github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo=
|
||||
github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ=
|
||||
@@ -332,8 +350,6 @@ 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.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/shuffle/shuffle-shared v0.6.94 h1:IWMwwrKjQgmmqpp6/Cy9gOT55+pbGrRgzExzXyiBHVU=
|
||||
github.com/shuffle/shuffle-shared v0.6.94/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
@@ -373,12 +389,18 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
|
||||
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 h1:R/OBkMoGgfy2fLhs2QhkCI1w4HLEQX92GCcJB6SSdNk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 h1:giGm8w67Ja7amYNfYMdme7xSp2pIxThWopw8+QP51Yk=
|
||||
@@ -387,10 +409,14 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0 h1:Ydage/
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
|
||||
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
|
||||
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
||||
go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU=
|
||||
go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=
|
||||
go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8=
|
||||
@@ -406,6 +432,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -462,6 +490,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -469,6 +499,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -507,12 +539,16 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -601,8 +637,12 @@ google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUE
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -614,6 +654,8 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
|
||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -627,6 +669,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -1048,6 +1048,20 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
licensed := shuffle.IsLicensed(ctx, *org)
|
||||
|
||||
workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000, 0)
|
||||
if err != nil {
|
||||
log.Printf("{WARNING] Failed getting apps (getworkflowapps): %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
orgApps := workflowapps
|
||||
activatedAppIds := []string{}
|
||||
for _, app := range orgApps {
|
||||
activatedAppIds = append(activatedAppIds, app.ID)
|
||||
}
|
||||
|
||||
returnValue := shuffle.HandleInfo{
|
||||
Success: true,
|
||||
Username: userInfo.Username,
|
||||
@@ -1069,6 +1083,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
Interests: orgInterests,
|
||||
Priorities: orgPriorities,
|
||||
Licensed: licensed,
|
||||
ActiveApps: activatedAppIds,
|
||||
}
|
||||
|
||||
returnData, err := json.Marshal(returnValue)
|
||||
@@ -3064,8 +3079,9 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: Check whether it's in use.
|
||||
if user.Id != app.Owner && user.Role != "admin" {
|
||||
if user.Id == app.Owner || (user.Role == "admin" && user.ActiveOrg.Id == app.ReferenceOrg) || shuffle.ArrayContains(app.Contributors, user.Id) {
|
||||
log.Printf("[DEBUG] Editing app %s with user %s (%s) in org %s", test.Id, user.Username, user.Id, user.ActiveOrg.Id)
|
||||
} else {
|
||||
log.Printf("[WARNING] Wrong user (%s) for app %s when verifying swagger", user.Username, app.Name)
|
||||
resp.WriteHeader(403)
|
||||
resp.Write([]byte(`{"success": false, "reason": "You don't have permissions to edit this app. Contact support@shuffler.io if this persists."}`))
|
||||
@@ -3139,7 +3155,18 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
||||
}
|
||||
}
|
||||
|
||||
api.Owner = user.Id
|
||||
if api.Owner == "" {
|
||||
api.Owner = user.Id
|
||||
}
|
||||
|
||||
if len(api.ReferenceOrg) == 0 {
|
||||
api.ReferenceOrg = user.ActiveOrg.Id
|
||||
}
|
||||
|
||||
if len(test.Image) > 0 {
|
||||
api.SmallImage = test.Image
|
||||
api.LargeImage = test.Image
|
||||
}
|
||||
|
||||
err = shuffle.DumpApi(basePath, api)
|
||||
if err != nil {
|
||||
@@ -3175,7 +3202,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
||||
err = shuffle.DeployAppToDatastore(ctx, api)
|
||||
//func DeployAppToDatastore(ctx context.Context, workflowapp WorkflowApp, bucketName string) error {
|
||||
if err != nil {
|
||||
log.Printf("Failed adding app to db: %s", err)
|
||||
log.Printf("[ERROR] Failed adding app to db: %s", err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed adding app to db: %s"}`, err)))
|
||||
return
|
||||
@@ -3184,7 +3211,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
||||
// 2. Get all the required code
|
||||
appbase, staticBaseline, err := shuffle.GetAppbase()
|
||||
if err != nil {
|
||||
log.Printf("Failed getting appbase: %s", err)
|
||||
log.Printf("[ERROR] Failed getting appbase: %s", err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed getting appbase code"}`))
|
||||
return
|
||||
@@ -3252,9 +3279,9 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
||||
err = shuffle.SetUser(ctx, &user, true)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed adding verification for user %s: %s", user.Username, err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Failed updating user"}`)))
|
||||
return
|
||||
//resp.WriteHeader(500)
|
||||
//resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Failed updating user"}`)))
|
||||
//return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3264,6 +3291,10 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
||||
Body: string(body),
|
||||
}
|
||||
|
||||
if !shuffle.ArrayContains(api.Contributors, user.Id) {
|
||||
api.Contributors = append(api.Contributors, user.Id)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] API LENGTH FOR %s: %d, ID: %s", api.Name, len(parsed.Body), newmd5)
|
||||
// FIXME: Might cause versioning issues if we re-use the same!!
|
||||
// FIXME: Need a way to track different versions of the same app properly.
|
||||
@@ -4842,7 +4873,7 @@ func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) {
|
||||
// FIXME - add org check too, and not just owner
|
||||
// Check workflow.Sharing == private / public / org too
|
||||
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
||||
if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
|
||||
if workflow.OrgId == user.ActiveOrg.Id {
|
||||
log.Printf("[AUDIT] User %s is accessing workflow %s as admin (public)", user.Username, workflow.ID)
|
||||
} else {
|
||||
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (public)", user.Username, workflow.ID)
|
||||
@@ -4939,6 +4970,10 @@ func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(`{"success": false, "reason": "The upload API is not yet implemented in self-hosted Shuffle. Please explore the app hotloading system: https://shuffler.io/docs/app_creation#uploading-an-app"}`))
|
||||
return
|
||||
|
||||
//https://stackoverflow.com/questions/22964950/http-request-formfile-handle-zip-files
|
||||
request.ParseMultipartForm(32 << 20)
|
||||
f, _, err := request.FormFile("shuffle_file")
|
||||
@@ -5088,6 +5123,8 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/apps/categories/run", shuffle.RunCategoryAction).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/upload", handleAppZipUpload).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/{appId}/activate", activateWorkflowAppDocker).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/{appId}/deactivate", activateWorkflowAppDocker).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/{appId}/distribute", activateWorkflowAppDocker).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/frameworkConfiguration", shuffle.GetFrameworkConfiguration).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/frameworkConfiguration", shuffle.SetFrameworkConfiguration).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/{appId}", shuffle.UpdateWorkflowAppConfig).Methods("PATCH", "OPTIONS")
|
||||
@@ -5107,6 +5144,13 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/apps/authentication/{appauthId}/config", shuffle.SetAuthenticationConfig).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/authentication/{appauthId}", shuffle.DeleteAppAuthentication).Methods("DELETE", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/authentication/group", shuffle.AddAppAuthenticationGroup).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/authentication/group", shuffle.GetAppAuthenticationGroup).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/authentication/group/{key}", shuffle.DeleteAppAuthenticationGroup).Methods("DELETE", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/authentication/groups", shuffle.AddAppAuthenticationGroup).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/authentication/groups", shuffle.GetAppAuthenticationGroup).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/authentication/groups/{key}", shuffle.DeleteAppAuthenticationGroup).Methods("DELETE", "OPTIONS")
|
||||
// Related to use-cases that are not directly workflows.
|
||||
r.HandleFunc("/api/v1/workflows/usecases/{key}", shuffle.HandleGetUsecase).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/workflows/usecases", shuffle.LoadUsecases).Methods("GET", "OPTIONS")
|
||||
@@ -5157,6 +5201,16 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/hooks/{key}/delete", shuffle.HandleDeleteHook).Methods("DELETE", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/hooks/{key}", shuffle.HandleDeleteHook).Methods("DELETE", "OPTIONS")
|
||||
|
||||
// This structure is horrendous. Needs fixing after we got the prototype up
|
||||
r.HandleFunc("/api/v1/detections", shuffle.HandleListDetectionCategories).Methods("GET", "OPTIONS")
|
||||
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/{detection_type}/selected_rules/{action}", shuffle.HandleFolderToggle).Methods("PUT", "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/{detection_type}/{fileId}/{action}", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS")
|
||||
|
||||
// OpenAPI configuration
|
||||
r.HandleFunc("/api/v1/verify_swagger", verifySwagger).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/verify_openapi", verifySwagger).Methods("POST", "OPTIONS")
|
||||
@@ -5173,9 +5227,11 @@ func initHandlers() {
|
||||
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/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/triggers/github/register", shuffle.HandleNewGithubRegister).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/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS")
|
||||
|
||||
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
|
||||
@@ -5204,8 +5260,13 @@ func initHandlers() {
|
||||
|
||||
// This is a new API that validates if a key has been seen before.
|
||||
// Not sure what the best course of action is for it.
|
||||
r.HandleFunc("/api/v1/getenvironments", shuffle.HandleGetEnvironments).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/setenvironments", shuffle.HandleSetEnvironments).Methods("PUT", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/stop", shuffle.HandleStopExecutions).Methods("GET", "POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/rerun", shuffle.HandleRerunExecutions).Methods("GET", "POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/stats", shuffle.HandleGetenvStats).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/config", shuffle.HandleSetenvConfig).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments", shuffle.HandleGetEnvironments).Methods("GET", "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")
|
||||
@@ -5214,8 +5275,10 @@ func initHandlers() {
|
||||
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}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/cache/config", shuffle.HandleCacheConfig).Methods("POST", "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}/stats/{key}", shuffle.GetSpecificStats).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")
|
||||
@@ -5226,7 +5289,7 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/datastore/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
|
||||
|
||||
// Docker orborus specific - downloads an image
|
||||
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("GET", "POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/login_openid", shuffle.HandleOpenId).Methods("GET", "POST", "OPTIONS")
|
||||
|
||||
@@ -5242,6 +5305,8 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleGetFileMeta).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/files/{fileId}/config", shuffle.HandleSetFileConfig).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/files/namespaces/{namespace}/share", shuffle.HandleShareNamespace).Methods("POST", "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")
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
|
||||
//gyaml "github.com/ghodss/yaml"
|
||||
|
||||
@@ -614,7 +614,7 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(workflowExecution.ExecutionOrg) > 0 && user.ActiveOrg.Id == workflowExecution.ExecutionOrg && user.Role == "admin" {
|
||||
if len(workflowExecution.ExecutionOrg) > 0 && user.ActiveOrg.Id == workflowExecution.ExecutionOrg {
|
||||
//log.Printf("[DEBUG] User %s is in correct org. Allowing org continuation for execution!", user.Username)
|
||||
} else {
|
||||
log.Printf("[WARNING] Bad authorization key when getting stream results %s.", actionResult.ExecutionId)
|
||||
@@ -965,7 +965,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
||||
if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
|
||||
if workflow.OrgId == user.ActiveOrg.Id {
|
||||
log.Printf("[INFO] User %s is deleting workflow %s as admin. Owner: %s", user.Username, workflow.ID, workflow.Owner)
|
||||
} else {
|
||||
log.Printf("[WARNING] Wrong user (%s) for workflow %s (delete workflow)", user.Username, workflow.ID)
|
||||
@@ -1743,15 +1743,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
||||
Environments: execInfo.Environments,
|
||||
}
|
||||
|
||||
//executionRequestWrapper, err := getWorkflowQueue(ctx, environment)
|
||||
//if err != nil {
|
||||
// executionRequestWrapper = ExecutionRequestWrapper{
|
||||
// Data: []ExecutionRequest{executionRequest},
|
||||
// }
|
||||
//} else {
|
||||
// executionRequestWrapper.Data = append(executionRequestWrapper.Data, executionRequest)
|
||||
//}
|
||||
|
||||
//log.Printf("Execution request: %#v", executionRequest)
|
||||
executionRequest.Priority = workflowExecution.Priority
|
||||
err = shuffle.SetWorkflowQueue(ctx, executionRequest, environment)
|
||||
@@ -1939,7 +1930,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
if !executionAuthValid {
|
||||
if user.Id != workflow.Owner && user.Role != "scheduler" && user.Role != fmt.Sprintf("workflow_%s", fileId) {
|
||||
if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
|
||||
if workflow.OrgId == user.ActiveOrg.Id {
|
||||
log.Printf("[AUDIT] Letting user %s execute %s because they're admin of the same org", user.Username, workflow.ID)
|
||||
} else {
|
||||
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID)
|
||||
@@ -2029,7 +2020,7 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
||||
if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
|
||||
if workflow.OrgId == user.ActiveOrg.Id {
|
||||
log.Printf("[AUDIT] User %s is accessing workflow %s as admin (stop schedule)", user.Username, workflow.ID)
|
||||
} else {
|
||||
log.Printf("[WARNING] Wrong user (%s) for workflow %s (stop schedule)", user.Username, workflow.ID)
|
||||
@@ -2039,19 +2030,6 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
//if user.Id != workflow.Owner || len(user.Id) == 0 {
|
||||
// if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
|
||||
// log.Printf("[INFO] User %s is accessing workflow %s as admin", user.Username, workflow.ID)
|
||||
// } else if workflow.Public {
|
||||
// log.Printf("[INFO] Letting user %s access workflow %s because it's public", user.Username, workflow.ID)
|
||||
// } else {
|
||||
// log.Printf("[WARNING] Wrong user (%s) for workflow %s (get workflow)", user.Username, workflow.ID)
|
||||
// resp.WriteHeader(401)
|
||||
// resp.Write([]byte(`{"success": false}`))
|
||||
// return
|
||||
// }
|
||||
//}
|
||||
|
||||
schedule, err := shuffle.GetSchedule(ctx, scheduleId)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed finding schedule %s", scheduleId)
|
||||
@@ -2279,7 +2257,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
||||
if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
|
||||
if workflow.OrgId == user.ActiveOrg.Id {
|
||||
log.Printf("[INFO] User %s is deleting workflow %s as admin. Owner: %s", user.Username, workflow.ID, workflow.Owner)
|
||||
} else {
|
||||
log.Printf("[WARNING] Wrong user (%s) for workflow %s (schedule start). Owner: %s", user.Username, workflow.ID, workflow.Owner)
|
||||
@@ -4094,12 +4072,12 @@ func LoadSpecificApps(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
appSdk := os.Getenv("SHUFFLE_APP_SDK_VERSION")
|
||||
if len(appSdk) == 0 {
|
||||
_, err := dockercli.ImagePull(ctx, "frikky/shuffle:app_sdk", types.ImagePullOptions{})
|
||||
_, err := dockercli.ImagePull(ctx, "frikky/shuffle:app_sdk", image.PullOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed to download new App SDK: %s", err)
|
||||
}
|
||||
} else {
|
||||
_, err := dockercli.ImagePull(ctx, fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", "ghcr.io", "frikky", appSdk), types.ImagePullOptions{})
|
||||
_, err := dockercli.ImagePull(ctx, fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", "ghcr.io", "frikky", appSdk), image.PullOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed to download new App SDK %s: %s", err)
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ services:
|
||||
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
||||
- SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY}
|
||||
- SHUFFLE_STATS_DISABLED=true
|
||||
- SHUFFLE_WORKER_SCALE=run
|
||||
- SHUFFLE_LOGS_DISABLED=true
|
||||
- SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:latest
|
||||
env_file: .env
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build environment
|
||||
FROM node:21 as builder
|
||||
FROM node:23 as builder
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
@@ -10,9 +10,10 @@ ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||
COPY package.json /usr/src/app/package.json
|
||||
|
||||
# Nocache yarn install
|
||||
#RUN yarn config set "strict-ssl" false -g
|
||||
#RUN yarn install --network-timeout 1000000
|
||||
|
||||
RUN npm config set fetch-retries 3 # Number of retry attempts (default is 2)
|
||||
RUN npm config set fetch-retry-mintimeout 5000 # Minimum wait time before retrying (in ms, default is 10000)
|
||||
RUN npm config set fetch-retry-maxtimeout 60000 # Maximum wait time before retrying (in ms, default is 60000)
|
||||
RUN npm config set fetch-timeout 60000 # Overall fetch timeout (in ms, default is 300000)
|
||||
RUN npm install --timeout=60000 --legacy-peer-deps
|
||||
|
||||
# copy only required files to not trigger rebuilding every time
|
||||
@@ -22,8 +23,7 @@ COPY ./src /usr/src/app/src/
|
||||
COPY ./*.sh /usr/src/app/
|
||||
COPY ./*.json /usr/src/app/
|
||||
|
||||
#RUN rm -rf /usr/src/app/node_modules/webpack
|
||||
#RUN yarn build --verbose
|
||||
|
||||
RUN npm run build --loglevel verbose 2>&1
|
||||
|
||||
# Production environment
|
||||
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 27 KiB |
@@ -4,35 +4,36 @@ import { Link, Route, Routes, BrowserRouter, useNavigate } from "react-router-do
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { removeCookies, useCookies } from "react-cookie";
|
||||
|
||||
import Workflows from "./views/Workflows";
|
||||
import GettingStarted from "./views/GettingStarted";
|
||||
import Workflows from "./views/Workflows.jsx";
|
||||
import GettingStarted from "./views/GettingStarted.jsx";
|
||||
import AngularWorkflow from "./views/AngularWorkflow.jsx";
|
||||
|
||||
import Header from "./components/NewHeader.jsx";
|
||||
import HealthPage from "./components/HealthPage.jsx";
|
||||
|
||||
//import Header from "./components/Header.jsx";
|
||||
import theme from "./theme";
|
||||
import Apps from "./views/Apps";
|
||||
import theme from "./theme.jsx";
|
||||
import Apps from "./views/Apps.jsx";
|
||||
import Apps2 from "./views/Apps2.jsx";
|
||||
import AppCreator from "./views/AppCreator";
|
||||
import AppCreator from "./views/AppCreator.jsx";
|
||||
import DetectionDashBoard from "./views/DetectionDashboard.jsx";
|
||||
|
||||
import Welcome from "./views/Welcome.jsx";
|
||||
import Dashboard from "./views/Dashboard.jsx";
|
||||
import DashboardView from "./views/DashboardViews.jsx";
|
||||
import AdminSetup from "./views/AdminSetup";
|
||||
import Admin from "./views/Admin";
|
||||
import AdminSetup from "./views/AdminSetup.jsx";
|
||||
import Admin from "./views/Admin.jsx";
|
||||
import Docs from "./views/Docs.jsx";
|
||||
import Usecases2 from "./views/Usecases2.jsx";
|
||||
//import Introduction from "./views/Introduction";
|
||||
import SetAuthentication from "./views/SetAuthentication";
|
||||
import SetAuthenticationSSO from "./views/SetAuthenticationSSO";
|
||||
import SetAuthentication from "./views/SetAuthentication.jsx";
|
||||
import SetAuthenticationSSO from "./views/SetAuthenticationSSO.jsx";
|
||||
import Search from "./views/Search.jsx";
|
||||
import RunWorkflow from "./views/RunWorkflow.jsx";
|
||||
import Admin2 from "./views/Admin2.jsx";
|
||||
|
||||
import LoginPage from "./views/LoginPage";
|
||||
import SettingsPage from "./views/SettingsPage";
|
||||
import LoginPage from "./views/LoginPage.jsx";
|
||||
import SettingsPage from "./views/SettingsPage.jsx";
|
||||
import KeepAlive from "./views/KeepAlive.jsx";
|
||||
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
@@ -40,8 +41,8 @@ import CssBaseline from '@mui/material/CssBaseline';
|
||||
|
||||
import UpdateAuthentication from "./views/UpdateAuthentication.jsx";
|
||||
import FrameworkWrapper from "./views/FrameworkWrapper.jsx";
|
||||
import ScrollToTop from "./components/ScrollToTop";
|
||||
import AlertTemplate from "./components/AlertTemplate";
|
||||
import ScrollToTop from "./components/ScrollToTop.jsx";
|
||||
import AlertTemplate from "./components/AlertTemplate.js";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import RuntimeDebugger from "./components/RuntimeDebugger.jsx"
|
||||
|
||||
@@ -58,6 +59,7 @@ import Drift from "react-driftjs";
|
||||
|
||||
import { AppContext } from './context/ContextApi.jsx';
|
||||
import Workflows2 from "./views/Workflows2.jsx";
|
||||
import AppExplorer from "./views/AppExplorer.jsx";
|
||||
|
||||
// Production - backend proxy forwarding in nginx
|
||||
var globalUrl = window.location.origin;
|
||||
@@ -207,37 +209,27 @@ const App = (message, props) => {
|
||||
}
|
||||
|
||||
|
||||
{curpath.includes("/workflows") && curpath.includes("/run") ?
|
||||
<div style={{ height: 60, }} />
|
||||
:
|
||||
isLoggedIn ?
|
||||
<div style={{ position: 'fixed', top: 16, left: 10, zIndex: 100000 }}>
|
||||
<LeftSideBar userdata={userdata} globalUrl={globalUrl} serverside={false} notifications={notifications} />
|
||||
{ window?.location?.pathname === "/" || window?.location?.pathname === "/training" || !(isLoggedIn && isLoaded) ? (
|
||||
<div style={{ minHeight: 68, maxHeight: 68 }}>
|
||||
<Header
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
userdata={userdata}
|
||||
cookies={cookies}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
isLoggedIn={isLoggedIn}
|
||||
curpath={curpath}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
<div style={{ minHeight: 68, maxHeight: 68, }}>
|
||||
<Header
|
||||
billingInfo={{}}
|
||||
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
checkLogin={checkLogin}
|
||||
cookies={cookies}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
isLoggedIn={isLoggedIn}
|
||||
userdata={userdata}
|
||||
|
||||
curpath={curpath}
|
||||
serverside={false}
|
||||
isMobile={false}
|
||||
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
) : (
|
||||
<div style={{ position: 'fixed', top: 32, left: 10, zIndex: 100000 }}>
|
||||
<LeftSideBar checkLogin={checkLogin} userdata={userdata} globalUrl={globalUrl} notifications={notifications} />
|
||||
</div>
|
||||
) }
|
||||
|
||||
{/*
|
||||
<div style={{ height: 60 }} />
|
||||
@@ -262,7 +254,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/admin"
|
||||
path="/admin2"
|
||||
element={
|
||||
<Admin
|
||||
userdata={userdata}
|
||||
@@ -279,6 +271,24 @@ const App = (message, props) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/admin"
|
||||
element={
|
||||
<Admin2
|
||||
cookies={cookies}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
globalUrl={globalUrl}
|
||||
checkLogin={checkLogin}
|
||||
userdata={userdata}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route exact path="/search" element={<Search serverside={false} isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} {...props} /> } />
|
||||
<Route
|
||||
exact
|
||||
@@ -387,7 +397,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/usecases"
|
||||
path="/usecases2"
|
||||
element={
|
||||
<Dashboard
|
||||
userdata={userdata}
|
||||
@@ -400,7 +410,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/usecases2"
|
||||
path="/usecases"
|
||||
element={
|
||||
<Usecases2
|
||||
userdata={userdata}
|
||||
@@ -426,7 +436,7 @@ const App = (message, props) => {
|
||||
<Route exact path="/apps/authentication" element={<UpdateAuthentication serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route
|
||||
exact
|
||||
path="/apps"
|
||||
path="/apps2"
|
||||
element={
|
||||
<Apps
|
||||
isLoaded={isLoaded}
|
||||
@@ -439,7 +449,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/apps2"
|
||||
path="/apps"
|
||||
element={
|
||||
<Apps2
|
||||
serverside={false}
|
||||
@@ -466,7 +476,8 @@ 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="/apps/:appid" element={<AppExplorer userdata={userdata} isLoggedIn={isLoggedIn} isLoaded={isLoaded} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} />
|
||||
<Route exact path="/apis/:appid" element={<ApiExplorerWrapper serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} isMobile={false} selectedApp={undefined} isLoaded={isLoaded}globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} />
|
||||
<Route
|
||||
exact
|
||||
path="/detections/sigma"
|
||||
@@ -474,7 +485,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/workflows"
|
||||
path="/workflows2"
|
||||
element={
|
||||
<Workflows
|
||||
checkLogin={checkLogin}
|
||||
@@ -491,7 +502,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/workflows2"
|
||||
path="/workflows"
|
||||
element={
|
||||
<Workflows2
|
||||
checkLogin={checkLogin}
|
||||
@@ -554,6 +565,7 @@ const App = (message, props) => {
|
||||
isMobile={isMobile}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
isLoggedIn={isLoggedIn}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
@@ -567,6 +579,7 @@ const App = (message, props) => {
|
||||
isMobile={isMobile}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
isLoggedIn={isLoggedIn}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
@@ -635,18 +648,58 @@ const App = (message, props) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/dashboards"
|
||||
element={
|
||||
<DashboardView
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
globalUrl={globalUrl}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/dashboard"
|
||||
element={
|
||||
<DashboardViews
|
||||
serverside={serverside}
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
globalUrl={globalUrl}
|
||||
wut={userdata}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/dashboards"
|
||||
element={
|
||||
<DashboardViews
|
||||
serverside={serverside}
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
globalUrl={globalUrl}
|
||||
wut={userdata}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/dashboard/:key"
|
||||
element={
|
||||
<DashboardViews
|
||||
serverside={serverside}
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
globalUrl={globalUrl}
|
||||
wut={userdata}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/dashboards/:key"
|
||||
element={
|
||||
<DashboardViews
|
||||
serverside={serverside}
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
globalUrl={globalUrl}
|
||||
wut={userdata}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/welcome"
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import React, { useState, useEffect, useContext, memo } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import OrganizationTab from '../components/OrganizationTab.jsx';
|
||||
import UserManagmentTab from '../components/UserManagmentTab.jsx';
|
||||
import CacheView from "../components/CacheView.jsx";
|
||||
import Files from "../components/Files.jsx";
|
||||
import AppAuthTab from "../components/AppAuthTab.jsx";
|
||||
import SchedulesTab from "../components/SchedulesTab.jsx";
|
||||
import EnvironmentTab from "../components/EnvironmentTab.jsx";
|
||||
import TenantsTab from "../components/TenantsTab.jsx";
|
||||
|
||||
import {
|
||||
Business as BusinessIcon,
|
||||
PermIdentity as PermIdentityIcon,
|
||||
HttpsOutlined as HttpsOutlinedIcon,
|
||||
InsertDriveFileOutlined as InsertDriveFileOutlinedIcon,
|
||||
StorageOutlined as StorageOutlinedIcon,
|
||||
AccessTimeOutlined as AccessTimeOutlinedIcon,
|
||||
FmdGoodOutlined as FmdGoodOutlinedIcon,
|
||||
GroupOutlined as GroupOutlinedIcon
|
||||
} from '@mui/icons-material';
|
||||
import theme from '../theme.jsx';
|
||||
import { Button, Tooltip } from '@mui/material';
|
||||
import { Index } from 'react-instantsearch-dom';
|
||||
import { Context } from '../context/ContextApi.jsx';
|
||||
|
||||
const AdminNavBar = (props) => {
|
||||
const location = useLocation();
|
||||
const { globalUrl, userdata, isCloud, isLoaded,removeCookie, handleStatusChange, selectedStatus, setSelectedStatus, handleEditOrg, serverside, notifications, handleGetOrg, orgId, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } = props;
|
||||
const [selectedItem, setSelectedItem] = useState("Organization");
|
||||
const [isSelectedFiles, setIsSelectedFiles] = useState(true);
|
||||
const [isSelectedDataStore, setIsSelectedDataStore] = useState(true);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const tabName = queryParams.get('tab');
|
||||
|
||||
if (tabName === "environments") {
|
||||
setSelectedItem("Locations");
|
||||
} else if (tabName === "suborgs") {
|
||||
setSelectedItem("Tenants");
|
||||
}else if (tabName === "cache") {
|
||||
setSelectedItem("Datastore");
|
||||
}else if (tabName) {
|
||||
setSelectedItem(tabName.charAt(0).toUpperCase() + tabName.slice(1));
|
||||
} else {
|
||||
setSelectedItem("Organization");
|
||||
}
|
||||
}, [location.search]);
|
||||
|
||||
|
||||
const items = [
|
||||
{ iconSrc: <BusinessIcon />, alt: "Organization Icon", text: "Organization", component: OrganizationTab, props: { globalUrl,removeCookie, selectedStatus, isLoaded, setSelectedStatus, handleStatusChange, handleEditOrg, handleGetOrg, userdata, isCloud, serverside, notifications, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } },
|
||||
{ iconSrc: <PermIdentityIcon />, alt: "Users Icon", text: "Users", component: UserManagmentTab, props: { globalUrl, userdata, serverside, isCloud, selectedOrganization, setSelectedOrganization, handleEditOrg } },
|
||||
{ iconSrc: <HttpsOutlinedIcon />, alt: "App Auth Icon", text: "App_auth", component: AppAuthTab, props: { globalUrl, userdata, isCloud, selectedOrganization } },
|
||||
{ iconSrc: <StorageOutlinedIcon />, alt: "Datastore Icon", text: "Datastore", component: CacheView, props: { globalUrl, userdata, selectedOrganization, serverside, isSelectedDataStore, orgId , isCloud} },
|
||||
{ iconSrc: <InsertDriveFileOutlinedIcon />, alt: "Files Icon", text: "Files", component: Files, props: { isCloud, globalUrl, userdata, serverside, selectedOrganization, isSelectedFiles } },
|
||||
{ iconSrc: <AccessTimeOutlinedIcon />, alt: "Trigger Icon", text: "Triggers", component: SchedulesTab, props: { globalUrl, userdata, isCloud, serverside } },
|
||||
{ iconSrc: <FmdGoodOutlinedIcon />, alt: "Environments Icon", text: "Locations", component: EnvironmentTab, props: { globalUrl, userdata, isCloud, selectedOrganization } },
|
||||
{ iconSrc: <GroupOutlinedIcon />, alt: "Tenants Icon", text: "Tenants", component: TenantsTab, props: {isCloud, globalUrl, userdata, serverside, selectedOrganization, setSelectedOrganization, checkLogin } }
|
||||
];
|
||||
|
||||
const setConfig = (newValue) => {
|
||||
setSelectedItem(newValue);
|
||||
if (newValue === "App Auth") {
|
||||
const tabName = newValue.toLowerCase().replace(/\s+/g, '_');
|
||||
navigate(`?tab=${tabName}`, { replace: true });
|
||||
} else {
|
||||
const tabName = newValue.toLowerCase().replace(/\s+/g, '_');
|
||||
navigate(`?tab=${tabName}`, { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
const renderComponent = () => {
|
||||
const selectedItemData = items.find(item => item.text === selectedItem);
|
||||
if (!selectedItemData) {
|
||||
setSelectedItem("Organization");
|
||||
// If no tab is specified, default to "Organization" tab
|
||||
return <OrganizationTab globalUrl={globalUrl} removeCookie={removeCookie} selectedStatus={selectedStatus} isLoaded={isLoaded} setSelectedStatus={setSelectedStatus} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} userdata={userdata} isCloud={isCloud} serverside={serverside} notifications={notifications} checkLogin={checkLogin} setNotifications={setNotifications} stripeKey={stripeKey} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization}/>;
|
||||
};
|
||||
|
||||
const ComponentToRender = selectedItemData.component;
|
||||
const componentProps = selectedItemData.props;
|
||||
|
||||
return <ComponentToRender {...componentProps} />;
|
||||
};
|
||||
|
||||
const defaultImage = "/images/logos/orange_logo.svg"
|
||||
const imageData =
|
||||
selectedOrganization?.image === undefined || selectedOrganization?.image.length === 0
|
||||
? defaultImage
|
||||
: selectedOrganization?.image;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<div style={{ flexDirection: 'column', width: 220, }}>
|
||||
<nav style={{ padding: '25px 25px 3px 25px', height: isCloud ? "calc(100% - 60px)" : "calc(100% - 30px)" , fontSize: '16px', borderTopLeftRadius: 8, borderBottomLeftRadius: 8, background: '#212121', color: '#9CA3AF' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', }}>
|
||||
<img loading="lazy" src={imageData} alt="Logo" style={{ width: '30px', borderRadius: 8, height: '30px', marginRight: '8px' }} />
|
||||
<div style={{
|
||||
fontFamily: theme?.typography?.fontFamily,
|
||||
fontSize: '16px',
|
||||
color: "#FFFFFF",
|
||||
fontWeight: 400,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: '100%',
|
||||
marginLeft: 5,
|
||||
}}>{selectedOrganization?.name}</div>
|
||||
</div>
|
||||
<div style={{ borderTop: '1px solid #494949', marginTop: 23 }} />
|
||||
{items.map((item, index) => (
|
||||
<Tooltip
|
||||
key={index}
|
||||
title={
|
||||
((item.text === "Users") || (item.text === "Files") || (item.text === "Triggers") || (item.text === "Locations")) && !(userdata?.support || userdata?.active_org?.role === "admin")
|
||||
? "Your role is not admin. Please ask the admin to change your role."
|
||||
: ""
|
||||
}
|
||||
placement="right"
|
||||
>
|
||||
<span style={{ display: "inline-block", width: "100%" }}>
|
||||
<Button
|
||||
key={item.text}
|
||||
variant="text"
|
||||
color="primary"
|
||||
sx={{
|
||||
gap: 1,
|
||||
"&:hover": {
|
||||
backgroundColor: "#323232 !important",
|
||||
},
|
||||
"&.MuiButton-root": {
|
||||
color: selectedItem === item.text ? "#FFFFFF" : "#9E9E9E",
|
||||
fontSize: 16,
|
||||
backgroundColor: "transparent",
|
||||
textTransform: "none",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
border: "none",
|
||||
marginTop: index === 0 ? "15px" : "5px",
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
borderLeft:
|
||||
selectedItem === item.text
|
||||
? "3px solid rgba(255, 132, 68, 1)"
|
||||
: "none",
|
||||
borderTopLeftRadius: selectedItem === item.text ? "2.5px" : null,
|
||||
borderBottomLeftRadius: selectedItem === item.text ? "2.5px" : null,
|
||||
paddingLeft: selectedItem === item.text ? "15px" : "10px",
|
||||
fontWeight: selectedItem === item.text ? 200 : "normal",
|
||||
flex: 1,
|
||||
},
|
||||
"&.Mui-disabled": {
|
||||
color: "#6F6F6F",
|
||||
},
|
||||
}}
|
||||
disabled={
|
||||
((item.text === "Users") || (item.text === "Files") || (item.text === "Triggers") || (item.text === "Locations")) && !(userdata?.support || userdata?.active_org?.role === "admin")
|
||||
}
|
||||
startIcon={item.iconSrc}
|
||||
onClick={() => setConfig(item.text)}
|
||||
>
|
||||
{item.text.replace(/_/g, " ")}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
<Wrapper2>{renderComponent()}</Wrapper2>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminNavBar;
|
||||
|
||||
const PaddingWrapper2 = memo(({ children }) => {
|
||||
|
||||
return (
|
||||
<div div style={{marginBottom: 30, width: "75%" , maxWidth: 1200, height: "100%", boxSizing: 'border-box'}}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
const Wrapper2 = memo(({children}) => {
|
||||
|
||||
return (
|
||||
<PaddingWrapper2>
|
||||
{children}
|
||||
</PaddingWrapper2>
|
||||
);
|
||||
})
|
||||
|
||||
const PaddingWrapper = memo(({ children }) => {
|
||||
const { leftSideBarOpenByClick, windowWidth } = useContext(Context);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: leftSideBarOpenByClick ? windowWidth <= 1300 ? 220 : 200 : 80,
|
||||
transition: "padding-left 0.3s ease",
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const Wrapper = memo(({ children }) => {
|
||||
return (
|
||||
<PaddingWrapper>
|
||||
{children}
|
||||
</PaddingWrapper>
|
||||
);
|
||||
})
|
||||
@@ -0,0 +1,275 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { Typography, Button } from '@mui/material';
|
||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
||||
import MailOutlineIcon from '@mui/icons-material/MailOutline';
|
||||
|
||||
const AnalyticsTab = (props) => {
|
||||
const { userdata, globalUrl, serverside } = props;
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState('all');
|
||||
const [expand, setExpand] = useState(false)
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
|
||||
const handleOptionChange = (option) => {
|
||||
setSelectedOption(option);
|
||||
// You can perform actions based on the selected option, such as filtering data
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
setChecked(event.target.checked);
|
||||
};
|
||||
|
||||
const allData = {
|
||||
labels: ['Email analysis', 'Email Management', 'EDR to Ticket', 'Ticket Analysis'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Revisions',
|
||||
data: [12, 19, 3, 5], // Data for revisions
|
||||
backgroundColor: '#FF8444',
|
||||
borderWidth: 1,
|
||||
// borderRadius: 60,
|
||||
barPercentage: 0.7,
|
||||
categoryPercentage: 0.5,
|
||||
},
|
||||
{
|
||||
label: 'Runs',
|
||||
data: [8, 15, 5, 8], // Data for runs
|
||||
backgroundColor: '#9747FF',
|
||||
borderWidth: 1,
|
||||
// borderRadius: 60,
|
||||
barPercentage: 0.7,
|
||||
categoryPercentage: 0.5
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let data;
|
||||
if (selectedOption === 'all') {
|
||||
data = allData;
|
||||
} else if (selectedOption === 'revisions') {
|
||||
data = {
|
||||
labels: allData.labels,
|
||||
datasets: [allData.datasets[0]] // Show only revisions data
|
||||
};
|
||||
} else if (selectedOption === 'run') {
|
||||
data = {
|
||||
labels: allData.labels,
|
||||
datasets: [allData.datasets[1]] // Show only runs data
|
||||
};
|
||||
}
|
||||
|
||||
// Options for the chart
|
||||
const options = {
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div style={{ width: 1030, marginLeft: 20, marginTop:10, paddingRight: 17, paddingLeft: 17}}>
|
||||
<div style={{ width: 985, display: 'flex', alignItems: 'start', paddingTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
|
||||
<div style={{ marginLeft: 18 }}>Timeline</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", width: "100%", marginTop: 16 }}>
|
||||
<div>
|
||||
<div style={{ width: 595, alignItems: 'start', paddingTop: 16, paddingBottom: checked ? 28 : 20, marginRight: 20, fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<div style={{ marginLeft: 18, marginRight: 310 }}>Apps</div>
|
||||
<Switch checked={checked}
|
||||
onChange={handleChange} /> Category
|
||||
<div style={{ marginLeft: 16, borderLeft: "1px solid #D9D9D9", width: 10, height: 15 }} />
|
||||
<Link onClick={() => { setExpand(prevExpand => !prevExpand); }} style={{ color: "#FF8444" }}>Expand</Link>
|
||||
</div>
|
||||
{expand ? null :
|
||||
<div style={{ marginTop: 8, display: "flex" }}>
|
||||
<div style={{ marginLeft: 19, }}>
|
||||
<Typography style={{ fontSize: 13, color: "#9E9E9E", textAlign: "start" }}>Onboarding</Typography>
|
||||
<Grid container spacing={2} style={{ marginTop: 1 }} >
|
||||
<Grid item xs={4}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
|
||||
{checked ?
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: '50%',
|
||||
top: 30,
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: '#2f2f2f',
|
||||
borderRadius: '50%',
|
||||
height: 24,
|
||||
width: 24,
|
||||
transition: 'transform 0.5s ease',
|
||||
}}><SearchIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
|
||||
{checked ?
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: '50%',
|
||||
top: 30,
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: '#2f2f2f',
|
||||
borderRadius: '50%',
|
||||
height: 24,
|
||||
width: 24,
|
||||
transition: 'transform 0.5s ease',
|
||||
}}><MailOutlineIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
|
||||
{checked ?
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: '50%',
|
||||
top: 30,
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: '#2f2f2f',
|
||||
borderRadius: '50%',
|
||||
height: 24,
|
||||
width: 24,
|
||||
transition: 'transform 0.5s ease',
|
||||
}}><NewReleasesIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
<div style={{ borderLeft: "1px solid #494949", justifyContent: "center", alignItems: "center", marginTop: 40, marginLeft: 20, marginRight: 20, height: 40 }}></div>
|
||||
<div style={{}}>
|
||||
<Typography style={{ fontSize: 13, color: "#9E9E9E", textAlign: "start" }}>Other</Typography>
|
||||
<Grid container spacing={2} style={{ marginTop: 1 }} >
|
||||
<Grid item xs={4}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
|
||||
{checked ?
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: '50%',
|
||||
top: 30,
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: '#2f2f2f',
|
||||
borderRadius: '50%',
|
||||
height: 24,
|
||||
width: 24,
|
||||
transition: 'transform 0.5s ease',
|
||||
}}><SearchIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
|
||||
{checked ?
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: '50%',
|
||||
top: 30,
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: '#2f2f2f',
|
||||
borderRadius: '50%',
|
||||
height: 24,
|
||||
width: 24,
|
||||
transition: 'transform 10s ease-in-out',
|
||||
}}><NewReleasesIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
|
||||
{checked ?
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: '50%',
|
||||
top: 30,
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: '#2f2f2f',
|
||||
borderRadius: '50%',
|
||||
height: 24,
|
||||
width: 24,
|
||||
transition: 'transform 10s ease-in-out',
|
||||
}}><MailOutlineIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
<div style={{ width: 595, height: 322, marginTop: 16, paddingTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
|
||||
<div style={{ marginLeft: 18, textAlign: "start" }}>Workflows</div>
|
||||
<div style={{textAlign:"center"}}>
|
||||
<Button style={{ textTransform: "capitalize", background: selectedOption === 'all' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'all' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('all')}>All</Button>
|
||||
<Button style={{ textTransform: "capitalize", background: selectedOption === 'revisions' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'revisions' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('revisions')}>Revisions</Button>
|
||||
<Button style={{ textTransform: "capitalize", background: selectedOption === 'run' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'run' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('run')}>Runs</Button>
|
||||
</div>
|
||||
<Bar data={data} options={options} style={{ width: 400, marginLeft: 25, padding:20,marginTop: 10 }} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: 375, height: 440, display: 'flex', alignItems: 'start', paddingTop: '16px', paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
|
||||
<div style={{ marginLeft: 18 }}>Insights</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: 985, paddingTop: 16, marginTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
|
||||
<div style={{ marginLeft: 18, textAlign: 'start', marginBottom: 16 }}>Sessions Overview</div>
|
||||
<div style={{ display: "flex", width: "100%", justifyContent: "center" }}>
|
||||
<div style={{ fontSize: '16px',width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}>
|
||||
<div style={{ marginTop: 21, color: "#ffffff", marginLeft:20,fontSize: 16, fontWeight: "bold", }}>
|
||||
2.8 Hours
|
||||
</div>
|
||||
<div style={{ marginTop: 13, marginBottom: 16, marginLeft:20, }}>
|
||||
Avg. Activity per session
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginLeft: 16, fontSize: '16px', width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}>
|
||||
<div style={{ marginTop: 21, marginLeft:20, color: "#ffffff", fontSize: 16, fontWeight: "bold", }}>
|
||||
/usercases/edr to ticket
|
||||
</div>
|
||||
<div style={{ marginTop: 13, marginLeft:20, marginBottom: 16, }}>
|
||||
Last visited page
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginLeft: 16, fontSize: '16px', width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}>
|
||||
<div style={{ marginTop: 21, marginLeft:20,color: "#ffffff", fontSize: 16, fontWeight: "bold", }}>
|
||||
/workflow/email management
|
||||
</div>
|
||||
<div style={{ marginTop: 13, marginLeft:20, marginBottom: 16, }}>
|
||||
Most visited page
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsTab;
|
||||
@@ -25,7 +25,8 @@ import {
|
||||
TableRow,
|
||||
InputAdornment,
|
||||
Divider,
|
||||
LinearProgress
|
||||
LinearProgress,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import throttle from "lodash/throttle";
|
||||
import theme from "../theme.jsx";
|
||||
@@ -100,6 +101,8 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se
|
||||
const [ExampleBody, setExampleBody] = useState({});
|
||||
const [filteredActions, setFilteredActions] = useState([]);
|
||||
|
||||
const [firstSendDone, setFirstSendDone] = useState(false)
|
||||
|
||||
const getJsonObject = (properties) => {
|
||||
|
||||
let jsonObject = {};
|
||||
@@ -1181,6 +1184,7 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var prefixCheck = "/v1";
|
||||
if (parentUrl.includes("/")) {
|
||||
const urlsplit = parentUrl.split("/");
|
||||
@@ -1240,7 +1244,7 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se
|
||||
}
|
||||
}
|
||||
|
||||
newActions = newActions2;
|
||||
newActions = newActions2
|
||||
|
||||
// Rearrange them by which has action_label
|
||||
const firstActions = newActions.filter(
|
||||
@@ -1255,9 +1259,9 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se
|
||||
data.action_label === null ||
|
||||
data.action_label === "No Label"
|
||||
);
|
||||
newActions = firstActions.concat(secondActions);
|
||||
setActions(newActions);
|
||||
setExampleBody(newActions[0]?.body);
|
||||
newActions = firstActions.concat(secondActions)
|
||||
setActions(newActions)
|
||||
setExampleBody(newActions[0]?.body)
|
||||
}, [openapi]);
|
||||
|
||||
return (
|
||||
@@ -1425,36 +1429,47 @@ const ActionsList = memo(({
|
||||
};
|
||||
|
||||
const handleSearch = (e) => {
|
||||
const query = e.target.value;
|
||||
setSearchQuery(query);
|
||||
const query = e?.target?.value?.toLowerCase().replaceAll("_", " ");
|
||||
|
||||
setSearchQuery(query)
|
||||
if (query.length === 0) {
|
||||
setVisibleActions(actions);
|
||||
setVisibleActions(actions)
|
||||
} else {
|
||||
setVisibleActions(
|
||||
actions.filter((action) =>
|
||||
action.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
actions?.filter((action) =>
|
||||
action?.name?.toLowerCase()?.replaceAll("_", " ")?.includes(searchQuery)
|
||||
)
|
||||
);
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: '350px', width: "25%", overflow: 'hidden', marginLeft: !(isLoggedIn || isLoaded) ? 5 : 0}}>
|
||||
<div style={{ borderBottom: '1px solid #494949', paddingTop: 10, paddingBottom: 10}}>
|
||||
<div>
|
||||
{info?.title ? (
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
src={openapi?.info["x-logo"]}
|
||||
width={48}
|
||||
height={48}
|
||||
alt="app logo"
|
||||
style={{ marginLeft: 20, borderRadius: 8 }}
|
||||
/>
|
||||
<Typography style={{ fontSize: 24, fontWeight: 'bold', marginLeft: 20, overflow: 'hidden', color: '#F1F1F1'
|
||||
}}>
|
||||
{info.title}
|
||||
</Typography>
|
||||
</div>
|
||||
<a
|
||||
href={`/apps/${openapi?.id}`}
|
||||
style={{ textDecoration: 'none', }}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Tooltip title="Go to app" placement="right">
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
src={openapi?.info["x-logo"]}
|
||||
width={48}
|
||||
height={48}
|
||||
alt="app logo"
|
||||
style={{ marginLeft: 20, borderRadius: 8 }}
|
||||
/>
|
||||
<Typography style={{ fontSize: 24, fontWeight: 'bold', marginLeft: 20, overflow: 'hidden', color: '#F1F1F1'
|
||||
}}>
|
||||
{info.title}
|
||||
</Typography>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</a>
|
||||
) : (
|
||||
<Typography style={{ fontSize: 24, fontWeight: 'bold', marginLeft: 20, overflow: 'hidden', color: '#F1F1F1'
|
||||
}}>
|
||||
@@ -1463,9 +1478,10 @@ const ActionsList = memo(({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
value={searchQuery}
|
||||
placeholder="Search here"
|
||||
placeholder="Search endpoints"
|
||||
onChange={handleSearch}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
@@ -1483,6 +1499,7 @@ const ActionsList = memo(({
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginLeft: 20,
|
||||
@@ -1542,7 +1559,7 @@ const ActionsList = memo(({
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{action.name.replaceAll("_", " ")}
|
||||
{action?.name?.replaceAll("_", " ")}
|
||||
</span>
|
||||
</Button>
|
||||
))
|
||||
@@ -1794,7 +1811,7 @@ const Action = memo((
|
||||
response.result = JSON.parse(response.result);
|
||||
} catch (parseError) {
|
||||
console.error("Error parsing result:", parseError);
|
||||
toast.error("Error parsing response result.");
|
||||
//toast.error("Error parsing response result.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2035,6 +2052,7 @@ const Action = memo((
|
||||
}, [editorRef.current]);
|
||||
|
||||
|
||||
const actionname = action?.name.charAt(0).toUpperCase() + action?.name.slice(1).replaceAll("_", " ")
|
||||
return (
|
||||
<div
|
||||
ref={actionRef}
|
||||
@@ -2042,6 +2060,7 @@ const Action = memo((
|
||||
data-index={index}
|
||||
key={action.name.replace(/ /g, "-").replace(/_/g, "-")}
|
||||
style={{
|
||||
marginTop: 50,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
minHeight: 400,
|
||||
@@ -2066,7 +2085,7 @@ const Action = memo((
|
||||
color: "rgba(241, 241, 241, 1)",
|
||||
}}
|
||||
>
|
||||
{action.name}
|
||||
{actionname}
|
||||
</Typography>
|
||||
<div
|
||||
style={{
|
||||
@@ -2222,11 +2241,12 @@ const Action = memo((
|
||||
}}
|
||||
|
||||
onChange={(e) => {
|
||||
const newUrl = e.target.value;
|
||||
const newUrl = e.target.value
|
||||
setActionUrl(newUrl);
|
||||
const params = extractParamsFromText(newUrl);
|
||||
setRequestParams(params);
|
||||
|
||||
|
||||
|
||||
if (newUrl.startsWith("http://") || newUrl.startsWith("https://")) {
|
||||
try {
|
||||
const url = new URL(newUrl);
|
||||
@@ -2235,9 +2255,12 @@ const Action = memo((
|
||||
|
||||
setPath(newPath);
|
||||
} catch (error) {
|
||||
console.error("Invalid URL:", error);
|
||||
console.error("Invalid URL:", newUrl, error);
|
||||
toast("The URL is not a valid one. Please check and try again.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//toast("The URL needs to start with http:// or https://")
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2259,6 +2282,13 @@ const Action = memo((
|
||||
}}
|
||||
disabled={disableExecuteButton}
|
||||
onClick={() => {
|
||||
/*
|
||||
if (!firstSendDone) {
|
||||
setFirstSendDone(true)
|
||||
setCurTab(2)
|
||||
}
|
||||
*/
|
||||
|
||||
if (actionUrl.length === 0) {
|
||||
toast.error("URL cannot be empty");
|
||||
return;
|
||||
|
||||
@@ -198,7 +198,7 @@ export const findSpecificApp = (framework, inputcategory) => {
|
||||
id: "",
|
||||
}
|
||||
} else {
|
||||
console.log("findSpecificApp: unknown category: ", category)
|
||||
//console.log("findSpecificApp: unknown category: ", category)
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -116,7 +116,7 @@ const AppGrid = (props) => {
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
if (response.success === true) {
|
||||
if (response?.success === true) {
|
||||
setFormMessage(response.reason);
|
||||
//toast("Thanks for submitting!")
|
||||
} else {
|
||||
@@ -307,7 +307,7 @@ const AppGrid = (props) => {
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(responseJson => {
|
||||
if (responseJson.success) {
|
||||
if (responseJson?.success) {
|
||||
setUserdata(responseJson);
|
||||
setAllActivatedAppIds(responseJson.active_apps)
|
||||
setIsLoggedIn(true);
|
||||
@@ -350,7 +350,7 @@ const AppGrid = (props) => {
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
if (responseJson?.success === false) {
|
||||
toast.error(responseJson.reason);
|
||||
} else {
|
||||
//toast.success(`App ${type}d Successfully!`);
|
||||
@@ -414,11 +414,15 @@ const AppGrid = (props) => {
|
||||
scrollbarColor: "#494949 #2f2f2f",
|
||||
}}
|
||||
>
|
||||
{hits.map((data, index) => {
|
||||
{hits?.map((data, index) => {
|
||||
const appUrl =
|
||||
isCloud
|
||||
? `/apps/${data.objectID}?queryID=${data.__queryID}`
|
||||
: `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`;
|
||||
isCloud === true ?
|
||||
`/apps/${data.objectID}`
|
||||
: `apps/${data.objectID}`;
|
||||
|
||||
if (data.name === "" && data.id === "") {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom
|
||||
@@ -552,7 +556,7 @@ const AppGrid = (props) => {
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{data.tags.slice(0, 1).map((tag, tagIndex) => (
|
||||
{data?.tags?.slice(0, 1)?.map((tag, tagIndex) => (
|
||||
<span key={tagIndex}>
|
||||
{normalizedString(tag)}
|
||||
{tagIndex < 1 ? ", " : ""}
|
||||
@@ -565,7 +569,7 @@ const AppGrid = (props) => {
|
||||
) : (
|
||||
<div style={{ width: 230, textOverflow: "ellipsis", overflow: 'hidden', whiteSpace: 'nowrap', }}>
|
||||
{data.tags &&
|
||||
data.tags.map((tag, tagIndex) => (
|
||||
data?.tags?.map((tag, tagIndex) => (
|
||||
<span key={tagIndex}>
|
||||
{normalizedString(tag)}
|
||||
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
||||
@@ -756,7 +760,7 @@ const AppGrid = (props) => {
|
||||
};
|
||||
|
||||
const transformRefinementListItems = items =>
|
||||
items.map(item => ({
|
||||
items?.map(item => ({
|
||||
...item,
|
||||
label: item.label === 'true' ? 'App Editor' : 'Python',
|
||||
}));
|
||||
@@ -1099,7 +1103,7 @@ const AppGrid = (props) => {
|
||||
}
|
||||
});
|
||||
|
||||
const categoryArray = Object.keys(categoryCountMap).map((category) => ({
|
||||
const categoryArray = Object.keys(categoryCountMap)?.map((category) => ({
|
||||
category,
|
||||
count: categoryCountMap[category],
|
||||
}));
|
||||
@@ -1165,7 +1169,7 @@ const AppGrid = (props) => {
|
||||
{!isLoading && (
|
||||
<Collapse in={isCategoreListExpanded}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', width: '100%' }}>
|
||||
{topCategories.map((data, index) => (
|
||||
{topCategories?.map((data, index) => (
|
||||
<Button
|
||||
key={data.category}
|
||||
style={{
|
||||
@@ -1243,7 +1247,7 @@ const AppGrid = (props) => {
|
||||
});
|
||||
}
|
||||
|
||||
const tagArray = Object.keys(tagCountMap).map((tag) => ({
|
||||
const tagArray = Object.keys(tagCountMap)?.map((tag) => ({
|
||||
tag,
|
||||
count: tagCountMap[tag],
|
||||
}));
|
||||
@@ -1304,7 +1308,7 @@ const AppGrid = (props) => {
|
||||
</Button>
|
||||
<Collapse in={isActionLabelExpanded}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', width: '100%', }}>
|
||||
{topTags && topTags.length > 0 && topTags.map((data, index) => (
|
||||
{topTags && topTags.length > 0 && topTags?.map((data, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
onClick={() => handleCheckboxChange(index)}
|
||||
@@ -1415,7 +1419,7 @@ const AppGrid = (props) => {
|
||||
|
||||
<Collapse in={isCreatedWithExpanded}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', width: '100%' }}>
|
||||
{AppCreatedWithOptions.map((data, index) => (
|
||||
{AppCreatedWithOptions?.map((data, index) => (
|
||||
<Button
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
@@ -1685,7 +1689,7 @@ const AppGrid = (props) => {
|
||||
maxHeight: 570,
|
||||
}}
|
||||
>
|
||||
{filteredUserAppdata.map((data, index) => {
|
||||
{filteredUserAppdata?.map((data, index) => {
|
||||
const isMouseOverOnCloudIcon = false;
|
||||
const xs = 12;
|
||||
const rowHandler = 12;
|
||||
@@ -1732,13 +1736,13 @@ const AppGrid = (props) => {
|
||||
};
|
||||
|
||||
const appUrl =
|
||||
isCloud === true
|
||||
? `/apps/${data.id}`
|
||||
isCloud === true ?
|
||||
`/apps/${data.id}`
|
||||
: `https://shuffler.io/apps/${data.id}`;
|
||||
|
||||
if (data.name === "" && data.id === "") {
|
||||
return null
|
||||
}
|
||||
if (data.name === "" && data.id === "") {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom
|
||||
@@ -1843,8 +1847,8 @@ const AppGrid = (props) => {
|
||||
<div style={{minWidth: 120, overflow: "hidden", }}>
|
||||
{data.generated !== true ?
|
||||
<div>
|
||||
{data.tags &&
|
||||
data.tags.slice(0,2).map((tag, tagIndex) => (
|
||||
{data?.tags &&
|
||||
data?.tags?.slice(0,2)?.map((tag, tagIndex) => (
|
||||
<span key={tagIndex}>
|
||||
{normalizedString(tag)}
|
||||
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
||||
@@ -1884,7 +1888,7 @@ const AppGrid = (props) => {
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
if (responseJson?.success === false) {
|
||||
toast.error(responseJson.reason);
|
||||
} else {
|
||||
toast.success("App Deactivated Successfully. Reload UI to see updated changes.")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import {
|
||||
@@ -23,14 +23,14 @@ 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 { CloudDownloadOutlined, Delete } from '@mui/icons-material';
|
||||
import { findSpecificApp } from '../components/AppFramework.jsx';
|
||||
import theme from "../theme.jsx";
|
||||
import YAML from 'yaml';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
|
||||
|
||||
const [frameworkData, setFrameworkData] = useState({})
|
||||
const [userdata, setUserdata] = useState({})
|
||||
@@ -41,6 +41,8 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
const [latestUsecase, setLatestUsecase] = useState([])
|
||||
const [foundAppUsecase, setFoundAppUsecase] = useState({})
|
||||
const [usecaseLoading, setUsecaseLoading] = useState(false)
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
|
||||
const [sharingConfiguration, setSharingConfiguration] = React.useState("you");
|
||||
const navigate = useNavigate();
|
||||
const parseUsecase = (subcase) => {
|
||||
const srcdata = findSpecificApp(frameworkData, subcase.type)
|
||||
@@ -247,6 +249,11 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
}
|
||||
|
||||
const getUsecase = (subcase, index, subindex) => {
|
||||
//console.log("Skipping getUsecase")
|
||||
// FIXME: Skipping for now as this screws over a lot of the prioritization system
|
||||
// due to them having "looked at" the usecase.
|
||||
return
|
||||
|
||||
subcase = parseUsecase(subcase)
|
||||
setPrevSubcase(subcase)
|
||||
|
||||
@@ -294,8 +301,101 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
setUsecaseLoading(true)
|
||||
getAvailableWorkflows()
|
||||
getFramework()
|
||||
handleUpdateSharingConfiguration()
|
||||
}, [app])
|
||||
|
||||
const handleUpdateSharingConfiguration = useCallback(() => {
|
||||
if (app?.sharing === true) {
|
||||
setSharingConfiguration("public")
|
||||
}else {
|
||||
setSharingConfiguration("you")
|
||||
}
|
||||
|
||||
}, [app?.id])
|
||||
|
||||
const deleteApp = (appId) => {
|
||||
toast("Attempting to delete app");
|
||||
fetch(globalUrl + "/api/v1/apps/" + appId, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
toast("Successfully deleted app");
|
||||
setTimeout(() => {
|
||||
//delete apps from local storage
|
||||
localStorage.removeItem("apps");
|
||||
getApps();
|
||||
}, 1000);
|
||||
} else {
|
||||
toast("Failed deleting app. Does it still exist?");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const deleteModal = deleteModalOpen ? (
|
||||
<Dialog
|
||||
open={deleteModalOpen}
|
||||
onClose={() => {
|
||||
setDeleteModalOpen(false);
|
||||
}}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
||||
border: theme?.palette?.DialogStyle?.border,
|
||||
fontFamily: theme?.typography?.fontFamily,
|
||||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||
zIndex: 1000,
|
||||
minWidth: "500px",
|
||||
overflow: "hidden",
|
||||
'& .MuiDialogContent-root': {
|
||||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||
},
|
||||
'& .MuiDialogTitle-root': {
|
||||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||
},
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTitle>
|
||||
<div style={{ textAlign: "center", color: "rgba(255,255,255,0.9)" }}>
|
||||
Are you sure? <div />
|
||||
Some workflows may stop working.
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent
|
||||
style={{ color: "rgba(255,255,255,0.65)", textAlign: "center" }}
|
||||
>
|
||||
<Button
|
||||
style={{}}
|
||||
onClick={() => {
|
||||
deleteApp(app.id);
|
||||
setDeleteModalOpen(false);
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
style={{marginLeft: 5}}
|
||||
onClick={() => {
|
||||
setDeleteModalOpen(false);
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
No
|
||||
</Button>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : null;
|
||||
|
||||
|
||||
const downloadApp = (inputdata) => {
|
||||
const id = inputdata.id;
|
||||
@@ -382,7 +482,7 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io" || window.location.host === "localhost:3000"
|
||||
window.location.host === "shuffler.io"
|
||||
? true
|
||||
: false;
|
||||
|
||||
@@ -395,8 +495,10 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
}
|
||||
|
||||
|
||||
var canEditApp = userdata !== undefined && (userdata?.admin === "true" || userdata?.id === app?.owner || app?.owner === "" || (userdata?.admin === "true" && userdata?.active_org?.id === app?.reference_org)) || !app?.generated
|
||||
|
||||
var canEditApp = userdata?.support ||
|
||||
userdata?.id === app?.owner ||
|
||||
(userdata?.admin === "true" && userdata?.active_org?.id === app?.reference_org) ||
|
||||
app?.contributors?.includes(userdata?.id)
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
@@ -425,6 +527,7 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{deleteModal}
|
||||
<DialogTitle
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -437,15 +540,22 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
fontFamily: theme?.typography?.fontFamily
|
||||
}}
|
||||
>
|
||||
{/*
|
||||
<Typography component="div" sx={{ fontWeight: 500, color: "#F1F1F1", fontSize: "20px" }}>
|
||||
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)' }
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 10,
|
||||
right: 10,
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
@@ -457,7 +567,7 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
<div style={{ display: "flex", flexDirection: "row", gap: 10, fontFamily: theme?.typography?.fontFamily }}>
|
||||
<img
|
||||
alt={app?.name}
|
||||
src={app?.large_image || app?.image_url}
|
||||
src={app?.large_image || app?.image_url || "/images/no_image.png"}
|
||||
style={{
|
||||
borderRadius: 4,
|
||||
maxWidth: 100,
|
||||
@@ -474,12 +584,16 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<Typography variant="h5" component="div" sx={{ fontWeight: 600 }}>
|
||||
{newAppname}
|
||||
</Typography>
|
||||
<a href={isCloud ? "/apps/" + (app?.id || app?.objectID) : `https://shuffler.io/apps/${app?.objectID || app?.id}`} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "rgba(255,255,255,0.9)", }}>
|
||||
<Typography variant="h5" component="div" sx={{ fontWeight: 600 }}>
|
||||
{newAppname}
|
||||
</Typography>
|
||||
</a>
|
||||
<Link
|
||||
to={"/apps/" + (app?.id || app?.objectID)}
|
||||
to={isCloud ? `/apps/${app?.id || app?.objectID}` : `/apps/${app?.id || app?.published_id || app?.objectID}`}
|
||||
style={{ textDecoration: "none", color: "#f85a3e", marginTop: "-2px" }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<IconButton
|
||||
style={{
|
||||
@@ -536,6 +650,36 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{(userdata?.id === app?.owner)? (
|
||||
<Tooltip title={"Delete app (confirm box will show)"}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
sx={{
|
||||
bgcolor: '#494949',
|
||||
'&:hover': { bgcolor: '#494949', border: 'none' },
|
||||
textTransform: 'none',
|
||||
borderRadius: 1,
|
||||
minWidth: '45px',
|
||||
width: '45px',
|
||||
height: '40px',
|
||||
padding: 2,
|
||||
color: "#fff",
|
||||
fontFamily: theme?.typography?.fontFamily,
|
||||
border: 'none'
|
||||
}}
|
||||
onClick={() => {
|
||||
setDeleteModalOpen(true);
|
||||
}}
|
||||
disabled={(sharingConfiguration === undefined || sharingConfiguration === null || sharingConfiguration === "public") }
|
||||
>
|
||||
<Delete />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
): null}
|
||||
|
||||
{(canEditApp && app?.generated) && (
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
@@ -549,10 +693,7 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
color: "#fff",
|
||||
fontFamily: theme?.typography?.fontFamily
|
||||
}}
|
||||
startIcon={canEditApp ? <EditIcon /> :
|
||||
(app?.generated && app?.activated && userdata?.id !== app?.owner && isCloud ?
|
||||
<ForkRightIcon /> : null
|
||||
)}
|
||||
startIcon={canEditApp ? <EditIcon /> : <ForkRightIcon />}
|
||||
onClick={() => {
|
||||
if (canEditApp) {
|
||||
const editUrl = "/apps/edit/" + (app?.id || app?.objectID);
|
||||
@@ -563,8 +704,9 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{canEditApp ? "Edit" : "Fork"}
|
||||
</Button>
|
||||
{canEditApp ? "Edit" : "Fork"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
@@ -685,13 +827,13 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
marginBottom: "16px",
|
||||
fontWeight: 600
|
||||
}}>
|
||||
{
|
||||
{/*
|
||||
(foundAppUsecase?.srcapp !== undefined && foundAppUsecase?.dstapp !== undefined) ? (
|
||||
"Connect " + foundAppUsecase?.srcapp?.replaceAll("_", " ") + " to " + foundAppUsecase?.dstapp?.replaceAll("_", " ")
|
||||
) : (
|
||||
"Connect " + app?.name.replaceAll("_", " ") + " to any tool"
|
||||
)
|
||||
}
|
||||
*/}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -703,7 +845,7 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
alignItems: 'center',
|
||||
mb: 3
|
||||
}}>
|
||||
{usecaseLoading ? (
|
||||
{/*usecaseLoading ? (
|
||||
<Stack direction="row" spacing={2} alignItems="center" sx={{ width: '100%' }}>
|
||||
<Stack direction="row" spacing={-1}>
|
||||
<Skeleton variant="circular" width={32} height={32} />
|
||||
@@ -752,7 +894,7 @@ const AppModal = ({ open, onClose, app, globalUrl }) => {
|
||||
{foundAppUsecase?.name || "Search for a Usecase"}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
)*/}
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import theme from '../theme.jsx';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material';
|
||||
|
||||
//import algoliasearch from 'algoliasearch/lite';
|
||||
import algoliasearch from 'algoliasearch';
|
||||
import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom';
|
||||
import {
|
||||
Grid,
|
||||
Paper,
|
||||
TextField,
|
||||
InputAdornment,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
|
||||
const Appsearch = props => {
|
||||
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp, placeholder,
|
||||
|
||||
} = props
|
||||
|
||||
let navigate = useNavigate();
|
||||
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
|
||||
const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [value, setValue] = useState("");
|
||||
window.title = "Shuffle | Apps | Find and integration any app"
|
||||
|
||||
const useCloseOnBlur = (setOpen) => {
|
||||
useEffect(() => {
|
||||
// Add event listener to detect clicks outside of the search box
|
||||
const handleClickOutside = (event) => {
|
||||
// Check if the click is outside the search box
|
||||
if (!event.target.closest('.search-box')) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
|
||||
// Clean up event listener
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [setOpen]); // Ensure that this effect runs whenever setOpen changes
|
||||
};
|
||||
|
||||
useCloseOnBlur(setOpen);
|
||||
|
||||
const SearchBox = ({ currentRefinement, refine, isSearchStalled }) => {
|
||||
useEffect(() => {
|
||||
//console.log("FIRST LOAD ONLY? RUN REFINEMENT: !", currentRefinement)
|
||||
if (defaultSearch !== undefined && defaultSearch !== null) {
|
||||
refine(defaultSearch)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: isMobile ? "center" : "" }}>
|
||||
<form noValidate action="" role="search">
|
||||
<TextField
|
||||
sx={{
|
||||
'& input[type="search"]': {
|
||||
filter:
|
||||
'brightness(0) invert(1)',
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
variant="standard"
|
||||
style={{
|
||||
width: isMobile ? 295 : 402,
|
||||
borderRadius: 8,
|
||||
border: 0,
|
||||
boxShadow: 0,
|
||||
margin: 10,
|
||||
height: isMobile ? 51 : "",
|
||||
textAlign: "center",
|
||||
// border: "1px solid rgba(241.19, 241.19, 241.19, 0.10)",
|
||||
boxShadow: "none",
|
||||
backgroundColor: "rgba(241.19, 241.19, 241.19, 0.10)",
|
||||
fontWeight: 400,
|
||||
marginLeft: 10,
|
||||
zIndex: 110,
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
style: {
|
||||
fontSize: isMobile ? "0.8em" : "1em",
|
||||
height: 50,
|
||||
zIndex: 1100,
|
||||
paddingLeft: 15,
|
||||
},
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<div
|
||||
style={{
|
||||
width: isMobile ? 42 : 42,
|
||||
height: isMobile ? 36 : 36,
|
||||
background: "#806BFF",
|
||||
borderRadius: 8,
|
||||
marginRight: 10,
|
||||
}}
|
||||
>
|
||||
<SearchIcon
|
||||
onClick={() => {
|
||||
navigate("/search?q=" + currentRefinement, { state: value, replace: true })
|
||||
//navigate("/search?q="+currentRefinement, { state: value, replace: true })
|
||||
//window.open("/apps"+currentRefinement, "_blank")
|
||||
}}
|
||||
style={{ cursor: 'pointer', width: isMobile ? 20 : "", marginright: 5, marginTop: isMobile ? 6 : 7 }}
|
||||
/>
|
||||
</div>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
autoComplete="off"
|
||||
type="search"
|
||||
color="primary"
|
||||
placeholder={placeholder !== undefined ? placeholder : "Search more than 2500 Apps"}
|
||||
id="shuffle_search_field"
|
||||
onChange={(event) => {
|
||||
// Remove "q" from URL
|
||||
// removeQuery("q")
|
||||
refine(event.currentTarget.value)
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.keyCode === 13) {
|
||||
navigate("/search?q=" + currentRefinement, { state: value, replace: true });
|
||||
}
|
||||
}}
|
||||
onClick={(event) => {
|
||||
setOpen(true);
|
||||
}}
|
||||
/>
|
||||
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Hits = ({ hits, currentRefinement }) => {
|
||||
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
|
||||
var counted = 0
|
||||
|
||||
return (
|
||||
<Grid container spacing={0} style={{ display: "flex", alignItems: "center", justifyContent: "center", width: isMobile ? 302 : 402, maxHeight: 250, minHeight: 250, overflowY: "auto", overflowX: "hidden", }}>
|
||||
{hits.map((data, index) => {
|
||||
const paperStyle = {
|
||||
backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,1)" : "#38383A",
|
||||
color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)",
|
||||
// border: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e",
|
||||
textAlign: "left",
|
||||
padding: 10,
|
||||
cursor: "pointer",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
width: 402,
|
||||
minHeight: 37,
|
||||
maxHeight: 52,
|
||||
}
|
||||
|
||||
if (counted === 12 / xs * rowHandler) {
|
||||
return null
|
||||
}
|
||||
|
||||
counted += 1
|
||||
var parsedname = data.name.valueOf()
|
||||
parsedname = (parsedname.charAt(0).toUpperCase() + parsedname.substring(1)).replaceAll("_", " ")
|
||||
return (
|
||||
<Paper key={index} elevation={0} style={paperStyle} onMouseOver={() => {
|
||||
setMouseHoverIndex(index)
|
||||
}} onMouseOut={() => {
|
||||
setMouseHoverIndex(-1)
|
||||
}} onClick={() => {
|
||||
if (setNewSelectedApp !== undefined) {
|
||||
setNewSelectedApp(data.name)
|
||||
} else {
|
||||
//need to add perfect url which redirect direct to app page
|
||||
const newname = data.name.toLowerCase().replaceAll(" ", "_")
|
||||
window.open("/apps/" + newname, "_blank")
|
||||
}
|
||||
}}>
|
||||
<div style={{ display: "flex" }}>
|
||||
<img alt={data.name} src={data.image_url} style={{ width: "100%", maxWidth: 30, minWidth: 30, minHeight: 30, borderRadius: 40, maxHeight: 30, display: "block", }} />
|
||||
<Typography variant="body1" style={{ marginTop: 2, marginLeft: 10, }}>
|
||||
{parsedname}
|
||||
</Typography>
|
||||
</div>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const InputHits = ConfiguredHits === undefined ? Hits : ConfiguredHits
|
||||
const CustomSearchBox = connectSearchBox(SearchBox)
|
||||
const CustomHits = connectHits(InputHits)
|
||||
|
||||
return (
|
||||
<div className="search-box" style={{ width: isMobile ? null : "100%", height: 95, alignItems: "center", justifyContent: "center", gap: 138, zIndex: 11000, }}>
|
||||
<InstantSearch searchClient={searchClient} indexName="appsearch">
|
||||
<div style={{ maxWidth: 450, margin: "auto", }}>
|
||||
<CustomSearchBox />
|
||||
</div>
|
||||
<div style={{ alignItems: "center", justifyContent: "center", width: "100%", display: "flex" }}>
|
||||
{open ? <CustomHits hitsPerPage={1} /> : null}
|
||||
</div>
|
||||
</InstantSearch>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Appsearch;
|
||||
@@ -88,7 +88,7 @@ const AppSearchButtons = (props) => {
|
||||
|
||||
const foundApp = findSpecificApp(appFramework, appType)
|
||||
if (foundApp === undefined || foundApp === null) {
|
||||
console.log("AppSearchButtons: App not found in appFramework: " + appType)
|
||||
//console.log("AppSearchButtons: App not found in appFramework: " + appType)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ const AppSearchButtons = (props) => {
|
||||
|
||||
|
||||
return (
|
||||
<Grid item xs={xsValue} style={{ alignItems: "center", marginTop: 5, }}
|
||||
<Grid item xs={xsValue} style={{ alignItems: "center", marginTop: 5, maxWidth: "50%", minWidth: "50%" }}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import classNames from "classnames";
|
||||
import theme from '../theme.jsx';
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TextField,
|
||||
IconButton,
|
||||
Button,
|
||||
Typography,
|
||||
Grid,
|
||||
Paper,
|
||||
Chip,
|
||||
Checkbox,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
BarChart,
|
||||
RadialBarChart,
|
||||
RadialAreaChart,
|
||||
RadialAxis,
|
||||
StackedBarSeries,
|
||||
TooltipArea,
|
||||
ChartTooltip,
|
||||
TooltipTemplate,
|
||||
RadialAreaSeries,
|
||||
RadialPointSeries,
|
||||
RadialArea,
|
||||
RadialLine,
|
||||
TreeMap,
|
||||
TreeMapSeries,
|
||||
TreeMapLabel,
|
||||
TreeMapRect,
|
||||
Line,
|
||||
LineChart,
|
||||
LineSeries,
|
||||
LinearYAxis,
|
||||
LinearXAxis,
|
||||
LinearYAxisTickSeries,
|
||||
LinearXAxisTickSeries,
|
||||
Area,
|
||||
AreaChart,
|
||||
AreaSeries,
|
||||
AreaSparklineChart,
|
||||
PointSeries,
|
||||
GridlineSeries,
|
||||
Gridline,
|
||||
Stripes,
|
||||
Gradient,
|
||||
GradientStop,
|
||||
LinearXAxisTickLabel,
|
||||
} from 'reaviz';
|
||||
|
||||
const inputdata = {
|
||||
"data": [{
|
||||
"key": "Intel",
|
||||
"data": [
|
||||
{ key: new Date('11/22/2019'), data: 3, metadata: {color: "green", "name": "Intel"}},
|
||||
{ key: new Date('11/24/2019'), data: 8, metadata: {color: "green", "name": "Intel"}},
|
||||
{ key: new Date('11/29/2019'), data: 2, metadata: {color: "green", "name": "Intel"}},
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "Popper",
|
||||
"data": [
|
||||
{ key: new Date('11/24/2019'), data: 9, metadata: {color: "red", "name": "Popper"}},
|
||||
{ key: new Date('11/29/2019'), data: 3, metadata: {color: "red", "name": "Popper"}},
|
||||
]
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
const LineChartWrapper = ({keys, inputname, height, width}) => {
|
||||
const [hovered, setHovered] = useState("");
|
||||
|
||||
//console.log("Date: ", new Date("2019-11-14T08:00:00.000Z"))
|
||||
//var inputdata = keys.data
|
||||
//const inputdata = keys.data === undefined ? [{"key": inputname, "data": keys}] : keys.data
|
||||
const inputdata = keys.data === undefined ? keys : keys.data
|
||||
|
||||
/*
|
||||
series={
|
||||
<AreaSeries
|
||||
symbols={
|
||||
<PointSeries show={false} />
|
||||
}
|
||||
area={
|
||||
<Area
|
||||
mask={<Stripes />}
|
||||
gradient={
|
||||
<Gradient
|
||||
stops={[
|
||||
<GradientStop offset="10%" stopOpacity={0} />,
|
||||
<GradientStop offset="80%" stopOpacity={1} />
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
gridlines={<GridlineSeries line={<Gridline direction="x" />} />}
|
||||
colorScheme={(colorInput) => {
|
||||
var color = "#f86a3e"
|
||||
//if (colorInput !== undefined && colorInput.length > 0) {
|
||||
// color = colorInput[0].metadata !== undefined && colorInput[0].metadata.color !== undefined ? colorInput[0].metadata.color : color
|
||||
//}
|
||||
|
||||
return color
|
||||
}}
|
||||
/>
|
||||
}
|
||||
*/
|
||||
|
||||
return (
|
||||
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, padding: 30, marginTop: 15, }}>
|
||||
<Typography variant="h4" style={{marginBotton: 15, }}>
|
||||
{inputname}
|
||||
</Typography>
|
||||
<BarChart
|
||||
width={"100%"}
|
||||
height={height}
|
||||
data={inputdata}
|
||||
gridlines={
|
||||
<GridlineSeries line={<Gridline direction="all" />} />
|
||||
}
|
||||
/>
|
||||
{/*
|
||||
<AreaSparklineChart
|
||||
style={{marginTop: 15, color: "white",}}
|
||||
height={height}
|
||||
width={width}
|
||||
data={inputdata}
|
||||
tooltip={
|
||||
<Tooltip
|
||||
tooltip={
|
||||
<ChartTooltip
|
||||
color={"#ffffff"}
|
||||
followCursor={true}
|
||||
modifiers={{
|
||||
offset: '5px, 5px'
|
||||
}}
|
||||
content={(data, color) => (
|
||||
<TooltipTemplate
|
||||
color={"#ffffff"}
|
||||
value={{
|
||||
x: data.x,
|
||||
y: data.y,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const AppStats = (defaultprops) => {
|
||||
const { globalUrl, appId , workflowId} = defaultprops;
|
||||
const [keys, setKeys] = useState([])
|
||||
const [widgetData, setWidgetData] = useState({});
|
||||
const [searches, setSearches] = useState([]);
|
||||
const [clickData, setClickData] = useState(undefined);
|
||||
const [conversionData, setConversionData] = useState(undefined);
|
||||
|
||||
const handleDataSetting = (inputdata, grouping) => {
|
||||
var newlist = []
|
||||
|
||||
for (var key in inputdata.events) {
|
||||
var newlist = []
|
||||
|
||||
for (var subkey in inputdata.events[key].data) {
|
||||
const subdata = inputdata.events[key].data[subkey]
|
||||
//console.log("Timestamp: ", subdata.key)
|
||||
|
||||
if (grouping === "day") {
|
||||
const daysplit = subdata.key.split("T")[0]
|
||||
//console.log("Grouping by day: ", daysplit)
|
||||
|
||||
const foundIndex = newlist.findIndex(data => data.key === daysplit)
|
||||
if (foundIndex !== undefined && foundIndex !== null && foundIndex >= 0) {
|
||||
newlist[foundIndex].data += 1
|
||||
newlist[foundIndex].y += 1
|
||||
} else {
|
||||
newlist.push({
|
||||
"key": daysplit,
|
||||
"x": daysplit,
|
||||
"data": 1,
|
||||
"y": 1,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.log("No grouping set?")
|
||||
try {
|
||||
inputdata.events[key].data[subkey].key = new Date(subdata.key)
|
||||
} catch (e) {
|
||||
console.log("Failed timestamp: ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fixing timestamps after sorting based on day
|
||||
for (var subkey in newlist) {
|
||||
const subdata = newlist[subkey]
|
||||
newlist[subkey].key = new Date(subdata.key)
|
||||
}
|
||||
|
||||
console.log("Inputdata: ", inputdata.events[key])
|
||||
if (inputdata.events[key].key === "click") {
|
||||
setClickData(newlist)
|
||||
} else if (inputdata.events[key].key === "conversion") {
|
||||
setConversionData(newlist)
|
||||
} else {
|
||||
console.log("No handler for ", inputdata.events[key].key)
|
||||
}
|
||||
}
|
||||
|
||||
//new Date('11/22/2019')
|
||||
setWidgetData(inputdata)
|
||||
}
|
||||
|
||||
const getAppStats = (appId) => {
|
||||
fetch(`${globalUrl}/api/v1/apps/${appId}/stats`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!: ", response.status);
|
||||
return;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
return
|
||||
}
|
||||
|
||||
handleDataSetting(responseJson, "day")
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("error: ", error)
|
||||
});
|
||||
}
|
||||
|
||||
const getWorkflowStats = (workflowId) => {
|
||||
fetch(`${globalUrl}/api/v1/workflow/${workflowId}/stats`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!: ", response.status);
|
||||
return;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
return
|
||||
}
|
||||
|
||||
handleDataSetting(responseJson, "day")
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("error: ", error)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
//setWidgetData(inputdata)
|
||||
getAppStats(appId)
|
||||
getWorkflowStats(workflowId)
|
||||
}, [])
|
||||
|
||||
const paperStyle = {
|
||||
textAlign: "center",
|
||||
padding: 40,
|
||||
margin: 5,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}
|
||||
|
||||
console.log("Widget: ", widgetData)
|
||||
const data = (
|
||||
<div className="content" style={{width: "100%", margin: "auto", paddingBottom: 200, textAlign: "center",}}>
|
||||
<div style={{display: "flex", margin: "auto", }}>
|
||||
<Paper style={paperStyle}>
|
||||
<Typography variant="h4">
|
||||
{widgetData.orgs}
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
Orgs
|
||||
</Typography>
|
||||
</Paper>
|
||||
<Paper style={paperStyle}>
|
||||
<Typography variant="h4">
|
||||
{widgetData.searches}
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
Searches
|
||||
</Typography>
|
||||
</Paper>
|
||||
{/*
|
||||
<Paper style={paperStyle}>
|
||||
<Typography variant="h4">
|
||||
{widgetData.clicks}
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
Clicks
|
||||
</Typography>
|
||||
</Paper>
|
||||
<Paper style={paperStyle}>
|
||||
<Typography variant="h4">
|
||||
{widgetData.conversions}
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
Conversions
|
||||
</Typography>
|
||||
</Paper>
|
||||
<Paper style={paperStyle}>
|
||||
<Typography variant="h4">
|
||||
{widgetData.forks}
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
Forks
|
||||
</Typography>
|
||||
</Paper>
|
||||
*/}
|
||||
</div>
|
||||
|
||||
{clickData === undefined || clickData === null || clickData?.length === 0 ?
|
||||
null
|
||||
:
|
||||
<LineChartWrapper keys={clickData} height={300} width={"100%"} inputname={"Clicks"}/>
|
||||
}
|
||||
|
||||
<div style={{marginTop: 25, }} />
|
||||
{conversionData === undefined || conversionData === null || conversionData?.length === 0 ?
|
||||
null
|
||||
:
|
||||
<LineChartWrapper keys={conversionData} height={300} width={"100%"} inputname={"Conversions"}/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
||||
const dataWrapper = (
|
||||
<div style={{ maxWidth: 1366, margin: "auto" }}>{data}</div>
|
||||
);
|
||||
|
||||
return dataWrapper;
|
||||
}
|
||||
|
||||
export default AppStats;
|
||||
@@ -50,6 +50,9 @@ const Appsearch = props => {
|
||||
return (
|
||||
<form noValidate action="" role="search">
|
||||
<TextField
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
autocomplete="off"
|
||||
fullWidth
|
||||
style={{backgroundColor: "#2F2F2F", borderRadius: borderRadius, width: "100%",}}
|
||||
InputProps={{
|
||||
|
||||
@@ -215,8 +215,8 @@ const Billing = memo((props) => {
|
||||
|
||||
var priceItem = "price_1MROFrDzMUgUjxHShcSxgHO1"
|
||||
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
|
||||
const failUrl = `${window.location.origin}/admin?admin_tab=billing&payment=failure`
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=success`
|
||||
const failUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=failure`
|
||||
var checkoutObject = {
|
||||
lineItems: [
|
||||
{
|
||||
@@ -2361,7 +2361,8 @@ const Billing = memo((props) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginTop: 40, marginLeft: 10 }}>
|
||||
{isCloud ? (
|
||||
<div style={{ marginTop: 40, marginLeft: 10 }}>
|
||||
<Typography
|
||||
style={{ marginBottom: 5, fontSize: 24, fontWeight: "bold" }}
|
||||
>
|
||||
@@ -2545,6 +2546,7 @@ const Billing = memo((props) => {
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
): null}
|
||||
</div>
|
||||
<div style={{ marginTop: 40, display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography
|
||||
|
||||
@@ -20,11 +20,18 @@ import {
|
||||
DialogTitle,
|
||||
DialogActions,
|
||||
Skeleton,
|
||||
Chip,
|
||||
Checkbox,
|
||||
MenuItem,
|
||||
DialogContent,
|
||||
FormControl,
|
||||
Select,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
Link as LinkIcon,
|
||||
AutoFixHigh as AutoFixHighIcon,
|
||||
AutoFixNormal as AutoFixNormalIcon,
|
||||
Edit as EditIcon,
|
||||
FileCopy as FileCopyIcon,
|
||||
SelectAll as SelectAllIcon,
|
||||
@@ -46,6 +53,8 @@ import {
|
||||
Business as BusinessIcon,
|
||||
Visibility as VisibilityIcon,
|
||||
VisibilityOff as VisibilityOffIcon,
|
||||
Clear as ClearIcon,
|
||||
Add as AddIcon,
|
||||
} from "@mui/icons-material";
|
||||
import { validateJson, } from "../views/Workflows.jsx";
|
||||
import { Context } from "../context/ContextApi.jsx";
|
||||
@@ -68,7 +77,7 @@ const scrollStyle2 = {
|
||||
|
||||
|
||||
const CacheView = memo((props) => {
|
||||
const { globalUrl, userdata, serverside, orgId, isSelectedDataStore } = props;
|
||||
const { globalUrl, userdata, serverside, orgId, isSelectedDataStore, selectedOrganization } = props;
|
||||
const [orgCache, setOrgCache] = React.useState("");
|
||||
const [listCache, setListCache] = React.useState([]);
|
||||
const [addCache, setAddCache] = React.useState("");
|
||||
@@ -82,14 +91,43 @@ const CacheView = memo((props) => {
|
||||
const [editCache, setEditCache] = React.useState(false);
|
||||
const [cachedLoaded, setCachedLoaded] = React.useState(false);
|
||||
const [show, setShow] = useState({});
|
||||
useEffect(() => {
|
||||
if(orgId?.length >0){
|
||||
listOrgCache(orgId);
|
||||
}
|
||||
}, [orgId]);
|
||||
const [showDistributionPopup, setShowDistributionPopup] = useState(false);
|
||||
const [selectedSubOrg, setSelectedSubOrg] = useState([]);
|
||||
const [selectedCacheKey, setSelectedCacheKey] = useState("");
|
||||
|
||||
const listOrgCache = (orgId) => {
|
||||
fetch(globalUrl + `/api/v1/orgs/${orgId}/list_cache`, {
|
||||
// Direct category migration from ../components/Files.jsx
|
||||
const [selectAllChecked, setSelectAllChecked] = React.useState(false)
|
||||
const [renderTextBox, setRenderTextBox] = React.useState(false);
|
||||
const [fileCategories, setFileCategories] = React.useState(["default"]);
|
||||
const [selectedCategory, setSelectedCategory] = React.useState("default");
|
||||
const [selectedFileId, setSelectedFileId] = React.useState("");
|
||||
const [updateToThisCategory, setUpdateToThisCategory] = useState("")
|
||||
const [showFileCategoryPopup, setShowFileCategoryPopup] = React.useState(false);
|
||||
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (orgId?.length > 0) {
|
||||
listOrgCache(orgId, selectedCategory)
|
||||
}
|
||||
}, [orgId])
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
fileCategories.push(event.target.value);
|
||||
setSelectedCategory(event.target.value);
|
||||
setRenderTextBox(false);
|
||||
}
|
||||
|
||||
if (event.key === 'Escape'){ // not working for some reasons
|
||||
console.log('escape pressed')
|
||||
setRenderTextBox(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const listOrgCache = (orgId, category) => {
|
||||
const url = `${globalUrl}/api/v1/orgs/${orgId}/list_cache${category !== undefined ? `?category=${category.replaceAll(" ", "_")}` : ""}`
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -109,6 +147,19 @@ const CacheView = memo((props) => {
|
||||
if (responseJson.success === true) {
|
||||
setListCache(responseJson.keys);
|
||||
setCachedLoaded(true);
|
||||
|
||||
if (fileCategories.length === 1 && fileCategories[0] === "default") {
|
||||
var newcategories = ["default"]
|
||||
for (var key in responseJson.keys) {
|
||||
if (responseJson.keys[key].category !== undefined && responseJson.keys[key].category !== null && responseJson.keys[key].category !== "" && !fileCategories.includes(responseJson.keys[key].category)) {
|
||||
newcategories.push(responseJson.keys[key].category);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("CATEGORIES: ", newcategories)
|
||||
|
||||
setFileCategories(newcategories)
|
||||
}
|
||||
}
|
||||
|
||||
if (responseJson.cursor !== undefined && responseJson.cursor !== null && responseJson.cursor !== "") {
|
||||
@@ -122,15 +173,12 @@ const CacheView = memo((props) => {
|
||||
|
||||
|
||||
const deleteCache = (orgId, key) => {
|
||||
//toast("Attempting to delete Cache");
|
||||
|
||||
// method: "DELETE",
|
||||
const method = "POST"
|
||||
//const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/${key}`
|
||||
const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache`
|
||||
const parsed = {
|
||||
"org_id": orgId,
|
||||
"key": key,
|
||||
"category": selectedCategory === "" || selectedCategory === "default" ? "" : selectedCategory,
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
@@ -145,7 +193,7 @@ const CacheView = memo((props) => {
|
||||
if (response.status === 200) {
|
||||
toast("Successfully deleted Cache");
|
||||
setTimeout(() => {
|
||||
listOrgCache(orgId);
|
||||
listOrgCache(orgId, selectedCategory)
|
||||
}, 1000);
|
||||
} else {
|
||||
toast("Failed deleting Cache. Does it still exist?");
|
||||
@@ -157,7 +205,12 @@ const CacheView = memo((props) => {
|
||||
};
|
||||
|
||||
const editOrgCache = (orgId) => {
|
||||
const cache = { key: dataValue.key , value: value };
|
||||
const cache = {
|
||||
key: dataValue.key,
|
||||
value: value,
|
||||
category: selectedCategory,
|
||||
}
|
||||
|
||||
setCacheInput([cache]);
|
||||
|
||||
fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, {
|
||||
@@ -181,7 +234,7 @@ const CacheView = memo((props) => {
|
||||
.then((responseJson) => {
|
||||
setAddCache(responseJson);
|
||||
toast("Cache Edited Successfully!");
|
||||
listOrgCache(orgId);
|
||||
listOrgCache(orgId, selectedCategory);
|
||||
setModalOpen(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -190,9 +243,13 @@ const CacheView = memo((props) => {
|
||||
};
|
||||
|
||||
const addOrgCache = (orgId) => {
|
||||
const cache = { key: key, value: value };
|
||||
const cache = {
|
||||
key: key,
|
||||
value: value,
|
||||
category: selectedCategory,
|
||||
}
|
||||
|
||||
setCacheInput([cache]);
|
||||
console.log("cache input:", cacheInput)
|
||||
|
||||
fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, {
|
||||
|
||||
@@ -214,8 +271,8 @@ const CacheView = memo((props) => {
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setAddCache(responseJson);
|
||||
toast("New Cache Added Successfully!");
|
||||
listOrgCache(orgId);
|
||||
toast("New key added Successfully!");
|
||||
listOrgCache(orgId, selectedCategory);
|
||||
setModalOpen(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -233,7 +290,9 @@ const CacheView = memo((props) => {
|
||||
setValue(JSON.stringify(parsedjson, null, 2))
|
||||
} catch (e) {
|
||||
console.log("Error parsing JSON: ", e)
|
||||
//return JSON.stringify(inputvalue);
|
||||
toast.info("Invalid JSON.", {
|
||||
autoClose: 1500,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,13 +355,14 @@ const CacheView = memo((props) => {
|
||||
>
|
||||
<DialogTitle>
|
||||
<span style={{ color: "white" }}>
|
||||
{ editCache ? "Edit Cache" : "Add Cache" }
|
||||
{ editCache ? "Edit Key" : "Add Key"}{selectedCategory === "" || selectedCategory === "default" ? "" : ` in category '${selectedCategory}'`}
|
||||
</span>
|
||||
</DialogTitle>
|
||||
<div style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: "#212121", }}>
|
||||
Key
|
||||
<TextField
|
||||
color="primary"
|
||||
disabled={editCache}
|
||||
style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor }}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
@@ -335,7 +395,7 @@ const CacheView = memo((props) => {
|
||||
autoFixJson(value)
|
||||
}}
|
||||
>
|
||||
<AutoFixHighIcon />
|
||||
<AutoFixNormalIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -368,6 +428,7 @@ const CacheView = memo((props) => {
|
||||
style={{ borderRadius: "2px", fontSize: 16, color: "#ff8544", textTransform:"none" }}
|
||||
onClick={() => {
|
||||
setModalOpen(false)
|
||||
setKey("")
|
||||
setValue("")
|
||||
setDataValue({})
|
||||
}}
|
||||
@@ -379,8 +440,12 @@ const CacheView = memo((props) => {
|
||||
variant="contained"
|
||||
style={{ borderRadius: "2px", backgroundColor: "#ff8544",color: "#1a1a1a", textTransform:"none" }}
|
||||
onClick={() => {
|
||||
if (value === "") {
|
||||
toast("Key or Value can not be empty");
|
||||
return;
|
||||
}
|
||||
{editCache ? editOrgCache(orgId) : addOrgCache(orgId)}
|
||||
|
||||
setKey("")
|
||||
setValue("")
|
||||
setDataValue({})
|
||||
}}
|
||||
@@ -392,13 +457,182 @@ const CacheView = memo((props) => {
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
const handleSelectSubOrg = (id, action) => {
|
||||
if (action === "all") {
|
||||
const childOrgs = userdata.orgs.filter(
|
||||
(data) => data.creator_org === userdata.active_org.id
|
||||
);
|
||||
setSelectedSubOrg((prev) => {
|
||||
if (prev.length === childOrgs.length) {
|
||||
// If all child orgs are already selected, clear the selection
|
||||
return [];
|
||||
} else {
|
||||
// Otherwise, select all child org IDs
|
||||
return childOrgs.map((data) => data.id);
|
||||
}
|
||||
});
|
||||
} else if (action === "none") {
|
||||
setSelectedSubOrg([]);
|
||||
} else {
|
||||
setSelectedSubOrg((prev) => {
|
||||
if (prev.includes(id)) {
|
||||
return prev.filter((data) => data !== id);
|
||||
} else {
|
||||
return [...prev, id];
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const changeDistribution = (id, selectedSubOrg) => {
|
||||
|
||||
editFileConfig(id, [...new Set(selectedSubOrg)], selectedCategory)
|
||||
}
|
||||
|
||||
|
||||
const editFileConfig = (id, selectedSubOrg, category) => {
|
||||
const data = {
|
||||
Key: id,
|
||||
action: "suborg_distribute",
|
||||
selected_suborgs: selectedSubOrg,
|
||||
category: category === undefined || category === "" || category === "default" ? "" : category,
|
||||
}
|
||||
|
||||
console.log("data: ", data);
|
||||
|
||||
const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/config`;
|
||||
|
||||
fetch(url, {
|
||||
mode: "cors",
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
crossDomain: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
})
|
||||
.then((response) =>
|
||||
response.json().then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
toast("Failed overwriting datastore");
|
||||
} else {
|
||||
toast("Successfully updated datastore!");
|
||||
setTimeout(() => {
|
||||
listOrgCache(orgId, selectedCategory);
|
||||
setShowDistributionPopup(false);
|
||||
}, 1000);
|
||||
}
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
toast("Err: " + error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const cacheDistributionModal = showDistributionPopup ? (
|
||||
<Dialog
|
||||
open={showDistributionPopup}
|
||||
onClose={() => setShowDistributionPopup(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
||||
border: theme?.palette?.DialogStyle?.border,
|
||||
fontFamily: theme?.typography?.fontFamily,
|
||||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||
zIndex: 1000,
|
||||
minWidth: "600px",
|
||||
minHeight: "320px",
|
||||
overflow: "auto",
|
||||
'& .MuiDialogContent-root': {
|
||||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||
},
|
||||
'& .MuiDialogTitle-root': {
|
||||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||
},
|
||||
'& .MuiDialogActions-root': {
|
||||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>
|
||||
<div style={{ color: "rgba(255,255,255,0.9)" }}>
|
||||
Select sub-org to distribute Datastore key
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
|
||||
<MenuItem value="none" onClick={()=> {handleSelectSubOrg(null, "none")}}>None</MenuItem>
|
||||
<MenuItem value="all" onClick={()=> {handleSelectSubOrg(null, "all")}}>All</MenuItem>
|
||||
{userdata.orgs.map((data, index) => {
|
||||
if (data.creator_org !== userdata.active_org.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const imagesize = 22;
|
||||
const imageStyle = {
|
||||
width: imagesize,
|
||||
height: imagesize,
|
||||
pointerEvents: "none",
|
||||
marginRight: 10,
|
||||
marginLeft: data.id === userdata.active_org.id ? 0 : 20,
|
||||
};
|
||||
|
||||
const image = data.image === "" ? (
|
||||
<img alt={data.name} src={theme.palette.defaultImage} style={imageStyle} />
|
||||
) : (
|
||||
<img alt={data.name} src={data.image} style={imageStyle} />
|
||||
);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
value={data.id}
|
||||
onClick={() => handleSelectSubOrg(data.id)}
|
||||
style={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<Checkbox
|
||||
checked={selectedSubOrg.includes(data.id)}
|
||||
/>
|
||||
{image}
|
||||
<span style={{ marginLeft: 8 }}>{data.name}</span>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
|
||||
<div style={{ display: "flex", marginTop: 20 }}>
|
||||
<Button
|
||||
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#ff8544" }}
|
||||
onClick={() => setShowDistributionPopup(false)}
|
||||
color="primary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#1a1a1a", backgroundColor: "#ff8544", marginLeft: 10 }}
|
||||
onClick={() => {
|
||||
changeDistribution(selectedCacheKey, selectedSubOrg);
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : null;
|
||||
|
||||
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" }}>
|
||||
{modalView}
|
||||
{cacheDistributionModal}
|
||||
<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 {selectedCategory === "" || selectedCategory === "default" ? "" : `- Category '${selectedCategory}'`}</h2>
|
||||
<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.
|
||||
<a
|
||||
@@ -422,16 +656,181 @@ const CacheView = memo((props) => {
|
||||
setValue("")
|
||||
}}
|
||||
>
|
||||
Add Cache
|
||||
Add Key
|
||||
</Button>
|
||||
<Button
|
||||
style={{ marginLeft: 16, marginRight: 15, backgroundColor: isSelectedDataStore?"#2F2F2F":null, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null,borderRadius:isSelectedDataStore?4:null, width:isSelectedDataStore?81:null, height:isSelectedDataStore?40:null, }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => listOrgCache(orgId)}
|
||||
onClick={() => listOrgCache(orgId,selectedCategory)}
|
||||
>
|
||||
<CachedIcon />
|
||||
</Button>
|
||||
|
||||
{fileCategories !== undefined &&
|
||||
fileCategories !== null &&
|
||||
fileCategories.length > 1 ? (
|
||||
<FormControl style={{ minWidth: 150, maxWidth: 150 }}>
|
||||
<Select
|
||||
labelId="input-namespace-select-label"
|
||||
id="input-namespace-select-id"
|
||||
style={{
|
||||
color: "white",
|
||||
minWidth: 122,
|
||||
maxWidth: 122,
|
||||
height: 35,
|
||||
float: "right",
|
||||
position: 'relative',
|
||||
top: 8
|
||||
}}
|
||||
value={selectedCategory}
|
||||
onChange={(event) => {
|
||||
//if (selectAllChecked || listCache.length > 0) {
|
||||
if (selectAllChecked || selectedFiles.length > 0) {
|
||||
setUpdateToThisCategory(event.target.value)
|
||||
setShowFileCategoryPopup(true)
|
||||
return
|
||||
}
|
||||
|
||||
setSelectedCategory(event.target.value)
|
||||
if (event.target.value === "all" || event.target.value === "default") {
|
||||
listOrgCache(orgId)
|
||||
} else {
|
||||
listOrgCache(orgId, event.target.value)
|
||||
}
|
||||
|
||||
|
||||
// Add it to the url as a query
|
||||
if (window.location.search.includes("category=")) {
|
||||
const newurl = window.location.href.replace(/category=[^&]+/, `category=${event.target.value}`)
|
||||
window.history.pushState({ path: newurl }, "", newurl)
|
||||
} else {
|
||||
window.history.pushState({ path: window.location.href }, "", `${window.location.href}&category=${event.target.value}`)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{fileCategories.map((data, index) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
value={data}
|
||||
style={{ color: "white" }}
|
||||
>
|
||||
{data.replaceAll("_", " ")}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<Dialog
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
||||
border: theme?.palette?.DialogStyle?.border,
|
||||
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,
|
||||
},
|
||||
},
|
||||
}}
|
||||
open={showFileCategoryPopup}
|
||||
onClose={() => {
|
||||
setShowFileCategoryPopup(false)
|
||||
}}
|
||||
>
|
||||
<DialogTitle>File Categories</DialogTitle>
|
||||
<DialogContent>
|
||||
Please note that your selected files ({selectedFileId?.length}) will be moved to the <kbd>{updateToThisCategory}</kbd> category.
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setShowFileCategoryPopup(false)
|
||||
}}
|
||||
style={{fontSize: 16, textTransform: 'none'}}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
//handleUpdateFileCategory(updateToThisCategory)
|
||||
toast.error("Not implemented.")
|
||||
}}
|
||||
style={{fontSize: 16, textTransform: 'none', color: "#1a1a1a", backgroundColor: "#ff8544"}}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
<div style={{display: "inline-flex", position:"relative", top: 8}}>
|
||||
{renderTextBox ?
|
||||
<Tooltip title={"Close"} style={{}} aria-label={""}>
|
||||
<Button
|
||||
style={{ marginLeft: 5, marginRight: 15, height: 35, borderRadius: 4, backgroundColor: "#494949", textTransform: 'none', fontSize: 16, color: "#f1f1f1" }}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setRenderTextBox(false);
|
||||
console.log(" close clicked")
|
||||
}}
|
||||
>
|
||||
<ClearIcon/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
:
|
||||
<Tooltip title={"Add new file category"} style={{}} aria-label={""}>
|
||||
<Button
|
||||
style={{ marginLeft: 5, marginRight: 15, width: 169, height: 35, borderRadius: 4, backgroundColor: "#494949", textTransform: 'none', fontSize: 16, color: "#f1f1f1" }}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setRenderTextBox(true);
|
||||
}}
|
||||
>
|
||||
<AddIcon/>
|
||||
Category (beta)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
{renderTextBox && <TextField
|
||||
onKeyPress={(event)=>{
|
||||
handleKeyDown(event);
|
||||
if(event.key === 'Enter' && selectedFileId.length > 0){
|
||||
//setShowFileCategoryPopup(true)
|
||||
setUpdateToThisCategory(event.target.value)
|
||||
}
|
||||
|
||||
}}
|
||||
style={{
|
||||
height: 35,
|
||||
width: 200,
|
||||
marginTop: 0,
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
height: 35,
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
paddingTop: 0,
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
placeholder="Category name"
|
||||
required
|
||||
margin="dense"
|
||||
defaultValue={""}
|
||||
autoFocus
|
||||
/>}</div>
|
||||
{isSelectedDataStore? null :<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
@@ -459,7 +858,7 @@ const CacheView = memo((props) => {
|
||||
overflowX: "auto",
|
||||
}}>
|
||||
<ListItem style={{width: isSelectedDataStore?"100%":null, borderBottom:isSelectedDataStore?"1px solid #494949":null, display: "table-row"}}>
|
||||
{["Key", "Value", "Actions", "Updated"].map((header, index) => (
|
||||
{["Key", "Value", "Actions", "Updated", "Distribution"].map((header, index) => (
|
||||
<ListItemText
|
||||
key={index}
|
||||
primary={header}
|
||||
@@ -483,7 +882,7 @@ const CacheView = memo((props) => {
|
||||
backgroundColor: "#212121",
|
||||
}}
|
||||
>
|
||||
{Array(4)
|
||||
{Array(5)
|
||||
.fill()
|
||||
.map((_, colIndex) => (
|
||||
<ListItemText
|
||||
@@ -507,16 +906,39 @@ const CacheView = memo((props) => {
|
||||
</ListItem>
|
||||
))
|
||||
: listCache?.length === 0 ? (
|
||||
<Typography style={{ textAlign: "center", marginTop: 20, marginBottom: 20, minWidth: 1000, }}>
|
||||
No Keys Found
|
||||
</Typography>
|
||||
<ListItem style={{ display: "table-row" }}>
|
||||
{Array(5).fill().map((_, index) => (
|
||||
<ListItemText
|
||||
key={index}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: "8px",
|
||||
textAlign: index === 0 ? "center" : "left",
|
||||
}}
|
||||
primary={index === 2 ? "No key found." : null}
|
||||
colSpan={index === 0 ? 5 : undefined}
|
||||
/>
|
||||
))}
|
||||
</ListItem>
|
||||
|
||||
): listCache?.map((data, index) => {
|
||||
var category = selectedCategory
|
||||
if (selectedCategory === "default") {
|
||||
category = ""
|
||||
}
|
||||
|
||||
if (data?.category === undefined && category === "") {
|
||||
} else if (data?.category !== category) {
|
||||
return null
|
||||
}
|
||||
|
||||
var bgColor = isSelectedDataStore? "#212121":"#27292d";
|
||||
if (index % 2 === 0) {
|
||||
bgColor = isSelectedDataStore? "#1A1A1A":"#1f2023";
|
||||
}
|
||||
|
||||
const validate = validateJson(data.value);
|
||||
const isDistributed = data?.suborg_distribution?.length > 0 ? true : false;
|
||||
return (
|
||||
<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
|
||||
@@ -524,7 +946,9 @@ const CacheView = memo((props) => {
|
||||
display: "table-cell",
|
||||
overflow: "hidden",
|
||||
verticalAlign: "middle",
|
||||
padding: "8px 8px 8px 15px"
|
||||
padding: "8px 8px 8px 15px",
|
||||
maxWidth: 200,
|
||||
overflowX: "auto",
|
||||
}}
|
||||
primary={data.key}
|
||||
/>
|
||||
@@ -576,13 +1000,14 @@ const CacheView = memo((props) => {
|
||||
primary={(
|
||||
<span style={{ display: "inline" }}>
|
||||
<Tooltip
|
||||
title="Edit item"
|
||||
title={data?.org_id !== selectedOrganization.id ? "You can not edit this cache as it is controlled by parent organization." : "Edit this key" }
|
||||
style={{}}
|
||||
aria-label={"Edit"}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
style={{ padding: "6px" }}
|
||||
disabled={data.org_id !== selectedOrganization.id ? true : false}
|
||||
onClick={() => {
|
||||
setEditCache(true)
|
||||
setDataValue({
|
||||
@@ -598,14 +1023,14 @@ const CacheView = memo((props) => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={"Public URL (types: text, raw, json)"}
|
||||
title={data?.org_id !== selectedOrganization.id ? "You can not access public URL for this key as it is controlled by parent organization." : "Public URL (types: text, raw, json)" }
|
||||
style={{ marginLeft: 0, }}
|
||||
aria-label={"Public URL"}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
style={{ padding: "6px" }}
|
||||
disabled={data.public_authorization === undefined || data.public_authorization === null || data.public_authorization === "" ? true : false}
|
||||
disabled={data.public_authorization === undefined || data.public_authorization === null || data.public_authorization === "" || data.org_id !== selectedOrganization.id ? true : false}
|
||||
onClick={() => {
|
||||
window.open(`${globalUrl}/api/v1/orgs/${orgId}/cache/${data.key}?type=text&authorization=${data.public_authorization}`, "_blank");
|
||||
}}
|
||||
@@ -616,18 +1041,40 @@ const CacheView = memo((props) => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={"Delete item"}
|
||||
title={data?.org_id !== selectedOrganization.id ? "You can not delete this key as it is controlled by parent organization." : "Delete this key" }
|
||||
aria-label={"Delete"}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
style={{ padding: "6px" }}
|
||||
disabled={data.org_id !== selectedOrganization.id ? true : false}
|
||||
onClick={() => {
|
||||
deleteCache(orgId, data.key);
|
||||
//deleteFile(orgId);
|
||||
}}
|
||||
>
|
||||
<img src="/icons/deleteIcon.svg" alt="delete" />
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{
|
||||
stroke: data.org_id === selectedOrganization.id ? "#fd4c62" : "#c8c8c8",
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M5 7.20001H6.6H19.4"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
<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"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
@@ -642,6 +1089,49 @@ const CacheView = memo((props) => {
|
||||
}}
|
||||
primary={new Date(data.edited * 1000).toISOString()}
|
||||
/>
|
||||
{selectedOrganization.id !== undefined && data?.org_id !== selectedOrganization.id ?
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip
|
||||
title="Parent organization controlled datastore. You can use, but not modify this key. Contact an admin of your parent organization if you need changes to this."
|
||||
placement="top"
|
||||
>
|
||||
<Chip
|
||||
label={"Parent"}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
style={{display: "table-cell", textAlign: 'center', verticalAlign: 'middle', }}
|
||||
/>
|
||||
:
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip
|
||||
title="Distributed to sub-organizations. This means the sub organizations can use this datastore key, but can not modify it."
|
||||
placement="top"
|
||||
>
|
||||
<Checkbox
|
||||
disabled={ userdata?.active_org?.role !== "admin" || (selectedOrganization.creator_org !== undefined && selectedOrganization.creator_org !== null && selectedOrganization.creator_org !== "" )? true : false}
|
||||
checked={isDistributed}
|
||||
style={{ margin: "auto" }}
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
setShowDistributionPopup(true)
|
||||
if(data?.suborg_distribution?.length > 0){
|
||||
setSelectedSubOrg(data.suborg_distribution)
|
||||
}else{
|
||||
setSelectedSubOrg([])
|
||||
}
|
||||
setSelectedCacheKey(data.key)
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
style={{display: "table-cell", textAlign: 'center', verticalAlign: 'middle', }}
|
||||
/>
|
||||
}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
@@ -653,4 +1143,4 @@ const CacheView = memo((props) => {
|
||||
);
|
||||
});
|
||||
|
||||
export default memo(CacheView);
|
||||
export default memo(CacheView);
|
||||
|
||||
@@ -0,0 +1,780 @@
|
||||
import React, { useEffect, useState, useContext } from "react";
|
||||
import {
|
||||
FormControl,
|
||||
Card,
|
||||
Tooltip,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
Grid,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemAvatar,
|
||||
IconButton,
|
||||
Avatar,
|
||||
Zoom,
|
||||
InputAdornment,
|
||||
Switch,
|
||||
Skeleton
|
||||
} from "@mui/material";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import {
|
||||
Edit as EditIcon,
|
||||
Polyline as PolylineIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Close as CloseIcon,
|
||||
Visibility as VisibilityIcon,
|
||||
VisibilityOff as VisibilityOffIcon,
|
||||
} from "@mui/icons-material";
|
||||
import theme from "../theme.jsx";
|
||||
import { styled } from '@mui/styles';
|
||||
import { Context } from "../context/ContextApi.jsx";
|
||||
|
||||
const CloudSyncTab = (props) => {
|
||||
const {
|
||||
userdata,
|
||||
globalUrl,
|
||||
serverside
|
||||
} = props;
|
||||
const [cloudSyncApikey, setCloudSyncApikey] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showApiKey, setShowApiKey] = useState(false);
|
||||
const [orgSyncResponse, setOrgSyncResponse] = React.useState("");
|
||||
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
|
||||
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
||||
const [selectedStatus, setSelectedStatus] = React.useState([]);
|
||||
const [orgRequest, setOrgRequest] = React.useState(true);
|
||||
const [userSettings, setUserSettings] = React.useState({});
|
||||
const [, forceUpdate] = React.useState();
|
||||
const itemColor = "white";
|
||||
const isCloud = window?.location?.host === "localhost:3002" || window?.location?.host === "shuffler.io";
|
||||
useEffect(() => { getSettings(); }, []);
|
||||
const GridItem = (props) => {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const [showEdit, setShowEdit] = React.useState(false);
|
||||
const [newValue, setNewValue] = React.useState(-100);
|
||||
|
||||
const primary = props.data.primary;
|
||||
const secondary = props.data.secondary;
|
||||
const primaryIcon = props.data.icon;
|
||||
const secondaryIcon = props.data.active ?
|
||||
<CheckCircleIcon style={{ color: "green" }} />
|
||||
:
|
||||
<CloseIcon style={{ color: "red" }} />
|
||||
|
||||
const submitFeatureEdit = (sync_features) => {
|
||||
if (!userdata.support) {
|
||||
console.log("User does not have support access and can't edit features");
|
||||
return
|
||||
}
|
||||
|
||||
sync_features.editing = true
|
||||
const data = {
|
||||
org_id: selectedOrganization.id,
|
||||
sync_features: sync_features,
|
||||
};
|
||||
console.log("sync_features: ", sync_features);
|
||||
const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
|
||||
fetch(url, {
|
||||
mode: "cors",
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
crossDomain: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
})
|
||||
.then((response) =>
|
||||
response.json().then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
toast("Failed updating org: ", responseJson.reason);
|
||||
} else {
|
||||
toast("Successfully edited org!");
|
||||
}
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
toast("Err: " + error.toString());
|
||||
});
|
||||
}
|
||||
|
||||
const enableFeature = () => {
|
||||
console.log("Enabling " + primary)
|
||||
|
||||
console.log(selectedOrganization.sync_features)
|
||||
// Check if primary is in sync_features
|
||||
var tmpprimary = primary.replaceAll(" ", "_")
|
||||
if (!(tmpprimary in selectedOrganization.sync_features)) {
|
||||
console.log("Primary not in sync_features: " + tmpprimary)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.data.active) {
|
||||
selectedOrganization.sync_features[tmpprimary].active = false
|
||||
} else {
|
||||
selectedOrganization.sync_features[tmpprimary].active = true
|
||||
}
|
||||
|
||||
setSelectedOrganization(selectedOrganization)
|
||||
forceUpdate(Math.random())
|
||||
submitFeatureEdit(selectedOrganization.sync_features)
|
||||
}
|
||||
|
||||
const submitEdit = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Check if primary is in sync_features
|
||||
var tmpprimary = primary.replaceAll(" ", "_")
|
||||
if (!(tmpprimary in selectedOrganization.sync_features)) {
|
||||
console.log("Primary not in sync_features: " + tmpprimary)
|
||||
return
|
||||
}
|
||||
|
||||
// Make it into a number
|
||||
var tmp = parseInt(newValue)
|
||||
if (isNaN(tmp)) {
|
||||
console.log("Not a number: " + newValue)
|
||||
return
|
||||
}
|
||||
|
||||
selectedOrganization.sync_features[tmpprimary].limit = tmp
|
||||
|
||||
setSelectedOrganization(selectedOrganization)
|
||||
forceUpdate(Math.random())
|
||||
submitFeatureEdit(selectedOrganization.sync_features)
|
||||
}
|
||||
const handleToggleFeature = (e) => {
|
||||
// Your logic for toggling the feature's active state
|
||||
console.log(`Toggling ${primary}`);
|
||||
if (!isCloud || userdata.support !== true) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
enableFeature()
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid
|
||||
item
|
||||
xs={4}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
margin: 4,
|
||||
backgroundColor: "#1a1a1a",
|
||||
borderRadius: 8,
|
||||
color: "white",
|
||||
minHeight: expanded ? 250 : "inherit",
|
||||
maxHeight: expanded ? 300 : "inherit",
|
||||
boxShadow: "none",
|
||||
}}
|
||||
>
|
||||
<ListItem
|
||||
style={{ cursor: "pointer", }}
|
||||
onClick={() => {
|
||||
setExpanded(prev => !prev);
|
||||
if(showEdit){
|
||||
setShowEdit(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar>{primaryIcon}</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
style={{ textTransform: "capitalize", color: "#F1F1F1", fontSize: 14, fontWeight: 400, }}
|
||||
primary={primary}
|
||||
/>
|
||||
{isCloud && userdata.support === true ?
|
||||
<Tooltip title="Edit features (support users only)">
|
||||
<EditIcon
|
||||
color="secondary"
|
||||
style={{ marginRight: 10, cursor: "pointer", }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('expanded', expanded)
|
||||
if (expanded){
|
||||
setExpanded(false)
|
||||
}
|
||||
if (showEdit) {
|
||||
setShowEdit(false)
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Edit")
|
||||
|
||||
setShowEdit(true)
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
: null}
|
||||
{userdata.support === true ?(
|
||||
<Tooltip title={props.data.active ? 'Disable feature' : 'Enable feature'}>
|
||||
<Switch
|
||||
checked={props.data.active}
|
||||
onChange={handleToggleFeature}
|
||||
color="primary"
|
||||
inputProps={{ 'aria-label': 'feature toggle' }}
|
||||
sx={{
|
||||
'& .MuiSwitch-switchBase.Mui-checked': {
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
|
||||
backgroundColor: props.data.active ? '#2BC07E' : "#9e9e9e",
|
||||
},
|
||||
"& .MuiSwitch-track": {
|
||||
backgroundColor: props.data.active ? '#2BC07E' : "#9e9e9e"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
):(
|
||||
<Tooltip title={props.data.active ? "Disable feature" : "Enable feature"}>
|
||||
<span
|
||||
style={{ cursor: "pointer", marginTop: 5, }}
|
||||
onClick={(e) => {
|
||||
if (!isCloud || userdata.support !== true) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
enableFeature()
|
||||
}}
|
||||
>
|
||||
{secondaryIcon}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
</ListItem>
|
||||
{expanded ?
|
||||
<div style={{ padding: 15 }}>
|
||||
<Typography>
|
||||
<b>Usage: </b>
|
||||
{props.data.limit === 0 ? (
|
||||
"Unlimited"
|
||||
) : (
|
||||
<span>
|
||||
{props.data.usage} / {props.data.limit === "" ? "Unlimited" : props.data.limit}
|
||||
</span>
|
||||
)}
|
||||
</Typography>
|
||||
{/*<Typography>
|
||||
Data sharing: {props.data.data_collection}
|
||||
</Typography>*/}
|
||||
<Typography style={{ maxHeight: 150, overflowX: "hidden", overflowY: "auto" }}><b>Description:</b> {secondary}</Typography>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
|
||||
{showEdit ?
|
||||
<FormControl fullWidth onSubmit={(e) => {
|
||||
console.log("Submit")
|
||||
submitEdit(e)
|
||||
}}>
|
||||
<span style={{ display: "flex",}}>
|
||||
|
||||
<TextField
|
||||
style={{ flex: 3, }}
|
||||
color="primary"
|
||||
label={"Edit value"}
|
||||
defaultValue={props.data.limit}
|
||||
sx={{
|
||||
marginTop: 0.5,
|
||||
marginBottom: 0.5
|
||||
}}
|
||||
onChange={(event) => {
|
||||
setNewValue(event.target.value)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
style={{ flex: 1, }}
|
||||
variant="contained"
|
||||
disabled={newValue < -1}
|
||||
onClick={(e) => {
|
||||
console.log("Submit 2")
|
||||
submitEdit(e)
|
||||
}}
|
||||
sx={{
|
||||
marginTop: 0.5,
|
||||
maarginBottom: 0.5
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</span>
|
||||
</FormControl>
|
||||
: null}
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
const handleGetOrg = (orgId) => {
|
||||
|
||||
if (serverside !== true && window.location.search !== undefined && window.location.search !== null) {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
const foundorgid = params["org_id"];
|
||||
if (foundorgid !== undefined && foundorgid !== null) {
|
||||
orgId = foundorgid;
|
||||
}
|
||||
}
|
||||
|
||||
if (orgId.length === 0) {
|
||||
toast("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Just use this one?
|
||||
|
||||
fetch(`${globalUrl}/api/v1/orgs/${orgId}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 401) {
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
toast("Failed getting your org. If this persists, please contact support.");
|
||||
} else {
|
||||
if (
|
||||
responseJson.sync_features === undefined ||
|
||||
responseJson.sync_features === null
|
||||
) {
|
||||
responseJson.sync_features = {};
|
||||
}
|
||||
|
||||
setSelectedOrganization(responseJson)
|
||||
var lists = {
|
||||
active: {
|
||||
triggers: [],
|
||||
features: [],
|
||||
sync: [],
|
||||
},
|
||||
inactive: {
|
||||
triggers: [],
|
||||
features: [],
|
||||
sync: [],
|
||||
},
|
||||
};
|
||||
setOrganizationFeatures(lists);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error getting org: ", error);
|
||||
toast("Error getting current organization");
|
||||
});
|
||||
};
|
||||
const handleStopOrgSync = (org_id) => {
|
||||
if (org_id === undefined || org_id === null) {
|
||||
toast("Couldn't get org " + org_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {};
|
||||
|
||||
const url = globalUrl + "/api/v1/orgs/" + org_id + "/stop_sync";
|
||||
fetch(url, {
|
||||
mode: "cors",
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
crossDomain: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
console.log("Cloud sync success?");
|
||||
toast("Successfully stopped cloud sync");
|
||||
} else {
|
||||
console.log("Cloud sync fail?");
|
||||
toast(
|
||||
"Failed stopping sync. Try again, and contact support if this persists."
|
||||
);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setTimeout(() => {
|
||||
handleGetOrg(org_id);
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast("Err: " + error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const enableCloudSync = (apikey, organization, disableSync) => {
|
||||
setOrgSyncResponse("");
|
||||
|
||||
const data = {
|
||||
apikey: apikey,
|
||||
organization: organization,
|
||||
disable: disableSync,
|
||||
};
|
||||
|
||||
const url = globalUrl + "/api/v1/cloud/setup";
|
||||
fetch(url, {
|
||||
mode: "cors",
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
crossDomain: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
setLoading(false);
|
||||
if (response.status === 200) {
|
||||
console.log("Cloud sync success?");
|
||||
} else {
|
||||
console.log("Cloud sync fail?");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
//setTimeout(() => {
|
||||
//}, 1000)
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log("RESP: ", responseJson);
|
||||
if (
|
||||
responseJson.success === false &&
|
||||
responseJson.reason !== undefined
|
||||
) {
|
||||
setOrgSyncResponse(responseJson.reason);
|
||||
toast("Failed to handle sync: " + responseJson.reason);
|
||||
} else if (!responseJson.success) {
|
||||
toast("Failed to handle sync.");
|
||||
} else {
|
||||
//getOrgs(); API no longer in use, as it's in handleInfo request
|
||||
if (disableSync) {
|
||||
toast("Successfully disabled sync!");
|
||||
setOrgSyncResponse("Successfully disabled syncronization");
|
||||
} else {
|
||||
toast("Cloud Syncronization successfully set up!");
|
||||
setOrgSyncResponse(
|
||||
"Successfully started syncronization. Cloud features you now have access to can be seen below."
|
||||
);
|
||||
}
|
||||
|
||||
selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync;
|
||||
setSelectedOrganization(selectedOrganization);
|
||||
setCloudSyncApikey("");
|
||||
|
||||
handleGetOrg(userdata.active_org.id);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
toast("Err: " + error.toString());
|
||||
});
|
||||
};
|
||||
const getSettings = () => {
|
||||
fetch(globalUrl + "/api/v1/getsettings", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 when getting settings :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setUserSettings(responseJson);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
if (
|
||||
selectedOrganization.id === undefined &&
|
||||
userdata !== undefined &&
|
||||
userdata.active_org !== undefined &&
|
||||
orgRequest === true
|
||||
) {
|
||||
setOrgRequest(false);
|
||||
handleGetOrg(userdata.active_org.id);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{padding: "27px 10px 19px 27px",}}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2
|
||||
style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}
|
||||
>
|
||||
Cloud syncronization
|
||||
</h2>
|
||||
<span style={{ color: "#C8C8C8", fontSize: 16, fontWeight: 400, }}>
|
||||
What does <a href="/docs/organizations#cloud_sync" target="_blank" rel="noopener noreferrer" style={{ color: "rgba(255, 132, 68, 1)", fontSize: 16, textDecoration: 'none', }}>cloud sync</a> do? Cloud synchronization is a way of getting more out of Shuffle. Shuffle will ALWAYS make every option open source, but features relying on other users can't be done without a collaborative approach.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isCloud ? (
|
||||
<div style={{ marginTop: 15, display: "flex" }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Typography style={{fontWeight: 400, fontSize: 16, color: "#F1F1F1"}}>
|
||||
Currently syncronizing:{" "}
|
||||
{selectedOrganization.cloud_sync_active === true
|
||||
? <span style={{ color: "#4CFD72", fontSize: 16, marginLeft: 16}}>True</span>
|
||||
: <span style={{ color: "#FD4C62", fontSize: 16, marginLeft: 16 }}>False</span>}
|
||||
</Typography>
|
||||
{selectedOrganization.cloud_sync_active ? (
|
||||
<Typography style={{}}>
|
||||
Syncronization interval:{" "}
|
||||
{selectedOrganization.sync_config.interval === 0
|
||||
? "60"
|
||||
: selectedOrganization.sync_config.interval}
|
||||
</Typography>
|
||||
) : null}
|
||||
<Typography
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
marginTop: 25,
|
||||
marginRight: 10,
|
||||
fontSize: 16,
|
||||
fontWeight: 400,
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
}}
|
||||
>
|
||||
Your Api key
|
||||
</Typography>
|
||||
{userSettings?.apikey === undefined || userSettings?.apikey === null || userSettings?.apikey?.length <=0 ? (
|
||||
<Skeleton variant="rectangular" animation="wave" sx={{backgroundColor: '#212121', border: '1px solid #646464', width: 500, height: 50, marginTop: 2 }}/>
|
||||
):
|
||||
<div style={{ display: "flex" }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
maxWidth: 500,
|
||||
height: 35
|
||||
}}
|
||||
InputProps={{
|
||||
sx: {
|
||||
height: "35px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
backgroundColor: '#212121',
|
||||
},
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => {
|
||||
setShowApiKey(!showApiKey)
|
||||
}}
|
||||
>
|
||||
{showApiKey ? <VisibilityIcon /> : <VisibilityOffIcon />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
disabled={true}
|
||||
autoComplete="cloud apikey"
|
||||
id="apikey_field"
|
||||
margin="normal"
|
||||
placeholder="Cloud Apikey"
|
||||
variant="outlined"
|
||||
value={userSettings?.apikey}
|
||||
defaultValue={userSettings?.apikey}
|
||||
type={!isCloud || showApiKey ? "text" : "password"}
|
||||
/>
|
||||
{selectedOrganization.cloud_sync_active ? (
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
height: 50,
|
||||
marginLeft: 10,
|
||||
marginTop: 17,
|
||||
}}
|
||||
variant={
|
||||
selectedOrganization.cloud_sync_active === true
|
||||
? "outlined"
|
||||
: "contained"
|
||||
}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
handleStopOrgSync(selectedOrganization.id);
|
||||
}}
|
||||
>
|
||||
Stop Sync
|
||||
</Button>
|
||||
) : null}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ display: "flex", marginBottom: 20 }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{
|
||||
backgroundColor: "#1a1a1a",
|
||||
marginRight: 10,
|
||||
height: 35,
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "35px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
disabled={selectedOrganization.cloud_sync}
|
||||
autoComplete="cloud apikey"
|
||||
id="apikey_field"
|
||||
margin="normal"
|
||||
placeholder="Cloud Apikey"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setCloudSyncApikey(event.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={
|
||||
(!selectedOrganization.cloud_sync &&
|
||||
cloudSyncApikey.length === 0) ||
|
||||
loading
|
||||
}
|
||||
style={{ marginTop: 15, height: 35, width: 150, textTransform: 'none', fontSize: 16, color: (!selectedOrganization.cloud_sync &&
|
||||
cloudSyncApikey.length === 0) ||
|
||||
loading ? null : "#1a1a1a", backgroundColor: (!selectedOrganization.cloud_sync &&
|
||||
cloudSyncApikey.length === 0) ||
|
||||
loading? null : "#FF8544" }}
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
enableCloudSync(
|
||||
cloudSyncApikey,
|
||||
selectedOrganization,
|
||||
selectedOrganization.cloud_sync
|
||||
);
|
||||
}}
|
||||
color="primary"
|
||||
variant={
|
||||
selectedOrganization.cloud_sync === true
|
||||
? "outlined"
|
||||
: "contained"
|
||||
}
|
||||
>
|
||||
{selectedOrganization.cloud_sync
|
||||
? "Stop sync"
|
||||
: "Start sync"}
|
||||
</Button>
|
||||
</div>
|
||||
{orgSyncResponse.length > 0 ? (
|
||||
<Typography style={{ marginTop: 5, marginBottom: 10 }}>
|
||||
Message from Shuffle Cloud: <b>{orgSyncResponse}</b>
|
||||
</Typography>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h2 style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}>
|
||||
Features
|
||||
</h2>
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 24, fontSize: 16, fontWeight: 400, marginLeft: 5, color: "#C8C8C8" }}>
|
||||
Features and Limitations that are currently available to you in your Cloud or Hybrid Organization. App Executions (App Runs) reset monthly. If the organization is a customer or in a trial, these features limitations are not always enforced. </Typography>
|
||||
<Grid container style={{ width: "100%", marginBottom: 15, }}>
|
||||
|
||||
{selectedOrganization.sync_features === undefined ||
|
||||
selectedOrganization.sync_features === null
|
||||
? <Grid container spacing={2} justifyContent="center">
|
||||
{[...Array(18)].map((_, i) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={i}>
|
||||
<div
|
||||
style={{
|
||||
margin: 4,
|
||||
borderRadius: 8,
|
||||
minHeight: "inherit",
|
||||
maxHeight: "inherit",
|
||||
boxShadow: "none",
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={50}
|
||||
width={343}
|
||||
sx={{ backgroundColor: '#1a1a1a', display: 'flex', borderRadius: 1 }}
|
||||
animation="wave"
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
: Object.keys(selectedOrganization.sync_features).map(function (
|
||||
key,
|
||||
index
|
||||
) {
|
||||
|
||||
if (key === "schedule" || key === "apps" || key === "updates" || key === "editing") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = selectedOrganization.sync_features[key];
|
||||
if (item === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const newkey = key.replaceAll("_", " ");
|
||||
const griditem = {
|
||||
primary: newkey,
|
||||
secondary:
|
||||
item.description === undefined ||
|
||||
item.description === null ||
|
||||
item.description.length === 0
|
||||
? "Not defined yet"
|
||||
: item.description,
|
||||
limit: item.limit,
|
||||
usage: item.usage === undefined ||
|
||||
item.usage === null ? 0 : item.usage,
|
||||
data_collection: "None",
|
||||
active: item.active,
|
||||
icon: <PolylineIcon style={{ color: "#1a1a1a" }} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<Zoom key={index}>
|
||||
<GridItem data={griditem} />
|
||||
</Zoom>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloudSyncTab;
|
||||
@@ -799,12 +799,16 @@ const ConfigureWorkflow = (props) => {
|
||||
>
|
||||
<div
|
||||
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: filled ? "default" : "pointer",
|
||||
}}
|
||||
id="app-config"
|
||||
>
|
||||
<div style={{display: "flex", }}
|
||||
onClick={() => {
|
||||
if (filled) {
|
||||
return
|
||||
}
|
||||
|
||||
setOpened(!opened);
|
||||
|
||||
// Scroll to it
|
||||
@@ -865,6 +869,7 @@ const ConfigureWorkflow = (props) => {
|
||||
isLoggedIn={true}
|
||||
getAppAuthentication={undefined}
|
||||
|
||||
workflow={workflow}
|
||||
setFinalized={setFinalized}
|
||||
/>
|
||||
</div>
|
||||
@@ -1377,7 +1382,7 @@ const ConfigureWorkflow = (props) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{margin: setConfigureWorkflowModalOpen !== undefined ? "0px 50px 0px 50px" : "35px 0px 0px 0px", maxHeight: 475, }}>
|
||||
<div style={{margin: setConfigureWorkflowModalOpen !== undefined ? "0px 50px 0px 50px" : "25px 0px 0px 0px", maxHeight: 475, }}>
|
||||
|
||||
|
||||
{setConfigureWorkflowModalOpen !== undefined ?
|
||||
@@ -1387,7 +1392,7 @@ const ConfigureWorkflow = (props) => {
|
||||
: null
|
||||
}
|
||||
|
||||
<div style={{marginTop: 10, }} />
|
||||
<div style={{marginTop: setConfigureWorkflowModalOpen !== undefined ? 10 : 0, }} />
|
||||
|
||||
{/*
|
||||
<WorkflowValidationTimeline
|
||||
@@ -1404,7 +1409,7 @@ const ConfigureWorkflow = (props) => {
|
||||
{requiredActions.length > 0 ? (
|
||||
<span>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Please configure the following steps to help us complete your workflow. This can also be done later.
|
||||
To complete the workflow setup, please configure the following steps.
|
||||
</Typography>
|
||||
|
||||
{setConfigureWorkflowModalOpen !== undefined ?
|
||||
@@ -1434,6 +1439,13 @@ const ConfigureWorkflow = (props) => {
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
|
||||
{/*
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Once done, you may continue to the workflow.
|
||||
</Typography>
|
||||
*/}
|
||||
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isD
|
||||
}
|
||||
|
||||
const action = folderDisabled ? "enable_folder" : "disable_folder";
|
||||
const url = `${globalUrl}/api/v1/detections/${action}`;
|
||||
//const url = `${globalUrl}/api/v1/detections/${detectionType}/selected_rules/${action}`;
|
||||
const url = `${globalUrl}/api/v1/detections/sigma/selected_rules/${action}`;
|
||||
|
||||
fetch(url, {
|
||||
method: "PUT",
|
||||
@@ -258,7 +259,7 @@ const DetectionExplorer = (props) => {
|
||||
rule.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
|
||||
const lakeNodes = environmentList !== undefined && environmentList !== null ? environmentList.filter((env) => env?.data_lake?.enabled === true).length : 0
|
||||
const lakeNodes = environmentList !== undefined && environmentList !== null ? environmentList.filter((env) => env?.archived === false && env?.data_lake?.enabled === true).length : 0
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@@ -284,7 +285,8 @@ const DetectionExplorer = (props) => {
|
||||
</Typography>
|
||||
|
||||
<div style={{display: "flex", }}>
|
||||
{workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0 ?
|
||||
|
||||
{/*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
|
||||
@@ -313,7 +315,7 @@ const DetectionExplorer = (props) => {
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
:
|
||||
: */}
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
@@ -331,11 +333,11 @@ const DetectionExplorer = (props) => {
|
||||
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">
|
||||
<a href="/admin?tab=Locations" style={{textDecoration: "none", color: "inherit", }} target="_blank" rel="noreferrer">
|
||||
<FmdGoodIcon style={{marginLeft: 15, marginTop: 5, color: lakeNodes > 0 ? green : red}} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
|
||||
@@ -137,6 +137,8 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i
|
||||
|
||||
setResponseValue(e.target.value)
|
||||
|
||||
toast.error("The automatic response system is NOT available for you yet. Please contact support@shuffler.io if you want to try this feature.")
|
||||
|
||||
// FIXME: Handle:
|
||||
// 1. Get the current cache for the detection
|
||||
// 2. Create a new mapping for Detection -> Response
|
||||
@@ -187,7 +189,7 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i
|
||||
<Switch
|
||||
checked={isEnabled && !folderDisabled}
|
||||
onChange={handleSwitchChange}
|
||||
disabled={false}
|
||||
disabled={true}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import OrgHeaderexpanded from "../components/OrgHeaderexpandedNew.jsx";
|
||||
import OrgHeader from '../components/OrgHeaderNew.jsx';
|
||||
import { toast } from "react-toastify";
|
||||
import CloudSyncTab from '../components/CloudSyncTab.jsx';
|
||||
import {
|
||||
FileCopy as FileCopyIcon,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Button,
|
||||
Tooltip,
|
||||
IconButton,
|
||||
} from "@mui/material";
|
||||
|
||||
const EditOrgTab = (props) => {
|
||||
const {
|
||||
userdata,
|
||||
globalUrl,
|
||||
serverside,
|
||||
selectedOrganization,
|
||||
setSelectedOrganization,
|
||||
handleGetOrg,
|
||||
selectedStatus, setSelectedStatus,
|
||||
handleEditOrg,
|
||||
} = props;
|
||||
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
|
||||
const [users, setUsers] = React.useState([]);
|
||||
const [orgRequest, setOrgRequest] = React.useState(true);
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
useEffect(() => {
|
||||
if(users.length === 0) {
|
||||
getUsers();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleStatusChange = (event) => {
|
||||
const { value } = event.target;
|
||||
setSelectedStatus(value);
|
||||
|
||||
handleEditOrg(
|
||||
selectedOrganization?.name,
|
||||
selectedOrganization?.description,
|
||||
selectedOrganization.id,
|
||||
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,
|
||||
},
|
||||
value.length === 0 ? ["none"] : value,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const getUsers = () => {
|
||||
fetch(globalUrl + "/api/v1/getusers", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
// Ahh, this happens because they're not admin
|
||||
// window.location.pathname = "/workflows"
|
||||
return;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setUsers(responseJson);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString());
|
||||
});
|
||||
};
|
||||
const mailsendingButton = (org) => {
|
||||
if (org === undefined || org === null) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (users.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 1 mail based on users that have only apps
|
||||
// Another based on those doing workflows
|
||||
// Another based on those trying usecases(?) or templates
|
||||
//
|
||||
// Start based on edr, siem & ticketing
|
||||
// Talk about enrichment?
|
||||
// Check suggested usecases
|
||||
// Check suggested workflows
|
||||
var your_apps = "- Connecting "
|
||||
|
||||
var subject_add = 0
|
||||
var subject = "POC to automate "
|
||||
|
||||
if (org.security_framework !== undefined && org.security_framework !== null) {
|
||||
if (org.security_framework.cases.name !== undefined && org.security_framework.cases.name !== null && org.security_framework.cases.name !== "") {
|
||||
your_apps += org.security_framework.cases.name.replace("_", " ", -1).replace(" API", "", -1) + ", "
|
||||
|
||||
if (subject_add < 2) {
|
||||
if (subject_add === 1) {
|
||||
subject += " and "
|
||||
}
|
||||
|
||||
subject_add += 1
|
||||
subject += org.security_framework.cases.name.replace("_", " ", -1).replace(" API", "", -1)
|
||||
}
|
||||
}
|
||||
|
||||
if (org.security_framework.siem.name !== undefined && org.security_framework.siem.name !== null && org.security_framework.siem.name !== "") {
|
||||
your_apps += org.security_framework.siem.name.replace("_", " ", -1).replace(" API", "", -1) + ", "
|
||||
if (subject_add < 2) {
|
||||
if (subject_add === 1) {
|
||||
subject += " and "
|
||||
}
|
||||
|
||||
subject_add += 1
|
||||
subject += org.security_framework.siem.name.replace("_", " ", -1).replace(" API", "", -1)
|
||||
}
|
||||
}
|
||||
|
||||
if (org.security_framework.communication.name !== undefined && org.security_framework.communication.name !== null && org.security_framework.communication.name !== "") {
|
||||
your_apps += org.security_framework.communication.name.replace("_", " ", -1).replace(" API", "", -1) + ", "
|
||||
|
||||
if (subject_add < 2) {
|
||||
if (subject_add === 1) {
|
||||
subject += " and "
|
||||
}
|
||||
|
||||
subject_add += 1
|
||||
subject += org.security_framework.communication.name.replace("_", " ", -1).replace(" API", "", -1)
|
||||
}
|
||||
}
|
||||
|
||||
if (org.security_framework.edr.name !== undefined && org.security_framework.edr.name !== null && org.security_framework.edr.name !== "") {
|
||||
your_apps += org.security_framework.edr.name.replace("_", " ", -1).replace(" API", "", -1) + ", "
|
||||
|
||||
if (subject_add < 2) {
|
||||
if (subject_add === 1) {
|
||||
subject += " and "
|
||||
}
|
||||
|
||||
subject_add += 1
|
||||
subject += org.security_framework.edr.name.replace("_", " ", -1).replace(" API", "", -1)
|
||||
}
|
||||
}
|
||||
|
||||
if (org.security_framework.intel.name !== undefined && org.security_framework.intel.name !== null && org.security_framework.intel.name !== "") {
|
||||
your_apps += org.security_framework.intel.name.replace("_", " ", -1).replace(" API", "", -1) + ", "
|
||||
|
||||
if (subject_add < 2) {
|
||||
if (subject_add === 1) {
|
||||
subject += " and "
|
||||
}
|
||||
|
||||
subject_add += 1
|
||||
subject += org.security_framework.intel.name.replace("_", " ", -1).replace(" API", "", -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Remove comma
|
||||
//subject += "?"
|
||||
your_apps = your_apps.substring(0, your_apps.length - 2)
|
||||
}
|
||||
|
||||
|
||||
// Add usecases they may not have tried (from recommendations): org.priorities where item type is usecase
|
||||
var usecases = "- Building usecases like "
|
||||
const active_usecase = org.priorities.filter((item) => item.type === "usecase" && item.active === true)
|
||||
if (active_usecase.length > 0) {
|
||||
for (var i = 0; i < active_usecase.length; i++) {
|
||||
if (active_usecase[i].name.includes("Suggested Usecase: ")) {
|
||||
usecases += active_usecase[i].name.replace("Suggested Usecase: ", "", -1) + ", "
|
||||
} else {
|
||||
usecases += active_usecase[i].name + ", "
|
||||
}
|
||||
}
|
||||
|
||||
usecases = usecases.substring(0, usecases.length - 2)
|
||||
}
|
||||
|
||||
if (your_apps.length <= 15) {
|
||||
your_apps = ""
|
||||
}
|
||||
|
||||
if (usecases.length <= 30) {
|
||||
usecases = ""
|
||||
}
|
||||
|
||||
var workflow_amount = "a few"
|
||||
var admins = ""
|
||||
|
||||
// Loop users
|
||||
var lastLogin = 0
|
||||
for (var i = 0; i < users.length; i++) {
|
||||
if (users[i].username.includes("shuffler")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (users[i].role === "admin") {
|
||||
admins += users[i].username + ","
|
||||
}
|
||||
|
||||
const data = users[i]
|
||||
for (var i = 0; i < data.login_info.length; i++) {
|
||||
if (data.login_info[i].timestamp > lastLogin) {
|
||||
lastLogin = data.login_info[i].timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Remove last comma
|
||||
admins = admins.substring(0, admins.length - 1)
|
||||
|
||||
if (your_apps.length > 5) {
|
||||
your_apps += "%0D%0A"
|
||||
}
|
||||
|
||||
if (usecases.length > 5) {
|
||||
usecases += "%0D%0A"
|
||||
}
|
||||
|
||||
// Get drift username from userdata.username before @ in email
|
||||
const username = userdata.username.substring(0, userdata.username.indexOf("@"))
|
||||
|
||||
// Check if timestamp is more than 2 weeks ago and add "a while back" to the message
|
||||
const timeComparison = 1209600
|
||||
const extra_timestamp_text = lastLogin === 0 ? 0 : (Date.now() / 1000 - lastLogin) > timeComparison ? " a while back" : ""
|
||||
console.log("LAST LOGIN: " + lastLogin, extra_timestamp_text)
|
||||
|
||||
// Check if cloud sync is active, and if so, add a message about it
|
||||
const cloudSyncInfo = selectedOrganization.cloud_sync === true ? "- Scale your onprem installation" : ""
|
||||
|
||||
var body = `Hey,%0D%0A%0D%0AI noticed you tried to use Shuffle${extra_timestamp_text}, and thought you may be interested in a POC. It looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting what you wanted out of Shuffle. If you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A
|
||||
|
||||
Some of the things we can help with:%0D%0A
|
||||
${your_apps}
|
||||
- Configuring and authenticating your apps%0D%0A
|
||||
${usecases}
|
||||
- Multi-Tenancy and creating special usecases%0D%0A
|
||||
${cloudSyncInfo}%0D%0A
|
||||
|
||||
If you're interested, please let me know a time that works for you, or set up a call here: https://drift.me/${username}`
|
||||
|
||||
return `mailto:${admins}?bcc=frikky@shuffler.io,binu@shuffler.io&subject=${subject}&body=${body}`
|
||||
}
|
||||
|
||||
if (
|
||||
selectedOrganization.id === undefined &&
|
||||
userdata !== undefined &&
|
||||
userdata.active_org !== undefined &&
|
||||
orgRequest
|
||||
) {
|
||||
setOrgRequest(false);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121', borderRadius: '16px', }}>
|
||||
<div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}} >
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<div style={{display:"flex"}}>
|
||||
<div style={{width:'70%'}}>
|
||||
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Organization overview</h2>
|
||||
<span style={{ color: "#9E9E9E" }}>
|
||||
On this page organization admins can configure organisations, and sub-orgs (MSSP).{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="/docs/organizations#organization"
|
||||
style={{ color: "#FF8444" }}
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div style={{display:"flex", alignItems:"center", marginLeft:50}}>
|
||||
<Tooltip
|
||||
title="Copy Organization ID"
|
||||
aria-label="Copy orgid"
|
||||
>
|
||||
<IconButton
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: "rgba(47, 47, 47, 1)",
|
||||
borderRadius: 200
|
||||
}}
|
||||
onClick={() => {
|
||||
const org_id = selectedOrganization.id;
|
||||
|
||||
// Check if organization ID exists
|
||||
if (!org_id) {
|
||||
toast("No organization ID found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Use clipboard API
|
||||
navigator.clipboard.writeText(org_id)
|
||||
.then(() => {
|
||||
toast.success(`${org_id} copied to clipboard`);
|
||||
})
|
||||
.catch((error) => {
|
||||
// Fallback for browsers that don't support clipboard API
|
||||
try {
|
||||
// Create temporary input element
|
||||
const tempInput = document.createElement('input');
|
||||
tempInput.value = org_id;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(tempInput);
|
||||
toast(`${org_id} copied to clipboard`);
|
||||
} catch (err) {
|
||||
toast("Failed to copy. Please try again.");
|
||||
console.error("Copy failed:", err);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FileCopyIcon style={{ color: "rgba(255,255,255,0.8)" }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{userdata.support === true ?
|
||||
<span style={{ display: "flex", alignItems: "center", marginLeft:16 }}>
|
||||
{/*<a href={mailsendingButton(selectedOrganization)} target="_blank" rel="noopener noreferrer" style={{textDecoration: "none"}} disabled={selectedStatus.length !== 0}>*/}
|
||||
<Button
|
||||
// variant="outlined"
|
||||
// color="primary"
|
||||
// disabled={selectedStatus.length !== 0}
|
||||
style={{
|
||||
width: 180,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
border: "1.5px solid #ff8544",
|
||||
background: "transparent",
|
||||
color: "#ff8544",
|
||||
fontSize: 16,
|
||||
textTransform: "none",
|
||||
boxShadow: "none",
|
||||
cursor: "pointer",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onClick={() => {
|
||||
console.log("Should send mail to admins of org with context")
|
||||
handleStatusChange({ target: { value: ["contacted"] } })
|
||||
// Open a new tab
|
||||
window.open(mailsendingButton(selectedOrganization), "_blank")
|
||||
}}
|
||||
>
|
||||
Sales mail
|
||||
</Button>
|
||||
|
||||
</span>
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* {isCloud ?
|
||||
<Tooltip
|
||||
title={`Your organization is in ${regiontag}. Click to change!`}
|
||||
style={{
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
style={{ cursor: "pointer", top: -10, right: 50, position: "absolute", }}
|
||||
onClick={() => {
|
||||
if (userdata.support === false) {
|
||||
toast("Region change is not directly implemented yet, and requires support help.")
|
||||
|
||||
if (window.drift !== undefined) {
|
||||
window.drift.api.startInteraction({
|
||||
interactionId: 386411,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Show region change modal
|
||||
console.log("Should open region change modal")
|
||||
setRegionChangeModalOpen(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{regiontag}
|
||||
</Avatar>
|
||||
</Tooltip>
|
||||
: null} */}
|
||||
</div>
|
||||
<OrgHeader
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
setSelectedOrganization={setSelectedOrganization}
|
||||
globalUrl={globalUrl}
|
||||
selectedOrganization={selectedOrganization}
|
||||
handleEditOrg={handleEditOrg}
|
||||
isEditOrgTab={true}
|
||||
handleGetOrg={handleGetOrg}
|
||||
/>
|
||||
<OrgHeaderexpanded
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
setSelectedOrganization={setSelectedOrganization}
|
||||
globalUrl={globalUrl}
|
||||
selectedOrganization={selectedOrganization}
|
||||
isEditOrgTab={true}
|
||||
handleGetOrg={handleGetOrg}
|
||||
serverside={serverside}
|
||||
/>
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
export default EditOrgTab;
|
||||
@@ -71,7 +71,7 @@ const EditWorkflow = (props) => {
|
||||
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
|
||||
|
||||
const [submitLoading, setSubmitLoading] = React.useState(false);
|
||||
const [showMoreClicked, setShowMoreClicked] = React.useState(expanded === true ? true : false);
|
||||
const [showMoreClicked, setShowMoreClicked] = React.useState(isEditing !== false ? true : false);
|
||||
|
||||
const [innerWorkflow, setInnerWorkflow] = React.useState(workflow)
|
||||
|
||||
@@ -88,6 +88,8 @@ const EditWorkflow = (props) => {
|
||||
const [inputMarkdown, setInputMarkdown] = React.useState(workflow?.form_control?.input_markdown !== undefined && workflow?.form_control?.input_markdown !== null ? workflow?.form_control?.input_markdown : "")
|
||||
const [scrollDone, setScrollDone] = React.useState(false)
|
||||
const [selectedYieldActions, setSelectedYieldActions] = React.useState(workflow?.form_control?.output_yields !== undefined && workflow?.form_control?.output_yields !== null ? JSON.parse(JSON.stringify(workflow?.form_control?.output_yields)) : [])
|
||||
const [selectedCleanupActions, setSelectedCleanupActions] = React.useState(workflow?.form_control?.cleanup_actions !== undefined && workflow?.form_control?.cleanup_actions !== null ? JSON.parse(JSON.stringify(workflow?.form_control?.cleanup_actions)) : [])
|
||||
|
||||
const [formWidth, setFormWidth] = React.useState(boxWidth === undefined || boxWidth === null ? 500 : boxWidth)
|
||||
|
||||
const classes = useStyles();
|
||||
@@ -103,9 +105,10 @@ const EditWorkflow = (props) => {
|
||||
const foundScroll = document.getElementById(scrollTo)
|
||||
if (foundScroll !== null) {
|
||||
// Smooth scroll
|
||||
foundScroll.scrollIntoView({ behavior: "smooth" })
|
||||
foundScroll.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
|
||||
}, 200)
|
||||
setScrollDone(true)
|
||||
|
||||
@@ -188,6 +191,10 @@ const EditWorkflow = (props) => {
|
||||
var upload = "";
|
||||
var total_count = 0
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io";
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
anchor={"right"}
|
||||
@@ -244,15 +251,13 @@ const EditWorkflow = (props) => {
|
||||
Workflows can be built from scratch, or from templates. <a href="/usecases2" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
|
||||
</Typography>
|
||||
|
||||
{/*
|
||||
<div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}>
|
||||
<WorkflowValidationTimeline
|
||||
|
||||
apps={apps}
|
||||
workflow={workflow}
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
<div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}>
|
||||
<WorkflowValidationTimeline
|
||||
|
||||
apps={apps}
|
||||
workflow={workflow}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showUpload === true ?
|
||||
<div style={{ float: "right" }}>
|
||||
@@ -290,7 +295,7 @@ const EditWorkflow = (props) => {
|
||||
bottom: 0,
|
||||
zIndex: 1002,
|
||||
backgroundColor: theme.palette.backgroundColor,
|
||||
height: 50,
|
||||
height: 75,
|
||||
paddingTop: 20,
|
||||
paddingLeft: 75,
|
||||
}}>
|
||||
@@ -324,15 +329,21 @@ const EditWorkflow = (props) => {
|
||||
innerWorkflow.form_control.input_markdown = inputMarkdown
|
||||
innerWorkflow.form_control.output_yields = selectedYieldActions
|
||||
innerWorkflow.form_control.form_width = formWidth
|
||||
innerWorkflow.form_control.cleanup_actions = selectedCleanupActions
|
||||
|
||||
innerWorkflow.name = name
|
||||
innerWorkflow.description = description
|
||||
|
||||
if (newWorkflowTags.length > 0) {
|
||||
innerWorkflow.tags = newWorkflowTags
|
||||
} else {
|
||||
innerWorkflow.tags = []
|
||||
}
|
||||
|
||||
if (selectedUsecases.length > 0) {
|
||||
innerWorkflow.usecase_ids = selectedUsecases
|
||||
} else {
|
||||
innerWorkflow.usecase_ids = []
|
||||
}
|
||||
|
||||
if (dueDate > 0) {
|
||||
@@ -361,7 +372,6 @@ const EditWorkflow = (props) => {
|
||||
setWorkflow({})
|
||||
} else {
|
||||
setWorkflow(innerWorkflow)
|
||||
console.log("editing workflow: ", innerWorkflow)
|
||||
}
|
||||
|
||||
setSubmitLoading(true)
|
||||
@@ -505,7 +515,7 @@ const EditWorkflow = (props) => {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
multiLine
|
||||
multiline
|
||||
rows={3}
|
||||
color="primary"
|
||||
defaultValue={innerWorkflow.description}
|
||||
@@ -537,81 +547,10 @@ const EditWorkflow = (props) => {
|
||||
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<DatePicker
|
||||
sx={{
|
||||
marginTop: 3,
|
||||
marginLeft: 3,
|
||||
}}
|
||||
value={dueDate}
|
||||
label="Due Date"
|
||||
format="YYYY-MM-DD"
|
||||
onChange={(newValue) => {
|
||||
setDueDate(newValue)
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
<div />
|
||||
|
||||
<FormControl style={{ marginTop: 15, }}>
|
||||
<FormLabel id="demo-row-radio-buttons-group-label">Type</FormLabel>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-labelledby="demo-row-radio-buttons-group-label"
|
||||
name="row-radio-buttons-group"
|
||||
defaultValue={innerWorkflow.workflow_type}
|
||||
onChange={(e) => {
|
||||
console.log("Data: ", e.target.value)
|
||||
|
||||
innerWorkflow.workflow_type = e.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
>
|
||||
<FormControlLabel value="trigger" control={<Radio />} label="Trigger" />
|
||||
<FormControlLabel value="subflow" control={<Radio />} label="Subflow" />
|
||||
<FormControlLabel value="standalone" control={<Radio />} label="Standalone" />
|
||||
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
|
||||
<TextField
|
||||
onBlur={(event) => {
|
||||
innerWorkflow.blogpost = event.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
defaultValue={innerWorkflow.blogpost}
|
||||
placeholder="A blogpost or other reference for how this work workflow was built, and what it's for."
|
||||
rows="1"
|
||||
label="blogpost"
|
||||
margin="dense"
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
onBlur={(event) => {
|
||||
innerWorkflow.video = event.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
defaultValue={innerWorkflow.video}
|
||||
placeholder="A youtube or loom link to the video"
|
||||
rows="1"
|
||||
label="Video"
|
||||
margin="dense"
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
|
||||
<TextField
|
||||
onBlur={(event) => {
|
||||
@@ -633,21 +572,21 @@ const EditWorkflow = (props) => {
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<Divider style={{ marginTop: 20, marginBottom: 20, }} />
|
||||
<Divider id="mssp_control" style={{ marginTop: 20, marginBottom: 20, }} />
|
||||
|
||||
<Typography variant="h4" style={{ marginTop: 50, }}>
|
||||
MSSP controls
|
||||
Multi-Tenancy, Backups & Security
|
||||
</Typography>
|
||||
|
||||
|
||||
<Typography variant="body1" style={{ marginTop: 50, }}>
|
||||
MSSP Suborg Distribution (<b>beta</b> - contact support@shuffler.io for more info)
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 30, marginBottom: 10, }}>
|
||||
Multi-Tenant Workflows. Make one workflow, and keep a separate, synced copy in all your tenants. Control distributed auth, runtime locations, files, datastore keys etc. (contact support@shuffler.io if you want a demo. Please try it!)
|
||||
</Typography>
|
||||
|
||||
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
|
||||
userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ?
|
||||
userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ?
|
||||
<Typography variant="body2" style={{ marginTop: 10, color: "rgba(255,255,255,0.7)" }}>
|
||||
Your organization does not have any suborgs yet OR your user may not have access to available suborgs. Please <a href="/admin?tab=suborgs" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">make one</a> or get access to suborgs by another admin, then try again.
|
||||
Your organization does not have any suborgs yet OR your user may not have access to available suborgs. Please <a href="/admin?tab=tenants" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">make one</a> or get access to suborgs by another admin, then try again.
|
||||
</Typography>
|
||||
:
|
||||
<Typography variant="body2" style={{ marginTop: 10, color: "rgba(255,255,255,0.7)" }}>
|
||||
@@ -687,6 +626,7 @@ const EditWorkflow = (props) => {
|
||||
All
|
||||
</MenuItem>
|
||||
{userdata.orgs.map((data, index) => {
|
||||
|
||||
var skipOrg = false;
|
||||
if (data.creator_org !== undefined && data.creator_org !== null && data.creator_org === userdata.active_org.id) {
|
||||
// Finds the parent org
|
||||
@@ -694,6 +634,8 @@ const EditWorkflow = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const correctRegion = data?.region_url === userdata?.region_url
|
||||
|
||||
const imagesize = 22
|
||||
const imageStyle = {
|
||||
width: imagesize,
|
||||
@@ -727,7 +669,11 @@ const EditWorkflow = (props) => {
|
||||
|
||||
|
||||
return (
|
||||
<MenuItem key={index} value={data.id}>
|
||||
<MenuItem
|
||||
key={index}
|
||||
value={data.id}
|
||||
disabled={isCloud && !correctRegion}
|
||||
>
|
||||
<Checkbox checked={innerWorkflow.suborg_distribution !== undefined && innerWorkflow.suborg_distribution !== null && innerWorkflow.suborg_distribution.includes(data.id)} />
|
||||
{image}{" "}
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
@@ -738,22 +684,19 @@ const EditWorkflow = (props) => {
|
||||
})}
|
||||
</Select>
|
||||
:
|
||||
<Link to={"/admin?tab=suborgs"} style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">
|
||||
<Link to={"/admin?tab=tenants"} style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">
|
||||
<Typography variant="body2" style={{ marginTop: 10, }}>
|
||||
Create a sub-org to distribute workflows to suborgs.
|
||||
</Typography>
|
||||
</Link>
|
||||
}
|
||||
|
||||
{/*<Divider style={{marginTop: 20, marginBottom: 20, }} />*/}
|
||||
|
||||
|
||||
|
||||
<Typography variant="body1" style={{ marginTop: 100, }}>
|
||||
<Typography variant="h6" style={{ marginTop: 75, }}>
|
||||
Git Backup Repository
|
||||
</Typography>
|
||||
<Typography variant="body2" style={{ textAlign: "left", marginTop: 5, }} color="textSecondary">
|
||||
Decide where this workflow is backed up in a Git repository. Will create logs and notifications if upload fails. <b>The repository and branch must already have been initialized</b>. Files will show up in the root folder in the format 'orgid/workflow status/workflow id.json' without images. Overrides your <a href="/admin?admin_tab=organization" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">default backup repository</a>. <a href="/docs/configuration#environment-variables" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">Credentials are encrypted.</a> Creates <a href="/admin?admin_tab=priorities" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">notifications</a> if it fails.
|
||||
Decide where this workflow is backed up in a Git repository. Will create logs and notifications if upload fails. <b>The repository and branch must already have been initialized</b>. Files will show up in the root folder in the format 'orgid/workflow status/workflow id.json' and be formatted, with removed images, to make diffing easy. Overrides your <a href="/admin?admin_tab=org_config" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">default backup repository</a>. <a href="/docs/configuration#environment-variables" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">Credentials are encrypted.</a> Creates <a href="/admin?admin_tab=notifications" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">notifications</a> if it fails.
|
||||
</Typography>
|
||||
<Grid container style={{ marginTop: 10, }} spacing={2}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
@@ -895,6 +838,60 @@ const EditWorkflow = (props) => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div id="cleanup">
|
||||
<Typography variant="h6" style={{ marginTop: 50, }}>
|
||||
Result cleanup ({selectedCleanupActions.length === 0 ? "No cleanup yet" : selectedCleanupActions.length === 1 ? "Cleaning up 1 node" : `Cleaning up ${selectedCleanupActions.length} nodes`})
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 20, }}>
|
||||
<b>Beta Feature</b>: When a workflow run is done, the data from the selected actions will be removed by replacing it with a default value. This is useful for cleaning up sensitive data, or data that is no longer needed. This is done after a workflow run is finished or aborted, and is not reversible. Data will remain in the workflow run result (last node value) even if the action result itself is cleaned up.
|
||||
</Typography>
|
||||
|
||||
<FormControl style={{ marginTop: 15, }}>
|
||||
<Select
|
||||
defaultValue=""
|
||||
id="result-cleanup-control"
|
||||
label="Cleaned Up nodes"
|
||||
multiple
|
||||
fullWidth
|
||||
style={{ width: 500, }}
|
||||
value={selectedCleanupActions === [] ? ["none"] : selectedCleanupActions}
|
||||
renderValue={(selected) => selected.join(', ')}
|
||||
onChange={(event) => {
|
||||
if (event.target.value.length > 0) {
|
||||
if (event.target.value.includes("none")) {
|
||||
setSelectedCleanupActions([])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const newvalue = event?.target?.value
|
||||
if (newvalue === undefined || newvalue === null) {
|
||||
} else {
|
||||
setSelectedCleanupActions(newvalue)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value="none">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
{workflow?.actions?.map((action, actionIndex) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={actionIndex}
|
||||
value={action.id}
|
||||
>
|
||||
<Tooltip title={action.app_name} key={actionIndex}>
|
||||
<img src={action.large_image !== undefined && action.large_image !== null && action.large_image.length > 0 ? action.large_image : theme.palette.defaultImage} style={{ width: 20, height: 20, marginRight: 10, }} />
|
||||
</Tooltip>
|
||||
{action.label}
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<Divider style={{ marginTop: 20, marginBottom: 20, }} />
|
||||
|
||||
|
||||
@@ -1094,11 +1091,11 @@ const EditWorkflow = (props) => {
|
||||
|
||||
<div id="output_control">
|
||||
<Typography variant="h6" style={{ marginTop: 50, }}>
|
||||
Output Control ({selectedYieldActions.length === 0 ? "No Returns" : selectedYieldActions.length === 1 ? "Returning 1 node" : `Returning ${selectedYieldActions.length} nodes`})
|
||||
Form Output Control ({selectedYieldActions.length === 0 ? "No Returns" : selectedYieldActions.length === 1 ? "Returning 1 node" : `Returning ${selectedYieldActions.length} nodes`})
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 20, }}>
|
||||
When running this workflow, the output will be shown as a Markdown object by default, with JSON objects being rendered. By adding nodes below, they will be shown while the workflow is running as soon as they get a result. Failing/Skipped nodes are not shown. This makes it possible to track progress for more complex usecases.
|
||||
When running this workflow as a form, the output will be shown as a Markdown object by default, with JSON objects being rendered. By adding nodes below, they will be shown while the workflow is running as soon as they get a result. Failing/Skipped nodes are not shown. This makes it possible to track progress for more complex usecases.
|
||||
</Typography>
|
||||
|
||||
<FormControl style={{ marginTop: 15, }}>
|
||||
@@ -1146,41 +1143,130 @@ const EditWorkflow = (props) => {
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
{!isEditing ? <>
|
||||
<div style={{ marginTop: 20, }}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox />}
|
||||
label="Create workflow as code"
|
||||
style={{ marginTop: '12px' }}
|
||||
onChange={(e) => {
|
||||
setWorkflowAsCode(e.target.checked)
|
||||
workflow.workflow_as_code = e.target.checked
|
||||
setWorkflow(workflow)
|
||||
setInnerWorkflow(workflow)
|
||||
<Divider style={{marginTop: 75, marginBottom: 75, }}/>
|
||||
|
||||
|
||||
<Typography variant="h4" style={{ }}>
|
||||
Publishing
|
||||
|
||||
<Chip
|
||||
style={{ marginLeft: 20, marginTop: 10, }}
|
||||
color={workflow?.public === true ? "primary" : "secondary"}
|
||||
variant={workflow?.public === true ? "default" : "outlined"}
|
||||
label={workflow?.public === true ? "Public" : "NOT Public"}
|
||||
/>
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
|
||||
Publishing is related to making this workflow itself public. When publishing a workflow, all the details (except sensitive info) become available to anyone. The fields below will help a user and Shuffle's system understand your workflow better. When a workflow is published, you keep the original, and a copy enters the Shuffle workflow search, and is associated with your <a href="/creators" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">creator</a> or partner account, if you have one. You can always unpublish the workflow after. When ready to publish, click the three dots next to a workflow on the main workflow page.
|
||||
|
||||
You can always unpublish a workflow after.
|
||||
</Typography>
|
||||
|
||||
|
||||
<LocalizationProvider style={{marginLeft: 0, }} dateAdapter={AdapterDayjs}>
|
||||
<DatePicker
|
||||
sx={{
|
||||
marginTop: 3,
|
||||
marginLeft: 3,
|
||||
}}
|
||||
value={dueDate}
|
||||
label="Due Date"
|
||||
format="YYYY-MM-DD"
|
||||
onChange={(newValue) => {
|
||||
setDueDate(newValue)
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
|
||||
</div>
|
||||
</> : null}
|
||||
<FormControl style={{ marginTop: 15, }}>
|
||||
<FormLabel id="demo-row-radio-buttons-group-label">Type</FormLabel>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-labelledby="demo-row-radio-buttons-group-label"
|
||||
name="row-radio-buttons-group"
|
||||
defaultValue={innerWorkflow.workflow_type}
|
||||
onChange={(e) => {
|
||||
console.log("Data: ", e.target.value)
|
||||
|
||||
<Tooltip color="primary" title={"Add more details"} placement="top">
|
||||
<Button
|
||||
style={{ margin: "auto", marginTop: 50, marginBottom: 10, textAlign: "center", textTransform: "none", }}
|
||||
variant="outlined"
|
||||
disabled={newWorkflow === true}
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
setShowMoreClicked(!showMoreClicked);
|
||||
innerWorkflow.workflow_type = e.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Agentic workflows takes an input based on input questions (forms) and performs actions based on it by itself, using Large Action Models & Singul">
|
||||
<FormControlLabel value="agentic" control={<Radio />} label="Agentic" />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Trigger workflows are typically running a schedule to get some data, doing some deduplication before sending it to a subflow or standalone workflow.">
|
||||
<FormControlLabel value="trigger" control={<Radio />} label="Trigger" />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Subflow workflows are typically used to subprocess some data, and in some cases return the result to the parent workflow.">
|
||||
<FormControlLabel value="subflow" control={<Radio />} label="Subflow" />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Standalone is default. This has no impact on Shuffle as a system.">
|
||||
<FormControlLabel value="standalone" control={<Radio />} label="Standalone" />
|
||||
</Tooltip>
|
||||
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
|
||||
<TextField
|
||||
onBlur={(event) => {
|
||||
innerWorkflow.blogpost = event.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
>
|
||||
{showMoreClicked ? <ExpandLessIcon style={{ marginRight: 10, }} /> : <ExpandMoreIcon style={{ marginRight: 10, }} />}
|
||||
{showMoreClicked ? "Less Options" : "More Options"}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
defaultValue={innerWorkflow.blogpost}
|
||||
placeholder="A blogpost or other reference for how this work workflow was built, and what it's for."
|
||||
rows="1"
|
||||
label="blogpost"
|
||||
margin="dense"
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
onBlur={(event) => {
|
||||
innerWorkflow.video = event.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
defaultValue={innerWorkflow.video}
|
||||
placeholder="A youtube or loom link to the video"
|
||||
rows="1"
|
||||
label="Video"
|
||||
margin="dense"
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip color="primary" title={"Add more details"} placement="top">
|
||||
<Button
|
||||
style={{ margin: "auto", marginTop: 50, marginBottom: 10, textAlign: "center", textTransform: "none", }}
|
||||
variant="outlined"
|
||||
disabled={newWorkflow === true}
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
setShowMoreClicked(!showMoreClicked);
|
||||
}}
|
||||
>
|
||||
{showMoreClicked ? <ExpandLessIcon style={{ marginRight: 10, }} /> : <ExpandMoreIcon style={{ marginRight: 10, }} />}
|
||||
{showMoreClicked ? "Less Options" : "More Options"}
|
||||
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -89,7 +89,6 @@ const Files = memo((props) => {
|
||||
console.log('escape pressed')
|
||||
setRenderTextBox(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const changeDistribution = (id, selectedSubOrg) => {
|
||||
@@ -1051,6 +1050,7 @@ const Files = memo((props) => {
|
||||
</Dialog>
|
||||
</FormControl>
|
||||
) : null}
|
||||
|
||||
<div style={{display: "inline-flex", position:"relative", top: 8}}>
|
||||
{renderTextBox ?
|
||||
<Tooltip title={"Close"} style={{}} aria-label={""}>
|
||||
@@ -1263,7 +1263,7 @@ const Files = memo((props) => {
|
||||
}
|
||||
const isDistributed = file?.suborg_distribution?.length > 0 ? true : false;
|
||||
const filenamesplit = file.filename.split(".")
|
||||
const iseditable = file.filesize < 2000000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1])
|
||||
const iseditable = file.filesize < 2000000 && file.status === "active" && (allowedFileTypes.includes(filenamesplit[filenamesplit.length-1]) || !file?.filename.includes("."))
|
||||
return (
|
||||
<ListItem
|
||||
key={index}
|
||||
@@ -1567,7 +1567,7 @@ const Files = memo((props) => {
|
||||
placement="top"
|
||||
>
|
||||
<Checkbox
|
||||
disabled={selectedOrganization.creator_org !== undefined && selectedOrganization.creator_org !== null && selectedOrganization.creator_org !== "" ? true : false}
|
||||
disabled={userdata?.active_org?.role !== "admin" || (selectedOrganization.creator_org !== undefined && selectedOrganization.creator_org !== null && selectedOrganization.creator_org !== "" ) ? true : false}
|
||||
checked={isDistributed}
|
||||
style={{ }}
|
||||
color="secondary"
|
||||
|
||||
@@ -644,6 +644,7 @@ const FixWorkflowValidationErrors = (props) => {
|
||||
console.log("Workflow validation: ", workflow.validation)
|
||||
return (
|
||||
<div>
|
||||
{/*
|
||||
{workflow.errors !== undefined && workflow.errors !== null ?
|
||||
<div>
|
||||
General errors: {workflow.errors.length}
|
||||
@@ -656,11 +657,8 @@ const FixWorkflowValidationErrors = (props) => {
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<Divider style={{marginTop: 15, marginBottom: 15, }}/>
|
||||
|
||||
|
||||
{workflow.validation.errors !== undefined && workflow.validation.errors !== null ?
|
||||
workflow.validation.errors !== undefined && workflow.validation.errors !== null ?
|
||||
<div>
|
||||
Validation errors: {workflow.validation.errors.length}
|
||||
{workflow.validation.errors.map((error, index) => {
|
||||
@@ -675,10 +673,12 @@ const FixWorkflowValidationErrors = (props) => {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
: null*/}
|
||||
|
||||
{/*
|
||||
<Divider style={{marginTop: 15, marginBottom: 15, }} />
|
||||
Apps loaded: {apps.length}
|
||||
*/}
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState, useContext, useCallback, useMemo } from "react";
|
||||
import React, { useEffect, useRef, useState, useContext, useCallback, useMemo, memo } from "react";
|
||||
import {
|
||||
ExpandLess as ExpandLessIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
Add as AddIcon,
|
||||
BorderColor,
|
||||
Close as CloseIcon,
|
||||
ConstructionOutlined,
|
||||
ConstructionOutlined as ConstructionOutlinedIcon,
|
||||
Toc as TocIcon,
|
||||
Settings as SettingsIcon
|
||||
} from "@mui/icons-material";
|
||||
import SearchBox from "./SearchData.jsx";
|
||||
import {
|
||||
@@ -27,11 +29,9 @@ import {
|
||||
Fade,
|
||||
Portal,
|
||||
Collapse,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import theme from "../theme.jsx";
|
||||
import {
|
||||
Settings as SettingsIcon
|
||||
} from "@mui/icons-material";
|
||||
import RecentWorkflow from "../components/RecentWorkflow.jsx";
|
||||
|
||||
import { useNavigate } from "react-router";
|
||||
@@ -143,8 +143,6 @@ useEffect(() => {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
maxHeight: 250,
|
||||
overflowY: "auto",
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "#494949 transparent",
|
||||
"& .MuiAutocomplete-listbox": {
|
||||
@@ -238,7 +236,7 @@ useEffect(() => {
|
||||
setOpenautomateTab(true);
|
||||
setOpenSecurityTab(false);
|
||||
setCurrentOpenTab("workflows");
|
||||
} else if ((lastTabOpenByUser === "apps" && currentPath.includes("/search")) || currentPath.includes("/search")) {
|
||||
} else if ((lastTabOpenByUser === "apps" && currentPath.includes("/apps")) || currentPath.includes("/apps")) {
|
||||
setOpenautomateTab(true);
|
||||
setOpenSecurityTab(false);
|
||||
setCurrentOpenTab("apps");
|
||||
@@ -500,16 +498,6 @@ useEffect(() => {
|
||||
})
|
||||
</MenuItem>
|
||||
</Link>
|
||||
<Link to="/usecases" style={hrefStyle}>
|
||||
<MenuItem
|
||||
onClick={(event) => {
|
||||
handleClose();
|
||||
}}
|
||||
style={{fontSize: 18}}
|
||||
>
|
||||
<LightbulbIcon style={{ marginRight: 5 }} /> Use Cases
|
||||
</MenuItem>
|
||||
</Link>
|
||||
|
||||
<Divider style={{ marginTop: 10, marginBottom: 10, }} />
|
||||
|
||||
@@ -537,7 +525,7 @@ useEffect(() => {
|
||||
<Divider style={{ marginBottom: 10, }} />
|
||||
|
||||
<Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}>
|
||||
Version: 2.0.0-rc2
|
||||
Version: 2.0.0
|
||||
</Typography>
|
||||
</Menu>
|
||||
</span>
|
||||
@@ -670,8 +658,8 @@ useEffect(() => {
|
||||
};
|
||||
|
||||
const getRegionTag = (region_url) => {
|
||||
//let regiontag = "UK";
|
||||
let regiontag = "EU";
|
||||
let regiontag = "UK";
|
||||
//let regiontag = "EU";
|
||||
if (
|
||||
region_url !== undefined &&
|
||||
region_url !== null &&
|
||||
@@ -704,16 +692,44 @@ useEffect(() => {
|
||||
|
||||
const CheckOrgStates = useCallback(() => {
|
||||
setOrgOptions(
|
||||
userdata?.orgs?.map((org) => ({
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
image: org.image,
|
||||
region_url: getRegionTag(org.region_url),
|
||||
})) || []
|
||||
userdata?.orgs?.map((org) => {
|
||||
let skipOrg = false;
|
||||
|
||||
if (
|
||||
org.creator_org !== undefined &&
|
||||
org.creator_org !== null &&
|
||||
org.creator_org.length > 0
|
||||
) {
|
||||
// Finds the parent org
|
||||
for (let key in userdata.child_orgs) {
|
||||
if (userdata.child_orgs[key].id === org.creator_org) {
|
||||
skipOrg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (skipOrg) {
|
||||
return null; // Skip this org
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
image: org.image,
|
||||
region_url: getRegionTag(org.region_url),
|
||||
margin_left:
|
||||
org.creator_org !== undefined &&
|
||||
org.creator_org !== null &&
|
||||
org.creator_org.length > 0 ? 20 : 0,
|
||||
};
|
||||
}) || []
|
||||
);
|
||||
|
||||
setActiveOrgName(userdata?.active_org?.name || "Select Organization");
|
||||
setSelectedOrg(userdata?.active_org?.name || "Select Organization");
|
||||
},[orgOptions, activeOrgName, selectedOrg]);
|
||||
}, [userdata]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof userdata?.id === "string" && userdata?.id?.length > 0) {
|
||||
@@ -735,37 +751,8 @@ useEffect(() => {
|
||||
fontSize: 18
|
||||
};
|
||||
|
||||
const modalView = (
|
||||
<Dialog
|
||||
open={searchBarModalOpen}
|
||||
onClose={() => {
|
||||
setSearchBarModalOpen(false);
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
minWidth: 750,
|
||||
height: 785,
|
||||
borderRadius: 16,
|
||||
border: "1px solid var(--Container-Stroke, #494949)",
|
||||
background: "var(--Container, #000000)",
|
||||
boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)",
|
||||
zIndex: 13000,
|
||||
paddingTop: 20,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogContent className='dialog-content' style={{}}>
|
||||
<SearchBox globalUrl={globalUrl} serverside={serverside} userdata={userdata} />
|
||||
</DialogContent>
|
||||
<Divider style={{overflow: "hidden"}}/>
|
||||
<span style={{display:"flex", width:"100%", height:30}}>
|
||||
</span>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
const getRegionFlag = (region_url) => {
|
||||
var region = "gb";
|
||||
var region = "UK";
|
||||
const regionMapping = {
|
||||
"US": "us",
|
||||
"EU": "eu",
|
||||
@@ -801,20 +788,8 @@ useEffect(() => {
|
||||
zoom: 0.8,
|
||||
height: "calc((100vh - 32px)*1.2)",
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (window?.location?.pathname?.includes("/workflows/")) {
|
||||
setExpandLeftNav(false);
|
||||
}
|
||||
}}
|
||||
onMouseOver={() => {
|
||||
if (window?.location?.pathname?.includes("/workflows/")) {
|
||||
setExpandLeftNav(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
>
|
||||
{modalView}
|
||||
{searchBarModalOpen ? <ModalView serverside={serverside} userdata={userdata} searchBarModalOpen={searchBarModalOpen} setSearchBarModalOpen={setSearchBarModalOpen} globalUrl={globalUrl} /> : null}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -823,14 +798,47 @@ useEffect(() => {
|
||||
alignItems: "center",
|
||||
padding: "24px 16px 24px 27px",
|
||||
}}
|
||||
>
|
||||
<a href="/" style={{ textDecoration: "none" }}>
|
||||
<img
|
||||
src={ShuffleLogo}
|
||||
alt="Shuffle Logo"
|
||||
style={{ width: 24, height: 24 }}
|
||||
/>
|
||||
</a>
|
||||
onMouseOver={()=>{
|
||||
if(window?.location?.pathname?.includes("/workflows/")) {
|
||||
setExpandLeftNav(true)
|
||||
}
|
||||
}}
|
||||
|
||||
onMouseLeave={()=>{
|
||||
if(window?.location?.pathname?.includes("/workflows/")) {
|
||||
setExpandLeftNav(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title="Go to Home"
|
||||
placement="top"
|
||||
arrow
|
||||
componentsProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
backgroundColor: "rgba(33, 33, 33, 1)",
|
||||
color: "rgba(241, 241, 241, 1)",
|
||||
fontSize: 12,
|
||||
border: "1px solid rgba(73, 73, 73, 1)",
|
||||
fontFamily: theme?.typography?.fontFamily,
|
||||
}
|
||||
},
|
||||
popper: {
|
||||
sx: {
|
||||
zIndex: 1000019,
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Link to="/">
|
||||
<img
|
||||
src={ShuffleLogo}
|
||||
alt="Shuffle Logo"
|
||||
style={{ width: 24, height: 24 }}
|
||||
/>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -871,7 +879,7 @@ useEffect(() => {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width:"100%", height: "100%", overflowY: "auto", overflowX: "hidden",transition: 'display 0.3s ease',paddingTop: 0.5 }} onMouseOver={()=>{!leftSideBarOpenByClick && setExpandLeftNav(true);}} onMouseLeave={()=>{!leftSideBarOpenByClick && setExpandLeftNav(false);setOpenAutocomplete(false);}}>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width:"100%", height: "100%", overflowY: "auto", overflowX: "hidden",transition: 'display 0.3s ease',paddingTop: 0.5 }} onMouseOver={()=>{(!leftSideBarOpenByClick || window?.location?.pathname?.includes("/workflows/")) && setExpandLeftNav(true)}} onMouseLeave={()=>{(!leftSideBarOpenByClick || window?.location?.pathname?.includes("/workflows/")) && setExpandLeftNav(false);setOpenAutocomplete(false)}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -1145,7 +1153,7 @@ useEffect(() => {
|
||||
color: currentOpenTab === "apps" && currentPath.includes("/apps") ? "#FFFFFF" : "#C8C8C8",
|
||||
justifyContent: "flex-start",
|
||||
textTransform: "none",
|
||||
backgroundColor: currentOpenTab === "apps" && expandLeftNav && currentPath.includes("/apps2") ? "#2f2f2f": "transparent",
|
||||
backgroundColor: currentOpenTab === "apps" && expandLeftNav && currentPath.includes("/apps") ? "#2f2f2f": "transparent",
|
||||
marginLeft: 16,
|
||||
fontSize: 18
|
||||
}}
|
||||
@@ -1153,7 +1161,7 @@ useEffect(() => {
|
||||
event.currentTarget.style.backgroundColor = "#2f2f2f";
|
||||
}}
|
||||
onMouseOut={(event)=>{
|
||||
event.currentTarget.style.backgroundColor = currentOpenTab === "apps" && expandLeftNav && currentPath.includes("/search") ? "#2f2f2f": "transparent";
|
||||
event.currentTarget.style.backgroundColor = currentOpenTab === "apps" && expandLeftNav && currentPath.includes("/apps") ? "#2f2f2f": "transparent";
|
||||
}}
|
||||
disableRipple={expandLeftNav ? false : true}
|
||||
>
|
||||
@@ -1176,7 +1184,7 @@ useEffect(() => {
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
marginTop: 1
|
||||
marginTop: 0
|
||||
}}
|
||||
>
|
||||
<span style={{ display: "inline-block", width: "100%" }}>
|
||||
@@ -1206,12 +1214,12 @@ useEffect(() => {
|
||||
: "transparent";
|
||||
}}
|
||||
>
|
||||
<ShieldOutlinedIcon
|
||||
<TocIcon
|
||||
style={{
|
||||
width: 18,
|
||||
height: 18,
|
||||
marginRight: expandLeftNav ? 10 : 0,
|
||||
color: userdata?.support ? "inherit" : "#6F6F6F",
|
||||
color: "inherit",
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
@@ -1224,7 +1232,7 @@ useEffect(() => {
|
||||
: "#C8C8C8"
|
||||
}}
|
||||
>
|
||||
Discover
|
||||
Content
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
@@ -1263,7 +1271,7 @@ useEffect(() => {
|
||||
<Collapse in={openSecurityTab} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
style={{
|
||||
maxHeight: openSecurityTab && expandLeftNav ? 100 : 0,
|
||||
maxHeight: openSecurityTab && expandLeftNav ? 135 : 0,
|
||||
overflow: "hidden",
|
||||
transition: "max-height 0.3s ease, opacity 0.3s ease",
|
||||
display: "flex",
|
||||
@@ -1277,19 +1285,18 @@ useEffect(() => {
|
||||
to={"/forms"}
|
||||
style={{
|
||||
...hrefStyle,
|
||||
pointerEvents: userdata?.support ? "auto" : "none",
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
if (!userdata?.support) return;
|
||||
setCurrentOpenTab("detection");
|
||||
localStorage.setItem("lastTabOpenByUser", "detection");
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: 35,
|
||||
color: userdata?.support ? "#C8C8C8" : "#6F6F6F",
|
||||
color: "#C8C8C8",
|
||||
justifyContent: "flex-start",
|
||||
textTransform: "none",
|
||||
backgroundColor:
|
||||
@@ -1297,11 +1304,10 @@ useEffect(() => {
|
||||
? "#2f2f2f"
|
||||
: "transparent",
|
||||
"&:hover": {
|
||||
backgroundColor: userdata?.support ? "#2f2f2f" : "transparent",
|
||||
backgroundColor: "#2f2f2f",
|
||||
},
|
||||
cursor: userdata?.support ? "pointer" : "not-allowed",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
disabled={userdata?.support === false}
|
||||
>
|
||||
<span style={{ position: "relative", left: !expandLeftNav ? 10 : 0, marginRight: 10, fontSize: 18 }}>
|
||||
•
|
||||
@@ -1313,11 +1319,9 @@ useEffect(() => {
|
||||
transition: "opacity 0.3s ease",
|
||||
fontSize: 18,
|
||||
color:
|
||||
userdata?.support && currentOpenTab === "detection" && currentPath.includes("/detection")
|
||||
currentOpenTab === "detection" && currentPath.includes("/detection")
|
||||
? "#F1F1F1"
|
||||
: userdata?.support
|
||||
? "#C8C8C8"
|
||||
: "#6F6F6F",
|
||||
: "#C8C8C8"
|
||||
}}
|
||||
>
|
||||
Forms
|
||||
@@ -1330,19 +1334,18 @@ useEffect(() => {
|
||||
to={"/admin?tab=datastore"}
|
||||
style={{
|
||||
...hrefStyle,
|
||||
pointerEvents: userdata?.support ? "auto" : "none",
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
if (!userdata?.support) return;
|
||||
setCurrentOpenTab("response");
|
||||
localStorage.setItem("lastTabOpenByUser", "response");
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: 35,
|
||||
color: userdata?.support ? "#C8C8C8" : "#6F6F6F",
|
||||
color: "#C8C8C8",
|
||||
justifyContent: "flex-start",
|
||||
textTransform: "none",
|
||||
backgroundColor:
|
||||
@@ -1350,9 +1353,9 @@ useEffect(() => {
|
||||
? "#2f2f2f"
|
||||
: "transparent",
|
||||
"&:hover": {
|
||||
backgroundColor: userdata?.support ? "#2f2f2f" : "transparent",
|
||||
backgroundColor: "#2f2f2f",
|
||||
},
|
||||
cursor: userdata?.support ? "pointer" : "not-allowed",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<span style={{ position: "relative", left: !expandLeftNav ? 10 : 0, marginRight: 10, fontSize: 18 }}>
|
||||
@@ -1365,11 +1368,9 @@ useEffect(() => {
|
||||
transition: "opacity 0.3s ease",
|
||||
fontSize: 18,
|
||||
color:
|
||||
userdata?.support && currentOpenTab === "response" && currentPath.includes("/response")
|
||||
currentOpenTab === "response" && currentPath.includes("/response")
|
||||
? "#F1F1F1"
|
||||
: userdata?.support
|
||||
? "#C8C8C8"
|
||||
: "#6F6F6F",
|
||||
: "#C8C8C8"
|
||||
}}
|
||||
>
|
||||
Datastore
|
||||
@@ -1377,24 +1378,24 @@ useEffect(() => {
|
||||
</Button>
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
<span style={{ display: "inline-block", width: "100%" }}>
|
||||
<Link
|
||||
to={"/admin?tab=files"}
|
||||
style={{
|
||||
...hrefStyle,
|
||||
pointerEvents: userdata?.support ? "auto" : "none",
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
if (!userdata?.support) return;
|
||||
setCurrentOpenTab("response");
|
||||
localStorage.setItem("lastTabOpenByUser", "response");
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: 35,
|
||||
color: userdata?.support ? "#C8C8C8" : "#6F6F6F",
|
||||
color: "#C8C8C8",
|
||||
justifyContent: "flex-start",
|
||||
textTransform: "none",
|
||||
backgroundColor:
|
||||
@@ -1402,9 +1403,9 @@ useEffect(() => {
|
||||
? "#2f2f2f"
|
||||
: "transparent",
|
||||
"&:hover": {
|
||||
backgroundColor: userdata?.support ? "#2f2f2f" : "transparent",
|
||||
backgroundColor: "#2f2f2f",
|
||||
},
|
||||
cursor: userdata?.support ? "pointer" : "not-allowed",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<span style={{ position: "relative", left: !expandLeftNav ? 10 : 0, marginRight: 10, fontSize: 18 }}>
|
||||
@@ -1417,11 +1418,9 @@ useEffect(() => {
|
||||
transition: "opacity 0.3s ease",
|
||||
fontSize: 18,
|
||||
color:
|
||||
userdata?.support && currentOpenTab === "response" && currentPath.includes("/response")
|
||||
currentOpenTab === "response" && currentPath.includes("/response")
|
||||
? "#F1F1F1"
|
||||
: userdata?.support
|
||||
? "#C8C8C8"
|
||||
: "#6F6F6F",
|
||||
: "#C8C8C8"
|
||||
}}
|
||||
>
|
||||
Files
|
||||
@@ -1429,8 +1428,59 @@ useEffect(() => {
|
||||
</Button>
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
<span style={{ display: "inline-block", width: "100%" }}>
|
||||
<Link
|
||||
to={"/admin?tab=locations"}
|
||||
style={{
|
||||
...hrefStyle,
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
setCurrentOpenTab("response");
|
||||
localStorage.setItem("lastTabOpenByUser", "response");
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: 35,
|
||||
color: "#C8C8C8",
|
||||
justifyContent: "flex-start",
|
||||
textTransform: "none",
|
||||
backgroundColor:
|
||||
currentOpenTab === "response" && currentPath.includes("/response")
|
||||
? "#2f2f2f"
|
||||
: "transparent",
|
||||
"&:hover": {
|
||||
backgroundColor: "#2f2f2f",
|
||||
},
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<span style={{ position: "relative", left: !expandLeftNav ? 10 : 0, marginRight: 10, fontSize: 18 }}>
|
||||
•
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
display: expandLeftNav ? "inline" : "none",
|
||||
opacity: expandLeftNav ? 1 : 0,
|
||||
transition: "opacity 0.3s ease",
|
||||
fontSize: 18,
|
||||
color:
|
||||
currentOpenTab === "response" && currentPath.includes("/response")
|
||||
? "#F1F1F1"
|
||||
: "#C8C8C8"
|
||||
}}
|
||||
>
|
||||
Hybrid Locations
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</span>
|
||||
</Box>
|
||||
</Collapse>
|
||||
|
||||
<Link to="/docs" style={hrefStyle}>
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
@@ -1465,6 +1515,38 @@ useEffect(() => {
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
|
||||
<Link to={isCloud ? "/admin?admin_tab=billingstats" : "/admin?admin_tab=locations"} style={hrefStyle}>
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
setCurrentOpenTab("admin");
|
||||
localStorage.setItem("lastTabOpenByUser", "admin");
|
||||
}}
|
||||
style={{
|
||||
...ButtonStyle,
|
||||
marginTop: 8,
|
||||
marginTop: 8,
|
||||
backgroundColor: currentOpenTab === "docs" && currentPath.includes("/admin") ? "#2f2f2f": "transparent",
|
||||
}}
|
||||
onMouseOver={(event)=>{
|
||||
event.currentTarget.style.backgroundColor = "#2f2f2f";
|
||||
}}
|
||||
onMouseOut={(event)=>{
|
||||
event.currentTarget.style.backgroundColor = currentOpenTab === "docs" && currentPath.includes("/admin") ? "#2f2f2f": "transparent";
|
||||
}}
|
||||
>
|
||||
<BusinessIcon style={{ width: 16, height: 16, marginRight: expandLeftNav ? 10 : 0, color: "rgba(255,255,255,0.5)", }} />
|
||||
<span
|
||||
style={{
|
||||
display: expandLeftNav ? "inline" : "none",
|
||||
color: currentOpenTab === "admin" && currentPath.includes("/admin") ? "#F1F1F1" : "#C8C8C8",
|
||||
}}
|
||||
>
|
||||
Admin
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
{recentworkflows?.length > 0 ?
|
||||
@@ -1550,6 +1632,7 @@ useEffect(() => {
|
||||
padding: option.id === "add_suborg" ? "0" : "12px 16px",
|
||||
marginTop: index !== 0 ? 8 : 0,
|
||||
borderRadius: 6,
|
||||
marginLeft: option.margin_left ? option.margin_left : 0,
|
||||
}}
|
||||
onMouseOver={(e) => {
|
||||
e.currentTarget.style.backgroundColor = "#444444";
|
||||
@@ -1619,12 +1702,15 @@ useEffect(() => {
|
||||
setAutocompleteValue(newInputValue);
|
||||
}}
|
||||
filterOptions={(options, params) => {
|
||||
const normalize = (str) => str.toLowerCase().replace(/[\s-]+/g, "");
|
||||
const input = normalize(params.inputValue);
|
||||
|
||||
return options.filter((option) =>
|
||||
option.name
|
||||
.toLowerCase()
|
||||
.includes(params.inputValue.toLowerCase())
|
||||
normalize(option.name).includes(input) ||
|
||||
normalize(option.region_url).includes(input)
|
||||
);
|
||||
}}
|
||||
|
||||
value={userOrgs}
|
||||
renderInput={(params) => (
|
||||
<Box
|
||||
@@ -1812,3 +1898,37 @@ useEffect(() => {
|
||||
};
|
||||
|
||||
export default LeftSideBar;
|
||||
|
||||
|
||||
const ModalView = memo(({searchBarModalOpen, setSearchBarModalOpen, globalUrl, serverside, userdata}) => {
|
||||
return (
|
||||
(
|
||||
<Dialog
|
||||
open={searchBarModalOpen}
|
||||
onClose={() => {
|
||||
setSearchBarModalOpen(false);
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
minWidth: 750,
|
||||
height: 785,
|
||||
borderRadius: 16,
|
||||
border: "1px solid var(--Container-Stroke, #494949)",
|
||||
background: "var(--Container, #000000)",
|
||||
boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)",
|
||||
zIndex: 13000,
|
||||
paddingTop: 20,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogContent className='dialog-content' style={{}}>
|
||||
<SearchBox globalUrl={globalUrl} serverside={serverside} userdata={userdata} />
|
||||
</DialogContent>
|
||||
<Divider style={{overflow: "hidden"}}/>
|
||||
<span style={{display:"flex", width:"100%", height:30}}>
|
||||
</span>
|
||||
</Dialog>
|
||||
)
|
||||
)
|
||||
});
|
||||
|
||||
@@ -869,8 +869,8 @@ const LicencePopup = (props) => {
|
||||
:
|
||||
shuffleVariant === 0 ? "price_1PZPSSEJjT17t98NLJoTMYja" : "price_1PZPQuEJjT17t98N3yORUtd9"
|
||||
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
|
||||
const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=success`
|
||||
const failUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=failure`
|
||||
|
||||
console.log("Priceitem: ", priceItem, shuffleVariant)
|
||||
var checkoutObject = {
|
||||
@@ -888,7 +888,7 @@ const LicencePopup = (props) => {
|
||||
}
|
||||
|
||||
if (stripe === undefined || stripe === null || stripe.redirectToCheckout === undefined) {
|
||||
window.open("https://shuffler.io/admin?admin_tab=billing&payment=stripe_error", "_self")
|
||||
window.open("https://shuffler.io/admin?admin_tab=billingstats&payment=stripe_error", "_self")
|
||||
}
|
||||
|
||||
stripe.redirectToCheckout(checkoutObject)
|
||||
|
||||
@@ -465,7 +465,7 @@ const Header = (props) => {
|
||||
</Link>
|
||||
|
||||
<Divider style={{ marginTop: 10, marginBottom: 10, }} />
|
||||
<Link to="/admin?admin_tab=priorities" style={hrefStyle}>
|
||||
<Link to="/admin?admin_tab=notifications" style={hrefStyle}>
|
||||
<MenuItem
|
||||
onClick={(event) => {
|
||||
handleClose();
|
||||
@@ -1107,7 +1107,7 @@ const Header = (props) => {
|
||||
);
|
||||
})}
|
||||
<Divider />
|
||||
<Link to="/admin?tab=suborgs" style={hrefStyle}>
|
||||
<Link to="/admin?tab=tenants" style={hrefStyle}>
|
||||
<MenuItem
|
||||
key={"add suborgs"}
|
||||
style={{
|
||||
|
||||
@@ -431,7 +431,7 @@ const AuthenticationOauth2 = (props) => {
|
||||
|
||||
const authentication_url = authenticationType.token_uri;
|
||||
const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication`;
|
||||
const workflowId = workflow !== undefined ? workflow.id : "";
|
||||
const workflowId = workflow !== undefined ? workflow.id : ""
|
||||
var state = `workflow_id%3D${workflowId}%26reference_action_id%3d${selectedAction.app_id}%26app_name%3d${selectedAction.app_name}%26app_id%3d${selectedAction.app_id}%26app_version%3d${selectedAction.app_version}%26authentication_url%3d${authentication_url}%26scope%3d${resources}%26client_id%3d${client_id}%26client_secret%3d${client_secret}`;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { Link, useNavigate, useLocation } from "react-router-dom";
|
||||
import Billing from "../components/Billing.jsx";
|
||||
import Priorities from "../components/Priorities.jsx";
|
||||
import Branding from "../components/Branding.jsx";
|
||||
import AnalyticsTab from '../components/AnalyticsTab.jsx';
|
||||
import EditOrgTab from '../components/EditOrgTab.jsx';
|
||||
import CloudSyncTab from '../components/CloudSyncTab.jsx';
|
||||
import SSOTab from "../components/ssoTab.jsx"
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import { Button, Tooltip } from '@mui/material';
|
||||
|
||||
const OrganizationTab = (props) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
userdata,
|
||||
globalUrl,
|
||||
serverside,
|
||||
isCloud,
|
||||
checkLogin,
|
||||
notifications,
|
||||
setNotifications,
|
||||
stripeKey, setSelectedOrganization,
|
||||
selectedStatus, setSelectedStatus,
|
||||
selectedOrganization, handleGetOrg,
|
||||
handleStatusChange, handleEditOrg,
|
||||
isLoaded,
|
||||
removeCookie
|
||||
} = props;
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState('org_config');
|
||||
const [organizationFeatures, setOrganizationFeatures] = useState({});
|
||||
const [billingInfo, setBillingInfo] = useState({});
|
||||
const [orgRequest, setOrgRequest] = React.useState(true);
|
||||
const [curIndex, setCurIndex] = React.useState(0);
|
||||
const [unreadNotifications, setUnreadNotifications] = React.useState(
|
||||
notifications?.filter((notification) => notification.read === false)?.length
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const tabName = queryParams.get('admin_tab');
|
||||
if (tabName) {
|
||||
const decodedTabName = decodeURIComponent(tabName);
|
||||
setSelectedTab(decodedTabName);
|
||||
if (decodedTabName === 'org_config') {
|
||||
setCurIndex(0);
|
||||
} else if(decodedTabName === 'sso'){
|
||||
setCurIndex(1)
|
||||
}else if (decodedTabName === 'notifications' || decodedTabName === 'priorities') {
|
||||
setCurIndex(2);
|
||||
} else if (decodedTabName === 'billingstats' || decodedTabName === 'billing') {
|
||||
setCurIndex(3);
|
||||
} else if (decodedTabName === 'branding(beta)') {
|
||||
setCurIndex(4);
|
||||
}
|
||||
// else if (decodedTabName === 'analytics') {
|
||||
// setCurIndex(5);
|
||||
// }
|
||||
}
|
||||
}, [location.search]);
|
||||
|
||||
const handleTabClick = (tabName) => {
|
||||
const formattedTabName = tabName.toLowerCase().replace(/[\s&]+/g, '');
|
||||
const encodedTabName = encodeURIComponent(formattedTabName);
|
||||
setSelectedTab(formattedTabName);
|
||||
document.title = `Shuffle - admin - ${formattedTabName}`;
|
||||
navigate(`?admin_tab=${encodedTabName}`);
|
||||
};
|
||||
|
||||
const handleNotifications = useCallback(() => {
|
||||
const unreadCount = notifications?.filter((notification) => notification.read === false).length;
|
||||
setUnreadNotifications(unreadCount);
|
||||
},[unreadNotifications,notifications]);
|
||||
|
||||
useEffect(() => {
|
||||
if ((unreadNotifications !== notifications?.filter((notification) => notification.read === false).length) !== unreadNotifications) {
|
||||
handleNotifications();
|
||||
}
|
||||
}, [notifications]);
|
||||
|
||||
const renderContent = () => {
|
||||
switch (selectedTab) {
|
||||
case 'org_config':
|
||||
return <EditOrgTab isCloud={isCloud} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} />;
|
||||
case 'sso':
|
||||
return <SSOTab isEditOrgTab={true} globalUrl={globalUrl} isCloud={isCloud} userdata={userdata} handleEditOrg={handleEditOrg} selectedOrganization={selectedOrganization}/>
|
||||
case `notifications`:
|
||||
case `priorities`:
|
||||
return (
|
||||
<Priorities
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
globalUrl={globalUrl}
|
||||
checkLogin={checkLogin}
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
clickedFromOrgTab={true}
|
||||
serverside={serverside}
|
||||
isLoaded={isLoaded}
|
||||
selectedOrganization={selectedOrganization}
|
||||
handleEditOrg={handleEditOrg}
|
||||
/>
|
||||
);
|
||||
case 'billingstats' :
|
||||
case 'billing' :
|
||||
return (
|
||||
<Billing
|
||||
isCloud={true}
|
||||
userdata={userdata}
|
||||
setSelectedOrganization={setSelectedOrganization}
|
||||
globalUrl={globalUrl}
|
||||
selectedOrganization={selectedOrganization}
|
||||
billingInfo={billingInfo}
|
||||
stripeKey={stripeKey}
|
||||
handleGetOrg={handleGetOrg}
|
||||
clickedFromOrgTab={true}
|
||||
handleEditOrg={handleEditOrg}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
/>
|
||||
);
|
||||
case 'branding(beta)':
|
||||
return <Branding
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
globalUrl={globalUrl}
|
||||
handleGetOrg={handleGetOrg}
|
||||
selectedOrganization={selectedOrganization}
|
||||
clickedFromOrgTab={true}
|
||||
setSelectedOrganization={setSelectedOrganization}
|
||||
/>;
|
||||
// case 'analytics':
|
||||
// return <AnalyticsTab isCloud={isCloud} userdata={userdata} globalUrl={globalUrl} />;
|
||||
default:
|
||||
return <EditOrgTab isCloud={isCloud} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: "100%", width: "100%", color: '#FFFFFF', backgroundColor: '#212121', borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", boxSizing: 'border-box' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-around', width: "100%", borderBottom: '1px solid #494949' ,boxSizing: 'border-box' }}>
|
||||
{['Org Configuration', "sso", "Notifications", 'Billing & Stats', 'Branding (Beta)'].map((tabName, index) => (
|
||||
<Tooltip
|
||||
key={index}
|
||||
title={
|
||||
((tabName === "sso")) && !(userdata?.support || userdata?.active_org?.role === "admin")
|
||||
? "Your role is not admin. Please ask the admin to change your role."
|
||||
: ""
|
||||
}
|
||||
placement="right"
|
||||
>
|
||||
<div style={{ pointerEvents: 'auto', width: '100%',}}>
|
||||
<Button
|
||||
key={tabName}
|
||||
onClick={() => {
|
||||
setCurIndex(index);
|
||||
handleTabClick(index === 0 ? "org_config" : tabName.toLowerCase().replace(/[\s&]+/g, ''));
|
||||
}}
|
||||
variant="text"
|
||||
sx={{
|
||||
"&.MuiButton-root": {
|
||||
padding: '28px 0',
|
||||
borderBottom: index === curIndex ? '2px solid #FF8444' : 'none',
|
||||
cursor: 'pointer',
|
||||
fontWeight: index === curIndex ? 'bold' : 'normal',
|
||||
color: index === curIndex ? "#FF8444" : "#FFFFFF",
|
||||
textTransform: 'none',
|
||||
fontSize: 16,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: 0,
|
||||
},
|
||||
"&: hover": {
|
||||
backgroundColor: "#323232"
|
||||
},
|
||||
"&.Mui-disabled": {
|
||||
color: "#6F6F6F",
|
||||
},
|
||||
}}
|
||||
disabled={
|
||||
((tabName === "sso")) && !(userdata?.support || userdata?.active_org?.role === "admin")
|
||||
}
|
||||
>
|
||||
{index === 2 && unreadNotifications > 0 ? (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: -12,
|
||||
right: -15,
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
backgroundColor: '#FF8444',
|
||||
color: '#FFFFFF',
|
||||
fontSize: 12,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
{unreadNotifications}
|
||||
</div>
|
||||
{tabName}
|
||||
</div>
|
||||
) : (
|
||||
<>{index === 1 ? "SSO" : tabName}</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', width: "100%", height: "100%", boxSizing:'border-box'}}>
|
||||
{renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationTab;
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext, memo } from "react";
|
||||
|
||||
import { toast } from "react-toastify";
|
||||
import theme from "../theme.jsx";
|
||||
import { v4 as uuidv4, v5 as uuidv5, validate as isUUID, } from "uuid";
|
||||
import {
|
||||
Paper,
|
||||
Tooltip,
|
||||
@@ -13,8 +14,17 @@ import {
|
||||
Card,
|
||||
Chip,
|
||||
Switch,
|
||||
Skeleton,
|
||||
Autocomplete,
|
||||
TextField,
|
||||
MenuItem,
|
||||
IconButton,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
OpenInNew as OpenInNewIcon,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { Context } from "../context/ContextApi.jsx";
|
||||
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
@@ -22,8 +32,15 @@ import Priority from "../components/Priority.jsx";
|
||||
import { constrainMatrix } from "reaviz";
|
||||
//import { useAlert
|
||||
|
||||
|
||||
const useStyles = makeStyles({
|
||||
notchedOutline: {
|
||||
borderColor: "#f85a3e !important",
|
||||
},
|
||||
});
|
||||
|
||||
const Priorities = memo((props) => {
|
||||
const { globalUrl, userdata,clickedFromOrgTab, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props;
|
||||
const { globalUrl, userdata,clickedFromOrgTab,selectedOrganization, handleEditOrg, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props;
|
||||
|
||||
const [showDismissed, setShowDismissed] = React.useState(false);
|
||||
const [showRead, setShowRead] = React.useState(false);
|
||||
@@ -31,8 +48,22 @@ const Priorities = memo((props) => {
|
||||
const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT");
|
||||
const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT");
|
||||
const [highlightKMS, setHighlightKMS] = React.useState(false)
|
||||
|
||||
const [workflows, setWorkflows] = React.useState([])
|
||||
const [openNotification, setOpenNotification] = React.useState(false);
|
||||
const [workflow, setWorkflow] = React.useState({})
|
||||
const [notificationWorkflow, setNotificationWorkflow] = React.useState(
|
||||
selectedOrganization.defaults === undefined
|
||||
? ""
|
||||
: selectedOrganization.defaults.notification_workflow === undefined ||
|
||||
selectedOrganization.defaults.notification_workflow.length === 0
|
||||
? ""
|
||||
: selectedOrganization.defaults.notification_workflow
|
||||
);
|
||||
|
||||
let navigate = useNavigate();
|
||||
const classes = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
getFramework()
|
||||
|
||||
@@ -60,6 +91,20 @@ const Priorities = memo((props) => {
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedOrganization === undefined || selectedOrganization === null || selectedOrganization?.id === undefined || selectedOrganization?.id === null || selectedOrganization?.id.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if(workflows?.length === 0) {
|
||||
getAvailableWorkflows()
|
||||
}
|
||||
|
||||
if (notificationWorkflow !== selectedOrganization?.defaults?.notification_workflow) {
|
||||
setNotificationWorkflow(selectedOrganization?.defaults?.notification_workflow)
|
||||
}
|
||||
}, [selectedOrganization])
|
||||
|
||||
if (userdata === undefined || userdata === null) {
|
||||
return
|
||||
}
|
||||
@@ -220,11 +265,341 @@ const Priorities = memo((props) => {
|
||||
const imagesize = 22
|
||||
const boxColor = "#86c142"
|
||||
|
||||
|
||||
const getAvailableWorkflows = () => {
|
||||
fetch(globalUrl + "/api/v1/workflows", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!");
|
||||
return;
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson !== undefined) {
|
||||
|
||||
// Add parent notification workflow if it's a child org
|
||||
// selectedOrganization,
|
||||
if (selectedOrganization.creator_org !== undefined && selectedOrganization.creator_org !== null && selectedOrganization.creator_org.length > 0) {
|
||||
|
||||
// Add to start of the list
|
||||
responseJson.unshift({
|
||||
"name": "Parent-Org's Notification Workflow",
|
||||
"id": "parent",
|
||||
})
|
||||
}
|
||||
|
||||
setWorkflows(responseJson)
|
||||
|
||||
if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) {
|
||||
|
||||
const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow)
|
||||
if (workflow !== undefined && workflow !== null) {
|
||||
setWorkflow(workflow)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error getting workflows: " + error);
|
||||
})
|
||||
}
|
||||
|
||||
const handleWorkflowSelectionUpdate = (e, isUserinput) => {
|
||||
if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
|
||||
console.log("Returning as there's no id")
|
||||
return null
|
||||
}
|
||||
setOpenNotification(false)
|
||||
setWorkflow(e.target.value)
|
||||
setNotificationWorkflow(e.target.value.id)
|
||||
handleEditOrg(
|
||||
selectedOrganization?.name,
|
||||
selectedOrganization.description,
|
||||
selectedOrganization.id,
|
||||
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: e.target.value.id,
|
||||
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,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<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, }}>
|
||||
<div style={{ maxHeight: 1700, overflowY: "auto", width: '100%', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}>
|
||||
<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 ({
|
||||
<Typography variant="h5" style={{ color: "rgba(241, 241, 241, 1)", fontSize: 24, fontWeight: 600, textAlign: "left" }}>
|
||||
Notification Workflow
|
||||
</Typography>
|
||||
<Typography style={{ color: "rgba(158, 158, 158, 1)", fontSize: 16, fontWeight: 400, marginTop: 5, }}>
|
||||
The notification workflow triggers when an error occurs in one of your workflows. Each individual one will only start a workflow once every 2 minutes. <b>You can point child org notifications into the parent org notification by choosing it in the list.</b>
|
||||
</Typography>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "row", alignItems: "center", }}>
|
||||
|
||||
{workflows !== undefined && workflows !== null && workflows.length > 0 ?
|
||||
<Autocomplete
|
||||
id="notification_workflow_search"
|
||||
autoHighlight
|
||||
open={openNotification}
|
||||
onOpen={() => {
|
||||
setOpenNotification(true);
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpenNotification(false);
|
||||
}}
|
||||
freeSolo
|
||||
//autoSelect
|
||||
value={workflows?.find(w => w.id === notificationWorkflow) || null}
|
||||
classes={{ inputRoot: classes.inputRoot }}
|
||||
ListboxProps={{
|
||||
style: {
|
||||
backgroundColor: "#212121",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
getOptionLabel={(option) => {
|
||||
if (
|
||||
option === undefined ||
|
||||
option === null ||
|
||||
option.name === undefined ||
|
||||
option.name === null
|
||||
) {
|
||||
return "No Workflow Selected";
|
||||
}
|
||||
|
||||
const newname = (
|
||||
option.name.charAt(0).toUpperCase() + option.name.substring(1)
|
||||
).replaceAll("_", " ");
|
||||
return newname;
|
||||
}}
|
||||
options={workflows}
|
||||
fullWidth
|
||||
style={{
|
||||
backgroundColor: "#212121",
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
height: 35,
|
||||
marginBottom: 40,
|
||||
}}
|
||||
onChange={(event, newValue) => {
|
||||
console.log("Found value: ", newValue)
|
||||
|
||||
var parsedinput = { target: { value: newValue } }
|
||||
|
||||
// For variables
|
||||
if (typeof newValue === 'string' && newValue.startsWith("$")) {
|
||||
parsedinput = {
|
||||
target: {
|
||||
value: {
|
||||
"name": newValue,
|
||||
"id": newValue,
|
||||
"actions": [],
|
||||
"triggers": [],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleWorkflowSelectionUpdate(parsedinput)
|
||||
}}
|
||||
renderOption={(props, data, state) => {
|
||||
if (data.id === workflow.id) {
|
||||
data = workflow;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip arrow placement="right" title={
|
||||
<span style={{}}>
|
||||
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
||||
<img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette?.borderRadius, }} />
|
||||
: null}
|
||||
<Typography>
|
||||
Choose {data.name}
|
||||
</Typography>
|
||||
</span>
|
||||
} placement="bottom">
|
||||
<MenuItem
|
||||
{...props}
|
||||
style={{
|
||||
// backgroundColor: theme.palette.inputColor,
|
||||
color: data.id === workflow.id ? "red" : "white",
|
||||
borderBottom: data.id === "parent" ? "2px solid rgba(255,255,255,0.5)" : null
|
||||
}}
|
||||
value={data}
|
||||
onClick={(e) => {
|
||||
props.onMouseDown?.(null);
|
||||
var parsedinput = { target: { value: data } }
|
||||
handleWorkflowSelectionUpdate(parsedinput)
|
||||
}}
|
||||
>
|
||||
{data.name}
|
||||
</MenuItem>
|
||||
</Tooltip>
|
||||
)
|
||||
}}
|
||||
renderInput={(params) => {
|
||||
return (
|
||||
<TextField
|
||||
{...params}
|
||||
style={{
|
||||
backgroundColor: "rgba(33, 33, 33, 1)",
|
||||
borderRadius: 4,
|
||||
height: 35,
|
||||
fontSize: 16,
|
||||
marginTop: "16px"
|
||||
}}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
style: {
|
||||
height: 35,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "0px 8px",
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
},
|
||||
inputProps: {
|
||||
...params.inputProps,
|
||||
style: {
|
||||
height: "100%",
|
||||
boxSizing: "border-box",
|
||||
}
|
||||
|
||||
}
|
||||
}}
|
||||
// label="Find a notification workflow"
|
||||
variant="outlined"
|
||||
placeholder="Select a notification workflow"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
:
|
||||
<TextField
|
||||
required
|
||||
InputProps={{
|
||||
style: {
|
||||
height: 35,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "0px 8px",
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
},
|
||||
inputProps: {
|
||||
style: {
|
||||
height: "100%",
|
||||
boxSizing: "border-box",
|
||||
}
|
||||
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: "rgba(33, 33, 33, 1)",
|
||||
borderRadius: 4,
|
||||
height: 35,
|
||||
fontSize: 16,
|
||||
marginBottom: 30
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="ID of the workflow to receive notifications"
|
||||
value={notificationWorkflow}
|
||||
onChange={(e) => {
|
||||
setNotificationWorkflow(e.target.value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
{/* <div style={{ minWidth: 150, maxWidth: 150, marginTop: 5, marginLeft: 10, }}>
|
||||
{orgSaveButton}
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
{notificationWorkflow === undefined || notificationWorkflow === null || notificationWorkflow.length === 0 ? null :
|
||||
<div>
|
||||
<Button variant="outlined" color="secondary" style={{marginTop: 5, textTransform: "none", }} onClick={() => {
|
||||
if (notificationWorkflow === "parent") {
|
||||
toast.error("Can't send test notifications to the parent org's notification workflow.")
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`${globalUrl}/api/v1/workflows/${notificationWorkflow}/execute`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
"title": "Test Notification",
|
||||
"description": "This is a test notification to check if the notification workflow is working correctly.",
|
||||
"org_id": selectedOrganization.id,
|
||||
"id": uuidv4(),
|
||||
"reference_url": "/admin?type=test&admin_tab=notifications",
|
||||
"created_at": Math.floor(new Date().getTime() / 1000),
|
||||
"updated_at": Math.floor(new Date().getTime() / 1000),
|
||||
})
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
toast.success("Test notification sent successfully.")
|
||||
} else {
|
||||
toast.error("Failed to send test notification. Please contact support if this persists")
|
||||
}
|
||||
}).catch((error) => {
|
||||
toast.error("Failed to send test notification (2). Please contact support if this persists")
|
||||
})
|
||||
}}>
|
||||
Send test notification
|
||||
</Button>
|
||||
<IconButton
|
||||
style={{marginLeft: 10, }}
|
||||
onClick={() => {
|
||||
if (notificationWorkflow === "parent") {
|
||||
toast.error("Can't open parent org's notification workflow from here.")
|
||||
return
|
||||
}
|
||||
|
||||
window.open(`/workflows/${notificationWorkflow}?view=executions`, "_blank")
|
||||
}}
|
||||
>
|
||||
<OpenInNewIcon color="primary" />
|
||||
</IconButton>
|
||||
</div>
|
||||
}
|
||||
|
||||
<Typography style={{marginTop: 50, 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>
|
||||
|
||||
@@ -261,10 +636,12 @@ const Priorities = memo((props) => {
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<NotificationComponent notifications={notifications} showRead={showRead} selectedExecutionId={selectedExecutionId} selectedWorkflow={selectedWorkflow} highlightKMS={highlightKMS} userdata={userdata} imagesize={imagesize} boxColor={boxColor} clickedFromOrgTab={clickedFromOrgTab} notificationWidth={notificationWidth} dismissNotification={dismissNotification}/>
|
||||
|
||||
{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 ? 60 : null, color: clickedFromOrgTab ? "#ffffff" : null }}>Suggestions</h2>
|
||||
<span style={{ fontSize: 16, 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.
|
||||
<a
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Tooltip,
|
||||
Typography,
|
||||
IconButton,
|
||||
Switch,
|
||||
} from '@mui/material';
|
||||
|
||||
import { toast } from "react-toastify"
|
||||
@@ -36,6 +37,9 @@ import {
|
||||
Insights as InsightsIcon,
|
||||
Replay as ReplayIcon,
|
||||
EditNote as EditNoteIcon,
|
||||
AccountTree as AccountTreeIcon,
|
||||
Cached as CachedIcon,
|
||||
FilterAltOff as FilterAltOffIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'
|
||||
@@ -73,6 +77,8 @@ const RuntimeDebugger = (props) => {
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10)
|
||||
const [resultRows, setResultRows] = useState([])
|
||||
const [selectedWorkflowExecutions, setSelectedWorkflowExecutions] = useState([])
|
||||
const [suborgWorkflowRuns, setSuborgWorkflowRuns] = useState(false)
|
||||
const [openWorkflowMenu, setOpenWorkflowMenu] = useState(false)
|
||||
const [workflows, setWorkflows] = useState([
|
||||
{"id": "", "name": "All Workflows",}
|
||||
])
|
||||
@@ -162,7 +168,7 @@ const RuntimeDebugger = (props) => {
|
||||
}, maxworkflows*300)
|
||||
}
|
||||
|
||||
const submitSearch = (workflowId, status, startTime, endTime, cursor, limit) => {
|
||||
const submitSearch = (workflowId, status, startTime, endTime, cursor, limit, suborg_runs) => {
|
||||
handleWorkflowUsageCount(workflows)
|
||||
//setResultRows([])
|
||||
setSearchLoading(true)
|
||||
@@ -175,6 +181,7 @@ const RuntimeDebugger = (props) => {
|
||||
start_time: startTime,
|
||||
end_time: endTime,
|
||||
ignore_org: ignoreOrg,
|
||||
suborg_runs: suborg_runs,
|
||||
}
|
||||
|
||||
fetch(`${globalUrl}/api/v1/workflows/search`, {
|
||||
@@ -344,10 +351,28 @@ const RuntimeDebugger = (props) => {
|
||||
source = "manual"
|
||||
}
|
||||
|
||||
var imageSource = "";
|
||||
if (params?.row?.org?.id?.length > 0) {
|
||||
if (params?.row?.org?.image?.length > 0){
|
||||
imageSource = params?.row.org?.image
|
||||
}else {
|
||||
imageSource = "/images/no_image.png"
|
||||
}
|
||||
}else {
|
||||
if (userdata.active_org.image?.length > 0){
|
||||
imageSource = userdata?.active_org?.image
|
||||
}else {
|
||||
imageSource = "/images/no_image.png"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span style={{}} onClick={() => {
|
||||
//setStatus(params.row.status)
|
||||
}}>
|
||||
{userdata?.active_org?.creator_org?.length === 0 && suborgWorkflowRuns ? (
|
||||
<img src={imageSource} alt={source} style={{borderRadius: theme.palette?.borderRadius, height: imageSize, width: imageSize, }} />
|
||||
) : null}
|
||||
<Tooltip title={source} placement="top">
|
||||
{foundSource}
|
||||
</Tooltip>
|
||||
@@ -372,7 +397,7 @@ const RuntimeDebugger = (props) => {
|
||||
headerName: 'Workflow Name',
|
||||
width: 250,
|
||||
renderCell: (params) => (
|
||||
<span style={{cursor: "pointer", }} onClick={() => {
|
||||
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", cursor: "pointer", }} onClick={() => {
|
||||
setWorkflowId(params.row.workflow.id)
|
||||
|
||||
for (let key in workflows) {
|
||||
@@ -382,8 +407,18 @@ const RuntimeDebugger = (props) => {
|
||||
}
|
||||
}
|
||||
}}>
|
||||
{params.row.workflow.name}
|
||||
</span>
|
||||
<span>{params.row.workflow.name}</span>
|
||||
{params?.row?.org?.id?.length > 0 &&
|
||||
<Tooltip title={(
|
||||
<div style={{display: "flex", }}>
|
||||
<img src={params.row.org.image || "/images/no_image.png"} alt={params.row.org.name} style={{height: 24, width: 24, borderRadius: 12, }} />
|
||||
<Typography variant="body2" style={{marginLeft: 5, }}>{params.row.org.name}</Typography>
|
||||
</div>
|
||||
)} placement="top" arrow>
|
||||
<AccountTreeIcon style={{color: "#B0B0B0", height: "24px", width: "24px", marginLeft: 10}} />
|
||||
</Tooltip>
|
||||
}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -630,7 +665,7 @@ const RuntimeDebugger = (props) => {
|
||||
return
|
||||
}
|
||||
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, rowsPerPage)
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, rowsPerPage, suborgWorkflowRuns)
|
||||
}, [workflowId, status, startTime, endTime])
|
||||
|
||||
const textfieldStyle = {
|
||||
@@ -679,8 +714,9 @@ const RuntimeDebugger = (props) => {
|
||||
|
||||
setWorkflow(e.target.value)
|
||||
setWorkflowId(e.target.value.id)
|
||||
setSuborgWorkflowRuns(false)
|
||||
|
||||
submitSearch(e.target.value.id, status, startTime, endTime, rowCursor, rowsPerPage)
|
||||
submitSearch(e.target.value.id, status, startTime, endTime, rowCursor, rowsPerPage, false)
|
||||
}
|
||||
|
||||
const executeWorkflow = (execution) => {
|
||||
@@ -773,84 +809,17 @@ const RuntimeDebugger = (props) => {
|
||||
|
||||
return (
|
||||
<div style={{minWidth: 1150, maxWidth: 1150, margin: "auto", }}>
|
||||
<div style={{display: "flex", }}>
|
||||
<div style={{display: "flex", paddingTop: 50, }}>
|
||||
<div style={{display: 'flex', flexDirection: 'column'}}>
|
||||
<h1 style={{flex: 3, }}>Workflow Run Debugger {totalCount !== 0 ? ` (~${totalCount})` : ""}</h1>
|
||||
<div style={{position: 'relative', right: 10, marginBottom: 10}}>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={searchQuery}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginTop: 20,
|
||||
marginLeft: 10,
|
||||
marginRight: 12,
|
||||
width: 693,
|
||||
height: 55,
|
||||
borderRadius: 8,
|
||||
fontSize: 16,
|
||||
marginBottom: 15,
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
height: 55,
|
||||
width: 693,
|
||||
borderRadius: 8,
|
||||
},
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon style={{ marginLeft: 5}} />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{searchQuery.length > 0 && (
|
||||
<ClearIcon
|
||||
style={{
|
||||
color: "white",
|
||||
cursor: "pointer",
|
||||
marginRight: 10
|
||||
}}
|
||||
onClick={() => setSearchQuery('')}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(to right, rgb(248, 106, 62), rgb(243, 64, 121))",
|
||||
color: "white",
|
||||
border: "none",
|
||||
padding: "10px 20px",
|
||||
width: 100,
|
||||
height: 35,
|
||||
borderRadius: 17.5,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</InputAdornment>
|
||||
),
|
||||
|
||||
}}
|
||||
onChange={(e)=>{handleQueryChange(e)}}
|
||||
color="primary"
|
||||
placeholder="Filter by Workflow Name, Status, Execution Argument, Results.."
|
||||
id="shuffle_search_field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{selectedWorkflowExecutions.length > 0 ?
|
||||
<div style={{display: "flex", width: "100%", }}>
|
||||
<h1 style={{flex: 3, whiteSpace: "nowrap" }}>Workflow Run Debugger {totalCount !== 0 ? ` (~${totalCount})` : ""}</h1>
|
||||
{selectedWorkflowExecutions.length > 0 ?
|
||||
<ButtonGroup>
|
||||
<Tooltip title="Reruns ALL selected workflows. This will make a new execution for them, and not continue the existing.">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
style={{maxHeight: 40, marginTop: 25, }}
|
||||
style={{maxHeight: 40, marginTop: 25, marginLeft: 20, }}
|
||||
onClick={() => {
|
||||
|
||||
for (var i = 0; i < selectedWorkflowExecutions.length; i++) {
|
||||
@@ -888,7 +857,7 @@ const RuntimeDebugger = (props) => {
|
||||
} else {
|
||||
toast("Aborted "+aborted+" workflows.")
|
||||
// Research
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, rowsPerPage)
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, rowsPerPage, suborgWorkflowRuns)
|
||||
|
||||
setSelectedWorkflowExecutions([])
|
||||
}
|
||||
@@ -906,7 +875,7 @@ const RuntimeDebugger = (props) => {
|
||||
<Button
|
||||
variant={ignoreOrg ? "contained" : "outlined"}
|
||||
color="secondary"
|
||||
style={{marginLeft: 100, maxHeight: 40, marginTop: 25, }}
|
||||
style={{marginLeft: 20, maxHeight: 40, marginTop: 25, }}
|
||||
onClick={() => {
|
||||
setIgnoreOrg(!ignoreOrg)
|
||||
}}
|
||||
@@ -914,10 +883,83 @@ const RuntimeDebugger = (props) => {
|
||||
{ignoreOrg ? "Ignoring Org" : "Ignore Org (Support Only)"}
|
||||
</Button>
|
||||
: null}
|
||||
</div>
|
||||
<div style={{display: "flex", justifyContent: "space-between", width: "100%"}}>
|
||||
<div style={{position: 'relative', right: 10, marginBottom: 10}}>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={searchQuery}
|
||||
style={{
|
||||
backgroundColor: "#1a1a1a",
|
||||
marginTop: 20,
|
||||
marginLeft: 10,
|
||||
marginRight: 12,
|
||||
width: 693,
|
||||
height: 51,
|
||||
borderRadius: 4,
|
||||
fontSize: 16,
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
backgroundColor: "#1a1a1a",
|
||||
fontSize: "1em",
|
||||
height: 51,
|
||||
width: 693,
|
||||
borderRadius: 4,
|
||||
},
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon style={{ marginLeft: 5}} />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{searchQuery.length > 0 && (
|
||||
<ClearIcon
|
||||
style={{
|
||||
color: "white",
|
||||
cursor: "pointer",
|
||||
marginRight: 10
|
||||
}}
|
||||
onClick={() => setSearchQuery('')}
|
||||
/>
|
||||
)}
|
||||
</InputAdornment>
|
||||
),
|
||||
|
||||
}}
|
||||
onChange={(e)=>{handleQueryChange(e)}}
|
||||
color="primary"
|
||||
placeholder="Filter by Workflow Name, Status, Execution Argument, Results"
|
||||
id="shuffle_search_field"
|
||||
/>
|
||||
</div>
|
||||
{userdata?.active_org?.creator_org?.length === 0 ? (
|
||||
<div style={{display: "flex", margin: 'auto',marginTop: 20,justifyContent: 'center', alignItems: 'center', }}>
|
||||
<Switch
|
||||
checked={suborgWorkflowRuns}
|
||||
onChange={() => {
|
||||
setSuborgWorkflowRuns(!suborgWorkflowRuns);
|
||||
//set selected workflow to all workflows when switching between suborg and all workflows
|
||||
setWorkflowId("")
|
||||
setWorkflow({"id": "", "name": "All Workflows"})
|
||||
setStatus("")
|
||||
setStartTime("")
|
||||
setEndTime("")
|
||||
setSearchQuery("")
|
||||
submitSearch("", "", "", "", rowCursor, rowsPerPage, !suborgWorkflowRuns)}
|
||||
}
|
||||
color="secondary"
|
||||
/>
|
||||
<Typography variant="body2" style={{color: "white", }}>Show workflow runs from suborgs</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={(e) => {
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, rowsPerPage)
|
||||
}} style={{display: "flex", }}>
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, rowsPerPage, suborgWorkflowRuns)
|
||||
}} style={{display: "flex", justifyContent: "center", alignItems: "center", }}>
|
||||
<FormControl fullWidth style={{marginTop: 5, }}>
|
||||
<InputLabel id="status-label">Status</InputLabel>
|
||||
<Select
|
||||
@@ -951,10 +993,13 @@ const RuntimeDebugger = (props) => {
|
||||
<Autocomplete
|
||||
id="workflow_search"
|
||||
value={workflow}
|
||||
open={openWorkflowMenu}
|
||||
onOpen={()=>{setOpenWorkflowMenu(true)}}
|
||||
onClose={() => {setOpenWorkflowMenu(false)}}
|
||||
classes={{ inputRoot: classes.inputRoot }}
|
||||
ListboxProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
backgroundColor: "#1a1a1a",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
@@ -973,11 +1018,11 @@ const RuntimeDebugger = (props) => {
|
||||
options={workflows}
|
||||
fullWidth
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
backgroundColor: "#1a1a1a",
|
||||
height: 50,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
marginTop: 5,
|
||||
borderRadius: 4,
|
||||
marginLeft: 5,
|
||||
marginBottom: 3,
|
||||
}}
|
||||
onChange={(event, newValue) => {
|
||||
console.log("Found value: ", newValue)
|
||||
@@ -1018,12 +1063,13 @@ const RuntimeDebugger = (props) => {
|
||||
}>
|
||||
<MenuItem
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
backgroundColor: "#1a1a1a",
|
||||
color: data.id === workflow.id ? "red" : "white",
|
||||
}}
|
||||
value={data}
|
||||
onClick={(e) => {
|
||||
var parsedinput = { target: { value: data } }
|
||||
setOpenWorkflowMenu(false)
|
||||
handleWorkflowSelectionUpdate(parsedinput)
|
||||
}}
|
||||
>
|
||||
@@ -1036,8 +1082,8 @@ const RuntimeDebugger = (props) => {
|
||||
return (
|
||||
<TextField
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
backgroundColor: "#1a1a1a",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
{...params}
|
||||
label="Workflow"
|
||||
@@ -1050,8 +1096,9 @@ const RuntimeDebugger = (props) => {
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<DateTimePicker
|
||||
sx={{
|
||||
marginTop: 1,
|
||||
marginLeft: 1,
|
||||
minHeight: 50,
|
||||
maxHeight: 50,
|
||||
minWidth: 240,
|
||||
maxWidth: 240,
|
||||
}}
|
||||
@@ -1064,10 +1111,11 @@ const RuntimeDebugger = (props) => {
|
||||
/>
|
||||
<DateTimePicker
|
||||
sx={{
|
||||
marginTop: 1,
|
||||
marginLeft: 1,
|
||||
minWidth: 240,
|
||||
maxWidth: 240,
|
||||
minHeight: 50,
|
||||
maxHeight: 50,
|
||||
}}
|
||||
ampm={false}
|
||||
label="Search until"
|
||||
@@ -1078,14 +1126,34 @@ const RuntimeDebugger = (props) => {
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
|
||||
<Tooltip title="Clear all filters and search parameters">
|
||||
<Button
|
||||
style={{ marginLeft: 10, minHeight: 60, marginTop: 10, backgroundColor: "#1a1a1a", border: "1px solid #424242", boxShadow: 'none', borderRadius: 4, width: 81, height: 51, marginRight: 15 }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setWorkflowId("")
|
||||
setWorkflow({"id": "", "name": "All Workflows"})
|
||||
setStatus("")
|
||||
setStartTime("")
|
||||
setEndTime("")
|
||||
setSearchQuery("")
|
||||
setSuborgWorkflowRuns(false)
|
||||
submitSearch("", "", "", "", rowCursor, rowsPerPage, false)
|
||||
}}
|
||||
>
|
||||
<FilterAltOffIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, rowsPerPage)
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, rowsPerPage, suborgWorkflowRuns)
|
||||
}}
|
||||
disabled={searchLoading}
|
||||
style={{height: 50, minWidth: 100, marginTop: 15, marginLeft: 10, }}
|
||||
style={{height: 50, minWidth: 100, marginTop: 15, }}
|
||||
>
|
||||
{searchLoading ? <CircularProgress size={30} /> : "Search"}
|
||||
</Button>
|
||||
@@ -1100,7 +1168,7 @@ const RuntimeDebugger = (props) => {
|
||||
disableSelectionOnClick
|
||||
onPageSizeChange={(newPageSize) => {
|
||||
setRowsPerPage(newPageSize)
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize)
|
||||
submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize, suborgWorkflowRuns)
|
||||
}}
|
||||
// event for when clicking next page
|
||||
// Hide page changer
|
||||
|
||||
@@ -109,6 +109,12 @@ const SearchData = props => {
|
||||
}
|
||||
}, [searchOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentRefinement !== inputValue) {
|
||||
refine(inputValue);
|
||||
}
|
||||
}, [currentRefinement]);
|
||||
|
||||
return (
|
||||
|
||||
<form id="search_form" noValidate type="searchbox" action="" role="search" onClick={() => {
|
||||
@@ -387,7 +393,7 @@ const SearchData = props => {
|
||||
if (responseJson.success === false) {
|
||||
toast(`Failed to ${type} the app for your organization. Please try again or contact support@shuffler.io for more info`)
|
||||
} else {
|
||||
toast(`App successfully ${type}d. Please refresh the page to use it.`)
|
||||
toast(`App successfully ${type}d. It may now be used in your workflows.`)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -495,7 +501,7 @@ const SearchData = props => {
|
||||
})
|
||||
}
|
||||
|
||||
var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}`
|
||||
var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `/apps/${hit.objectID}`
|
||||
parsedUrl += `?queryID=${hit.__queryID}`
|
||||
|
||||
return (
|
||||
|
||||
@@ -678,7 +678,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
}
|
||||
{img2 !== undefined && img2 !== "" && dstapp !== undefined && dstapp !== "" ?
|
||||
<Tooltip title={dstapp.replaceAll(":default", "").replaceAll("_", " ").replaceAll(" API", "")} placement="top">
|
||||
<div style={{display: "flex", }}>
|
||||
<div style={{display : dstapp === "NA" ? "none" : "flex" }}>
|
||||
<TrendingFlatIcon style={{ marginTop: 7, }} />
|
||||
<div style={dstapp !== undefined && dstapp.includes(":default") ? imagestyleWrapperDefault : imagestyleWrapper}>
|
||||
<img src={img2} style={dstapp !== undefined && dstapp.includes(":default") ? imagestyleDefault : imagestyle} />
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
Close as CloseIcon,
|
||||
East as EastIcon,
|
||||
Interests as InterestsIcon,
|
||||
OpenInNew as OpenInNewIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import {
|
||||
@@ -35,7 +36,8 @@ import {
|
||||
grey,
|
||||
} from "../views/AngularWorkflow.jsx"
|
||||
|
||||
import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup.jsx";
|
||||
//import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup2.jsx";
|
||||
import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup2.jsx";
|
||||
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
|
||||
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx";
|
||||
import FixWorkflowValidationErrors from "../components/FixWorkflowValidationErrors.jsx";
|
||||
@@ -47,11 +49,14 @@ const WorkflowTemplatePopup = (props) => {
|
||||
isModalOpenDefault,
|
||||
setIsClicked,
|
||||
inputWorkflowId,
|
||||
inputWorkflow,
|
||||
onClose,
|
||||
} = props;
|
||||
|
||||
const [isActive, setIsActive] = useState(workflowBuilt === true);
|
||||
const [isActive, setIsActive] = useState(workflowBuilt === true || (workflowBuilt !== undefined && workflowBuilt !== null && workflowBuilt?.length > 0) || (inputWorkflow !== undefined && inputWorkflow !== null && inputWorkflow.id !== undefined && inputWorkflow.id !== null && inputWorkflow.id !== "") ? true : false)
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(isModalOpenDefault === true ? true : false)
|
||||
const [modalOpen, setModalOpen] = useState(isModalOpenDefault === true);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [workflowLoading, setWorkflowLoading] = useState(false)
|
||||
const [showLoginButton, setShowLoginButton] = useState(false);
|
||||
@@ -65,7 +70,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
const [showTryitOut, setShowTryitout] = React.useState(showTryit === true ? true : false)
|
||||
|
||||
const [loadingWorkflow, setLoadingWorkflow] = React.useState(false)
|
||||
const [workflow, setWorkflow] = useState({});
|
||||
const [workflow, setWorkflow] = useState(inputWorkflow !== undefined && inputWorkflow !== null && inputWorkflow.id !== undefined && inputWorkflow.id !== null && inputWorkflow.id !== "" ? inputWorkflow : {})
|
||||
const [_, setUpdate] = useState(0)
|
||||
|
||||
const fetchWorkflow = (id) => {
|
||||
@@ -169,6 +174,12 @@ const WorkflowTemplatePopup = (props) => {
|
||||
}
|
||||
}, [configurationFinished, workflow])
|
||||
|
||||
useEffect(() => {
|
||||
if (isModalOpenDefault === true) {
|
||||
setModalOpen(true);
|
||||
}
|
||||
}, [isModalOpenDefault]);
|
||||
|
||||
const imageSize = 32
|
||||
const defaultBorder = "1px solid rgba(255,255,255,0.6)"
|
||||
const imagestyleWrapper = {
|
||||
@@ -455,7 +466,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
//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."
|
||||
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."
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason !== "") {
|
||||
setErrorMessage(defaultMessage + "\n\n" + responseJson.reason)
|
||||
} else {
|
||||
@@ -512,6 +523,18 @@ const WorkflowTemplatePopup = (props) => {
|
||||
return false
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setModalOpen(false);
|
||||
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
|
||||
if (setIsClicked !== undefined) {
|
||||
setIsClicked(false);
|
||||
}
|
||||
}
|
||||
|
||||
const ModalView = () => {
|
||||
if (modalOpen === false) {
|
||||
return null
|
||||
@@ -524,19 +547,13 @@ const WorkflowTemplatePopup = (props) => {
|
||||
<Drawer
|
||||
anchor={"right"}
|
||||
open={modalOpen}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
|
||||
if (setIsClicked !== undefined) {
|
||||
setIsClicked(false)
|
||||
}
|
||||
}}
|
||||
onClose={handleClose}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
minWidth: isHomePage ? null : isMobile ? 300 : 850,
|
||||
maxWidth: isHomePage ? null : isMobile ? 300 : 850,
|
||||
minWidth: isHomePage ? null : isMobile ? 300 : 750,
|
||||
maxWidth: isHomePage ? null : isMobile ? 300 : 750,
|
||||
paddingTop: isMobile ? null : 75,
|
||||
itemAlign: "center",
|
||||
},
|
||||
@@ -546,17 +563,15 @@ const WorkflowTemplatePopup = (props) => {
|
||||
style={{
|
||||
zIndex: 5000,
|
||||
position: "absolute",
|
||||
top: 14,
|
||||
right: 14,
|
||||
top: 110,
|
||||
right: 110,
|
||||
color: "white",
|
||||
}}
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<DialogContent style={{marginTop: 0, marginLeft: isHomePage ? null : isMobile ? null : 75, maxWidth: 470, }}>
|
||||
<DialogContent style={{marginTop: 0, marginLeft: isHomePage ? null : isMobile ? null : 75, maxWidth: 470, marginTop: 20 }}>
|
||||
<Typography variant="h4" style={{ fontSize: isMobile ? 20 : null}}>
|
||||
<b>Configure Workflow</b>
|
||||
</Typography>
|
||||
@@ -564,7 +579,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
{title === undefined || title === null || title === "" ? null :
|
||||
<span>
|
||||
<Typography variant="body2" color="textSecondary" style={{marginTop: 25, }}>
|
||||
Selected Workflow:
|
||||
Selected Usecase:
|
||||
</Typography>
|
||||
<div style={{marginBottom: 0, }} id="workflow-template">
|
||||
<WorkflowTemplatePopup2
|
||||
@@ -575,9 +590,10 @@ const WorkflowTemplatePopup = (props) => {
|
||||
dstapp={dstapp}
|
||||
title={title}
|
||||
description={description}
|
||||
visualOnly={true}
|
||||
|
||||
visualOnly={true}
|
||||
workflowBuilt={workflowBuilt}
|
||||
inputWorkflow={workflow}
|
||||
shownColor={shownColor}
|
||||
/>
|
||||
|
||||
@@ -585,7 +601,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
</span>
|
||||
}
|
||||
|
||||
<div style={{marginTop: 15, }}>
|
||||
<div style={{marginTop: 0, }}>
|
||||
{/* Fix the timeline when errors are fixed.. how? */}
|
||||
<WorkflowValidationTimeline
|
||||
workflow={workflow}
|
||||
@@ -601,21 +617,23 @@ const WorkflowTemplatePopup = (props) => {
|
||||
</div>
|
||||
|
||||
{workflowLoading === true ?
|
||||
<div style={{marginTop: 75, textAlign: "center", }}>
|
||||
<Typography variant="h4"> Generating the Workflow...
|
||||
<div style={{marginTop: 60, textAlign: "center", }}>
|
||||
<Typography variant="h4"> Generating Workflows...
|
||||
</Typography>
|
||||
<CircularProgress style={{marginLeft: 0, marginTop: 25, }}/>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
{usecaseDetails === undefined ? null :
|
||||
<Typography variant="h6" style={{marginTop: 75, }}>
|
||||
{usecaseDetails?.description}
|
||||
{usecaseDetails === undefined || usecaseDetails === null || workflow.id !== undefined ? null :
|
||||
<Typography variant="body1" style={{marginTop: 60, }} color="textSecondary">
|
||||
{usecaseDetails?.description}
|
||||
</Typography>
|
||||
}
|
||||
<Typography variant="h6" style={{marginTop: 75, }}>
|
||||
{errorMessage !== "" ? errorMessage : ""}
|
||||
</Typography>
|
||||
{errorMessage !== "" ?
|
||||
<Typography variant="h6" style={{marginTop: 75, }}>
|
||||
{errorMessage !== "" ? errorMessage : ""}
|
||||
</Typography>
|
||||
: null}
|
||||
{showLoginButton ?
|
||||
<Link to="/register?message=Please login to create workflows&view=usecases"
|
||||
style={{
|
||||
@@ -643,6 +661,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
variant="outlined"
|
||||
style={{
|
||||
textTransform: "none",
|
||||
marginTop: 15,
|
||||
}}
|
||||
onClick={() => {
|
||||
//setWorkflowLoading(true)
|
||||
@@ -730,11 +749,11 @@ const WorkflowTemplatePopup = (props) => {
|
||||
|
||||
const parsedDescription = description !== undefined && description !== null ? description.replaceAll("_", " ") : ""
|
||||
|
||||
const boxHeight = 104
|
||||
const boxHeight = visualOnly ? 75 : 104
|
||||
const highlightColor = shownColor !== undefined && shownColor !== null && shownColor !== "" ? shownColor : "#f85a3e"
|
||||
|
||||
var hasInterest = false
|
||||
if (userdata.interests !== undefined && userdata.interests !== null && userdata.interests.length > 0) {
|
||||
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 === "") {
|
||||
@@ -742,12 +761,12 @@ const WorkflowTemplatePopup = (props) => {
|
||||
}
|
||||
|
||||
if (modalOpen) {
|
||||
console.log("COMPARE: ", userdata.interests[interestkey].name.trim().toLowerCase().replaceAll(" ", "_"), comparisonTitle)
|
||||
//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)
|
||||
//console.log("FOUND: ", comparisonTitle)
|
||||
}
|
||||
|
||||
hasInterest = true
|
||||
@@ -791,7 +810,16 @@ const WorkflowTemplatePopup = (props) => {
|
||||
}}
|
||||
onClick={() => {
|
||||
if (visualOnly === true) {
|
||||
console.log("Not showing more than visuals.")
|
||||
console.log("Not showing more than visuals. Workflow built: ", workflowBuilt, workflow)
|
||||
|
||||
if (workflowBuilt !== undefined && workflowBuilt !== null && workflowBuilt?.length > 0) {
|
||||
window.open("/workflows/" + workflowBuilt, "_blank")
|
||||
} else if (workflow.id !== undefined && workflow.id !== null && workflow.id !== "") {
|
||||
window.open("/workflows/" + workflow.id, "_blank")
|
||||
} else {
|
||||
toast("Click 'Try this usecase' to generate workflows for this usecase.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -822,7 +850,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
: null}
|
||||
|
||||
<div style={{ display: "flex", itemAlign: "left", textAlign: "left", }}>
|
||||
<div style={{display: "flex", flex: 1, marginLeft: 25, marginTop: showTryitOut && !isActive ? 14 : 30, }}>
|
||||
<div style={{display: "flex", flex: 1, marginLeft: 25, marginTop: visualOnly ? 18 : showTryitOut && !isActive ? 14 : 30, }}>
|
||||
<div style={{zIndex: 51}}>
|
||||
{img1 !== undefined && img1 !== "" && srcapp !== undefined && srcapp !== "" ?
|
||||
<Tooltip title={srcapp.replaceAll(":default", "").replaceAll("_", " ").replaceAll(" API", "")} placement="top">
|
||||
@@ -849,7 +877,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
}
|
||||
|
||||
</div>
|
||||
<div style={{ marginLeft: 20, overflow: "hidden", maxHeight: 30, marginTop: showTryitOut && !isActive ? 8 : 23, }}>
|
||||
<div style={{ marginLeft: 20, overflow: "hidden", maxHeight: 30, marginTop: visualOnly ? 12 : 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>
|
||||
@@ -858,13 +886,19 @@ const WorkflowTemplatePopup = (props) => {
|
||||
</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>
|
||||
visualOnly === true ?
|
||||
<Tooltip title="Open the workflow in a new tab" placement="top">
|
||||
<OpenInNewIcon color="primary" sx={{ borderRadius: 4 }} style={{ position: "absolute", top: 22, right: 20, }} />
|
||||
</Tooltip>
|
||||
:
|
||||
<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 ?
|
||||
{!isActive && hasInterest === true && !visualOnly ?
|
||||
<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>
|
||||
@@ -872,7 +906,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
</div>
|
||||
|
||||
|
||||
{showTryitOut && !isActive ?
|
||||
{showTryitOut && !isActive && !visualOnly ?
|
||||
<Fade in={showTryitOut} timeout={300}>
|
||||
<Button
|
||||
variant="text"
|
||||
@@ -898,4 +932,4 @@ const WorkflowTemplatePopup = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowTemplatePopup
|
||||
export default WorkflowTemplatePopup
|
||||
|
||||
@@ -28,7 +28,16 @@ import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||
import theme from "../theme.jsx";
|
||||
const itemHeight = 24
|
||||
|
||||
export const getParentNodes = (workflow, action) => {
|
||||
export const getParentNodes = (workflow, action, count) => {
|
||||
if (count === undefined) {
|
||||
count = 0
|
||||
}
|
||||
|
||||
// 50 levels of parent nodes
|
||||
if (count > 50) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (action === undefined || action === null) {
|
||||
return []
|
||||
}
|
||||
@@ -81,8 +90,7 @@ export const getParentNodes = (workflow, action) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: This part is only handling first level,
|
||||
// but needs to recurse
|
||||
// FIXME: recursion
|
||||
var incomingEdges = []
|
||||
for (var branchkey in workflow.branches) {
|
||||
const branch = workflow.branches[branchkey]
|
||||
@@ -90,13 +98,19 @@ export const getParentNodes = (workflow, action) => {
|
||||
continue
|
||||
}
|
||||
|
||||
// Go up in the levels
|
||||
// FIXME: Go up in the levels
|
||||
// This somehow creates infinite recursion for now, so
|
||||
// we are skipping it.
|
||||
// This function is also not used for cytoscape recursion,
|
||||
// so it doesn't matter much (yet)
|
||||
/*
|
||||
const parents = getParentNodes(workflow, {
|
||||
id: branch.source_id,
|
||||
})
|
||||
}, count+1)
|
||||
if (parents.length > 0) {
|
||||
incomingEdges = incomingEdges.concat(parents)
|
||||
}
|
||||
*/
|
||||
|
||||
incomingEdges.push(branch)
|
||||
}
|
||||
@@ -215,8 +229,6 @@ const WorkflowValidationTimeline = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
//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) {
|
||||
@@ -287,7 +299,7 @@ const WorkflowValidationTimeline = (props) => {
|
||||
var scheduleNotStarted = false
|
||||
|
||||
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.validation_ran === false) {
|
||||
console.log("Validation didn't run. Why?")
|
||||
//console.log("Validation didn't run or get set for workflow. Why?")
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -367,10 +379,13 @@ const WorkflowValidationTimeline = (props) => {
|
||||
action.result = foundResult
|
||||
|
||||
action.status = foundResult.status
|
||||
}
|
||||
} else {
|
||||
action.status = "SUCCESS"
|
||||
}
|
||||
}
|
||||
|
||||
const lastitem = index === relevantactions.length - 1
|
||||
|
||||
if (!lastitem) {
|
||||
if (action.app_name === "Shuffle Tools") {
|
||||
if (action.status === "SUCCESS") {
|
||||
@@ -386,9 +401,10 @@ const WorkflowValidationTimeline = (props) => {
|
||||
nodecolor = grey
|
||||
branchcolor = grey
|
||||
}
|
||||
} else {
|
||||
nodecolor = green
|
||||
branchcolor = green
|
||||
}
|
||||
|
||||
|
||||
} else if (action.status === "SKIPPED") {
|
||||
branchcolor = grey
|
||||
} else {
|
||||
@@ -489,7 +505,7 @@ const WorkflowValidationTimeline = (props) => {
|
||||
|
||||
if (!showMiddle && relevantactions.length > 2 && index > 0 && index === relevantactions.length - 2) {
|
||||
if (founderror.length > 0) {
|
||||
middleError += founderror+"\n"
|
||||
middleError += action.label+": "+founderror+"\n\n"
|
||||
|
||||
middleBranchColor = branchcolor
|
||||
}
|
||||
@@ -497,11 +513,18 @@ const WorkflowValidationTimeline = (props) => {
|
||||
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>
|
||||
}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography variant="body1" style={{margin: 5, whiteSpace: "pre-line", }}>
|
||||
{middleError}
|
||||
</Typography>
|
||||
}
|
||||
inputProps={{
|
||||
paperProps: {
|
||||
backgroundColor: "red",
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconButton style={{width: 30, height: 30, backgroundColor: "rgba(255,255,255,0.0)", borderRadius: 30, marginTop: 2, }}>
|
||||
<ErrorOutlineIcon style={{color: "red", }} />
|
||||
</IconButton>
|
||||
@@ -517,7 +540,7 @@ const WorkflowValidationTimeline = (props) => {
|
||||
// Returns for anything non-middle
|
||||
if (relevantactions.length > 2 && index >= 1 && index < relevantactions.length - 2) {
|
||||
if (founderror.length > 0) {
|
||||
middleError += founderror+"\n"
|
||||
middleError += action.label+": "+founderror+"\n\n"
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -0,0 +1,649 @@
|
||||
import { useEffect } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
Typography,
|
||||
Switch,
|
||||
Button,
|
||||
Tooltip,
|
||||
TextField,
|
||||
Grid,
|
||||
Skeleton,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { Link } from "react-router-dom";
|
||||
import theme from "../theme.jsx";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
notchedOutline: {
|
||||
borderColor: "#f85a3e !important",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handleEditOrg})=>{
|
||||
|
||||
const classes = useStyles();
|
||||
const [show2faSetup, setShow2faSetup] = React.useState(false);
|
||||
const [autoPrivision, setAutoProvision] = React.useState(selectedOrganization?.sso_config?.auto_provision)
|
||||
const [ssoEntrypoint, setSsoEntrypoint] = React.useState(
|
||||
selectedOrganization.sso_config === undefined
|
||||
? ""
|
||||
: selectedOrganization.sso_config.sso_entrypoint === undefined ||
|
||||
selectedOrganization.sso_config.sso_entrypoint.length === 0
|
||||
? ""
|
||||
: selectedOrganization.sso_config.sso_entrypoint
|
||||
);
|
||||
const [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined
|
||||
? false
|
||||
: selectedOrganization.sso_config.SSORequired === undefined
|
||||
? false
|
||||
: selectedOrganization.sso_config.SSORequired);
|
||||
|
||||
const [ssoCertificate, setSsoCertificate] = React.useState(
|
||||
selectedOrganization.sso_config === undefined
|
||||
? ""
|
||||
: selectedOrganization.sso_config.sso_certificate === undefined ||
|
||||
selectedOrganization.sso_config.sso_certificate.length === 0
|
||||
? ""
|
||||
: selectedOrganization.sso_config.sso_certificate
|
||||
);
|
||||
|
||||
const [openidClientId, setOpenidClientId] = React.useState(
|
||||
selectedOrganization.sso_config === undefined
|
||||
? ""
|
||||
: selectedOrganization.sso_config.client_id === undefined ||
|
||||
selectedOrganization.sso_config.client_id.length === 0
|
||||
? ""
|
||||
: selectedOrganization.sso_config.client_id
|
||||
);
|
||||
const [openidClientSecret, setOpenidClientSecret] = React.useState(
|
||||
selectedOrganization.sso_config === undefined
|
||||
? ""
|
||||
: selectedOrganization.sso_config.client_secret === undefined ||
|
||||
selectedOrganization.sso_config.client_secret.length === 0
|
||||
? ""
|
||||
: selectedOrganization.sso_config.client_secret
|
||||
);
|
||||
const [openidAuthorization, setOpenidAuthorization] = React.useState(
|
||||
selectedOrganization.sso_config === undefined
|
||||
? ""
|
||||
: selectedOrganization.sso_config.openid_authorization === undefined ||
|
||||
selectedOrganization.sso_config.openid_authorization.length === 0
|
||||
? ""
|
||||
: selectedOrganization.sso_config.openid_authorization
|
||||
);
|
||||
const [openidToken, setOpenidToken] = React.useState(
|
||||
selectedOrganization.sso_config === undefined
|
||||
? ""
|
||||
: selectedOrganization.sso_config.openid_token === undefined ||
|
||||
selectedOrganization.sso_config.openid_token.length === 0
|
||||
? ""
|
||||
: selectedOrganization.sso_config.openid_token
|
||||
)
|
||||
|
||||
useEffect(()=>{
|
||||
|
||||
if (openidClientSecret !== selectedOrganization?.sso_config?.client_secret) {
|
||||
setOpenidClientSecret(selectedOrganization?.sso_config?.client_secret)
|
||||
}
|
||||
|
||||
if (openidClientId !== selectedOrganization?.sso_config?.client_id) {
|
||||
setOpenidClientId(selectedOrganization?.sso_config?.client_id)
|
||||
}
|
||||
|
||||
if (openidAuthorization !== selectedOrganization?.sso_config?.openid_authorization) {
|
||||
setOpenidAuthorization(selectedOrganization?.sso_config?.openid_authorization)
|
||||
}
|
||||
|
||||
if (openidToken !== selectedOrganization?.sso_config?.openid_token) {
|
||||
setOpenidToken(selectedOrganization?.sso_config?.openid_token)
|
||||
}
|
||||
|
||||
if (ssoCertificate !== selectedOrganization?.sso_config?.sso_certificate) {
|
||||
setSsoCertificate(selectedOrganization?.sso_config?.sso_certificate)
|
||||
}
|
||||
|
||||
if (ssoEntrypoint !== selectedOrganization?.sso_config?.sso_entrypoint) {
|
||||
setSsoEntrypoint(selectedOrganization?.sso_config?.sso_entrypoint)
|
||||
}
|
||||
|
||||
if (SSORequired !== selectedOrganization?.sso_config?.SSORequired) {
|
||||
setSSORequired(selectedOrganization?.sso_config?.SSORequired)
|
||||
}
|
||||
if (autoPrivision !== selectedOrganization?.sso_config?.auto_provision) {
|
||||
setAutoProvision(selectedOrganization?.sso_config?.auto_provision)
|
||||
}
|
||||
},[selectedOrganization])
|
||||
|
||||
const orgSaveButton = (
|
||||
<Tooltip title="Save any unsaved data" placement="bottom">
|
||||
<Button
|
||||
style={{ width: 244, height: 51, flex: 1, textTransform: 'capitalize', padding: "16px, 24px, 16px, 24px", borderRadius: 4, backgroundColor: "#ff8544", color: "#1a1a1a", fontSize: 16, }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={
|
||||
userdata === undefined ||
|
||||
userdata === null ||
|
||||
userdata.admin !== "true"
|
||||
}
|
||||
onClick={() =>
|
||||
handleEditOrg(
|
||||
selectedOrganization?.name,
|
||||
selectedOrganization?.description,
|
||||
selectedOrganization.id,
|
||||
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: ssoEntrypoint,
|
||||
sso_certificate: ssoCertificate,
|
||||
client_id: openidClientId,
|
||||
client_secret: openidClientSecret,
|
||||
openid_authorization: openidAuthorization,
|
||||
openid_token: openidToken,
|
||||
SSORequired: SSORequired,
|
||||
auto_provision: autoPrivision,
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
Save Changes
|
||||
{/* <SaveIcon /> */}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const toggleBetweenRequiredOrOptional = (event) => {
|
||||
if (
|
||||
ssoEntrypoint === "" &&
|
||||
openidAuthorization === "" &&
|
||||
openidToken === ""
|
||||
) {
|
||||
if (!SSORequired) {
|
||||
toast.error(
|
||||
"Please fill in fields for either OpenID connect or SSO before continuing. "
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
toast.info("Toggled SSO. Remember to save.");
|
||||
}
|
||||
|
||||
setSSORequired(event.target.checked);
|
||||
};
|
||||
|
||||
const handleChangeAutoProvision = (event) => {
|
||||
|
||||
if (
|
||||
ssoEntrypoint === "" &&
|
||||
openidAuthorization === "" &&
|
||||
openidToken === ""
|
||||
) {
|
||||
if (!autoPrivision) {
|
||||
toast.error(
|
||||
"Please fill in fields for either OpenID connect or SSO before continuing. "
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setAutoProvision((prev)=> !prev);
|
||||
toast.info("Toggled Auto Provisioning. Remember to save.");
|
||||
}
|
||||
};
|
||||
|
||||
const HandleTestSSO = () => {
|
||||
const url = `${globalUrl}/api/v1/orgs/${selectedOrganization?.id}/change`;
|
||||
const data = {
|
||||
org_id: selectedOrganization?.id,
|
||||
sso_test: true,
|
||||
};
|
||||
|
||||
fetch(url, {
|
||||
mode: "cors",
|
||||
credentials: "include",
|
||||
crossDomain: true,
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
toast.error(
|
||||
"Failed to test SSO. Please try again later or contact support@shuffler.io if issue persists.",
|
||||
{ duration: 3000 }
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((responjson) => {
|
||||
if (!responjson) return;
|
||||
|
||||
if (responjson["reason"] === "SSO_REDIRECT") {
|
||||
toast.info(
|
||||
"Redirecting to SSO login page as SSO is required for this organization.",
|
||||
{
|
||||
duration: 3000,
|
||||
onClose: () => {
|
||||
window.location.href = responjson["url"];
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
toast.error(
|
||||
"No SSO found for this org. Please set up SSO for this org.",
|
||||
{ duration: 3000 }
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error for SSO test:", error);
|
||||
toast.error(
|
||||
"An error occurred while testing SSO. Please try again.",
|
||||
{ duration: 3000 }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", height: "100%",boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121', borderRadius: '16px', }}>
|
||||
<div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}} >
|
||||
<div style={{ width: "100%", overflowX: 'hidden', maxWidth: 883}}>
|
||||
<Typography style={{ width: "100%", fontWeight: 'bold', fontSize: 24}}>
|
||||
SSO Configuration
|
||||
</Typography>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
justifyContent: 'flex-start',
|
||||
marginTop: 20
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
style={{ marginTop: 5, marginBottom: 5, color: "rgba(158, 158, 158, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400 }}
|
||||
>
|
||||
Make SAML SSO or OpenID Authentication Required or Optional for Your Organization.
|
||||
</Typography>
|
||||
<div>
|
||||
<Switch
|
||||
checked={SSORequired}
|
||||
onChange={toggleBetweenRequiredOrOptional}
|
||||
sx={{marginBottom: 0.6, marginTop: 0.6}}
|
||||
name="onOffSwitch"
|
||||
color="primary"
|
||||
title="Make SAML SSO or OpenID Authentication Required or Optional for Your Organization"
|
||||
/>
|
||||
{SSORequired ? "Required" : "Optional"}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* auto privisiong in sso */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
justifyContent: 'flex-start',
|
||||
marginTop: 30
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
style={{ marginTop: 5, marginBottom: 5, color: "rgba(158, 158, 158, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400 }}
|
||||
>
|
||||
Auto-provisioning of users in SSO. By default, users are auto-provisioned in SSO when they login. If you enable this, no new user will be added in your organization when they login via SSO.
|
||||
</Typography>
|
||||
<div>
|
||||
<Switch
|
||||
checked={autoPrivision}
|
||||
onChange={handleChangeAutoProvision}
|
||||
sx={{marginBottom: 0.6, marginTop: 0.6}}
|
||||
name="onOffSwitch"
|
||||
color="primary"
|
||||
title="Disable auto-provisioning of users in SSO"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginTop: 30,
|
||||
width: "100%",
|
||||
paddingBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Typography style={{color: "rgba(158, 158, 158, 1)", margin: "5px 0px 5px 0px", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }}>
|
||||
You can test your SSO configuration by clicking the button below.
|
||||
Before testing, ensure you have set Open ID Connect or SAML SSO
|
||||
credentials.
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title={
|
||||
!(
|
||||
ssoEntrypoint?.length > 0 ||
|
||||
ssoCertificate?.length > 0 ||
|
||||
openidAuthorization?.length > 0 ||
|
||||
openidClientId?.length > 0
|
||||
)
|
||||
? "Please ensure all SSO credentials are set before testing."
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<span style={{ width: 100 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
style={{ width: 100, textTransform: "none", margin: "10px 10px 10px 0px" }}
|
||||
disabled={
|
||||
!(
|
||||
ssoEntrypoint?.length > 0 ||
|
||||
ssoCertificate?.length > 0 ||
|
||||
openidAuthorization?.length > 0 ||
|
||||
openidClientId?.length > 0
|
||||
)
|
||||
}
|
||||
onClick={HandleTestSSO}
|
||||
>
|
||||
Test SSO
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Grid item xs={12} sx={{marginTop: 2}}>
|
||||
<span style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography style={{ textAlign: "left", color: "rgba(241, 241, 241, 1)", fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontSize: 24, fontWeight: "bold", }}>OpenID connect</Typography>
|
||||
<span style={{ marginTop: 8, color: "rgba(158, 158, 158, 1)", fontSize: 16, fontWeight: 400 }}>
|
||||
Configure and Authorize SAML / SSO or OpenID connect. {" "}
|
||||
<a
|
||||
target="_blank"
|
||||
href="/docs/extensions#single-signon"
|
||||
style={{ color: "rgba(255, 132, 68, 1)" }}
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
<Typography style={{ textAlign: "left", fontSize: 16, marginTop: 8, color: "rgba(158, 158, 158, 1)", fontWeight: 400 }}>
|
||||
IdP URL for Shuffle OpenID: <Link to={`${globalUrl}/api/v1/login_openid`} target="_blank" style={{ color: "rgba(241, 241, 241, 1)", textDecoration: "none", fontSize: 16,}}>{`${globalUrl}/api/v1/login_openid`}</Link>
|
||||
</Typography>
|
||||
<Grid container style={{ marginTop: 8, }} spacing={2}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>Client ID</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="The OpenID client ID from the identity provider"
|
||||
value={openidClientId}
|
||||
onChange={(e) => {
|
||||
setOpenidClientId(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
||||
fontWeight: 400,
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>Client Secret</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE"
|
||||
value={openidClientSecret}
|
||||
onChange={(e) => {
|
||||
setOpenidClientSecret(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
||||
fontWeight: 400,
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container style={{ marginTop: 10, }} spacing={2}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>Authorization URL</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
placeholder="The OpenID authorization URL (usually ends with /authorize)"
|
||||
value={openidAuthorization}
|
||||
onChange={(e) => {
|
||||
setOpenidAuthorization(e.target.value)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
||||
fontWeight: 400,
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>Token URL</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
placeholder="The OpenID token URL (usually ends with /token)"
|
||||
value={openidToken}
|
||||
onChange={(e) => {
|
||||
setOpenidToken(e.target.value)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
||||
fontWeight: 400,
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/**/}
|
||||
{/*isCloud ? null : */}
|
||||
<Grid item xs={12} sx={{ marginTop: 3.5 }} >
|
||||
<Typography variant="h4" style={{ textAlign: "left", color: "rgba(241, 241, 241, 1)", fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontSize: 24, fontWeight: 600, }}>SAML SSO (v1.1)</Typography>
|
||||
<Typography variant="body2" style={{ textAlign: "left", marginTop: 8, color: "rgba(241, 241, 241, 1)", fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontSize: 16, fontWeight: 400, color: "rgba(158, 158, 158, 1)" }} color="textSecondary">
|
||||
IdP URL for Shuffle SAML/SSO: <Link to={`${globalUrl}/api/v1/login_sso`} target="_blank" style={{ color: "rgba(241, 241, 241, 1)", textDecoration: "none" }}>{`${globalUrl}/api/v1/login_sso`}</Link>
|
||||
</Typography>
|
||||
<Grid container style={{ marginTop: 10, }} spacing={2}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>SSO Entrypoint (IdP)</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="The entrypoint URL from your provider"
|
||||
value={ssoEntrypoint}
|
||||
onChange={(e) => {
|
||||
setSsoEntrypoint(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
||||
fontWeight: 400,
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>SSO Certificate (X509)</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
placeholder="The X509 certificate to use"
|
||||
value={ssoCertificate}
|
||||
onChange={(e) => {
|
||||
setSsoCertificate(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
||||
fontWeight: 400,
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<div style={{ textAlign: "center", margin: "50px auto 0px auto", }}>
|
||||
{orgSaveButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SSOTab
|
||||
@@ -58,7 +58,7 @@ const data = [
|
||||
"curve-style": "unbundled-bezier",
|
||||
label: "data(label)",
|
||||
"text-margin-y": "-15px",
|
||||
width: "3px",
|
||||
width: "2px",
|
||||
color: "white",
|
||||
"line-fill": "linear-gradient",
|
||||
"line-gradient-stop-positions": ["0.0", "100"],
|
||||
@@ -95,6 +95,9 @@ const data = [
|
||||
{
|
||||
selector: `node[type="COMMENT"]`,
|
||||
css: {
|
||||
label: function(element) {
|
||||
return element.data("label")
|
||||
},
|
||||
shape: "roundrectangle",
|
||||
color: "data(color)",
|
||||
width: "data(width)",
|
||||
@@ -128,10 +131,8 @@ const data = [
|
||||
selector: `node[example="noapp"]`,
|
||||
css: {
|
||||
// Make background image padding on the left side 20px
|
||||
"background-width": "75%",
|
||||
"background-height": "75%",
|
||||
"background-position-x": "17px",
|
||||
"background-position-y": "17px",
|
||||
"background-width": "100%",
|
||||
"background-height": "100%",
|
||||
|
||||
"background-color": "data(iconBackground)",
|
||||
"background-fill": "data(fillstyle)",
|
||||
@@ -156,6 +157,38 @@ const data = [
|
||||
"background-gradient-stop-colors": "data(fillGradient)",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[app_id="shuffle_agent"]`,
|
||||
css: {
|
||||
"height": "74px",
|
||||
"width": "222px",
|
||||
"background-image": "data(large_image)",
|
||||
"label": function(element) {
|
||||
var elementname = element.data("label")
|
||||
if (elementname === null || elementname === undefined) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (elementname.length > 15) {
|
||||
elementname = elementname.substring(0, 15) + ".."
|
||||
}
|
||||
|
||||
return elementname
|
||||
},
|
||||
"background-width": "65px",
|
||||
"background-height": "65px",
|
||||
"background-position-x": "20px",
|
||||
//"background-position-x": "center", // Crashes
|
||||
"background-repeat": "no-repeat",
|
||||
|
||||
"font-size": "14px",
|
||||
"text-halign": "center",
|
||||
"text-valign": "center",
|
||||
"text-margin-x": "-140px",
|
||||
"text-margin-y": "0px",
|
||||
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[app_name="Testing"]`,
|
||||
css: {
|
||||
@@ -423,7 +456,7 @@ const data = [
|
||||
{
|
||||
selector: "edge.success-highlight",
|
||||
css: {
|
||||
width: "5px",
|
||||
width: "3px",
|
||||
"target-arrow-color": "#41dcab",
|
||||
"line-color": "#41dcab",
|
||||
"transition-property": "line-color, width",
|
||||
|
||||
@@ -14,7 +14,7 @@ const theme = createTheme(adaptV4Theme({
|
||||
contrastText: "#000000",
|
||||
},
|
||||
text: {
|
||||
secondary: "rgba(255,255,255,0.7)",
|
||||
secondary: "rgba(255,255,255,0.8)",
|
||||
},
|
||||
type: "dark",
|
||||
//inputColor: "#383B40",
|
||||
@@ -24,6 +24,7 @@ const theme = createTheme(adaptV4Theme({
|
||||
//platformColor: "#1c1c1d",
|
||||
platformColor: "#212121",
|
||||
backgroundColor: "#1a1a1a",
|
||||
distributionColor: "#40E0D0",
|
||||
|
||||
green: "#5cc879",
|
||||
borderRadius: 10,
|
||||
@@ -75,10 +76,18 @@ const theme = createTheme(adaptV4Theme({
|
||||
fontSize: 11,
|
||||
},
|
||||
defaultImage: "/images/no_image.png",
|
||||
singulOrange: "/images/singul_orange.png",
|
||||
singulGreen: "/images/singul_green.png",
|
||||
singulBlackWhite: "/images/singul_black_white.png",
|
||||
},
|
||||
typography: {
|
||||
fontFamily: `"Roboto", "Helvetica", "Arial", "inter", sans-serif`,
|
||||
useNextVariants: true,
|
||||
fontWeightLight: 300,
|
||||
fontWeightRegular: 400,
|
||||
fontWeightMedium: 500,
|
||||
fontWeightSemiBold: 600,
|
||||
fontWeightBold: 700,
|
||||
h1: {
|
||||
fontSize: 40,
|
||||
},
|
||||
@@ -100,18 +109,45 @@ const theme = createTheme(adaptV4Theme({
|
||||
},
|
||||
},
|
||||
MuiCssBaseline: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: `
|
||||
@font-face {
|
||||
font-family: 'roboto';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 300;
|
||||
src: local('roboto'), local('roboto'), format('truetype');
|
||||
unicodeRange: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF;
|
||||
}
|
||||
`,
|
||||
},
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: `
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: local('Roboto SemiBold'), local('Roboto-SemiBold');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold');
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -11,29 +11,16 @@ const Admin2 = (props) => {
|
||||
const [organizationFeatures, setOrganizationFeatures] = useState({});
|
||||
const [orgRequest, setOrgRequest] = React.useState(true);
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
|
||||
if (document !== undefined) {
|
||||
if (selectedOrganization?.name !== undefined) {
|
||||
document.title = selectedOrganization?.name + " - Admin - Shuffle"
|
||||
} else {
|
||||
document.title = "Admin - Shuffle"
|
||||
}
|
||||
}
|
||||
|
||||
const handleGetOrg = (orgId) => {
|
||||
// if (
|
||||
// serverside !== true &&
|
||||
// window.location.search !== undefined &&
|
||||
// window.location.search !== null
|
||||
// ) {
|
||||
// const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
// const params = Object.fromEntries(urlSearchParams.entries());
|
||||
// const foundorgid = params["org_id"];
|
||||
// if (foundorgid !== undefined && foundorgid !== null) {
|
||||
// orgId = foundorgid;
|
||||
// }
|
||||
// }
|
||||
console.log("getting organization details for: ", orgId);
|
||||
|
||||
// if (orgId === undefined) {
|
||||
// toast(
|
||||
// "Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.",
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Just use this one?
|
||||
|
||||
fetch(`${globalUrl}/api/v1/orgs/${orgId}`, {
|
||||
method: "GET",
|
||||
@@ -124,7 +111,8 @@ const Admin2 = (props) => {
|
||||
setSelectedStatus(leads);
|
||||
}
|
||||
|
||||
setSelectedOrganization(responseJson);
|
||||
|
||||
setSelectedOrganization(responseJson)
|
||||
var lists = {
|
||||
active: {
|
||||
triggers: [],
|
||||
@@ -330,7 +318,7 @@ const Admin2 = (props) => {
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 29, zoom: 0.9}}>
|
||||
<AdminNavBar userdata={userdata} isLoaded={isLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud} serverside={serverside} />
|
||||
<AdminNavBar userdata={userdata} isLoaded={isLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -95,14 +95,18 @@ const ApiExplorerWrapper = (props) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (openapi?.id === "HTTP") {
|
||||
selectedAppData.name = "HTTP"
|
||||
}
|
||||
|
||||
if (selectedAppData !== undefined && selectedAppData !== null && Object.getOwnPropertyNames(selectedAppData).length > 0) {
|
||||
HandleAppAuthentication(selectedAppData?.name)
|
||||
}
|
||||
}, [selectedAppData, openapi])
|
||||
|
||||
useEffect(() => {
|
||||
getAppData(appid)
|
||||
if (appid !== undefined && appid !== null && appid.length !== 0) {
|
||||
getAppData(appid)
|
||||
HandleGetLocations()
|
||||
}
|
||||
|
||||
@@ -136,7 +140,10 @@ const ApiExplorerWrapper = (props) => {
|
||||
const runAlgoliaAppSearch = (appname) => {
|
||||
const index = searchClient.initIndex("appsearch");
|
||||
|
||||
console.log("Running appsearch for: ", appname);
|
||||
if (appname === "HTTP" || appname === "http") {
|
||||
navigate("/apis")
|
||||
return
|
||||
}
|
||||
|
||||
index
|
||||
.search(appname)
|
||||
@@ -151,19 +158,25 @@ const ApiExplorerWrapper = (props) => {
|
||||
|
||||
if (newname?.includes(appsearchname)) {
|
||||
found = true
|
||||
|
||||
getAppData(hit.objectID)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
toast.error("Failed to get app data or App doesn't exist (1). Redirecting..");
|
||||
toast.error(`Failed to get API data for '${appname}' (1). Contact support@shuffler.io if this persists.`, {
|
||||
"autoClose": 10000,
|
||||
})
|
||||
|
||||
setTimeout(()=>{
|
||||
navigate("/search?tab=apps");
|
||||
},3000)
|
||||
}
|
||||
} else {
|
||||
toast.error("Failed to get app data or App doesn't exist (2). Redirecting..");
|
||||
toast.error(`Failed to get API data for '${appname}' (2). Contact support@shuffler.io if this persists.`, {
|
||||
"autoClose": 10000,
|
||||
})
|
||||
setTimeout(()=>{
|
||||
navigate("/search?tab=apps");
|
||||
},3000)
|
||||
@@ -177,6 +190,29 @@ const ApiExplorerWrapper = (props) => {
|
||||
// Fetch data when appid is available
|
||||
const getAppData = useCallback((appid) => {
|
||||
if (appid === undefined || appid === null || appid.length === 0) {
|
||||
toast.warning("No app ID loaded. Showing default API testing window. ")
|
||||
setOpenapi({
|
||||
"id": "HTTP",
|
||||
"servers": [
|
||||
{"url": "https://shuffler.io"},
|
||||
],
|
||||
"info": {
|
||||
"title": "HTTP",
|
||||
"x-logo": theme.palette?.defaultImage,
|
||||
},
|
||||
"paths": {
|
||||
"/api/v1/workflows/usecases": {
|
||||
"get": {
|
||||
"summary": "Custom Action",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
setAppLoaded(true)
|
||||
|
||||
//setTimeout(() => {
|
||||
// navigate("/search?tab=apps")
|
||||
//}, 3000)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -197,16 +233,21 @@ const ApiExplorerWrapper = (props) => {
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
toast.error("Failed to get app data or App doesn't exist (3). Redirecting..");
|
||||
setTimeout(()=>{
|
||||
navigate("/search?tab=apps");
|
||||
},3000)
|
||||
setTimeout(() => {
|
||||
navigate("/search?tab=apps")
|
||||
}, 3000)
|
||||
return;
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === true) {
|
||||
handleDecodeOfOpenApiData(responseJson);
|
||||
if (responseJson.openapi === undefined || responseJson.openapi === null) {
|
||||
toast.warning("Loaded App, but no API found. Redirecting back to app..")
|
||||
navigate(`/apps/${appid}`)
|
||||
} else {
|
||||
handleDecodeOfOpenApiData(responseJson);
|
||||
}
|
||||
} else {
|
||||
toast.error("Failed to get app data or App doesn't exist (4)");
|
||||
}
|
||||
@@ -337,7 +378,7 @@ const ApiExplorerWrapper = (props) => {
|
||||
const parsedHeaders = {};
|
||||
|
||||
if (typeof headers === 'string' && headers) {
|
||||
const splitHeaders = headers.split("\n");
|
||||
const splitHeaders = headers.split(`\n`)
|
||||
|
||||
splitHeaders.forEach(header => {
|
||||
let splitItem;
|
||||
@@ -399,6 +440,13 @@ const ApiExplorerWrapper = (props) => {
|
||||
selectedAppData.name = appname
|
||||
}
|
||||
|
||||
console.log("APPNAME: ", appname, openapi.id)
|
||||
if (openapi?.id === "HTTP" || appname === "HTTP" || appname === "http") {
|
||||
setAppAuthentication(data)
|
||||
setSelectedAuthentication({})
|
||||
return
|
||||
}
|
||||
|
||||
const filteredData = data.filter((appAuth) => appAuth?.app?.id === appid || appAuth?.app?.name?.replaceAll(" ", "_").toLowerCase() === selectedAppData?.name?.replaceAll(" ", "_").toLowerCase());
|
||||
if (filteredData.length === 0) {
|
||||
setAppAuthentication([])
|
||||
@@ -481,7 +529,7 @@ const ApiExplorerWrapper = (props) => {
|
||||
return array
|
||||
.map(item => (item.key.trim().length > 0 && item.value.trim().length > 0 ? `${item.key}=${item.value}` : ""))
|
||||
.filter(str => str.length > 0)
|
||||
.join("\n");
|
||||
.join(``);
|
||||
};
|
||||
|
||||
var appid = "";
|
||||
@@ -491,7 +539,7 @@ const ApiExplorerWrapper = (props) => {
|
||||
}else if (openapi?.id?.length > 0) {
|
||||
appid = openapi?.id;
|
||||
}else{
|
||||
toast.error("App id is missing. Please try again.");
|
||||
toast.error("App id is missing and we can't run the API. Please contact support@shuffler.io if this persists.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -615,7 +663,7 @@ const ApiExplorerWrapper = (props) => {
|
||||
}
|
||||
}
|
||||
} else if (validate.result.status === 404) {
|
||||
toast.error("Page not found. Please try a different URL.")
|
||||
//toast.error("Page not found. Please try a different URL.")
|
||||
} else if (validate.result.error !== undefined && validate.result.error !== null && validate.result.error.length > 0) {
|
||||
if (validate.result.error.toLowerCase().includes("max retries")) {
|
||||
toast.error("Are you sure the URL is correct? It seems like the server is not responding.")
|
||||
@@ -678,6 +726,7 @@ const ApiExplorerWrapper = (props) => {
|
||||
'& .MuiList-root': {
|
||||
backgroundColor: "#1f1f1f",
|
||||
},
|
||||
maxWidth: 500,
|
||||
},
|
||||
}
|
||||
}}
|
||||
@@ -709,6 +758,7 @@ const ApiExplorerWrapper = (props) => {
|
||||
No Selection
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
|
||||
{appAuthentication?.length > 0 ? appAuthentication.map((appAuth) => (
|
||||
<div
|
||||
key={appAuth?.id}
|
||||
@@ -718,13 +768,13 @@ const ApiExplorerWrapper = (props) => {
|
||||
backgroundColor: '#1f1f1f',
|
||||
color: 'white',
|
||||
padding: '5px',
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
value={appAuth?.id}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
color: 'white',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
@@ -732,6 +782,11 @@ const ApiExplorerWrapper = (props) => {
|
||||
setAuthenticationName(appAuth.app?.name)
|
||||
}}
|
||||
>
|
||||
{appAuth?.app?.large_image !== undefined && appAuth?.app?.large_image !== null && appAuth?.app?.large_image.length > 0 ?
|
||||
<Tooltip title={appAuth?.app?.name} placement="top">
|
||||
<img src={appAuth?.app?.large_image} alt={appAuth?.app?.name} style={{ maxWidth: 20, maxHeight: 20, marginRight: 10, borderRadius: 5 }} />
|
||||
</Tooltip>
|
||||
: null}
|
||||
{appAuth?.validation?.valid === true ?
|
||||
<Tooltip title="Validated" placement="top">
|
||||
<CheckCircleIcon style={{ color: green, marginRight: 10, }} />
|
||||
@@ -1631,7 +1686,9 @@ const ApiExplorerWrapper = (props) => {
|
||||
>
|
||||
<div style={{display: "flex", }}>
|
||||
<div style={{width: 600, margin: "auto", display: "flex", }}>
|
||||
{isLoggedIn === true ?
|
||||
{openapi?.id === "HTTP" ?
|
||||
null
|
||||
: isLoggedIn === true ?
|
||||
<Button
|
||||
variant={authHighlighted ? "contained" : "outlined"}
|
||||
style={{
|
||||
@@ -1667,17 +1724,20 @@ const ApiExplorerWrapper = (props) => {
|
||||
}
|
||||
|
||||
{appAuthentication?.length > 0 ?
|
||||
<div style={{display: "flex", flex: 2, }}>
|
||||
<Typography style={{textAlign: "center", flex: 1, color: "white", marginTop: 15, }} variant="body1">
|
||||
or use
|
||||
</Typography>
|
||||
<div style={{display: "flex", flex: 2, textAlign: "center", }}>
|
||||
{openapi?.id === "HTTP" ? null :
|
||||
<Typography style={{textAlign: "center", flex: 1, color: "white", marginTop: 15, }} variant="body1">
|
||||
or use
|
||||
</Typography>
|
||||
}
|
||||
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 'auto',
|
||||
margin: 'auto',
|
||||
maxWidth: 200,
|
||||
}}>
|
||||
<AuthenticationList />
|
||||
</div>
|
||||
|
||||
@@ -276,7 +276,14 @@ export const appCategories = [
|
||||
"color": "#FFC107",
|
||||
"icon": "network",
|
||||
"action_labels": ["Get Rules", "Allow IP", "Block IP",],
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"name": "AI",
|
||||
"color": "#FFC107",
|
||||
"icon": "AI",
|
||||
"action_labels": ["Answer Question", "Run Action", "Run LLM",],
|
||||
},
|
||||
{
|
||||
"name": "Other",
|
||||
"color": "#FFC107",
|
||||
"icon": "other",
|
||||
@@ -418,6 +425,7 @@ const AppCreator = (defaultprops) => {
|
||||
const [appBuilding, setAppBuilding] = useState(false);
|
||||
const [fileDownloadEnabled, setFileDownloadEnabled] = useState(false);
|
||||
const [actionAmount, setActionAmount] = useState(increaseAmount);
|
||||
const [newAppGroup, setNewAppGroup] = useState("")
|
||||
|
||||
const [oauth2Scopes, setOauth2Scopes] = useState([]);
|
||||
const [oauth2Type, setOauth2Type] = useState("delegated");
|
||||
@@ -2616,10 +2624,26 @@ const AppCreator = (defaultprops) => {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
//if (response.status !== 200) {
|
||||
// setErrorCode("An error occurred during validation")
|
||||
// throw new Error("NOT 200 :O")
|
||||
//}
|
||||
if (response.status === 403) {
|
||||
var urlParams = new URLSearchParams(window.location.search)
|
||||
if (urlParams.has("id")) {
|
||||
toast.error("Please log in to build this app. If this error persists, please contact support@shuffler.io")
|
||||
} else {
|
||||
toast.error("Failed to save the app as you are not the owner. Redirecting you to the forking page. When there, save again.")
|
||||
if (props.match.params.appid !== undefined && props.match.params.appid !== null && props.match.params.appid.length > 0) {
|
||||
setTimeout(() => {
|
||||
window.open(`/apps/new?id=${props.match.params.appid}`, "_blank")
|
||||
}, 2500)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
setErrorCode("An error occurred during validation")
|
||||
//throw new Error("NOT 200 :O")
|
||||
}
|
||||
|
||||
setAppBuilding(false);
|
||||
return response.json();
|
||||
@@ -3167,6 +3191,12 @@ const AppCreator = (defaultprops) => {
|
||||
setOauth2Scopes(chips)
|
||||
setUpdate(Math.random())
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
var newchips = oauth2Scopes
|
||||
newchips.push(e.target.value)
|
||||
setOauth2Scopes(newchips)
|
||||
setUpdate(Math.random())
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
@@ -4616,23 +4646,25 @@ const AppCreator = (defaultprops) => {
|
||||
}}
|
||||
/>
|
||||
*/}
|
||||
<h4>Choose a Category</h4>
|
||||
<Select
|
||||
fullWidth
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setNewWorkflowCategories([e.target.value]);
|
||||
setUpdate("added " + e.target.value);
|
||||
}}
|
||||
value={newWorkflowCategories.length === 0 ? "Select a category" : newWorkflowCategories[0]}
|
||||
|
||||
style={{ backgroundColor: inputColor, color: "white", height: "50px" }}
|
||||
>
|
||||
{categories.map((data, index) => {
|
||||
<div style={{display: "flex", }}>
|
||||
<div style={{flex: 2, }}>
|
||||
<h4>Choose a Category</h4>
|
||||
<Select
|
||||
fullWidth
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setNewWorkflowCategories([e.target.value]);
|
||||
setUpdate("added " + e.target.value);
|
||||
}}
|
||||
value={newWorkflowCategories.length === 0 ? "Select a category" : newWorkflowCategories[0]}
|
||||
|
||||
style={{ backgroundColor: inputColor, color: "white", height: "50px" }}
|
||||
>
|
||||
{categories.map((data, index) => {
|
||||
if (data === undefined || data === null || data === "" || data === undefined || data === null || data === "") {
|
||||
return null
|
||||
}
|
||||
@@ -4645,9 +4677,38 @@ const AppCreator = (defaultprops) => {
|
||||
>
|
||||
{data.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
</Select>
|
||||
</div>
|
||||
{/*
|
||||
<div style={{flex: 1, marginLeft: 25, }}>
|
||||
<Tooltip title="Helps when searching for apps. E.g. Google or Microsoft are their own groups." placement="top">
|
||||
<h4 style={{marginBottom: 0, }}>Group</h4>
|
||||
</Tooltip>
|
||||
<TextField
|
||||
style={{
|
||||
}}
|
||||
fullWidth
|
||||
placeholder="Group"
|
||||
type="text"
|
||||
id="standard-required"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
defaultValue={newAppGroup}
|
||||
onChange={(e) => {
|
||||
setNewAppGroup(e.target.value)
|
||||
setUpdate("group added "+e.target.value)
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
<h4>Tags</h4>
|
||||
<MuiChipsInput
|
||||
style={{ marginTop: 10 }}
|
||||
@@ -5435,6 +5496,7 @@ const AppCreator = (defaultprops) => {
|
||||
minWidth: 174,
|
||||
minHeight: 174,
|
||||
objectFit: "contain",
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -5450,6 +5512,7 @@ const AppCreator = (defaultprops) => {
|
||||
margin: "auto",
|
||||
marginTop: 30,
|
||||
marginLeft: 40,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
}}
|
||||
onClick={() => {
|
||||
upload.click();
|
||||
@@ -5832,6 +5895,7 @@ const AppCreator = (defaultprops) => {
|
||||
//setOpenApiModal(true)
|
||||
toast.info("Action merging & fork management coming soon")
|
||||
}}
|
||||
disabled={true}
|
||||
style={{marginLeft: 10, }}
|
||||
>
|
||||
<CallMergeIcon
|
||||
@@ -5846,7 +5910,7 @@ const AppCreator = (defaultprops) => {
|
||||
href="https://shuffler.io/docs/app_creation#app-creator-instructions"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
Click here to learn more about app creation
|
||||
Click to learn more about app creation
|
||||
</a>
|
||||
<div
|
||||
style={{
|
||||
@@ -5940,7 +6004,6 @@ const AppCreator = (defaultprops) => {
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
paddingTop: 5,
|
||||
marginTop: 5,
|
||||
marginRight: 15,
|
||||
backgroundColor: inputColor,
|
||||
@@ -6196,7 +6259,7 @@ const AppCreator = (defaultprops) => {
|
||||
{testView}
|
||||
*/}
|
||||
|
||||
<div style={{height: 50, padding: 15, display: "flex", marginTop: 35, position: "fixed", bottom: 0, left: 0, width: "100%", backgroundColor: theme.palette?.backgroundColor, borderTop: "1px solid rgba(255,255,255,0.3)",}}>
|
||||
<div style={{height: isCloud ? 50 : 80, padding: 15, display: "flex", marginTop: 35, position: "fixed", bottom: 0, left: 0, width: "100%", backgroundColor: theme.palette?.backgroundColor, borderTop: "1px solid rgba(255,255,255,0.3)",}}>
|
||||
<div style={{width: 450, margin: "auto", display: "flex", textAlign: "center", }}>
|
||||
{appDownloadData.length > 0 ?
|
||||
<Tooltip title="Download the OpenAPI specification for the App" placement="bottom">
|
||||
|
||||
@@ -176,16 +176,18 @@ export const GetParsedPaths = (inputdata, basekey) => {
|
||||
}
|
||||
|
||||
if (typeof inputdata !== "object") {
|
||||
return parsedValues;
|
||||
return parsedValues
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(inputdata)) {
|
||||
for (var [key, value] of Object.entries(inputdata)) {
|
||||
key = key.replaceAll(" ", "_")
|
||||
|
||||
// Check if loop or JSON
|
||||
const extra = basekey.length > 0 ? splitkey : "";
|
||||
const basekeyname = `${basekey
|
||||
.slice(1, basekey.length)
|
||||
.split(".")
|
||||
.join(splitkey)}${extra}${key}`;
|
||||
.join(splitkey)}${extra}${key}`
|
||||
|
||||
// Handle direct loop!
|
||||
if (!isNaN(key) && basekey === "") {
|
||||
@@ -205,7 +207,8 @@ export const GetParsedPaths = (inputdata, basekey) => {
|
||||
type: "list",
|
||||
name: `${splitkey}list`,
|
||||
autocomplete: `${basekey.replaceAll(" ", "_")}.#`,
|
||||
});
|
||||
})
|
||||
|
||||
const returnValues = GetParsedPaths(value, `${basekey}.#`);
|
||||
for (var subkey in returnValues) {
|
||||
parsedValues.push(returnValues[subkey]);
|
||||
@@ -1258,7 +1261,6 @@ const Apps = (props) => {
|
||||
style={{
|
||||
width: 150,
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
backgroundColor: inputColor,
|
||||
color: "white",
|
||||
height: 35,
|
||||
marginleft: 10,
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
Close as CloseIcon,
|
||||
Cached as CachedIcon,
|
||||
CloudDownload as CloudDownloadIcon,
|
||||
ForkRight as ForkRightIcon,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
@@ -48,11 +49,14 @@ const searchClient = algoliasearch(
|
||||
);
|
||||
|
||||
// AppCard Component
|
||||
const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab, handleAppClick, leftSideBarOpenByClick, userdata, fetchApps }) => {
|
||||
const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab, handleAppClick, leftSideBarOpenByClick, userdata, fetchApps, appsToShow, setAppsToShow, setUserApps, }) => {
|
||||
const navigate = useNavigate();
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000";
|
||||
const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`;
|
||||
var canEditApp = userdata.admin === "true" || userdata.id === data?.owner || data?.owner === "" || (userdata.admin === "true" && userdata.active_org.id === data?.reference_org) || !data?.generated
|
||||
//const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`;
|
||||
const appUrl = `/apps/${data.id}`
|
||||
|
||||
var canEditApp = userdata?.support || userdata?.id === data?.owner ||
|
||||
(userdata?.admin === "true" && userdata?.active_org?.id === data?.reference_org) || data?.contributors?.includes(userdata?.id)
|
||||
|
||||
const paperStyle = {
|
||||
backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#212121",
|
||||
@@ -66,7 +70,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)",
|
||||
marginBottom: 20,
|
||||
transition: "width 0.3s ease",
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid item xs={12} key={index}>
|
||||
@@ -128,6 +132,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
margin: "12px 0",
|
||||
fontFamily: theme?.typography?.fontFamily,
|
||||
}}>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
@@ -139,6 +144,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
maxWidth: "90%",
|
||||
gap: 8
|
||||
}}>
|
||||
|
||||
<div style={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
@@ -147,6 +153,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
}}>
|
||||
{data.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div style={{
|
||||
overflow: "hidden",
|
||||
@@ -157,6 +164,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
}}>
|
||||
{data.categories ? data.categories.join(", ") : "NA"}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
@@ -182,8 +190,9 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Deactivate button */}
|
||||
{currTab === 0 && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true && (
|
||||
{(currTab === 0 || currTab === 1) && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true && (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
gap: 8,
|
||||
@@ -191,7 +200,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
paddingRight: 20
|
||||
}}>
|
||||
{
|
||||
canEditApp && (
|
||||
canEditApp ? (
|
||||
<button style={{ backgroundColor: "rgba(73, 73, 73, 1)", border: "none", cursor: "pointer", color: "white", borderRadius: 3, display: "flex", alignItems: "center", justifyContent: "center", height: 35 }}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
@@ -204,9 +213,22 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
) : (
|
||||
<button style={{ backgroundColor: "rgba(73, 73, 73, 1)", border: "none", cursor: "pointer", color: "white", borderRadius: 3, display: "flex", alignItems: "center", justifyContent: "center", height: 35 }}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const editUrl = "/apps/new?id=" + data?.id;
|
||||
navigate(editUrl)
|
||||
}}
|
||||
>
|
||||
<ForkRightIcon />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
<Button
|
||||
disabled={data?.reference_org === userdata?.active_org?.id}
|
||||
className="deactivate-button"
|
||||
sx={{
|
||||
width: 110,
|
||||
@@ -228,7 +250,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const url = `${globalUrl}/api/v1/apps/${data.id}/deactivate`;
|
||||
toast("Deactivating app. Please wait...");
|
||||
//toast("Deactivating app. Please wait...");
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -240,11 +262,14 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
.then((response) => response.json())
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
toast.error(responseJson.reason);
|
||||
if (responseJson?.reason !== undefined && responseJson?.reason !== null && responseJson?.reason !== "") {
|
||||
toast.error(responseJson.reason);
|
||||
} else {
|
||||
toast.error("Failed to deactivate app. Please try again later.")
|
||||
}
|
||||
} else {
|
||||
toast.success("App Deactivated Successfully.");
|
||||
fetchApps();
|
||||
}
|
||||
toast.success("App deactivated successfully. Will take effect on refresh..")
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("app error: ", error.toString());
|
||||
@@ -613,6 +638,7 @@ const Hits = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
@@ -622,7 +648,7 @@ const Hits = ({
|
||||
}}>
|
||||
{hoverEffect === index && (
|
||||
<div>
|
||||
{allActivatedAppIds && allActivatedAppIds.includes(data.objectID) ? (
|
||||
{allActivatedAppIds && allActivatedAppIds?.includes(data.objectID) ? (
|
||||
<Button
|
||||
style={{
|
||||
width: 110,
|
||||
@@ -641,7 +667,9 @@ const Hits = ({
|
||||
}}>
|
||||
Deactivate
|
||||
</Button>
|
||||
|
||||
) : (
|
||||
|
||||
<Button
|
||||
style={{
|
||||
backgroundColor: "#FF8544",
|
||||
@@ -1853,6 +1881,7 @@ const Apps2 = (props) => {
|
||||
app={selectedApp}
|
||||
userdata={userdata}
|
||||
globalUrl={globalUrl}
|
||||
getApps={getApps}
|
||||
/>
|
||||
<AppCreationModal
|
||||
open={createAppModalOpen}
|
||||
@@ -2228,6 +2257,10 @@ const Apps2 = (props) => {
|
||||
leftSideBarOpenByClick={leftSideBarOpenByClick}
|
||||
userdata={userdata}
|
||||
fetchApps={fetchApps}
|
||||
|
||||
setUserApps={setUserApps}
|
||||
appsToShow={appsToShow}
|
||||
setAppsToShow={setAppsToShow}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -2277,6 +2310,9 @@ const Apps2 = (props) => {
|
||||
<AppCard key={index} data={data} index={index} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} globalUrl={globalUrl} deactivatedIndexes={deactivatedIndexes} currTab={currTab} userdata={userdata}
|
||||
handleAppClick={handleAppClick} leftSideBarOpenByClick={leftSideBarOpenByClick}
|
||||
fetchApps={fetchApps}
|
||||
setUserApps={setUserApps}
|
||||
appsToShow={appsToShow}
|
||||
setAppsToShow={setAppsToShow}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -397,7 +397,7 @@ const Dashboard = (props) => {
|
||||
if (foundQuery !== null && foundQuery !== undefined) {
|
||||
setSelectedUsecaseCategory(foundQuery)
|
||||
|
||||
const newitem = removeParam("selected", cursearch);
|
||||
const newitem = removeParam("selected", cursearch);
|
||||
navigate(curpath + newitem)
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ export const CopyToClipboard = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const Paragrah = (props) => {
|
||||
export const Paragraph = (props) => {
|
||||
const element = React.createElement(
|
||||
`p`,
|
||||
{},
|
||||
@@ -123,7 +123,7 @@ export const Paragrah = (props) => {
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{element}
|
||||
</div>
|
||||
)
|
||||
@@ -184,15 +184,15 @@ export const CodeHandler = (props) => {
|
||||
if (validate.valid === false) {
|
||||
// Check if https://shuffler.io in the url
|
||||
// if so, then we change it for the current url
|
||||
if (propvalue.includes("https://shuffler.io")) {
|
||||
newprop = propvalue.replace("https://shuffler.io", window.location.origin)
|
||||
}
|
||||
if (propvalue.includes("https://shuffler.io/api")) {
|
||||
newprop = propvalue.replace("https://shuffler.io/api", window.location.origin+"/api")
|
||||
|
||||
// Check if it contains Bearer APIKEY
|
||||
// If so, replace apikey
|
||||
//if (newprop.includes("Bearer APIKEY")) {
|
||||
// newprop = newprop.replace("Bearer APIKEY", "Bearer API
|
||||
//}
|
||||
|
||||
const foundurl = localStorage.getItem("globalUrl")
|
||||
if (foundurl !== undefined && foundurl !== null && foundurl !== "") {
|
||||
newprop = propvalue.replace("https://shuffler.io/api", foundurl+"/api")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need to check if it's singletick or multi
|
||||
@@ -383,6 +383,7 @@ const Docs = (defaultprops) => {
|
||||
if (hash.includes('?')) {
|
||||
hash = hash.split('?')[0]
|
||||
}
|
||||
|
||||
if (hash) {
|
||||
const element = document.getElementById(hash.toLowerCase())
|
||||
if (element) {
|
||||
@@ -526,7 +527,7 @@ const Docs = (defaultprops) => {
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
padding: 15,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
marginBottom: 30,
|
||||
marginBottom: 25,
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
@@ -618,7 +619,8 @@ const Docs = (defaultprops) => {
|
||||
<Divider
|
||||
style={{
|
||||
width: "90%",
|
||||
marginTop: 40,
|
||||
marginTop: 60,
|
||||
marginBottom: 20,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
@@ -664,6 +666,8 @@ const Docs = (defaultprops) => {
|
||||
minHeight: "93vh",
|
||||
maxHeight: "93vh",
|
||||
marginTop: 70,
|
||||
maxWidth: 250,
|
||||
overflow: "hidden",
|
||||
}
|
||||
|
||||
const fetchDocList = () => {
|
||||
@@ -705,13 +709,12 @@ const Docs = (defaultprops) => {
|
||||
// Find <img> tags and translate them into ![]() format
|
||||
const imgRegex = /<img.*?src="(.*?)"/g;
|
||||
const tocRegex = /^## Table of contents[\s\S]*?(?=^## )|^## Table of contents[\s\S]*$/gm;
|
||||
const newdata = responseJson.reason.replace(imgRegex, '')
|
||||
.replace(tocRegex, "");
|
||||
const newdata = responseJson.reason.replace(imgRegex, '').replace(tocRegex, "");
|
||||
setData(newdata);
|
||||
if (docId === undefined) {
|
||||
document.title = "Shuffle documentation introduction";
|
||||
document.title = "Shuffle automation documentation";
|
||||
} else {
|
||||
document.title = "Shuffle " + docId + " documentation";
|
||||
document.title = "Shuffle " + docId.replace("_", " ") + " documentation";
|
||||
}
|
||||
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.includes("404: Not Found")) {
|
||||
@@ -782,88 +785,6 @@ const Docs = (defaultprops) => {
|
||||
fetchDocs(props.match.params.key);
|
||||
}
|
||||
|
||||
// const parseElementScroll = () => {
|
||||
// const offset = 45;
|
||||
// var parent = document.getElementById("markdown_wrapper_outer");
|
||||
// if (parent !== null) {
|
||||
// //console.log("IN PARENT")
|
||||
// var elements = parent.getElementsByTagName("h2");
|
||||
//
|
||||
// const name = window.location.hash
|
||||
// .slice(1, window.location.hash.length)
|
||||
// .toLowerCase()
|
||||
// .split("%20")
|
||||
// .join(" ")
|
||||
// .split("_")
|
||||
// .join(" ")
|
||||
// .split("-")
|
||||
// .join(" ")
|
||||
// .split("?")[0]
|
||||
//
|
||||
// //console.log(name)
|
||||
// var found = false;
|
||||
// for (var key in elements) {
|
||||
// const element = elements[key];
|
||||
// if (element.innerHTML === undefined) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// // Fix location..
|
||||
// if (element.innerHTML.toLowerCase() === name) {
|
||||
// //console.log(element.offsetTop)
|
||||
// element.scrollIntoView({ behavior: "smooth" });
|
||||
// //element.scrollTo({
|
||||
// // top: element.offsetTop+offset,
|
||||
// // behavior: "smooth"
|
||||
// //})
|
||||
// found = true;
|
||||
// //element.scrollTo({
|
||||
// // top: element.offsetTop-100,
|
||||
// // behavior: "smooth"
|
||||
// //})
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // H#
|
||||
// if (!found) {
|
||||
// elements = parent.getElementsByTagName("h3");
|
||||
// //console.log("NAMe: ", name)
|
||||
// found = false;
|
||||
// for (key in elements) {
|
||||
// const element = elements[key];
|
||||
// if (element.innerHTML === undefined) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// // Fix location..
|
||||
// if (element.innerHTML.toLowerCase() === name) {
|
||||
// element.scrollIntoView({ behavior: "smooth" });
|
||||
// //element.scrollTo({
|
||||
// // top: element.offsetTop-offset,
|
||||
// // behavior: "smooth"
|
||||
// //})
|
||||
// found = true;
|
||||
// //element.scrollTo({
|
||||
// // top: element.offsetTop-100,
|
||||
// // behavior: "smooth"
|
||||
// //})
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// //console.log(element)
|
||||
//
|
||||
// //console.log("NAME: ", name)
|
||||
// //console.log(document.body.innerHTML)
|
||||
// // parent = document.getElementById(parent);
|
||||
//
|
||||
// //var descendants = parent.getElementsByTagName(tagname);
|
||||
//
|
||||
// // this.scrollDiv.current.scrollIntoView({ behavior: 'smooth' });
|
||||
//
|
||||
// //$(".parent").find("h2:contains('Statistics')").parent();
|
||||
// };
|
||||
|
||||
const markdownStyle = {
|
||||
color: "rgba(255, 255, 255, 0.90)",
|
||||
overflow: "hidden",
|
||||
@@ -872,7 +793,7 @@ const Docs = (defaultprops) => {
|
||||
maxWidth: "100%",
|
||||
minWidth: "100%",
|
||||
overflow: "hidden",
|
||||
fontSize: isMobile ? "1.3rem" : "1.1rem",
|
||||
fontSize: isMobile ? "1.3rem" : "1rem",
|
||||
};
|
||||
|
||||
const alertNote = {
|
||||
@@ -1014,7 +935,7 @@ const Docs = (defaultprops) => {
|
||||
h5: Heading,
|
||||
h6: Heading,
|
||||
a: OuterLink,
|
||||
p: Paragrah,
|
||||
p: Paragraph,
|
||||
blockquote: Blockquote,
|
||||
}
|
||||
|
||||
@@ -1084,25 +1005,23 @@ const Docs = (defaultprops) => {
|
||||
<div style={IndexBar}>
|
||||
{tocLines.length > 0 ?
|
||||
(
|
||||
<h4 style={{ fontWeight: 600, margin: 0, fontSize: "16px", marginBottom: "8px" }}>Table Of Content</h4>
|
||||
<h4 style={{ fontWeight: 600, margin: 0, fontSize: "16px", marginBottom: "8px" }}>Table of Content</h4>
|
||||
|
||||
) : null}
|
||||
<nav>
|
||||
{tocLines.map((data, index) => {
|
||||
return (
|
||||
<div className="toc">
|
||||
<div className="toc"
|
||||
>
|
||||
<ListItemButton
|
||||
key={data.text}
|
||||
href={`#${data.id}`}
|
||||
style={{
|
||||
color: activeId === data.id ? "#f86a3e" : "inherit",
|
||||
textDecoration: "none",
|
||||
fontSize: "14px",
|
||||
fontWeight: 400,
|
||||
padding: "4px 0",
|
||||
paddingLeft: "8px",
|
||||
paddingRight: "8px",
|
||||
padding: "4px 8px 4px 8px",
|
||||
lineHeight: "20px",
|
||||
color: activeId === data.id ? "#f86a3e" : "rgba(255,255,255,0.6)",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
handleCollapse(index)
|
||||
@@ -1296,7 +1215,7 @@ const DocsWrapper = memo(({isLoggedIn, isLoaded, children })=>{
|
||||
minWidth: isMobile ? null : (isLoggedIn && isLoaded) ? leftSideBarOpenByClick ? 800 : 900 : null, margin: "auto",
|
||||
position: (isLoggedIn && isLoaded) && leftSideBarOpenByClick ? "relative" : "static",
|
||||
left: (isLoggedIn && isLoaded) && leftSideBarOpenByClick ? 120 : (isLoggedIn && isLoaded) && !leftSideBarOpenByClick ? 80 : 0,
|
||||
marginLeft: windowWidth < 1920 ? leftSideBarOpenByClick && (isLoggedIn && isLoaded) ? 160 : (isLoggedIn && isLoaded) && !leftSideBarOpenByClick ? 80 : 0 : "auto", width: "100%",
|
||||
marginLeft: windowWidth < 1920 ? leftSideBarOpenByClick && (isLoggedIn && isLoaded) ? 90 : (isLoggedIn && isLoaded) && !leftSideBarOpenByClick ? 80 : 0 : "auto", width: "100%",
|
||||
transition: "left 0.3s ease-in-out, min-width 0.3s ease-in-out, max-width 0.3s ease-in-out, position 0.3s ease-in-out, margin 0.3s ease-in-out, margin-left 0.3s ease"
|
||||
}}>
|
||||
{children}
|
||||
|
||||
@@ -80,25 +80,15 @@ const useStyles = makeStyles((theme) => ({
|
||||
datagrid: {
|
||||
border: 0,
|
||||
"& .MuiDataGrid-columnsContainer": {
|
||||
backgroundColor:
|
||||
theme.palette.type === "light" ? "#fafafa" : theme.palette.inputColor,
|
||||
},
|
||||
"& .MuiDataGrid-iconSeparator": {
|
||||
display: "none",
|
||||
},
|
||||
"& .MuiDataGrid-colCell, .MuiDataGrid-cell": {
|
||||
borderRight: `1px solid ${
|
||||
theme.palette.type === "light" ? "white" : "#303030"
|
||||
}`,
|
||||
},
|
||||
"& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": {
|
||||
borderBottom: `1px solid ${
|
||||
theme.palette.type === "light" ? "#f0f0f0" : "#303030"
|
||||
}`,
|
||||
},
|
||||
"& .MuiDataGrid-cell": {
|
||||
color:
|
||||
theme.palette.type === "light" ? "white" : "rgba(255,255,255,0.65)",
|
||||
},
|
||||
"& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption":
|
||||
{
|
||||
|
||||
@@ -170,6 +170,9 @@ const LoginDialog = (props) => {
|
||||
);
|
||||
setMFAField(true);
|
||||
return;
|
||||
} else if (responseJson["reason"] === "MFA_SETUP") {
|
||||
window.location.href = `/login/${responseJson.url}/mfa-setup`;
|
||||
return;
|
||||
}
|
||||
|
||||
setLoginInfo("Successful login, rerouting");
|
||||
@@ -183,9 +186,9 @@ const LoginDialog = (props) => {
|
||||
|
||||
if (responseJson.tutorials === undefined || responseJson.tutorials === null || !responseJson.tutorials.includes("welcome")) {
|
||||
console.log("RUN Welcome!!")
|
||||
setTimeout(() => {
|
||||
navigate("/welcome?tab=2")
|
||||
},200)
|
||||
setTimeout(() => {
|
||||
navigate("/welcome?tab=2")
|
||||
},200)
|
||||
// window.location.pathname = ""
|
||||
return
|
||||
}
|
||||
|
||||
@@ -77,6 +77,24 @@ const RunWorkflow = (defaultprops) => {
|
||||
const [boxWidth, setBoxWidth] = React.useState(500)
|
||||
const [inputQuestions, setInputQuestions] = React.useState([])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (workflow === undefined || workflow === null || Object.keys(workflow).length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (workflow.input_questions === undefined || workflow.input_questions === null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Checks if it's a user input-node based or not
|
||||
if ((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) {
|
||||
} else {
|
||||
setInputQuestions(workflow.input_questions)
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
}, [workflow])
|
||||
|
||||
const IframeWrapper = (props) => {
|
||||
var propsCopy = JSON.parse(JSON.stringify(props))
|
||||
propsCopy.width = 400
|
||||
@@ -117,7 +135,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
props.match = {}
|
||||
props.match.params = params
|
||||
|
||||
const defaultTitle = workflow.name !== undefined ? "Shuffle - Form for " + workflow.name : "Shuffle - Form to Run Workflows"
|
||||
const defaultTitle = workflow.name !== undefined ? "Form for " + workflow.name : "Shuffle - Form to Run Workflows"
|
||||
if (document != undefined && document.title != defaultTitle) {
|
||||
document.title = defaultTitle
|
||||
}
|
||||
@@ -152,7 +170,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log("EXEC: ", executionArgument)
|
||||
//console.log("EXEC: ", executionArgument)
|
||||
for (var key in executionArgument) {
|
||||
if (executionArgument[key] === undefined || executionArgument[key] === null || executionArgument[key] === "") {
|
||||
return false
|
||||
@@ -705,6 +723,8 @@ const RunWorkflow = (defaultprops) => {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setInputQuestions(responseJson.input_questions)
|
||||
}
|
||||
|
||||
if (responseJson.form_control.input_markdown !== undefined && responseJson.form_control.input_markdown !== null && responseJson.form_control.input_markdown.length > 0) {
|
||||
@@ -894,7 +914,6 @@ const RunWorkflow = (defaultprops) => {
|
||||
|
||||
const fetchUpdates = (execution_id, authorization, getorg, replaceMarkdown) => {
|
||||
if (execution_id === undefined || execution_id === null || execution_id === "") {
|
||||
console.log("No execution id: ", execution_id)
|
||||
stop()
|
||||
return
|
||||
}
|
||||
@@ -1225,9 +1244,9 @@ const RunWorkflow = (defaultprops) => {
|
||||
</div>
|
||||
}
|
||||
|
||||
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
|
||||
{workflow?.input_questions !== undefined && workflow?.input_questions !== null && workflow?.input_questions?.length > 0 ?
|
||||
<div style={{marginBottom: 5, }}>
|
||||
{inputQuestions.map((question, index) => {
|
||||
{inputQuestions?.map((question, index) => {
|
||||
|
||||
// Multiple choice checks for semicolon-splits
|
||||
var multiChoiceOptions = question.value !== undefined && question.value !== null && question.value.length > 0 && question.value.includes(";") ? question.value.split(";") : []
|
||||
@@ -1241,9 +1260,12 @@ const RunWorkflow = (defaultprops) => {
|
||||
return (
|
||||
<div style={{marginBottom: 10}} key={index}>
|
||||
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{question.name}
|
||||
</Typography>
|
||||
|
||||
{multiChoiceOptions.length > 1 ?
|
||||
<div>
|
||||
{question.name}
|
||||
<Select
|
||||
disabled={disabledButtons}
|
||||
fullWidth
|
||||
@@ -1280,7 +1302,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginTop: 5,
|
||||
}}
|
||||
label={question.value.charAt(0).toUpperCase() + question.value.slice(1)}
|
||||
label={question?.value?.charAt(0)?.toUpperCase() + question?.value?.slice(1)}
|
||||
required
|
||||
|
||||
disabled={disabledButtons}
|
||||
@@ -1642,7 +1664,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
maxHeight: 500,
|
||||
position: "absolute",
|
||||
left: 150,
|
||||
top: 0,
|
||||
top: 75,
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
|
||||
|
||||
@@ -719,6 +719,7 @@ const Settings = (props) => {
|
||||
//onChange={e => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{/*
|
||||
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
|
||||
<TextField
|
||||
style={{
|
||||
@@ -771,6 +772,7 @@ const Settings = (props) => {
|
||||
onChange={(e) => setLastname(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
<h2>APIKEY</h2>
|
||||
<a
|
||||
target="_blank"
|
||||
|
||||
@@ -148,7 +148,7 @@ const SetAuthentication = (props) => {
|
||||
console.log("App: ", app)
|
||||
|
||||
return (
|
||||
<div style={{width: 1000, margin: "auto", marginTop: 50, }}>
|
||||
<div style={{width: 1000, margin: "auto", paddingTop: 50, }}>
|
||||
{loadFail !== "" ?
|
||||
loadFail
|
||||
:
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
Chip,
|
||||
Checkbox,
|
||||
Fade,
|
||||
Skeleton,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
@@ -140,12 +141,166 @@ const UsecaseListComponent = (props) => {
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
const [apps, setApps] = useState([])
|
||||
|
||||
const [autoOpenUsecase, setAutoOpenUsecase] = useState(null);
|
||||
|
||||
const classes = useStyles();
|
||||
let navigate = useNavigate();
|
||||
|
||||
const [mitreTags, setMitreTags] = useState([]);
|
||||
|
||||
// Add loading state
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// Add this useEffect to handle URL parameters on load
|
||||
useEffect(() => {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
|
||||
const selectedUsecase = params["selected_object"];
|
||||
if (selectedUsecase && keys.length > 0) {
|
||||
const usecaseName = selectedUsecase.toLowerCase().replaceAll("_", " ");
|
||||
|
||||
// Find the matching usecase in the keys
|
||||
for (const category of keys) {
|
||||
const foundUsecase = category.list.find(
|
||||
usecase => usecase.name.toLowerCase().replaceAll("_", " ") === usecaseName
|
||||
);
|
||||
|
||||
if (foundUsecase) {
|
||||
setAutoOpenUsecase(foundUsecase);
|
||||
|
||||
// Wait for render then scroll
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(usecaseName);
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
inline: "center"
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [keys]);
|
||||
|
||||
// Add useEffect to handle auto-opening
|
||||
useEffect(() => {
|
||||
if (autoOpenUsecase) {
|
||||
getUsecase(autoOpenUsecase, 0, 0);
|
||||
setAutoOpenUsecase(null);
|
||||
}
|
||||
}, [autoOpenUsecase]);
|
||||
|
||||
// Loading skeleton component
|
||||
const LoadingSkeleton = () => (
|
||||
<div style={{paddingTop: 75, minHeight: 1000, textAlign: "left"}}>
|
||||
{/* Header skeleton */}
|
||||
<Skeleton variant="text" width={200} height={40} sx={{ bgcolor: 'grey.800' }} />
|
||||
<Skeleton variant="text" width="60%" height={24} sx={{ marginTop: 3, bgcolor: 'grey.800' }} />
|
||||
|
||||
{/* Apps selection skeleton */}
|
||||
<Skeleton variant="text" width={150} height={24} sx={{ marginTop: 5, marginBottom: 10 }} />
|
||||
<Paper style={{
|
||||
height: 60,
|
||||
width: "97.5%",
|
||||
backgroundColor: theme.palette.platformColor,
|
||||
borderRadius: theme.palette?.borderRadius || 5,
|
||||
display: "flex",
|
||||
padding: "0 25px",
|
||||
}}>
|
||||
{/* App icons skeleton */}
|
||||
<div style={{flex: 10, display: "flex", gap: 25, alignItems: "center"}}>
|
||||
{[1, 2, 3, 4, 5].map((app) => (
|
||||
<Skeleton
|
||||
key={app}
|
||||
variant="circular"
|
||||
width={40}
|
||||
height={40}
|
||||
sx={{ bgcolor: 'grey.800' }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Add more apps button skeleton */}
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
width={150}
|
||||
height={40}
|
||||
sx={{
|
||||
marginTop: "10px",
|
||||
borderRadius: 20,
|
||||
bgcolor: 'grey.800'
|
||||
}}
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
{/* Usecase categories skeleton - 3 sections: Collect, Enrich, Detect */}
|
||||
{[1, 2, 3,4,5].map((category) => (
|
||||
<div key={category} style={{marginTop: category === 1 ? 20: 45}}>
|
||||
{/* Category title with color indicator */}
|
||||
<Typography variant="body1" style={{marginBottom: 15}}>
|
||||
<Skeleton
|
||||
variant="text"
|
||||
width={200}
|
||||
height={24}
|
||||
sx={{
|
||||
bgcolor: category === 1 ? '#f85a3e33' :
|
||||
category === 2 ? '#ffb00d33' :
|
||||
category === 3 ? '#2196f333' :
|
||||
category === 4 ? '#4caf5033' :
|
||||
category === 5 ? '#9c27b033' : '#00000033'
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
<Grid container spacing={1}>
|
||||
{[1, 2, 3].map((item) => (
|
||||
<Grid item xs={isMobile ? 12 : 4} key={item}>
|
||||
<Paper
|
||||
style={{
|
||||
backgroundColor: theme.palette.platformColor,
|
||||
borderRadius: theme.palette?.borderRadius || 5,
|
||||
padding: "10px 20px",
|
||||
height: 80,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 15
|
||||
}}
|
||||
>
|
||||
{/* App icons container */}
|
||||
<div style={{display: "flex", gap: 5}}>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
width={30}
|
||||
height={30}
|
||||
sx={{ bgcolor: 'grey.800' }}
|
||||
/>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
width={30}
|
||||
height={30}
|
||||
sx={{ bgcolor: 'grey.800' }}
|
||||
/>
|
||||
</div>
|
||||
{/* Usecase title */}
|
||||
<Skeleton
|
||||
variant="text"
|
||||
width="70%"
|
||||
height={24}
|
||||
sx={{ bgcolor: 'grey.800' }}
|
||||
/>
|
||||
</Paper>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
|
||||
const parseUsecase = (subcase, inputFramework) => {
|
||||
var useFramework = frameworkData
|
||||
@@ -187,6 +342,7 @@ const UsecaseListComponent = (props) => {
|
||||
}, [frameworkData])
|
||||
|
||||
const loadApps = () => {
|
||||
setIsLoading(true);
|
||||
fetch(`${globalUrl}/api/v1/apps`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -220,9 +376,11 @@ const UsecaseListComponent = (props) => {
|
||||
}
|
||||
|
||||
setApps(responseJson);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("App loading error: " + error.toString());
|
||||
setIsLoading(false);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -231,15 +389,23 @@ const UsecaseListComponent = (props) => {
|
||||
}, [])
|
||||
|
||||
if (keys === undefined || keys === null || keys.length === 0) {
|
||||
return null
|
||||
return <LoadingSkeleton />;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Timeout 50ms to delay it slightly
|
||||
const getUsecase = (subcase, index, subindex) => {
|
||||
subcase = parseUsecase(subcase)
|
||||
setPrevSubcase(subcase)
|
||||
// Update URL with selected usecase
|
||||
const usecaseName = subcase.name.toLowerCase().replaceAll(" ", "_")
|
||||
const newUrl = `?selected_object=${usecaseName}`
|
||||
|
||||
// Force URL update even if it's the same usecase
|
||||
navigate(newUrl, { replace: true })
|
||||
|
||||
// Parse and fetch usecase data
|
||||
subcase = parseUsecase(subcase)
|
||||
setPrevSubcase(subcase)
|
||||
|
||||
fetch(`${globalUrl}/api/v1/workflows/usecases/${escape(subcase.name.replaceAll(" ", "_"))}`, {
|
||||
method: "GET",
|
||||
@@ -415,6 +581,19 @@ const UsecaseListComponent = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Then, add a cleanup function for URL params when drawer closes
|
||||
const handleUsecaseClose = () => {
|
||||
// Remove the selected_object parameter from URL
|
||||
navigate("/usecases", { replace: true })
|
||||
|
||||
// Reset relevant state
|
||||
setInputUsecase({})
|
||||
setExpandedIndex(-1)
|
||||
setExpandedItem(-1)
|
||||
setFirstLoad(false)
|
||||
setSelectedWorkflows([])
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{paddingTop: 75, minHeight: 1000, textAlign: "left",}}>
|
||||
<Typography variant="h4" style={{color: "white", }}>
|
||||
@@ -621,7 +800,7 @@ const UsecaseListComponent = (props) => {
|
||||
parsedUsecase.dstapp = newsubcase.dstapp
|
||||
|
||||
|
||||
var workflowBuilt = false
|
||||
var workflowBuilt = ""
|
||||
const newname = subcase.name.toLowerCase().replaceAll(" ", "_")
|
||||
for (var workflowkey in workflows) {
|
||||
const workflow = workflows[workflowkey]
|
||||
@@ -635,7 +814,7 @@ const UsecaseListComponent = (props) => {
|
||||
|
||||
//console.log("WORKFLOW: ", newname, newusecases)
|
||||
if (newusecases.includes(newname)) {
|
||||
workflowBuilt = true
|
||||
workflowBuilt = workflow.id
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -645,10 +824,10 @@ const UsecaseListComponent = (props) => {
|
||||
|
||||
return (
|
||||
<Grid id={fixedName} item xs={isMobile ? 12 : 4} key={subindex} style={{}} onClick={() => {
|
||||
if (fixedName === "reporting") {
|
||||
getUsecase(subcase, index, subindex)
|
||||
return
|
||||
}
|
||||
// if (fixedName === "reporting") {
|
||||
// getUsecase(subcase, index, subindex)
|
||||
// return
|
||||
// }
|
||||
|
||||
//setSelectedWorkflows([])
|
||||
if (selectedItem) {
|
||||
@@ -676,7 +855,10 @@ const UsecaseListComponent = (props) => {
|
||||
showTryit={false}
|
||||
shownColor={""}
|
||||
workflowBuilt={workflowBuilt}
|
||||
inputWorkflowId={workflowBuilt}
|
||||
usecaseDetails={usecaseDetails}
|
||||
isModalOpenDefault={autoOpenUsecase?.name === subcase.name}
|
||||
onClose={handleUsecaseClose}
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
@@ -716,6 +898,7 @@ const Usecases2 = (props) => {
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io";
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedUsecaseCategory.length === 0) {
|
||||
@@ -728,71 +911,8 @@ const Usecases2 = (props) => {
|
||||
}
|
||||
}, [selectedUsecaseCategory])
|
||||
|
||||
const checkSelectedParams = () => {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
const params = Object.fromEntries(urlSearchParams.entries())
|
||||
|
||||
const curpath = typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname;
|
||||
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
|
||||
|
||||
const foundQuery = params["selected"]
|
||||
if (foundQuery !== null && foundQuery !== undefined) {
|
||||
setSelectedUsecaseCategory(foundQuery)
|
||||
|
||||
const newitem = removeParam("selected", cursearch);
|
||||
navigate(curpath + newitem)
|
||||
}
|
||||
|
||||
const baseItem = document.getElementById("reporting")
|
||||
if (baseItem !== undefined && baseItem !== null) {
|
||||
baseItem.click()
|
||||
|
||||
// Find close window button -> go to top
|
||||
const foundButton = document.getElementById("close_selection")
|
||||
if (foundButton !== undefined && foundButton !== null) {
|
||||
foundButton.click()
|
||||
}
|
||||
|
||||
// Scroll back to top
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
|
||||
const foundQuery2 = params["selected_object"]
|
||||
if (foundQuery2 !== null && foundQuery2 !== undefined) {
|
||||
// Take a random object, quickly click it, then go to this one
|
||||
// Something is weird with loading apps without it
|
||||
|
||||
const queryName = foundQuery2.toLowerCase().replaceAll("_", " ")
|
||||
// Waiting a bit for it to render
|
||||
setTimeout(() => {
|
||||
const foundItem = document.getElementById(queryName)
|
||||
if (foundItem !== undefined && foundItem !== null) {
|
||||
foundItem.click()
|
||||
// Scroll to it
|
||||
|
||||
setTimeout(() => {
|
||||
foundItem.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
inline: "center"
|
||||
})
|
||||
}, 100)
|
||||
} else {
|
||||
//console.log("Couldn't find item with name ", queryName)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (usecases.length > 0) {
|
||||
//console.log(usecases)
|
||||
checkSelectedParams()
|
||||
}
|
||||
}, [usecases])
|
||||
|
||||
const getFramework = () => {
|
||||
setIsLoading(true);
|
||||
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -830,6 +950,7 @@ const Usecases2 = (props) => {
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString());
|
||||
setIsLoading(false);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1220,8 +1341,7 @@ const Usecases2 = (props) => {
|
||||
) : null
|
||||
|
||||
const data =
|
||||
<div className="content" style={{width: isMobile ? "100%": leftSideBarOpenByClick ? 1000: 1200, margin: "auto", paddingBottom: 200, textAlign: "center", paddingLeft: leftSideBarOpenByClick ? 200 : 0, transition: "padding-left 0.3s ease, width 0.3s ease"}}>
|
||||
|
||||
<div className="content" style={{width: isMobile ? "100%": leftSideBarOpenByClick ? 1000: 1200, margin: "auto", paddingBottom: 200, textAlign: "center", paddingLeft: leftSideBarOpenByClick ? 50 : 0, transition: "padding-left 0.3s ease, width 0.3s ease"}}>
|
||||
<UsecaseListComponent
|
||||
userdata={userdata}
|
||||
isLoggedIn={isLoggedIn}
|
||||
|
||||
@@ -447,6 +447,7 @@ const Welcome = (props) => {
|
||||
}
|
||||
|
||||
navigate("/welcome?tab=2")
|
||||
setActiveStep(1)
|
||||
setShowWelcome(true)
|
||||
}}>
|
||||
<CardActionArea style={actionObject}>
|
||||
|
||||
@@ -136,7 +136,7 @@ export const GetIconInfo = (action) => {
|
||||
{ key: "cache_add", values: ["set_cache"] },
|
||||
{ key: "cache_get", values: ["get_cache"] },
|
||||
{ key: "filter", values: ["filter"] },
|
||||
{ key: "merge", values: ["join", "merge", "route", "router"] },
|
||||
{ key: "merge", values: ["join", "merge", "route", "router", "routing"] },
|
||||
{
|
||||
key: "search",
|
||||
values: ["search", "find", "locate", "index", "analyze", "anal", "match", "check cache", "check", "verify", "validate"],
|
||||
@@ -200,7 +200,7 @@ export const GetIconInfo = (action) => {
|
||||
},
|
||||
{
|
||||
key: "compare",
|
||||
values: ["compare", "convert", "to", "filter", "translate", "parse"],
|
||||
values: ["compare", "convert", "to", "filter", "translate", "parse", "generate", ],
|
||||
},
|
||||
{ key: "close", values: ["close", "stop", "cancel", "block"] },
|
||||
{ key: "communication", values: ["communication", "comms", "email", "mail",] },
|
||||
@@ -427,11 +427,15 @@ const chipStyle = {
|
||||
color: "white",
|
||||
};
|
||||
|
||||
export const collapseField = (field) => {
|
||||
export const collapseField = (field, inputdata) => {
|
||||
if (field === undefined || field === null) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (field.namespace !== undefined && field.namespace !== null && field.namespace.length === 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (field.name === "headers" || field.name === "cookies") {
|
||||
return true
|
||||
}
|
||||
@@ -451,44 +455,44 @@ export const collapseField = (field) => {
|
||||
}
|
||||
|
||||
export const validateJson = (showResult) => {
|
||||
if (showResult === undefined || showResult === null) {
|
||||
return {
|
||||
valid: false,
|
||||
result: "",
|
||||
}
|
||||
}
|
||||
if (showResult === undefined || showResult === null) {
|
||||
return {
|
||||
valid: false,
|
||||
result: "",
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof showResult === 'string') {
|
||||
showResult = showResult.split(" False").join(" false")
|
||||
showResult = showResult.split(" True").join(" true")
|
||||
if (typeof showResult === 'string') {
|
||||
showResult = showResult.split(" False").join(" false")
|
||||
showResult = showResult.split(" True").join(" true")
|
||||
|
||||
showResult.replaceAll("False,", "false,")
|
||||
showResult.replaceAll("True,", "true,")
|
||||
}
|
||||
showResult.replaceAll("False,", "false,")
|
||||
showResult.replaceAll("True,", "true,")
|
||||
}
|
||||
|
||||
if (typeof showResult === "object" || typeof showResult === "array") {
|
||||
return {
|
||||
valid: true,
|
||||
result: showResult,
|
||||
}
|
||||
}
|
||||
if (typeof showResult === "object" || typeof showResult === "array") {
|
||||
return {
|
||||
valid: true,
|
||||
result: showResult,
|
||||
}
|
||||
}
|
||||
|
||||
if (showResult[0] === "\"") {
|
||||
return {
|
||||
valid: false,
|
||||
result: showResult,
|
||||
}
|
||||
}
|
||||
if (showResult[0] === "\"") {
|
||||
return {
|
||||
valid: false,
|
||||
result: showResult,
|
||||
}
|
||||
}
|
||||
|
||||
var jsonvalid = true
|
||||
try {
|
||||
if (!showResult.includes("{") && !showResult.includes("[")) {
|
||||
jsonvalid = false
|
||||
|
||||
return {
|
||||
valid: jsonvalid,
|
||||
result: showResult,
|
||||
};
|
||||
return {
|
||||
valid: jsonvalid,
|
||||
result: showResult,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -505,94 +509,94 @@ export const validateJson = (showResult) => {
|
||||
|
||||
var result = showResult;
|
||||
try {
|
||||
result = jsonvalid ? JSON.parse(showResult, { "storeAsString": true }) : showResult;
|
||||
result = jsonvalid ? JSON.parse(showResult, {"storeAsString": true}) : showResult;
|
||||
} catch (e) {
|
||||
////console.log("Failed parsing JSON even though its valid: ", e)
|
||||
jsonvalid = false;
|
||||
}
|
||||
|
||||
if (jsonvalid === false) {
|
||||
if (jsonvalid === false) {
|
||||
|
||||
if (typeof showResult === 'string') {
|
||||
showResult = showResult.trim()
|
||||
}
|
||||
if (typeof showResult === 'string') {
|
||||
showResult = showResult.trim()
|
||||
}
|
||||
|
||||
try {
|
||||
var newstr = showResult.replaceAll("'", '"')
|
||||
try {
|
||||
var newstr = showResult.replaceAll("'", '"')
|
||||
|
||||
// Basic workarounds for issues with Python Dicts -> JSON
|
||||
if (newstr.includes(": None")) {
|
||||
newstr = newstr.replaceAll(": None", ': null')
|
||||
}
|
||||
// Basic workarounds for issues with Python Dicts -> JSON
|
||||
if (newstr.includes(": None")) {
|
||||
newstr = newstr.replaceAll(": None", ': null')
|
||||
}
|
||||
|
||||
if (newstr.includes("[\"{") && newstr.includes("}\"]")) {
|
||||
newstr = newstr.replaceAll("[\"{", '[{')
|
||||
newstr = newstr.replaceAll("}\"]", '}]')
|
||||
}
|
||||
if (newstr.includes("[\"{") && newstr.includes("}\"]")) {
|
||||
newstr = newstr.replaceAll("[\"{", '[{')
|
||||
newstr = newstr.replaceAll("}\"]", '}]')
|
||||
}
|
||||
|
||||
if (newstr.includes("{\"[") && newstr.includes("]\"}")) {
|
||||
newstr = newstr.replaceAll("{\"[", '[{')
|
||||
newstr = newstr.replaceAll("]\"}", '}]')
|
||||
}
|
||||
if (newstr.includes("{\"[") && newstr.includes("]\"}")) {
|
||||
newstr = newstr.replaceAll("{\"[", '[{')
|
||||
newstr = newstr.replaceAll("]\"}", '}]')
|
||||
}
|
||||
|
||||
result = JSON.parse(newstr)
|
||||
jsonvalid = true
|
||||
} catch (e) {
|
||||
result = JSON.parse(newstr)
|
||||
jsonvalid = true
|
||||
} catch (e) {
|
||||
|
||||
//console.log("Failed parsing JSON even though its valid (2): ", e)
|
||||
jsonvalid = false
|
||||
}
|
||||
}
|
||||
//console.log("Failed parsing JSON even though its valid (2): ", e)
|
||||
jsonvalid = false
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonvalid && typeof result === "number") {
|
||||
jsonvalid = false
|
||||
}
|
||||
if (jsonvalid && typeof result === "number") {
|
||||
jsonvalid = false
|
||||
}
|
||||
|
||||
// This is where we start recursing
|
||||
if (jsonvalid) {
|
||||
// Check fields if they can be parsed too
|
||||
try {
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
||||
//console.log("CHECKING STRING: ", value)
|
||||
// This is where we start recursing
|
||||
if (jsonvalid) {
|
||||
// Check fields if they can be parsed too
|
||||
try {
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
||||
//console.log("CHECKING STRING: ", value)
|
||||
|
||||
const inside_result = validateJson(value)
|
||||
if (inside_result.valid) {
|
||||
//console.log("INSIDE RESULT: ", inside_result.result)
|
||||
const inside_result = validateJson(value)
|
||||
if (inside_result.valid) {
|
||||
//console.log("INSIDE RESULT: ", inside_result.result)
|
||||
|
||||
if (typeof inside_result.result === "string") {
|
||||
const newres = JSON.parse(inside_result.result)
|
||||
if (typeof inside_result.result === "string") {
|
||||
const newres = JSON.parse(inside_result.result)
|
||||
|
||||
result[key] = newres
|
||||
} else {
|
||||
result[key] = inside_result.result
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result[key] = newres
|
||||
} else {
|
||||
result[key] = inside_result.result
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Usually only reaches here if raw array > dict > value
|
||||
if (typeof showResult !== "array") {
|
||||
for (const [subkey, subvalue] of Object.entries(value)) {
|
||||
if (typeof subvalue === "string" && (subvalue.startsWith("{") || subvalue.startsWith("["))) {
|
||||
const inside_result = validateJson(subvalue)
|
||||
if (inside_result.valid) {
|
||||
if (typeof inside_result.result === "string") {
|
||||
const newres = JSON.parse(inside_result.result)
|
||||
result[key][subkey] = newres
|
||||
} else {
|
||||
result[key][subkey] = inside_result.result
|
||||
}
|
||||
}
|
||||
}
|
||||
// Usually only reaches here if raw array > dict > value
|
||||
if (typeof showResult !== "array") {
|
||||
for (const [subkey, subvalue] of Object.entries(value)) {
|
||||
if (typeof subvalue === "string" && (subvalue.startsWith("{") || subvalue.startsWith("["))) {
|
||||
const inside_result = validateJson(subvalue)
|
||||
if (inside_result.valid) {
|
||||
if (typeof inside_result.result === "string") {
|
||||
const newres = JSON.parse(inside_result.result)
|
||||
result[key][subkey] = newres
|
||||
} else {
|
||||
result[key][subkey] = inside_result.result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
//console.log("Failed parsing inside json subvalues: ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
//console.log("Failed parsing inside json subvalues: ", e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: jsonvalid,
|
||||
@@ -600,6 +604,7 @@ export const validateJson = (showResult) => {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
//Custom hook for handling styling of the dropzone
|
||||
const useDropzoneStyles = () => {
|
||||
const { leftSideBarOpenByClick } = useContext(Context);
|
||||
@@ -1841,9 +1846,13 @@ const Workflows = (props) => {
|
||||
|
||||
var parsedworkflows = [];
|
||||
for (var key in newSubflows) {
|
||||
if (key === data.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
const foundWorkflow = workflows.find(
|
||||
(workflow) => workflow.id === newSubflows[key]
|
||||
);
|
||||
)
|
||||
if (foundWorkflow !== undefined && foundWorkflow !== null) {
|
||||
parsedworkflows.push(foundWorkflow);
|
||||
}
|
||||
@@ -1854,7 +1863,7 @@ const Workflows = (props) => {
|
||||
"Appending subflows during export: ",
|
||||
parsedworkflows.length
|
||||
);
|
||||
data.subflows = parsedworkflows;
|
||||
data.subflows = parsedworkflows
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ import {
|
||||
ArrowRight as ArrowRightIcon,
|
||||
Visibility as VisibilityIcon,
|
||||
EditNote as EditNoteIcon,
|
||||
ErrorOutline as ErrorOutlineIcon,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
// Additional Components
|
||||
@@ -108,6 +109,8 @@ import { InstantSearch, Configure, connectHits, connectSearchBox, connectRefinem
|
||||
import { debounce } from "lodash";
|
||||
import { removeQuery } from "../components/ScrollToTop.jsx";
|
||||
|
||||
import {green, yellow, red, grey } from "../views/AngularWorkflow.jsx"
|
||||
|
||||
|
||||
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240");
|
||||
|
||||
@@ -649,7 +652,6 @@ const DropzoneWrapper = memo(({ onDrop, WorkflowView }) => {
|
||||
|
||||
const Workflows2 = (props) => {
|
||||
const { globalUrl, isLoggedIn, isLoaded, userdata, checkLogin } = props;
|
||||
const { leftSideBarOpenByClick } = useContext(Context);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [currTab, setCurrTab] = useState(0);
|
||||
@@ -1115,12 +1117,9 @@ const Workflows2 = (props) => {
|
||||
<Button
|
||||
style={{}}
|
||||
onClick={() => {
|
||||
console.log("Editing: ", editingWorkflow);
|
||||
if (selectedWorkflowId) {
|
||||
deleteWorkflow(selectedWorkflowId)
|
||||
setTimeout(() => {
|
||||
getAvailableWorkflows();
|
||||
}, 1000);
|
||||
|
||||
} else if (selectedWorkflowIndexes.length > 0) {
|
||||
// Do backwards so it doesn't change
|
||||
toast("Starting deletion of workflows. This might take a while.")
|
||||
@@ -1131,13 +1130,13 @@ const Workflows2 = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
getAvailableWorkflows()
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
getAvailableWorkflows()
|
||||
}, 5000)
|
||||
setSelectedWorkflowIndexes([]);
|
||||
}
|
||||
setDeleteModalOpen(false);
|
||||
|
||||
setDeleteModalOpen(false)
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
@@ -1188,32 +1187,32 @@ const Workflows2 = (props) => {
|
||||
"",
|
||||
data.status,
|
||||
)
|
||||
.then((response) => {
|
||||
if (response !== undefined) {
|
||||
// SET THE FULL THING
|
||||
data.id = response.id;
|
||||
.then((response) => {
|
||||
if (response !== undefined) {
|
||||
// SET THE FULL THING
|
||||
data.id = response.id;
|
||||
|
||||
// Actually create it
|
||||
setNewWorkflow(
|
||||
data.name,
|
||||
data.description,
|
||||
data.tags,
|
||||
data.default_return_value,
|
||||
data,
|
||||
false,
|
||||
[],
|
||||
"",
|
||||
data.status
|
||||
).then((response) => {
|
||||
if (response !== undefined) {
|
||||
toast(`Successfully imported ${data.name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast("Import error: " + error.toString());
|
||||
});
|
||||
// Actually create it
|
||||
setNewWorkflow(
|
||||
data.name,
|
||||
data.description,
|
||||
data.tags,
|
||||
data.default_return_value,
|
||||
data,
|
||||
false,
|
||||
[],
|
||||
"",
|
||||
data.status
|
||||
).then((response) => {
|
||||
if (response !== undefined) {
|
||||
toast(`Successfully imported ${data.name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast("Import error: " + error.toString());
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Error in dropzone: ", e);
|
||||
@@ -1936,6 +1935,10 @@ const Workflows2 = (props) => {
|
||||
|
||||
var parsedworkflows = [];
|
||||
for (var key in newSubflows) {
|
||||
if (key === data.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
const foundWorkflow = workflows.find(
|
||||
(workflow) => workflow.id === newSubflows[key]
|
||||
);
|
||||
@@ -2363,7 +2366,6 @@ const Workflows2 = (props) => {
|
||||
|
||||
<MenuItem
|
||||
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
|
||||
disabled={isDistributed}
|
||||
onClick={() => {
|
||||
setDeleteModalOpen(true);
|
||||
setSelectedWorkflowId(data.id);
|
||||
@@ -2480,7 +2482,7 @@ const Workflows2 = (props) => {
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", minWidth: 320, position: "relative", border: highlightIds.includes(data.id) ? "2px solid #f85a3e" : isDistributed || hasSuborgs ? "2px solid #40E0D0" : "inherit", borderRadius: theme.palette?.borderRadius, backgroundColor: "#212121", fontFamily: theme?.typography?.fontFamily }}>
|
||||
<div style={{ width: "100%", minWidth: 320, position: "relative", border: highlightIds.includes(data.id) ? "2px solid #f85a3e" : isDistributed || hasSuborgs ? `2px solid ${theme.palette.distributionColor}` : "inherit", borderRadius: theme.palette?.borderRadius, backgroundColor: "#212121", fontFamily: theme?.typography?.fontFamily }}>
|
||||
<Paper square style={paperAppStyle}>
|
||||
{selectedCategory !== "" ?
|
||||
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
|
||||
@@ -2564,13 +2566,13 @@ const Workflows2 = (props) => {
|
||||
{(isDistributed || hasSuborgs) && (
|
||||
<div style={{
|
||||
backgroundColor: "rgba(64,224,208,0.1)", // Matching the teal color used in border
|
||||
border: "1px solid #40E0D0",
|
||||
border: `1px solid ${theme.palette.distributionColor}`,
|
||||
borderRadius: 4,
|
||||
padding: "8px 12px",
|
||||
marginTop: 8,
|
||||
}}>
|
||||
<Typography variant="body2" style={{
|
||||
color: "#40E0D0",
|
||||
color: theme.palette.distributionColor,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
@@ -2808,6 +2810,7 @@ const Workflows2 = (props) => {
|
||||
{workflowMenuButtons}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{(data.sharing !== undefined && data.sharing !== null && data.sharing === "form") || (data?.form_control?.input_markdown !== undefined && data?.form_control?.input_markdown !== null && data?.form_control?.input_markdown !== "") && type !== "public" ?
|
||||
<Tooltip title="Edit Form" placement="top">
|
||||
<div style={{ position: "absolute", top: 45, right: 8, }}>
|
||||
@@ -2822,10 +2825,33 @@ const Workflows2 = (props) => {
|
||||
>
|
||||
<EditNoteIcon />
|
||||
</IconButton>
|
||||
{workflowMenuButtons}
|
||||
</div>
|
||||
</Tooltip>
|
||||
: null}
|
||||
: null}
|
||||
|
||||
{(data?.validation?.validation_ran === true && data?.validation?.valid === false && data?.validation?.errors?.length > 0 ) ?
|
||||
<Tooltip title={`Explore more than ${data?.validation?.errors?.length} errors. When the last execution finishes without errors AND notifications stop occuring, this icon disappears.`} placement="top">
|
||||
<div style={{ position: "absolute", top: 45, right: 8, }}>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
aria-controls="long-menu"
|
||||
aria-haspopup="true"
|
||||
onClick={() => {
|
||||
window.open(`/admin?admin_tab=notifications&workflow=${data.id}`, "_blank")
|
||||
}}
|
||||
style={{
|
||||
padding: "0px",
|
||||
color: "#979797",
|
||||
}}
|
||||
>
|
||||
<ErrorOutlineIcon style={{
|
||||
marginRight: 2,
|
||||
}} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
: null}
|
||||
</Grid>
|
||||
</Paper>
|
||||
</div>
|
||||
@@ -2852,6 +2878,7 @@ const Workflows2 = (props) => {
|
||||
if (editingWorkflow.id !== undefined) {
|
||||
console.log("Building original workflow");
|
||||
method = "PUT";
|
||||
//extraData = "/" + editingWorkflow.id + "?skip_save=true";
|
||||
extraData = "/" + editingWorkflow.id + "?skip_save=true";
|
||||
workflowdata = editingWorkflow;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
*.tgz
|
||||
charts/
|
||||
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
line-length: disable
|
||||
braces: disable
|
||||
comments:
|
||||
require-starting-space: true
|
||||
ignore-shebangs: true
|
||||
min-spaces-from-content: 1
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v2
|
||||
name: shuffle
|
||||
description: A Helm chart for deploying Shuffle on Kubernetes
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: 0.0.0
|
||||
dependencies:
|
||||
- name: common
|
||||
version: ^2.23.0
|
||||
repository: oci://registry-1.docker.io/bitnamicharts
|
||||
- name: opensearch
|
||||
version: ^1.3.0
|
||||
repository: oci://registry-1.docker.io/bitnamicharts
|
||||
condition: opensearch.enabled
|
||||
@@ -0,0 +1,607 @@
|
||||
# Shuffle Helm chart
|
||||
|
||||
## Chart Template
|
||||
|
||||
The Bitnami Chart Template was used for creating this chart:
|
||||
|
||||
https://github.com/bitnami/charts/tree/7e44e64626f5b1fc6d56889cdfdeadc1f62c7cf1/template/CHART_NAME
|
||||
|
||||
Original license text:
|
||||
|
||||
```
|
||||
Copyright Broadcom, Inc. All Rights Reserved.
|
||||
|
||||
SPDX-License-Identifier: APACHE-2.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# Install shuffle via helm (the shuffle namespace is hardcoded into the shuffle source code)
|
||||
helm install shuffle oci://ghcr.io/shuffle/shuffle/charts/shuffle --namespace shuffle --create-namespace
|
||||
```
|
||||
|
||||
Make sure that no other application is deployed to the shuffle namespace, as shuffle deletes kubernetes resources in this namespace.
|
||||
|
||||
Only a single deployment of shuffle is supported per namespace.
|
||||
|
||||
## Uninstallation
|
||||
|
||||
```sh
|
||||
# Uninstall shuffle via helm
|
||||
helm uninstall shuffle --namespace shuffle
|
||||
|
||||
# Remove additional resources created by shuffle (such as workers and apps)
|
||||
kubectl delete svc --namespace shuffle -l "app.kubernetes.io/managed-by in (shuffle-orborus,shuffle-worker)"
|
||||
kubectl delete deploy --namespace shuffle -l "app.kubernetes.io/managed-by in (shuffle-orborus,shuffle-worker)"
|
||||
```
|
||||
|
||||
## Secret Parameters
|
||||
|
||||
The helm chart was designed to not contain any secret data and does not allow configuring secret data using helm values.
|
||||
Instead, secret values must be passed to services using `extraEnvVarsSecret` or `extraEnvVars`.
|
||||
|
||||
The secrets need to be manually created. It is possible to run this helm chart without specifying any secrets.
|
||||
You will be prompted to create an admin user when visiting the shuffle dashboard for the first time.
|
||||
Note that information will not be encrypted without specifying the `SHUFFLE_ENCRYPTION_MODIFIER` value.
|
||||
|
||||
### Mounting env variables into a service
|
||||
|
||||
After creating secrets which hold sensitive information, you can mount them as environment variables into a
|
||||
service via the `extraEnvVarsSecret` or `extraEnvVars` values.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
# Use a single secret, which holds environment variables.
|
||||
# Remember that the secret keys must exactly match the environment variable names.
|
||||
extraEnvVarsSecret: shuffle-backend-env
|
||||
|
||||
# Or mount each value explicitly
|
||||
extraEnvVars:
|
||||
- name: SHUFFLE_DEFAULT_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "shuffle-initial-user"
|
||||
key: username
|
||||
- name: SHUFFLE_DEFAULT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "shuffle-initial-user"
|
||||
key: password
|
||||
- name: SHUFFLE_DEFAULT_APIKEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "shuffle-initial-user"
|
||||
key: apikey
|
||||
- name: SHUFFLE_ENCRYPTION_MODIFIER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "shuffle-encryption"
|
||||
key: modifier
|
||||
```
|
||||
|
||||
### Backend
|
||||
|
||||
A list of environment variables containing secret values for the backend.
|
||||
|
||||
```yaml
|
||||
# OpenSearch password
|
||||
SHUFFLE_OPENSEARCH_PASSWORD: ""
|
||||
|
||||
# Basic auth credentials for downloading apps from git
|
||||
SHUFFLE_DOWNLOAD_AUTH_USERNAME: ""
|
||||
SHUFFLE_DOWNLOAD_AUTH_PASSWORD: ""
|
||||
|
||||
# Automatically create the initial admin user. Username and password have a min length of 3.
|
||||
# If not set, you are prompted with an admin user creation dialog when visiting the shuffle frontend for the first time.
|
||||
SHUFFLE_DEFAULT_USERNAME: admin
|
||||
SHUFFLE_DEFAULT_PASSWORD: MySecretAdminPassword1234!
|
||||
SHUFFLE_DEFAULT_APIKEY: "72E41083-A6F6-4A1B-8538-B06B577F47F0" # Shuffle uses uuid v4
|
||||
|
||||
# Encryption modifier. This HAS to be set to encrypt any authentication being used in Shuffle.
|
||||
# This is put together with other relevant values to ensure multiple parts are needed to decrypt.
|
||||
# If this key is lost or changed, you will have to reauthenticate all apps.
|
||||
# The encryption modifier is added to encrypted values to prevent rainbow table attacks. It can be any random string.
|
||||
SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
### Global parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| `global.imageRegistry` | Global Docker image registry | `""` |
|
||||
| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` |
|
||||
| `global.defaultStorageClass` | Global default StorageClass for Persistent Volume(s) | `""` |
|
||||
| `global.compatibility.openshift.adaptSecurityContext` | Adapt the securityContext sections of the deployment to make them compatible with Openshift restricted-v2 SCC: remove runAsUser, runAsGroup and fsGroup and let the platform use their allowed default IDs. Possible values: auto (apply if the detected running cluster is Openshift), force (perform the adaptation always), disabled (do not perform adaptation) | `auto` |
|
||||
| `global.compatibility.omitEmptySeLinuxOptions` | If set to true, removes the seLinuxOptions from the securityContexts when it is set to an empty object | `false` |
|
||||
|
||||
### Common parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------- | --------------- |
|
||||
| `kubeVersion` | Override Kubernetes version | `""` |
|
||||
| `nameOverride` | String to partially override common.names.name | `""` |
|
||||
| `fullnameOverride` | String to fully override common.names.fullname | `""` |
|
||||
| `namespaceOverride` | String to fully override common.names.namespace | `""` |
|
||||
| `commonLabels` | Labels to add to all deployed objects | `{}` |
|
||||
| `commonAnnotations` | Annotations to add to all deployed objects | `{}` |
|
||||
| `clusterDomain` | Kubernetes cluster domain name | `cluster.local` |
|
||||
| `extraDeploy` | Array of extra objects to deploy with the release | `[]` |
|
||||
| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` |
|
||||
| `diagnosticMode.command` | Command to override all containers in the chart release | `["sleep"]` |
|
||||
| `diagnosticMode.args` | Args to override all containers in the chart release | `["infinity"]` |
|
||||
|
||||
### Shared Shuffle Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| --------------------- | ------------------------------------------------------------- | --------------- |
|
||||
| `shuffle.baseUrl` | The external base URL under which Shuffle is reachable. | `""` |
|
||||
| `shuffle.org` | Default shuffle organization | `Shuffle` |
|
||||
| `shuffle.appRegistry` | The registry from / to which shuffle apps are pulled / pushed | `""` |
|
||||
| `shuffle.timezone` | The timezone used by Shuffle | `Europe/Berlin` |
|
||||
|
||||
### backend Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
||||
| `backend.image.registry` | backend image registry | `ghcr.io` |
|
||||
| `backend.image.repository` | backend image repository | `shuffle/shuffle-backend` |
|
||||
| `backend.image.digest` | backend image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` |
|
||||
| `backend.image.pullPolicy` | backend image pull policy | `IfNotPresent` |
|
||||
| `backend.image.pullSecrets` | backend image pull secrets | `[]` |
|
||||
| `backend.replicaCount` | Number of backend replicas to deploy | `1` |
|
||||
| `backend.containerPorts.http` | backend HTTP container port | `5001` |
|
||||
| `backend.extraContainerPorts` | Optionally specify extra list of additional ports for backend containers | `[]` |
|
||||
| `backend.livenessProbe.enabled` | Enable livenessProbe on backend containers | `false` |
|
||||
| `backend.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `0` |
|
||||
| `backend.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `15` |
|
||||
| `backend.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `1` |
|
||||
| `backend.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `4` |
|
||||
| `backend.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` |
|
||||
| `backend.readinessProbe.enabled` | Enable readinessProbe on backend containers | `false` |
|
||||
| `backend.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `0` |
|
||||
| `backend.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `5` |
|
||||
| `backend.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` |
|
||||
| `backend.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `3` |
|
||||
| `backend.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` |
|
||||
| `backend.startupProbe.enabled` | Enable startupProbe on backend containers | `false` |
|
||||
| `backend.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `0` |
|
||||
| `backend.startupProbe.periodSeconds` | Period seconds for startupProbe | `1` |
|
||||
| `backend.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` |
|
||||
| `backend.startupProbe.failureThreshold` | Failure threshold for startupProbe | `60` |
|
||||
| `backend.startupProbe.successThreshold` | Success threshold for startupProbe | `1` |
|
||||
| `backend.customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` |
|
||||
| `backend.customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` |
|
||||
| `backend.customStartupProbe` | Custom startupProbe that overrides the default one | `{}` |
|
||||
| `backend.resourcesPreset` | Set backend container resources according to one common preset (allowed values: none, nano, small, medium, large, xlarge, 2xlarge). This is ignored if backend.resources is set (backend.resources is recommended for production). | `small` |
|
||||
| `backend.resources` | Set backend container requests and limits for different resources like CPU or memory (essential for production workloads) | `{}` |
|
||||
| `backend.podSecurityContext.enabled` | Enable backend pods' Security Context | `true` |
|
||||
| `backend.podSecurityContext.fsGroupChangePolicy` | Set filesystem group change policy for backend pods | `Always` |
|
||||
| `backend.podSecurityContext.sysctls` | Set kernel settings using the sysctl interface for backend pods | `[]` |
|
||||
| `backend.podSecurityContext.supplementalGroups` | Set filesystem extra groups for backend pods | `[]` |
|
||||
| `backend.podSecurityContext.fsGroup` | Set fsGroup in backend pods' Security Context | `1001` |
|
||||
| `backend.containerSecurityContext.enabled` | Enabled backend container' Security Context | `true` |
|
||||
| `backend.containerSecurityContext.seLinuxOptions` | Set SELinux options in backend container | `{}` |
|
||||
| `backend.containerSecurityContext.runAsUser` | Set runAsUser in backend container' Security Context | `1000` |
|
||||
| `backend.containerSecurityContext.runAsGroup` | Set runAsGroup in backend container' Security Context | `1000` |
|
||||
| `backend.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in backend container' Security Context | `true` |
|
||||
| `backend.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in backend container' Security Context | `true` |
|
||||
| `backend.containerSecurityContext.privileged` | Set privileged in backend container' Security Context | `false` |
|
||||
| `backend.containerSecurityContext.allowPrivilegeEscalation` | Set allowPrivilegeEscalation in backend container' Security Context | `false` |
|
||||
| `backend.containerSecurityContext.capabilities.drop` | List of capabilities to be dropped in backend container | `["ALL"]` |
|
||||
| `backend.containerSecurityContext.seccompProfile.type` | Set seccomp profile in backend container | `RuntimeDefault` |
|
||||
| `backend.command` | Override default backend container command (useful when using custom images) | `[]` |
|
||||
| `backend.args` | Override default backend container args (useful when using custom images) | `[]` |
|
||||
| `backend.automountServiceAccountToken` | Mount Service Account token in backend pods | `true` |
|
||||
| `backend.hostAliases` | backend pods host aliases | `[]` |
|
||||
| `backend.daemonsetAnnotations` | Annotations for backend daemonset | `{}` |
|
||||
| `backend.deploymentAnnotations` | Annotations for backend deployment | `{}` |
|
||||
| `backend.statefulsetAnnotations` | Annotations for backend statefulset | `{}` |
|
||||
| `backend.podLabels` | Extra labels for backend pods | `{}` |
|
||||
| `backend.podAnnotations` | Annotations for backend pods | `{}` |
|
||||
| `backend.podAffinityPreset` | Pod affinity preset. Ignored if `backend.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
| `backend.podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `backend.affinity` is set. Allowed values: `soft` or `hard` | `soft` |
|
||||
| `backend.nodeAffinityPreset.type` | Node affinity preset type. Ignored if `backend.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
| `backend.nodeAffinityPreset.key` | Node label key to match. Ignored if `backend.affinity` is set | `""` |
|
||||
| `backend.nodeAffinityPreset.values` | Node label values to match. Ignored if `backend.affinity` is set | `[]` |
|
||||
| `backend.affinity` | Affinity for backend pods assignment | `{}` |
|
||||
| `backend.nodeSelector` | Node labels for backend pods assignment | `{}` |
|
||||
| `backend.tolerations` | Tolerations for backend pods assignment | `[]` |
|
||||
| `backend.updateStrategy.type` | backend deployment strategy type | `RollingUpdate` |
|
||||
| `backend.updateStrategy.type` | backend statefulset strategy type | `RollingUpdate` |
|
||||
| `backend.podManagementPolicy` | Pod management policy for backend statefulset | `OrderedReady` |
|
||||
| `backend.priorityClassName` | backend pods' priorityClassName | `""` |
|
||||
| `backend.topologySpreadConstraints` | Topology Spread Constraints for backend pod assignment spread across your cluster among failure-domains | `[]` |
|
||||
| `backend.schedulerName` | Name of the k8s scheduler (other than default) for backend pods | `""` |
|
||||
| `backend.terminationGracePeriodSeconds` | Seconds backend pods need to terminate gracefully | `""` |
|
||||
| `backend.lifecycleHooks` | for backend containers to automate configuration before or after startup | `{}` |
|
||||
| `backend.extraEnvVars` | Array with extra environment variables to add to backend containers | `[]` |
|
||||
| `backend.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for backend containers | `""` |
|
||||
| `backend.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for backend containers | `""` |
|
||||
| `backend.extraVolumes` | Optionally specify extra list of additional volumes for the backend pods | `[]` |
|
||||
| `backend.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the backend containers | `[]` |
|
||||
| `backend.sidecars` | Add additional sidecar containers to the backend pods | `[]` |
|
||||
| `backend.initContainers` | Add additional init containers to the backend pods | `[]` |
|
||||
| `backend.pdb.create` | Enable/disable a Pod Disruption Budget creation | `true` |
|
||||
| `backend.pdb.minAvailable` | Minimum number/percentage of pods that should remain scheduled | `""` |
|
||||
| `backend.pdb.maxUnavailable` | Maximum number/percentage of pods that may be made unavailable. Defaults to `1` if both `backend.pdb.minAvailable` and `backend.pdb.maxUnavailable` are empty. | `""` |
|
||||
| `backend.autoscaling.vpa.enabled` | Enable VPA for backend pods | `false` |
|
||||
| `backend.autoscaling.vpa.annotations` | Annotations for VPA resource | `{}` |
|
||||
| `backend.autoscaling.vpa.controlledResources` | VPA List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory | `[]` |
|
||||
| `backend.autoscaling.vpa.maxAllowed` | VPA Max allowed resources for the pod | `{}` |
|
||||
| `backend.autoscaling.vpa.minAllowed` | VPA Min allowed resources for the pod | `{}` |
|
||||
| `backend.autoscaling.vpa.updatePolicy.updateMode` | Autoscaling update policy | `Auto` |
|
||||
| `backend.autoscaling.hpa.enabled` | Enable HPA for backend pods | `false` |
|
||||
| `backend.autoscaling.hpa.minReplicas` | Minimum number of replicas | `""` |
|
||||
| `backend.autoscaling.hpa.maxReplicas` | Maximum number of replicas | `""` |
|
||||
| `backend.autoscaling.hpa.targetCPU` | Target CPU utilization percentage | `""` |
|
||||
| `backend.autoscaling.hpa.targetMemory` | Target Memory utilization percentage | `""` |
|
||||
| `backend.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` |
|
||||
| `backend.serviceAccount.name` | The name of the ServiceAccount to use. | `""` |
|
||||
| `backend.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` |
|
||||
| `backend.serviceAccount.automountServiceAccountToken` | Automount service account token for the backend service account | `true` |
|
||||
| `backend.serviceAccount.imagePullSecrets` | Add image pull secrets to the backend service account | `[]` |
|
||||
| `backend.rbac.create` | Specifies whether RBAC resources should be created | `true` |
|
||||
| `backend.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` |
|
||||
| `backend.networkPolicy.allowExternal` | Don't require server label for connections | `true` |
|
||||
| `backend.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
|
||||
| `backend.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
|
||||
| `backend.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
|
||||
| `backend.cleanupSchedule` | The interval in seconds at which the cleanup job runs | `300` |
|
||||
| `backend.openSearch.url` | The URL at which OpenSearch is available | `http://{{ .Release.Name }}-opensearch:9200` |
|
||||
| `backend.openSearch.username` | The username that is used for authenticating with OpenSearch | `admin` |
|
||||
| `backend.openSearch.certificateFile` | The path to a custom OpenSearch certificate file | `""` |
|
||||
| `backend.openSearch.skipSSLVerify` | Skip SSL verification | `false` |
|
||||
| `backend.openSearch.indexPrefix` | A prefix for OpenSearch indices | `""` |
|
||||
| `backend.apps.downloadLocation` | The location to a git repository from which default appps are downloaded on startup. | `https://github.com/shuffle/python-apps` |
|
||||
| `backend.apps.downloadBranch` | The branch from which apps should be downloaded on startup. | `master` |
|
||||
| `backend.apps.forceUpdate` | Force an update of apps on startup. | `false` |
|
||||
|
||||
### frontend Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| `frontend.image.registry` | frontend image registry | `ghcr.io` |
|
||||
| `frontend.image.repository` | frontend image repository | `shuffle/shuffle-frontend` |
|
||||
| `frontend.image.digest` | frontend image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` |
|
||||
| `frontend.image.pullPolicy` | frontend image pull policy | `IfNotPresent` |
|
||||
| `frontend.image.pullSecrets` | frontend image pull secrets | `[]` |
|
||||
| `frontend.replicaCount` | Number of frontend replicas to deploy | `1` |
|
||||
| `frontend.containerPorts.http` | frontend HTTP container port | `80` |
|
||||
| `frontend.containerPorts.https` | frontend HTTPS container port | `443` |
|
||||
| `frontend.extraContainerPorts` | Optionally specify extra list of additional ports for frontend containers | `[]` |
|
||||
| `frontend.livenessProbe.enabled` | Enable livenessProbe on frontend containers | `false` |
|
||||
| `frontend.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `0` |
|
||||
| `frontend.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `15` |
|
||||
| `frontend.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `1` |
|
||||
| `frontend.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `4` |
|
||||
| `frontend.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` |
|
||||
| `frontend.readinessProbe.enabled` | Enable readinessProbe on frontend containers | `false` |
|
||||
| `frontend.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `0` |
|
||||
| `frontend.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `5` |
|
||||
| `frontend.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` |
|
||||
| `frontend.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `3` |
|
||||
| `frontend.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` |
|
||||
| `frontend.startupProbe.enabled` | Enable startupProbe on frontend containers | `false` |
|
||||
| `frontend.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `0` |
|
||||
| `frontend.startupProbe.periodSeconds` | Period seconds for startupProbe | `1` |
|
||||
| `frontend.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` |
|
||||
| `frontend.startupProbe.failureThreshold` | Failure threshold for startupProbe | `60` |
|
||||
| `frontend.startupProbe.successThreshold` | Success threshold for startupProbe | `1` |
|
||||
| `frontend.customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` |
|
||||
| `frontend.customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` |
|
||||
| `frontend.customStartupProbe` | Custom startupProbe that overrides the default one | `{}` |
|
||||
| `frontend.resourcesPreset` | Set frontend container resources according to one common preset (allowed values: none, nano, small, medium, large, xlarge, 2xlarge). This is ignored if frontend.resources is set (frontend.resources is recommended for production). | `nano` |
|
||||
| `frontend.resources` | Set frontend container requests and limits for different resources like CPU or memory (essential for production workloads) | `{}` |
|
||||
| `frontend.podSecurityContext.enabled` | Enable frontend pods' Security Context | `false` |
|
||||
| `frontend.podSecurityContext.fsGroupChangePolicy` | Set filesystem group change policy for frontend pods | `Always` |
|
||||
| `frontend.podSecurityContext.sysctls` | Set kernel settings using the sysctl interface for frontend pods | `[]` |
|
||||
| `frontend.podSecurityContext.supplementalGroups` | Set filesystem extra groups for frontend pods | `[]` |
|
||||
| `frontend.podSecurityContext.fsGroup` | Set fsGroup in frontend pods' Security Context | `1001` |
|
||||
| `frontend.containerSecurityContext.enabled` | Enabled frontend container' Security Context | `false` |
|
||||
| `frontend.containerSecurityContext.seLinuxOptions` | Set SELinux options in frontend container | `{}` |
|
||||
| `frontend.containerSecurityContext.runAsUser` | Set runAsUser in frontend container' Security Context | `101` |
|
||||
| `frontend.containerSecurityContext.runAsGroup` | Set runAsGroup in frontend container' Security Context | `101` |
|
||||
| `frontend.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in frontend container' Security Context | `true` |
|
||||
| `frontend.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in frontend container' Security Context | `true` |
|
||||
| `frontend.containerSecurityContext.privileged` | Set privileged in frontend container' Security Context | `false` |
|
||||
| `frontend.containerSecurityContext.allowPrivilegeEscalation` | Set allowPrivilegeEscalation in frontend container' Security Context | `false` |
|
||||
| `frontend.containerSecurityContext.capabilities.drop` | List of capabilities to be dropped in frontend container | `["ALL"]` |
|
||||
| `frontend.containerSecurityContext.seccompProfile.type` | Set seccomp profile in frontend container | `RuntimeDefault` |
|
||||
| `frontend.command` | Override default frontend container command (useful when using custom images) | `[]` |
|
||||
| `frontend.args` | Override default frontend container args (useful when using custom images) | `[]` |
|
||||
| `frontend.automountServiceAccountToken` | Mount Service Account token in frontend pods | `false` |
|
||||
| `frontend.hostAliases` | frontend pods host aliases | `[]` |
|
||||
| `frontend.daemonsetAnnotations` | Annotations for frontend daemonset | `{}` |
|
||||
| `frontend.deploymentAnnotations` | Annotations for frontend deployment | `{}` |
|
||||
| `frontend.statefulsetAnnotations` | Annotations for frontend statefulset | `{}` |
|
||||
| `frontend.podLabels` | Extra labels for frontend pods | `{}` |
|
||||
| `frontend.podAnnotations` | Annotations for frontend pods | `{}` |
|
||||
| `frontend.podAffinityPreset` | Pod affinity preset. Ignored if `frontend.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
| `frontend.podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `frontend.affinity` is set. Allowed values: `soft` or `hard` | `soft` |
|
||||
| `frontend.nodeAffinityPreset.type` | Node affinity preset type. Ignored if `frontend.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
| `frontend.nodeAffinityPreset.key` | Node label key to match. Ignored if `frontend.affinity` is set | `""` |
|
||||
| `frontend.nodeAffinityPreset.values` | Node label values to match. Ignored if `frontend.affinity` is set | `[]` |
|
||||
| `frontend.affinity` | Affinity for frontend pods assignment | `{}` |
|
||||
| `frontend.nodeSelector` | Node labels for frontend pods assignment | `{}` |
|
||||
| `frontend.tolerations` | Tolerations for frontend pods assignment | `[]` |
|
||||
| `frontend.updateStrategy.type` | frontend deployment strategy type | `RollingUpdate` |
|
||||
| `frontend.updateStrategy.type` | frontend statefulset strategy type | `RollingUpdate` |
|
||||
| `frontend.podManagementPolicy` | Pod management policy for frontend statefulset | `OrderedReady` |
|
||||
| `frontend.priorityClassName` | frontend pods' priorityClassName | `""` |
|
||||
| `frontend.topologySpreadConstraints` | Topology Spread Constraints for frontend pod assignment spread across your cluster among failure-domains | `[]` |
|
||||
| `frontend.schedulerName` | Name of the k8s scheduler (other than default) for frontend pods | `""` |
|
||||
| `frontend.terminationGracePeriodSeconds` | Seconds frontend pods need to terminate gracefully | `""` |
|
||||
| `frontend.lifecycleHooks` | for frontend containers to automate configuration before or after startup | `{}` |
|
||||
| `frontend.extraEnvVars` | Array with extra environment variables to add to frontend containers | `[]` |
|
||||
| `frontend.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for frontend containers | `""` |
|
||||
| `frontend.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for frontend containers | `""` |
|
||||
| `frontend.extraVolumes` | Optionally specify extra list of additional volumes for the frontend pods | `[]` |
|
||||
| `frontend.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the frontend containers | `[]` |
|
||||
| `frontend.sidecars` | Add additional sidecar containers to the frontend pods | `[]` |
|
||||
| `frontend.initContainers` | Add additional init containers to the frontend pods | `[]` |
|
||||
| `frontend.pdb.create` | Enable/disable a Pod Disruption Budget creation | `true` |
|
||||
| `frontend.pdb.minAvailable` | Minimum number/percentage of pods that should remain scheduled | `""` |
|
||||
| `frontend.pdb.maxUnavailable` | Maximum number/percentage of pods that may be made unavailable. Defaults to `1` if both `frontend.pdb.minAvailable` and `frontend.pdb.maxUnavailable` are empty. | `""` |
|
||||
| `frontend.autoscaling.vpa.enabled` | Enable VPA for frontend pods | `false` |
|
||||
| `frontend.autoscaling.vpa.annotations` | Annotations for VPA resource | `{}` |
|
||||
| `frontend.autoscaling.vpa.controlledResources` | VPA List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory | `[]` |
|
||||
| `frontend.autoscaling.vpa.maxAllowed` | VPA Max allowed resources for the pod | `{}` |
|
||||
| `frontend.autoscaling.vpa.minAllowed` | VPA Min allowed resources for the pod | `{}` |
|
||||
| `frontend.autoscaling.vpa.updatePolicy.updateMode` | Autoscaling update policy | `Auto` |
|
||||
| `frontend.autoscaling.hpa.enabled` | Enable HPA for frontend pods | `false` |
|
||||
| `frontend.autoscaling.hpa.minReplicas` | Minimum number of replicas | `""` |
|
||||
| `frontend.autoscaling.hpa.maxReplicas` | Maximum number of replicas | `""` |
|
||||
| `frontend.autoscaling.hpa.targetCPU` | Target CPU utilization percentage | `""` |
|
||||
| `frontend.autoscaling.hpa.targetMemory` | Target Memory utilization percentage | `""` |
|
||||
| `frontend.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` |
|
||||
| `frontend.serviceAccount.name` | The name of the ServiceAccount to use. | `""` |
|
||||
| `frontend.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` |
|
||||
| `frontend.serviceAccount.automountServiceAccountToken` | Automount service account token for the frontend service account | `true` |
|
||||
| `frontend.serviceAccount.imagePullSecrets` | Add image pull secrets to the frontend service account | `[]` |
|
||||
| `frontend.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` |
|
||||
| `frontend.networkPolicy.allowExternal` | Don't require server label for connections | `true` |
|
||||
| `frontend.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
|
||||
| `frontend.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
|
||||
| `frontend.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
|
||||
|
||||
### orborus Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
|
||||
| `orborus.image.registry` | orborus image registry | `ghcr.io` |
|
||||
| `orborus.image.repository` | orborus image repository | `shuffle/shuffle-orborus` |
|
||||
| `orborus.image.digest` | orborus image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` |
|
||||
| `orborus.image.pullPolicy` | orborus image pull policy | `IfNotPresent` |
|
||||
| `orborus.image.pullSecrets` | orborus image pull secrets | `[]` |
|
||||
| `orborus.replicaCount` | Number of orborus replicas to deploy | `1` |
|
||||
| `orborus.containerPorts.http` | orborus HTTP container port | `8080` |
|
||||
| `orborus.extraContainerPorts` | Optionally specify extra list of additional ports for orborus containers | `[]` |
|
||||
| `orborus.livenessProbe.enabled` | Enable livenessProbe on orborus containers | `false` |
|
||||
| `orborus.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `0` |
|
||||
| `orborus.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `15` |
|
||||
| `orborus.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `1` |
|
||||
| `orborus.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `4` |
|
||||
| `orborus.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` |
|
||||
| `orborus.readinessProbe.enabled` | Enable readinessProbe on orborus containers | `false` |
|
||||
| `orborus.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `0` |
|
||||
| `orborus.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `5` |
|
||||
| `orborus.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` |
|
||||
| `orborus.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `3` |
|
||||
| `orborus.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` |
|
||||
| `orborus.startupProbe.enabled` | Enable startupProbe on orborus containers | `false` |
|
||||
| `orborus.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `0` |
|
||||
| `orborus.startupProbe.periodSeconds` | Period seconds for startupProbe | `1` |
|
||||
| `orborus.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` |
|
||||
| `orborus.startupProbe.failureThreshold` | Failure threshold for startupProbe | `60` |
|
||||
| `orborus.startupProbe.successThreshold` | Success threshold for startupProbe | `1` |
|
||||
| `orborus.customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` |
|
||||
| `orborus.customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` |
|
||||
| `orborus.customStartupProbe` | Custom startupProbe that overrides the default one | `{}` |
|
||||
| `orborus.resourcesPreset` | Set orborus container resources according to one common preset (allowed values: none, nano, small, medium, large, xlarge, 2xlarge). This is ignored if orborus.resources is set (orborus.resources is recommended for production). | `nano` |
|
||||
| `orborus.resources` | Set orborus container requests and limits for different resources like CPU or memory (essential for production workloads) | `{}` |
|
||||
| `orborus.podSecurityContext.enabled` | Enable orborus pods' Security Context | `true` |
|
||||
| `orborus.podSecurityContext.fsGroupChangePolicy` | Set filesystem group change policy for orborus pods | `Always` |
|
||||
| `orborus.podSecurityContext.sysctls` | Set kernel settings using the sysctl interface for orborus pods | `[]` |
|
||||
| `orborus.podSecurityContext.supplementalGroups` | Set filesystem extra groups for orborus pods | `[]` |
|
||||
| `orborus.podSecurityContext.fsGroup` | Set fsGroup in orborus pods' Security Context | `1001` |
|
||||
| `orborus.containerSecurityContext.enabled` | Enabled orborus container' Security Context | `true` |
|
||||
| `orborus.containerSecurityContext.seLinuxOptions` | Set SELinux options in orborus container | `{}` |
|
||||
| `orborus.containerSecurityContext.runAsUser` | Set runAsUser in orborus container' Security Context | `101` |
|
||||
| `orborus.containerSecurityContext.runAsGroup` | Set runAsGroup in orborus container' Security Context | `101` |
|
||||
| `orborus.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in orborus container' Security Context | `true` |
|
||||
| `orborus.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in orborus container' Security Context | `true` |
|
||||
| `orborus.containerSecurityContext.privileged` | Set privileged in orborus container' Security Context | `false` |
|
||||
| `orborus.containerSecurityContext.allowPrivilegeEscalation` | Set allowPrivilegeEscalation in orborus container' Security Context | `false` |
|
||||
| `orborus.containerSecurityContext.capabilities.drop` | List of capabilities to be dropped in orborus container | `["ALL"]` |
|
||||
| `orborus.containerSecurityContext.seccompProfile.type` | Set seccomp profile in orborus container | `RuntimeDefault` |
|
||||
| `orborus.command` | Override default orborus container command (useful when using custom images) | `[]` |
|
||||
| `orborus.args` | Override default orborus container args (useful when using custom images) | `[]` |
|
||||
| `orborus.automountServiceAccountToken` | Mount Service Account token in orborus pods | `true` |
|
||||
| `orborus.hostAliases` | orborus pods host aliases | `[]` |
|
||||
| `orborus.daemonsetAnnotations` | Annotations for orborus daemonset | `{}` |
|
||||
| `orborus.deploymentAnnotations` | Annotations for orborus deployment | `{}` |
|
||||
| `orborus.statefulsetAnnotations` | Annotations for orborus statefulset | `{}` |
|
||||
| `orborus.podLabels` | Extra labels for orborus pods | `{}` |
|
||||
| `orborus.podAnnotations` | Annotations for orborus pods | `{}` |
|
||||
| `orborus.podAffinityPreset` | Pod affinity preset. Ignored if `orborus.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
| `orborus.podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `orborus.affinity` is set. Allowed values: `soft` or `hard` | `soft` |
|
||||
| `orborus.nodeAffinityPreset.type` | Node affinity preset type. Ignored if `orborus.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
| `orborus.nodeAffinityPreset.key` | Node label key to match. Ignored if `orborus.affinity` is set | `""` |
|
||||
| `orborus.nodeAffinityPreset.values` | Node label values to match. Ignored if `orborus.affinity` is set | `[]` |
|
||||
| `orborus.affinity` | Affinity for orborus pods assignment | `{}` |
|
||||
| `orborus.nodeSelector` | Node labels for orborus pods assignment | `{}` |
|
||||
| `orborus.tolerations` | Tolerations for orborus pods assignment | `[]` |
|
||||
| `orborus.updateStrategy.type` | orborus deployment strategy type | `RollingUpdate` |
|
||||
| `orborus.updateStrategy.type` | orborus statefulset strategy type | `RollingUpdate` |
|
||||
| `orborus.podManagementPolicy` | Pod management policy for orborus statefulset | `OrderedReady` |
|
||||
| `orborus.priorityClassName` | orborus pods' priorityClassName | `""` |
|
||||
| `orborus.topologySpreadConstraints` | Topology Spread Constraints for orborus pod assignment spread across your cluster among failure-domains | `[]` |
|
||||
| `orborus.schedulerName` | Name of the k8s scheduler (other than default) for orborus pods | `""` |
|
||||
| `orborus.terminationGracePeriodSeconds` | Seconds orborus pods need to terminate gracefully | `""` |
|
||||
| `orborus.lifecycleHooks` | for orborus containers to automate configuration before or after startup | `{}` |
|
||||
| `orborus.extraEnvVars` | Array with extra environment variables to add to orborus containers | `[]` |
|
||||
| `orborus.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for orborus containers | `""` |
|
||||
| `orborus.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for orborus containers | `""` |
|
||||
| `orborus.extraVolumes` | Optionally specify extra list of additional volumes for the orborus pods | `[]` |
|
||||
| `orborus.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the orborus containers | `[]` |
|
||||
| `orborus.sidecars` | Add additional sidecar containers to the orborus pods | `[]` |
|
||||
| `orborus.initContainers` | Add additional init containers to the orborus pods | `[]` |
|
||||
| `orborus.pdb.create` | Enable/disable a Pod Disruption Budget creation | `true` |
|
||||
| `orborus.pdb.minAvailable` | Minimum number/percentage of pods that should remain scheduled | `""` |
|
||||
| `orborus.pdb.maxUnavailable` | Maximum number/percentage of pods that may be made unavailable. Defaults to `1` if both `orborus.pdb.minAvailable` and `orborus.pdb.maxUnavailable` are empty. | `""` |
|
||||
| `orborus.autoscaling.vpa.enabled` | Enable VPA for orborus pods | `false` |
|
||||
| `orborus.autoscaling.vpa.annotations` | Annotations for VPA resource | `{}` |
|
||||
| `orborus.autoscaling.vpa.controlledResources` | VPA List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory | `[]` |
|
||||
| `orborus.autoscaling.vpa.maxAllowed` | VPA Max allowed resources for the pod | `{}` |
|
||||
| `orborus.autoscaling.vpa.minAllowed` | VPA Min allowed resources for the pod | `{}` |
|
||||
| `orborus.autoscaling.vpa.updatePolicy.updateMode` | Autoscaling update policy | `Auto` |
|
||||
| `orborus.autoscaling.hpa.enabled` | Enable HPA for orborus pods | `false` |
|
||||
| `orborus.autoscaling.hpa.minReplicas` | Minimum number of replicas | `""` |
|
||||
| `orborus.autoscaling.hpa.maxReplicas` | Maximum number of replicas | `""` |
|
||||
| `orborus.autoscaling.hpa.targetCPU` | Target CPU utilization percentage | `""` |
|
||||
| `orborus.autoscaling.hpa.targetMemory` | Target Memory utilization percentage | `""` |
|
||||
| `orborus.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` |
|
||||
| `orborus.serviceAccount.name` | The name of the ServiceAccount to use. | `""` |
|
||||
| `orborus.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` |
|
||||
| `orborus.serviceAccount.automountServiceAccountToken` | Automount service account token for the orborus service account | `true` |
|
||||
| `orborus.serviceAccount.imagePullSecrets` | Add image pull secrets to the orborus service account | `[]` |
|
||||
| `orborus.rbac.create` | Specifies whether RBAC resources should be created | `true` |
|
||||
| `orborus.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` |
|
||||
| `orborus.networkPolicy.allowExternal` | Don't require server label for connections | `true` |
|
||||
| `orborus.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
|
||||
| `orborus.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
|
||||
| `orborus.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
|
||||
|
||||
### worker Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
|
||||
| `worker.image.registry` | worker image registry | `ghcr.io` |
|
||||
| `worker.image.repository` | worker image repository | `shuffle/shuffle-worker` |
|
||||
| `worker.image.digest` | worker image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` |
|
||||
| `worker.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` |
|
||||
| `worker.serviceAccount.name` | The name of the ServiceAccount to use. | `""` |
|
||||
| `worker.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` |
|
||||
| `worker.serviceAccount.automountServiceAccountToken` | Automount service account token for the worker service account | `true` |
|
||||
| `worker.serviceAccount.imagePullSecrets` | Add image pull secrets to the worker service account | `[]` |
|
||||
| `worker.rbac.create` | Specifies whether RBAC resources should be created | `true` |
|
||||
| `worker.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` |
|
||||
| `worker.networkPolicy.allowExternal` | Don't require server label for connections | `true` |
|
||||
| `worker.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
|
||||
| `worker.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
|
||||
| `worker.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
|
||||
|
||||
### app Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------------------------------------- | ---------------------------------------------------------------------------------- | ------ |
|
||||
| `app.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` |
|
||||
| `app.serviceAccount.name` | The name of the ServiceAccount to use. | `""` |
|
||||
| `app.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` |
|
||||
| `app.serviceAccount.automountServiceAccountToken` | Automount service account token for the app service account | `true` |
|
||||
| `app.serviceAccount.imagePullSecrets` | Add image pull secrets to the app service account | `[]` |
|
||||
| `app.rbac.create` | Specifies whether RBAC resources should be created | `true` |
|
||||
| `app.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` |
|
||||
| `app.networkPolicy.allowExternal` | Don't require server label for connections | `true` |
|
||||
| `app.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
|
||||
| `app.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
|
||||
| `app.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
|
||||
|
||||
### Traffic Exposure Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------- | --------------- |
|
||||
| `ingress.enabled` | Enable ingress record generation for frontend and backend | `false` |
|
||||
| `ingress.pathType` | Ingress path type for the frontend path | `Prefix` |
|
||||
| `ingress.backendPathType` | Ingress path type for the backend path | `Prefix` |
|
||||
| `ingress.apiVersion` | Force Ingress API version (automatically detected if not set) | `""` |
|
||||
| `ingress.hostname` | Default host for the ingress record | `shuffle.local` |
|
||||
| `ingress.ingressClassName` | IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) | `nginx` |
|
||||
| `ingress.path` | Ingress path for Shuffle frontend | `"/"` |
|
||||
| `ingress.backendPath` | Ingress path for Shuffle backend | `"/api/"` |
|
||||
| `ingress.annotations` | Additional annotations for the Ingress resource. | `{}` |
|
||||
| `ingress.tls` | Enable TLS configuration for the host defined at `ingress.hostname` parameter | `false` |
|
||||
| `ingress.selfSigned` | Create a TLS secret for this ingress record using self-signed certificates generated by Helm | `false` |
|
||||
| `ingress.extraHosts` | An array with additional hostname(s) to be covered with the ingress record | `[]` |
|
||||
| `ingress.extraPaths` | An array with additional arbitrary paths that may need to be added to the ingress under the main host | `[]` |
|
||||
| `ingress.extraTls` | TLS configuration for additional hostname(s) to be covered with this ingress record | `[]` |
|
||||
| `ingress.secrets` | Custom TLS certificates as secrets | `[]` |
|
||||
| `ingress.extraRules` | Additional rules to be covered with this ingress record | `[]` |
|
||||
|
||||
### Istio Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------- | ------------------------ |
|
||||
| `istio.enabled` | Enable creation of an Istio Gateway and VirtualService for frontend and backend | `false` |
|
||||
| `istio.apiVersion` | The istio apiVersion to use for Gateway and VirtualService resources | `networking.istio.io/v1` |
|
||||
| `istio.hosts` | One or more hosts exposed by Istio | `[]` |
|
||||
| `istio.gateway.annotations` | Additional annotations for the Gateway resource | `{}` |
|
||||
| `istio.gateway.selector` | The selector matches the ingress gateway pod labels | `{ istio: ingress }` |
|
||||
| `istio.gateway.http.enabled` | Enable HTTP server port 80 | `true` |
|
||||
| `istio.gateway.http.httpsRedirect` | If set to true, a 301 redirect is send for all HTTP connections | `false` |
|
||||
| `istio.gateway.https.enabled` | Enable HTTPS server on port 443 | `false` |
|
||||
| `istio.gateway.https.tlsCredentialName` | The name of the secret that holds the TLS certs including the CA certificates. | `""` |
|
||||
| `istio.gateway.https.tlsCipherSuites` | If specified, only support the specified cipher list. | `[]` |
|
||||
| `istio.gateway.extraServers` | Additional servers for the Gateway resource | `[]` |
|
||||
| `istio.virtualService.annotations` | Additional annotations for the VirtualService resource. | `{}` |
|
||||
| `istio.virtualService.backendHeaders` | Header manipulation rules for backend traffic | `{}` |
|
||||
| `istio.virtualService.frontendHeaders` | Header manipulation rules for frontend traffic | `{}` |
|
||||
|
||||
### Persistence Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------------------------- | ------------------------------------------------- | ------------------- |
|
||||
| `persistence.enabled` | Enable persistence using Persistent Volume Claims | `true` |
|
||||
| `persistence.apps.existingClaim` | Name of an existing PVC to use | `""` |
|
||||
| `persistence.apps.storageClass` | PVC Storage Class for shuffle-apps volume | `""` |
|
||||
| `persistence.apps.subPath` | The sub path used in the volume | `""` |
|
||||
| `persistence.apps.accessModes` | The access mode of the volume | `["ReadWriteOnce"]` |
|
||||
| `persistence.apps.size` | The size of the volume | `5Gi` |
|
||||
| `persistence.apps.annotations` | Annotations for the PVC | `{}` |
|
||||
| `persistence.apps.selector` | Selector to match an existing Persistent Volume | `{}` |
|
||||
| `persistence.appBuilder.storageClass` | PVC Storage Class for backend-apps-claim volume | `""` |
|
||||
| `persistence.appBuilder.accessModes` | The access mode of the volume | `["ReadWriteOnce"]` |
|
||||
| `persistence.appBuilder.size` | The size of the volume | `5Gi` |
|
||||
| `persistence.appBuilder.annotations` | Annotations for the PVC | `{}` |
|
||||
| `persistence.appBuilder.selector` | Selector to match an existing Persistent Volume | `{}` |
|
||||
| `persistence.files.existingClaim` | Name of an existing PVC to use | `""` |
|
||||
| `persistence.files.storageClass` | PVC Storage Class for shuffle-files volume | `""` |
|
||||
| `persistence.files.subPath` | The sub path used in the volume | `""` |
|
||||
| `persistence.files.accessModes` | The access mode of the volume | `["ReadWriteOnce"]` |
|
||||
| `persistence.files.size` | The size of the volume | `5Gi` |
|
||||
| `persistence.files.annotations` | Annotations for the PVC | `{}` |
|
||||
| `persistence.files.selector` | Selector to match an existing Persistent Volume | `{}` |
|
||||
|
||||
### Init Container Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||
| `volumePermissions.enabled` | Enable init container that changes the owner/group of the PV mount point to `runAsUser:fsGroup` | `false` |
|
||||
| `volumePermissions.image.registry` | OS Shell + Utility image registry | `docker.io` |
|
||||
| `volumePermissions.image.repository` | OS Shell + Utility image repository | `bitnami/os-shell` |
|
||||
| `volumePermissions.image.pullPolicy` | OS Shell + Utility image pull policy | `IfNotPresent` |
|
||||
| `volumePermissions.image.pullSecrets` | OS Shell + Utility image pull secrets | `[]` |
|
||||
| `volumePermissions.resourcesPreset` | Set init container resources according to one common preset (allowed values: none, nano, small, medium, large, xlarge, 2xlarge). This is ignored if volumePermissions.resources is set (volumePermissions.resources is recommended for production). | `nano` |
|
||||
| `volumePermissions.resources` | Set init container requests and limits for different resources like CPU or memory (essential for production workloads) | `{}` |
|
||||
| `volumePermissions.containerSecurityContext.enabled` | Enabled init container' Security Context | `true` |
|
||||
| `volumePermissions.containerSecurityContext.seLinuxOptions` | Set SELinux options in init container | `{}` |
|
||||
| `volumePermissions.containerSecurityContext.runAsUser` | Set init container's Security Context runAsUser | `0` |
|
||||
|
||||
### OpenSearch Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| -------------------- | ----------------------------------------------------- | ------ |
|
||||
| `opensearch.enabled` | Switch to enable or disable the opensearch helm chart | `true` |
|
||||
|
||||
### Vault Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| --------------- | -------------------------------------------------------------------------- | ----- |
|
||||
| `vault.role` | Specify the Vault role, which should be used to get the secret from Vault. | `""` |
|
||||
| `vault.secrets` | A list of VaultSecrets to create | `[]` |
|
||||
|
||||
### Other Parameters
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
CHART NAME: {{ .Chart.Name }}
|
||||
CHART VERSION: {{ .Chart.Version }}
|
||||
APP VERSION: {{ .Chart.AppVersion }}
|
||||
|
||||
** Please be patient while the chart is being deployed **
|
||||
|
||||
{{- if .Values.diagnosticMode.enabled }}
|
||||
The chart has been deployed in diagnostic mode. All probes have been disabled and the command has been overwritten with:
|
||||
|
||||
command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 4 }}
|
||||
args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 4 }}
|
||||
|
||||
Get the list of pods by executing:
|
||||
|
||||
kubectl get pods --namespace {{ include "common.names.namespace" . | quote }} -l app.kubernetes.io/instance={{ .Release.Name }}
|
||||
|
||||
Access the pod you want to debug by executing
|
||||
|
||||
kubectl exec --namespace {{ include "common.names.namespace" . | quote }} -ti <NAME OF THE POD> -- bash
|
||||
|
||||
{{- end }}
|
||||
|
||||
To access shuffle using port-forwarding:
|
||||
|
||||
1. Run `kubectl port-forward -n shuffle svc/shuffle-frontend 8080:http`
|
||||
2. Visit http://localhost:8080 with your browser
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
{{/*
|
||||
Return the common name for backend componentes
|
||||
*/}}
|
||||
{{- define "shuffle.backend.name" -}}
|
||||
{{- printf "%s-backend" (include "common.names.fullname" .) | trunc 63 -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common name for frontend components
|
||||
*/}}
|
||||
{{- define "shuffle.frontend.name" -}}
|
||||
{{- printf "%s-frontend" (include "common.names.fullname" .) | trunc 63 -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common name for orborus components
|
||||
*/}}
|
||||
{{- define "shuffle.orborus.name" -}}
|
||||
{{- printf "%s-orborus" (include "common.names.fullname" .) | trunc 63 -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common name for worker components
|
||||
*/}}
|
||||
{{- define "shuffle.worker.name" -}}
|
||||
{{- printf "%s-worker" (include "common.names.fullname" .) | trunc 63 -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common name for app components
|
||||
*/}}
|
||||
{{- define "shuffle.app.name" -}}
|
||||
{{- printf "%s-app" (include "common.names.fullname" .) | trunc 63 -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common labels for backend components
|
||||
The shuffle app builder requires the io.kompose.service=backend label to be set on the backend pod.
|
||||
*/}}
|
||||
{{- define "shuffle.backend.labels" -}}
|
||||
{{- include "common.labels.standard" . }}
|
||||
app.kubernetes.io/component: backend
|
||||
io.kompose.service: backend
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common labels for frontend components
|
||||
*/}}
|
||||
{{- define "shuffle.frontend.labels" -}}
|
||||
{{- include "common.labels.standard" . }}
|
||||
app.kubernetes.io/component: frontend
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common labels for orborus components
|
||||
*/}}
|
||||
{{- define "shuffle.orborus.labels" -}}
|
||||
{{- include "common.labels.standard" . }}
|
||||
app.kubernetes.io/component: orborus
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common labels for worker components
|
||||
*/}}
|
||||
{{- define "shuffle.worker.labels" -}}
|
||||
{{- include "common.labels.standard" . }}
|
||||
app.kubernetes.io/component: worker
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the common labels for app components
|
||||
*/}}
|
||||
{{- define "shuffle.app.labels" -}}
|
||||
{{- include "common.labels.standard" . }}
|
||||
app.kubernetes.io/component: app
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the match labels for backend components
|
||||
*/}}
|
||||
{{- define "shuffle.backend.matchLabels" -}}
|
||||
{{- include "common.labels.matchLabels" . }}
|
||||
app.kubernetes.io/component: backend
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the match labels for frontend components
|
||||
*/}}
|
||||
{{- define "shuffle.frontend.matchLabels" -}}
|
||||
{{- include "common.labels.matchLabels" . }}
|
||||
app.kubernetes.io/component: frontend
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the match labels for orborus components
|
||||
*/}}
|
||||
{{- define "shuffle.orborus.matchLabels" -}}
|
||||
{{- include "common.labels.matchLabels" . }}
|
||||
app.kubernetes.io/component: orborus
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the match labels for worker components
|
||||
NOTE: This does not match the labels from shuffle.worker.labels, but the labels set by the orborus GoLang app.
|
||||
*/}}
|
||||
{{- define "shuffle.worker.matchLabels" -}}
|
||||
app.kubernetes.io/name: shuffle-worker
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the match labels for app components
|
||||
NOTE: This does not match the labels from shuffle.worker.labels, but the labels set by the orborus GoLang app.
|
||||
*/}}
|
||||
{{- define "shuffle.app.matchLabels" -}}
|
||||
app.kubernetes.io/name: shuffle-app
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper image name (for the init container volume-permissions image)
|
||||
*/}}
|
||||
{{- define "shuffle.volumePermissions.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.volumePermissions.image "global" .Values.global ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Shuffle backend image name
|
||||
*/}}
|
||||
{{- define "shuffle.backend.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.backend.image "global" .Values.global ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Docker Image Registry Secret Names for the backend pod
|
||||
*/}}
|
||||
{{- define "shuffle.backend.imagePullSecrets" -}}
|
||||
{{- include "common.images.renderPullSecrets" (dict "images" (list .Values.backend.image) "context" $) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Shuffle frontend image name
|
||||
*/}}
|
||||
{{- define "shuffle.frontend.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.frontend.image "global" .Values.global ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Docker Image Registry Secret Names for the frontend pod
|
||||
*/}}
|
||||
{{- define "shuffle.frontend.imagePullSecrets" -}}
|
||||
{{- include "common.images.renderPullSecrets" (dict "images" (list .Values.frontend.image) "context" $) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Shuffle orborus image name
|
||||
*/}}
|
||||
{{- define "shuffle.orborus.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.orborus.image "global" .Values.global ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Docker Image Registry Secret Names for the orborus pod
|
||||
*/}}
|
||||
{{- define "shuffle.orborus.imagePullSecrets" -}}
|
||||
{{- include "common.images.renderPullSecrets" (dict "images" (list .Values.orborus.image) "context" $) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Shuffle worker image name
|
||||
*/}}
|
||||
{{- define "shuffle.worker.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.worker.image "global" .Values.global ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use for the Shuffle backend
|
||||
*/}}
|
||||
{{- define "shuffle.backend.serviceAccount.name" -}}
|
||||
{{- if .Values.backend.serviceAccount.create -}}
|
||||
{{ default (include "shuffle.backend.name" .) .Values.backend.serviceAccount.name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.backend.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Docker Image Registry Secret Names for the backend service account
|
||||
*/}}
|
||||
{{- define "shuffle.backend.serviceAccount.imagePullSecrets" -}}
|
||||
{{- $pullSecrets := list }}
|
||||
|
||||
{{- range .Values.global.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- range .Values.backend.serviceAccount.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if (not (empty $pullSecrets)) -}}
|
||||
imagePullSecrets:
|
||||
{{- range $pullSecrets | uniq }}
|
||||
- name: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use for the Shuffle frontend
|
||||
*/}}
|
||||
{{- define "shuffle.frontend.serviceAccount.name" -}}
|
||||
{{- if .Values.frontend.serviceAccount.create -}}
|
||||
{{ default (include "shuffle.frontend.name" .) .Values.frontend.serviceAccount.name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.frontend.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Docker Image Registry Secret Names for the frontend service account
|
||||
*/}}
|
||||
{{- define "shuffle.frontend.serviceAccount.imagePullSecrets" -}}
|
||||
{{- $pullSecrets := list }}
|
||||
|
||||
{{- range .Values.global.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- range .Values.frontend.serviceAccount.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if (not (empty $pullSecrets)) -}}
|
||||
imagePullSecrets:
|
||||
{{- range $pullSecrets | uniq }}
|
||||
- name: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use for Shuffle orborus
|
||||
*/}}
|
||||
{{- define "shuffle.orborus.serviceAccount.name" -}}
|
||||
{{- if .Values.orborus.serviceAccount.create -}}
|
||||
{{ default (include "shuffle.orborus.name" .) .Values.orborus.serviceAccount.name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.orborus.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Docker Image Registry Secret Names for the orborus service account
|
||||
*/}}
|
||||
{{- define "shuffle.orborus.serviceAccount.imagePullSecrets" -}}
|
||||
{{- $pullSecrets := list }}
|
||||
|
||||
{{- range .Values.global.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- range .Values.orborus.serviceAccount.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if (not (empty $pullSecrets)) -}}
|
||||
imagePullSecrets:
|
||||
{{- range $pullSecrets | uniq }}
|
||||
- name: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use for Shuffle workers
|
||||
*/}}
|
||||
{{- define "shuffle.worker.serviceAccount.name" -}}
|
||||
{{- if .Values.worker.serviceAccount.create -}}
|
||||
{{ default (include "shuffle.worker.name" .) .Values.worker.serviceAccount.name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.worker.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Docker Image Registry Secret Names for the worker service account
|
||||
*/}}
|
||||
{{- define "shuffle.worker.serviceAccount.imagePullSecrets" -}}
|
||||
{{- $pullSecrets := list }}
|
||||
|
||||
{{- range .Values.global.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- range .Values.worker.serviceAccount.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if (not (empty $pullSecrets)) -}}
|
||||
imagePullSecrets:
|
||||
{{- range $pullSecrets | uniq }}
|
||||
- name: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use for Shuffle apps
|
||||
*/}}
|
||||
{{- define "shuffle.app.serviceAccount.name" -}}
|
||||
{{- if .Values.app.serviceAccount.create -}}
|
||||
{{ default (include "shuffle.app.name" .) .Values.app.serviceAccount.name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.app.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Docker Image Registry Secret Names for the app service account
|
||||
*/}}
|
||||
{{- define "shuffle.app.serviceAccount.imagePullSecrets" -}}
|
||||
{{- $pullSecrets := list }}
|
||||
|
||||
{{- range .Values.global.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- range .Values.app.serviceAccount.imagePullSecrets -}}
|
||||
{{- if kindIs "map" . -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" .name "context" .)) -}}
|
||||
{{- else -}}
|
||||
{{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" .)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if (not (empty $pullSecrets)) -}}
|
||||
imagePullSecrets:
|
||||
{{- range $pullSecrets | uniq }}
|
||||
- name: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,28 @@
|
||||
# This PVC is always enabled, regardless of .Values.persistence.enabled,
|
||||
# as app building does not work without it.
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: backend-apps-claim # Hardcoded by shuffle-app-builder
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
annotations:
|
||||
{{- if eq .Values.persistence.resourcePolicy "keep" }}
|
||||
helm.sh/resource-policy: keep
|
||||
{{- end }}
|
||||
{{- if or .Values.persistence.appBuilder.annotations .Values.commonAnnotations }}
|
||||
{{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.persistence.appBuilder.annotations .Values.commonAnnotations ) "context" . ) }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
accessModes:
|
||||
{{- range .Values.persistence.appBuilder.accessModes }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.appBuilder.size }}
|
||||
{{- if .Values.persistence.appBuilder.selector }}
|
||||
selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.appBuilder.selector "context" $) | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- include "common.storage.class" ( dict "persistence" .Values.persistence.appBuilder "global" .Values.global ) | nindent 2 }}
|
||||
@@ -0,0 +1,30 @@
|
||||
{{- if .Values.persistence.enabled }}
|
||||
{{- if (not .Values.persistence.apps.existingClaim) }}
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ printf "%s-apps" (include "shuffle.backend.name" .) }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
annotations:
|
||||
{{- if eq .Values.persistence.resourcePolicy "keep" }}
|
||||
helm.sh/resource-policy: keep
|
||||
{{- end }}
|
||||
{{- if or .Values.persistence.apps.annotations .Values.commonAnnotations }}
|
||||
{{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.persistence.apps.annotations .Values.commonAnnotations ) "context" . ) }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
accessModes:
|
||||
{{- range .Values.persistence.apps.accessModes }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.apps.size }}
|
||||
{{- if .Values.persistence.apps.selector }}
|
||||
selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.apps.selector "context" $) | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- include "common.storage.class" ( dict "persistence" .Values.persistence.apps "global" .Values.global ) | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "shuffle.backend.name" . }}-env
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
BACKEND_PORT: "5001"
|
||||
{{- if .Values.shuffle.baseUrl }}
|
||||
BASE_URL: "{{ .Values.shuffle.baseUrl }}"
|
||||
SSO_REDIRECT_URL: "{{ .Values.shuffle.baseUrl }}"
|
||||
{{- else }}
|
||||
BASE_URL: "http://{{ include "shuffle.backend.name" . }}:5001"
|
||||
{{- end }}
|
||||
ORG_ID: "{{ .Values.shuffle.org }}"
|
||||
SHUFFLE_APP_DOWNLOAD_LOCATION: "{{ .Values.backend.apps.downloadLocation }}"
|
||||
SHUFFLE_DOWNLOAD_AUTH_BRANCH: "{{ .Values.backend.apps.downloadBranch }}"
|
||||
SHUFFLE_APP_FORCE_UPDATE: "{{ .Values.backend.apps.forceUpdate }}"
|
||||
SHUFFLE_CHAT_DISABLED: "true"
|
||||
SHUFFLE_OPENSEARCH_URL: {{ include "common.tplvalues.render" (dict "value" .Values.backend.openSearch.url "context" $) }}
|
||||
SHUFFLE_OPENSEARCH_USERNAME: "{{ .Values.backend.openSearch.username }}"
|
||||
SHUFFLE_OPENSEARCH_CERTIFICATE_FILE: "{{ .Values.backend.openSearch.certificateFile }}"
|
||||
SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY: "{{ .Values.backend.openSearch.skipSSLVerify }}"
|
||||
SHUFFLE_OPENSEARCH_INDEX_PREFIX: "{{ .Values.backend.openSearch.indexPrefix }}"
|
||||
SHUFFLE_RERUN_SCHEDULE: "{{ .Values.backend.cleanupSchedule }}"
|
||||
TZ: "{{ .Values.shuffle.timezone }}"
|
||||
REGISTRY_URL: "{{ .Values.shuffle.appRegistry }}"
|
||||
@@ -0,0 +1,224 @@
|
||||
apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }}
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "shuffle.backend.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if or .Values.backend.deploymentAnnotations .Values.commonAnnotations }}
|
||||
{{- $annotations := include "common.tplvalues.merge" (dict "values" (list .Values.backend.deploymentAnnotations .Values.commonAnnotations) "context" .) }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if not .Values.backend.autoscaling.hpa.enabled }}
|
||||
replicas: {{ .Values.backend.replicaCount }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.updateStrategy }}
|
||||
strategy: {{- toYaml .Values.backend.updateStrategy | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- $podLabels := include "common.tplvalues.merge" (dict "values" (list .Values.backend.podLabels .Values.commonLabels) "context" .) }}
|
||||
selector:
|
||||
matchLabels: {{- include "shuffle.backend.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- if .Values.backend.podAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" (dict "value" .Values.backend.podAnnotations "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" $podLabels "context" $ ) | nindent 8 }}
|
||||
spec:
|
||||
{{- include "shuffle.backend.imagePullSecrets" . | nindent 6 }}
|
||||
serviceAccountName: {{ template "shuffle.backend.serviceAccount.name" . }}
|
||||
automountServiceAccountToken: {{ .Values.backend.automountServiceAccountToken }}
|
||||
{{- if .Values.backend.hostAliases }}
|
||||
hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.backend.hostAliases "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.affinity }}
|
||||
affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.backend.affinity "context" $) | nindent 8 }}
|
||||
{{- else }}
|
||||
affinity:
|
||||
podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.backend.podAffinityPreset "component" "backend" "customLabels" $podLabels "context" $) | nindent 10 }}
|
||||
podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.backend.podAntiAffinityPreset "component" "backend" "customLabels" $podLabels "context" $) | nindent 10 }}
|
||||
nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.backend.nodeAffinityPreset.type "key" .Values.backend.nodeAffinityPreset.key "values" .Values.backend.nodeAffinityPreset.values) | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.nodeSelector }}
|
||||
nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.backend.nodeSelector "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.tolerations }}
|
||||
tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.backend.tolerations "context" .) | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.priorityClassName }}
|
||||
priorityClassName: {{ .Values.backend.priorityClassName | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.schedulerName }}
|
||||
schedulerName: {{ .Values.backend.schedulerName | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.topologySpreadConstraints }}
|
||||
topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.backend.topologySpreadConstraints "context" .) | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.podSecurityContext.enabled }}
|
||||
securityContext: {{- omit .Values.backend.podSecurityContext "enabled" | toYaml | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.terminationGracePeriodSeconds }}
|
||||
terminationGracePeriodSeconds: {{ .Values.backend.terminationGracePeriodSeconds }}
|
||||
{{- end }}
|
||||
initContainers:
|
||||
{{- if and .Values.volumePermissions.enabled .Values.persistence.enabled }}
|
||||
- name: volume-permissions
|
||||
image: {{ include "shuffle.volumePermissions.image" . }}
|
||||
imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }}
|
||||
command:
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- |
|
||||
chown -vR {{ .Values.backend.containerSecurityContext.runAsUser }}:{{ .Values.backend.podSecurityContext.fsGroup }} /app/generated && \
|
||||
chown -vR {{ .Values.backend.containerSecurityContext.runAsUser }}:{{ .Values.backend.podSecurityContext.fsGroup }} /shuffle-apps && \
|
||||
chown -vR {{ .Values.backend.containerSecurityContext.runAsUser }}:{{ .Values.backend.podSecurityContext.fsGroup }} /shuffle-files
|
||||
{{- if .Values.volumePermissions.containerSecurityContext.enabled }}
|
||||
securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.volumePermissions.containerSecurityContext "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.volumePermissions.resources }}
|
||||
resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }}
|
||||
{{- else if ne .Values.volumePermissions.resourcesPreset "none" }}
|
||||
resources: {{- include "common.resources.preset" (dict "type" .Values.volumePermissions.resourcesPreset) | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: shuffle-app-builder
|
||||
mountPath: /app/generated
|
||||
- name: shuffle-apps
|
||||
mountPath: /shuffle-apps
|
||||
{{- if .Values.persistence.apps.subPath }}
|
||||
subPath: {{ .Values.persistence.apps.subPath }}
|
||||
{{- end }}
|
||||
- name: shuffle-files
|
||||
mountPath: /shuffle-files
|
||||
{{- if .Values.persistence.files.subPath }}
|
||||
subPath: {{ .Values.persistence.apps.subPath }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.initContainers }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.backend.initContainers "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: backend
|
||||
image: {{ template "shuffle.backend.image" . }}
|
||||
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
|
||||
{{- if .Values.backend.containerSecurityContext.enabled }}
|
||||
securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.backend.containerSecurityContext "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.diagnosticMode.enabled }}
|
||||
command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }}
|
||||
{{- else if .Values.backend.command }}
|
||||
command: {{- include "common.tplvalues.render" (dict "value" .Values.backend.command "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.diagnosticMode.enabled }}
|
||||
args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }}
|
||||
{{- else if .Values.backend.args }}
|
||||
args: {{- include "common.tplvalues.render" (dict "value" .Values.backend.args "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
env:
|
||||
- name: RUNNING_MODE
|
||||
value: kubernetes
|
||||
- name: IS_KUBERNETES
|
||||
value: "true"
|
||||
- name: SHUFFLE_APP_HOTLOAD_FOLDER
|
||||
value: /shuffle-apps
|
||||
- name: SHUFFLE_FILE_LOCATION
|
||||
value: /shuffle-files
|
||||
{{- if .Values.backend.extraEnvVars }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.backend.extraEnvVars "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "shuffle.backend.name" . }}-env
|
||||
{{- if .Values.backend.extraEnvVarsCM }}
|
||||
- configMapRef:
|
||||
name: {{ include "common.tplvalues.render" (dict "value" .Values.backend.extraEnvVarsCM "context" $) }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.extraEnvVarsSecret }}
|
||||
- secretRef:
|
||||
name: {{ include "common.tplvalues.render" (dict "value" .Values.backend.extraEnvVarsSecret "context" $) }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.resources }}
|
||||
resources: {{- toYaml .Values.backend.resources | nindent 12 }}
|
||||
{{- else if ne .Values.backend.resourcesPreset "none" }}
|
||||
resources: {{- include "common.resources.preset" (dict "type" .Values.backend.resourcesPreset) | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.backend.containerPorts.http }}
|
||||
{{- if .Values.backend.extraContainerPorts }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.backend.extraContainerPorts "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if not .Values.diagnosticMode.enabled }}
|
||||
{{- if .Values.backend.customLivenessProbe }}
|
||||
livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.backend.customLivenessProbe "context" $) | nindent 12 }}
|
||||
{{- else if .Values.backend.livenessProbe.enabled }}
|
||||
livenessProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.backend.livenessProbe "enabled") "context" $) | nindent 12 }}
|
||||
httpGet:
|
||||
path: /api/v1/health
|
||||
port: {{ .Values.backend.containerPorts.http }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.customReadinessProbe }}
|
||||
readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.backend.customReadinessProbe "context" $) | nindent 12 }}
|
||||
{{- else if .Values.backend.readinessProbe.enabled }}
|
||||
readinessProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.backend.readinessProbe "enabled") "context" $) | nindent 12 }}
|
||||
httpGet:
|
||||
path: /api/v1/health
|
||||
port: {{ .Values.backend.containerPorts.http }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.customStartupProbe }}
|
||||
startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.backend.customStartupProbe "context" $) | nindent 12 }}
|
||||
{{- else if .Values.backend.startupProbe.enabled }}
|
||||
startupProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.backend.startupProbe "enabled") "context" $) | nindent 12 }}
|
||||
httpGet:
|
||||
path: /api/v1/health
|
||||
port: {{ .Values.backend.containerPorts.http }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.lifecycleHooks }}
|
||||
lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.backend.lifecycleHooks "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: shuffle-app-builder
|
||||
mountPath: /app/generated
|
||||
- name: shuffle-apps
|
||||
mountPath: /shuffle-apps
|
||||
{{- if .Values.persistence.apps.subPath }}
|
||||
subPath: {{ .Values.persistence.apps.subPath }}
|
||||
{{- end }}
|
||||
- name: shuffle-files
|
||||
mountPath: /shuffle-files
|
||||
{{- if .Values.persistence.files.subPath }}
|
||||
subPath: {{ .Values.persistence.apps.subPath }}
|
||||
{{- end }}
|
||||
- name: empty-dir
|
||||
mountPath: /tmp
|
||||
subPath: tmp-dir
|
||||
{{- if .Values.backend.extraVolumeMounts }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.backend.extraVolumeMounts "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.sidecars }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.backend.sidecars "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: empty-dir
|
||||
emptyDir: {}
|
||||
- name: shuffle-app-builder
|
||||
persistentVolumeClaim:
|
||||
claimName: backend-apps-claim
|
||||
- name: shuffle-apps
|
||||
{{- if .Values.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (printf "%s-apps" (include "shuffle.backend.name" .)) .Values.persistence.apps.existingClaim }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: shuffle-files
|
||||
{{- if .Values.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (printf "%s-files" (include "shuffle.backend.name" .)) .Values.persistence.apps.existingClaim }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.extraVolumes }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.backend.extraVolumes "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,30 @@
|
||||
{{- if .Values.persistence.enabled }}
|
||||
{{- if (not .Values.persistence.files.existingClaim) }}
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ printf "%s-files" (include "shuffle.backend.name" .) }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
annotations:
|
||||
{{- if eq .Values.persistence.resourcePolicy "keep" }}
|
||||
helm.sh/resource-policy: keep
|
||||
{{- end }}
|
||||
{{- if or .Values.persistence.files.annotations .Values.commonAnnotations }}
|
||||
{{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.persistence.files.annotations .Values.commonAnnotations ) "context" . ) }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
accessModes:
|
||||
{{- range .Values.persistence.files.accessModes }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.files.size }}
|
||||
{{- if .Values.persistence.files.selector }}
|
||||
selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.files.selector "context" $) | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- include "common.storage.class" ( dict "persistence" .Values.persistence.files "global" .Values.global ) | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,43 @@
|
||||
{{- if .Values.backend.autoscaling.hpa.enabled }}
|
||||
apiVersion: {{ include "common.capabilities.hpa.apiVersion" . }}
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "shuffle.backend.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }}
|
||||
kind: Deployment
|
||||
name: {{ include "shuffle.backend.name" . }}
|
||||
minReplicas: {{ .Values.backend.autoscaling.hpa.minReplicas }}
|
||||
maxReplicas: {{ .Values.backend.autoscaling.hpa.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.backend.autoscaling.hpa.targetMemory }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
{{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .) }}
|
||||
targetAverageUtilization: {{ .Values.backend.autoscaling.hpa.targetMemory }}
|
||||
{{- else }}
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.worker.autoscaling.hpa.targetMemory }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.autoscaling.hpa.targetCPU }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
{{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .) }}
|
||||
targetAverageUtilization: {{ .Values.backend.autoscaling.hpa.targetCPU }}
|
||||
{{- else }}
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.worker.autoscaling.hpa.targetCPU }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,66 @@
|
||||
{{- if .Values.backend.networkPolicy.enabled }}
|
||||
kind: NetworkPolicy
|
||||
apiVersion: {{ include "common.capabilities.networkPolicy.apiVersion" . }}
|
||||
metadata:
|
||||
name: {{ template "shuffle.backend.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.backend.podLabels .Values.commonLabels ) "context" . ) }}
|
||||
podSelector:
|
||||
matchLabels: {{- include "shuffle.backend.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 6 }}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
egress:
|
||||
{{- if .Values.backend.networkPolicy.allowExternalEgress }}
|
||||
- {}
|
||||
{{- else }}
|
||||
# Allow DNS resolution with an in-cluster DNS server
|
||||
- ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
{{- if .Values.backend.networkPolicy.extraEgress }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.backend.networkPolicy.extraEgress "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
ingress:
|
||||
- ports:
|
||||
- port: {{ .Values.backend.containerPorts.http }}
|
||||
protocol: TCP
|
||||
{{- if not .Values.backend.networkPolicy.allowExternal }}
|
||||
from:
|
||||
# Allow traffic from orborus
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ .Release.Namespace }}
|
||||
podSelector:
|
||||
matchLabels: {{ include "shuffle.orborus.matchLabels" . | nindent 14 }}
|
||||
|
||||
# Allow traffic from workers
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ .Release.Namespace }}
|
||||
podSelector:
|
||||
matchLabels: {{ include "shuffle.worker.matchLabels" . | nindent 14 }}
|
||||
|
||||
# Allow traffic from apps
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ .Release.Namespace }}
|
||||
podSelector:
|
||||
matchLabels: {{ include "shuffle.app.matchLabels" . | nindent 14 }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.networkPolicy.extraIngress }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.backend.networkPolicy.extraIngress "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- if .Values.backend.pdb.create }}
|
||||
apiVersion: {{ include "common.capabilities.policy.apiVersion" . }}
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "shuffle.backend.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.backend.pdb.minAvailable }}
|
||||
minAvailable: {{ .Values.backend.pdb.minAvailable }}
|
||||
{{- end }}
|
||||
{{- if or .Values.backend.pdb.maxUnavailable ( not .Values.backend.pdb.minAvailable ) }}
|
||||
maxUnavailable: {{ .Values.backend.pdb.maxUnavailable | default 1 }}
|
||||
{{- end }}
|
||||
{{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.backend.podLabels .Values.commonLabels ) "context" . ) }}
|
||||
selector:
|
||||
matchLabels: {{- include "shuffle.backend.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 6 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,18 @@
|
||||
{{ if .Values.backend.rbac.create }}
|
||||
kind: RoleBinding
|
||||
apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }}
|
||||
metadata:
|
||||
name: {{ include "shuffle.backend.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "shuffle.backend.serviceAccount.name" . }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ include "shuffle.backend.name" . }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,18 @@
|
||||
{{ if .Values.backend.rbac.create }}
|
||||
kind: Role
|
||||
apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }}
|
||||
metadata:
|
||||
name: {{ include "shuffle.backend.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["list"]
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["get", "create", "delete"]
|
||||
{{- end }}
|
||||
@@ -0,0 +1,14 @@
|
||||
{{- if .Values.backend.serviceAccount.create }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "shuffle.backend.serviceAccount.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if or .Values.backend.serviceAccount.annotations .Values.commonAnnotations }}
|
||||
{{- $annotations := include "common.tplvalues.merge" (dict "values" (list .Values.backend.serviceAccount.annotations .Values.commonAnnotations) "context" .) }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
automountServiceAccountToken: {{ .Values.backend.serviceAccount.automountServiceAccountToken }}
|
||||
{{- include "shuffle.backend.serviceAccount.imagePullSecrets" . | nindent 0 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ template "shuffle.backend.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" (dict "customLabels" .Values.commonLabels "context" $) | nindent 4 }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.backend.containerPorts.http }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
{{- $podLabels := include "common.tplvalues.merge" (dict "values" (list .Values.backend.podLabels .Values.commonLabels) "context" .) }}
|
||||
selector: {{- include "shuffle.backend.matchLabels" (dict "customLabels" $podLabels "context" $) | nindent 4 }}
|
||||
@@ -0,0 +1,38 @@
|
||||
{{- if and (.Capabilities.APIVersions.Has "autoscaling.k8s.io/v1/VerticalPodAutoscaler") .Values.backend.autoscaling.vpa.enabled }}
|
||||
apiVersion: autoscaling.k8s.io/v1
|
||||
kind: VerticalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "shuffle.backend.name" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "shuffle.backend.labels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- if or .Values.backend.autoscaling.vpa.annotations .Values.commonAnnotations }}
|
||||
{{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.backend.autoscaling.vpa.annotations .Values.commonAnnotations ) "context" . ) }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
resourcePolicy:
|
||||
containerPolicies:
|
||||
- containerName: backend
|
||||
{{- with .Values.backend.autoscaling.vpa.controlledResources }}
|
||||
controlledResources:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.backend.autoscaling.vpa.maxAllowed }}
|
||||
maxAllowed:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.backend.autoscaling.vpa.minAllowed }}
|
||||
minAllowed:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
targetRef:
|
||||
apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }}
|
||||
kind: Deployment
|
||||
name: {{ include "backend.names.name" . }}
|
||||
{{- if .Values.backend.autoscaling.vpa.updatePolicy }}
|
||||
updatePolicy:
|
||||
{{- with .Values.backend.autoscaling.vpa.updatePolicy.updateMode }}
|
||||
updateMode: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||