Merge branch 'nightly' of github.com:Shuffle/Shuffle into nightly

This commit is contained in:
Aditya
2025-07-02 20:21:56 +05:30
194 changed files with 28711 additions and 11890 deletions
-1
View File
@@ -1,5 +1,4 @@
# Default execution environment for workers # Default execution environment for workers
ORG_ID=Shuffle
ENVIRONMENT_NAME=Shuffle ENVIRONMENT_NAME=Shuffle
# Sanitize liquid.py input # Sanitize liquid.py input
+11
View File
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
@@ -1,11 +1,15 @@
name: Nightly Release name: nightly-dockerbuild
on:
release:
types: [published]
branches:
- main
- nightly
on:
workflow_dispatch:
push:
branches:
- nightly
paths:
- "**"
- "!.github/**"
- "!**.md"
- "!docker-compose.yml"
jobs: jobs:
main: main:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -16,29 +20,24 @@ jobs:
include: include:
- app: frontend - app: frontend
path: frontend path: frontend
version: nightly
experimental: true experimental: true
- app: backend - app: backend
path: backend path: backend
version: nightly
experimental: true experimental: true
- app: orborus - app: orborus
path: functions/onprem/orborus path: functions/onprem/orborus
version: nightly
experimental: true experimental: true
- app: worker - app: worker
path: functions/onprem/worker path: functions/onprem/worker
version: nightly
experimental: true experimental: true
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set version
id: set_version
run: |
if [[ ${{ github.event_name }} == 'release' ]]; then
echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
else
echo "VERSION=nightly-untagged-latest" >> $GITHUB_OUTPUT
fi
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -74,9 +73,9 @@ jobs:
cache-from: type=local,src=/tmp/.buildx-cache cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache
tags: | tags: |
ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ steps.set_version.outputs.VERSION }} ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ matrix.version }}
${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ steps.set_version.outputs.VERSION }} ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ matrix.version }}
frikky/shuffle-${{ matrix.app }}:${{ steps.set_version.outputs.VERSION }} frikky/shuffle-${{ matrix.app }}:${{ matrix.version }}
frikky/shuffle:${{ matrix.app }} frikky/shuffle:${{ matrix.app }}
- name: Image digest - name: Image digest
+6 -5
View File
@@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: branches:
- nightly - main
paths: paths:
- "**" - "**"
- "!.github/**" - "!.github/**"
@@ -20,19 +20,19 @@ jobs:
include: include:
- app: frontend - app: frontend
path: frontend path: frontend
version: nightly version: 2.0.2
experimental: true experimental: true
- app: backend - app: backend
path: backend path: backend
version: nightly version: 2.0.2
experimental: true experimental: true
- app: orborus - app: orborus
path: functions/onprem/orborus path: functions/onprem/orborus
version: nightly version: 2.0.2
experimental: true experimental: true
- app: worker - app: worker
path: functions/onprem/worker path: functions/onprem/worker
version: nightly version: 2.0.2
experimental: true experimental: true
steps: steps:
- name: Checkout - name: Checkout
@@ -74,6 +74,7 @@ jobs:
cache-to: type=local,dest=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache
tags: | tags: |
ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ matrix.version }} ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ matrix.version }}
ghcr.io/shuffle/shuffle-${{ matrix.app }}:latest
${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ matrix.version }} ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ matrix.version }}
frikky/shuffle-${{ matrix.app }}:${{ matrix.version }} frikky/shuffle-${{ matrix.app }}:${{ matrix.version }}
frikky/shuffle:${{ matrix.app }} frikky/shuffle:${{ matrix.app }}
+12 -7
View File
@@ -33,16 +33,21 @@ jobs:
sudo apt-get install helm -y --no-install-recommends sudo apt-get install helm -y --no-install-recommends
- name: Set versions - name: Set versions
id: set_versions
run: | run: |
if [[ ${{ github.event_name }} == 'release' ]]; then if [[ ${{ github.event_name }} == 'release' ]]; then
CHART_VERSION="${{ github.event.release.tag_name }}" TAG_NAME="${{ github.event.release.tag_name }}"
APP_VERSION="${{ github.event.release.tag_name }}"
# Remove the v prefix
VERSION=${TAG_NAME#v}
APP_VERSION="${VERSION}"
CHART_VERSION="${VERSION}"
else else
CHART_VERSION="0.0.0-nightly-untagged-latest"
APP_VERSION="nightly" APP_VERSION="nightly"
CHART_VERSION="0.0.0-nightly-untagged-latest"
fi fi
echo "APP_VERSION set to ${APP_VERSION}"
echo "CHART_VERSION set to ${CHART_VERSION}. Validating..." echo "CHART_VERSION set to ${CHART_VERSION}. Validating..."
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string # https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
@@ -55,8 +60,8 @@ jobs:
exit 1; exit 1;
fi fi
echo "CHART_VERSION=${CHART_VERSION}" >> $GITHUB_OUTPUT echo "CHART_VERSION=${CHART_VERSION}" >> "$GITHUB_ENV"
echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_OUTPUT echo "APP_VERSION=${APP_VERSION}" >> "$GITHUB_ENV"
- name: Update helm dependencies - name: Update helm dependencies
run: helm dependency update ./functions/kubernetes/charts/shuffle run: helm dependency update ./functions/kubernetes/charts/shuffle
@@ -68,4 +73,4 @@ jobs:
run: helm registry login ghcr.io --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} run: helm registry login ghcr.io --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }}
- name: Push helm chart - name: Push helm chart
run: helm push ./functions/kubernetes/charts/shuffle-*.tgz oci://ghcr.io/shuffle/shuffle/charts run: helm push ./functions/kubernetes/charts/shuffle-*.tgz oci://ghcr.io/shuffle/charts
-16
View File
@@ -1,16 +0,0 @@
name: Automation - Add all new issues to roadmap project
on:
issues:
types:
- opened
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.5.0
with:
project-url: https://github.com/orgs/Shuffle/projects/8
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
+53 -18
View File
@@ -14,16 +14,21 @@ jobs:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
architecture: [x64, arm64] architecture: [x64, arm64]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install Docker Compose
run: |
sudo apt-get update
sudo apt-get install -y docker-compose
docker-compose --version
- name: Set up opensearch directory - name: Set up opensearch directory
run: chmod -R 777 shuffle-database run: chmod -R 777 shuffle-database
- name: Build the stack - name: Build the stack
run: docker-compose up -d run: docker compose up -d
- name: Wait for 30 seconds - name: Wait for 30 seconds
run: sleep 30 run: sleep 30
@@ -55,7 +60,6 @@ jobs:
echo "The word 'Shuffle' was not found in the response." echo "The word 'Shuffle' was not found in the response."
exit 1 exit 1
fi fi
- name: Register a user and check the status code - name: Register a user and check the status code
run: | run: |
MAX_RETRIES=30 MAX_RETRIES=30
@@ -96,31 +100,36 @@ jobs:
echo "User registration failed after $MAX_RETRIES attempts." echo "User registration failed after $MAX_RETRIES attempts."
exit 1 exit 1
- name: Run Selenium testing for frontend
run: |
cd $GITHUB_WORKSPACE/frontend
# write some log to see the current directory
chmod +x frontend-testing.sh
./frontend-testing.sh
- name: Get the API key and run a health check - name: Get the API key and run a health check
id: health_check
run: | run: |
RESPONSE=$(curl -s -k -u admin:StrongShufflePassword321! 'https://localhost:9200/users/_search') RESPONSE=$(curl -s -k -u admin:StrongShufflePassword321! 'https://localhost:9200/users/_search')
echo "Raw Response: $RESPONSE"
API_KEY=$(echo "$RESPONSE" | jq -r '.hits.hits[0]._source.apikey') API_KEY=$(echo "$RESPONSE" | jq -r '.hits.hits[0]._source.apikey')
if [ -n "$API_KEY" ]; then if [ -n "$API_KEY" ] && [ "$API_KEY" != "null" ]; then
echo "Admin API key: $API_KEY" echo "Admin API key: $API_KEY"
echo "API_KEY=$API_KEY" >> $GITHUB_ENV
else else
echo "Failed to retrieve the API key for the admin user." echo "Failed to retrieve the API key for the admin user."
exit 1 exit 1
fi fi
echo "Waiting 1 minute before sending the health API request..."
sleep 60
echo "Checking health API..."
HEALTH_RESPONSE=$(curl -s 'http://localhost:3001/api/v1/health?force=true' \ HEALTH_RESPONSE=$(curl -s 'http://localhost:3001/api/v1/health?force=true' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \ -H "Authorization: Bearer $API_KEY")
-H 'Accept-Language: en-US,en;q=0.9' \
-H 'Connection: keep-alive' \ echo "Health API Response: $HEALTH_RESPONSE"
-H 'Sec-Fetch-Dest: document' \
-H 'Sec-Fetch-Mode: navigate' \
-H 'Sec-Fetch-Site: none' \
-H "Authorization: Bearer $API_KEY" \
-H 'Sec-Fetch-User: ?1' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"')
WORKFLOWS_CREATE=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.create') WORKFLOWS_CREATE=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.create')
WORKFLOWS_RUN=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run') WORKFLOWS_RUN=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run')
@@ -128,9 +137,35 @@ jobs:
WORKFLOWS_RUN_STATUS=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run_status') WORKFLOWS_RUN_STATUS=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run_status')
WORKFLOWS_DELETE=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.delete') WORKFLOWS_DELETE=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.delete')
echo "WORKFLOWS_CREATE: $WORKFLOWS_CREATE"
echo "WORKFLOWS_RUN: $WORKFLOWS_RUN"
echo "WORKFLOWS_RUN_FINISHED: $WORKFLOWS_RUN_FINISHED"
echo "WORKFLOWS_RUN_STATUS: $WORKFLOWS_RUN_STATUS"
echo "WORKFLOWS_DELETE: $WORKFLOWS_DELETE"
if [ "$WORKFLOWS_CREATE" = "true" ] && [ "$WORKFLOWS_RUN" = "true" ] && [ "$WORKFLOWS_RUN_FINISHED" = "true" ] && [ "$WORKFLOWS_RUN_STATUS" = "FINISHED" ] && [ "$WORKFLOWS_DELETE" = "true" ]; then if [ "$WORKFLOWS_CREATE" = "true" ] && [ "$WORKFLOWS_RUN" = "true" ] && [ "$WORKFLOWS_RUN_FINISHED" = "true" ] && [ "$WORKFLOWS_RUN_STATUS" = "FINISHED" ] && [ "$WORKFLOWS_DELETE" = "true" ]; then
echo "Health endpoint check was successful." echo "Health endpoint check was successful."
exit 0
else else
echo "Health endpoint check failed. Response did not meet expected criteria." echo "Health check failed."
exit 1 exit 1
fi fi
notify:
needs: build
if: failure()
runs-on: ubuntu-latest
steps:
- name: Send Twilio Alert
run: |
WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
MESSAGE="🚨 Shuffle Workflow Failed!
Repository: ${{ github.repository }}
Branch: ${{ github.ref_name }}
Workflow URL: $WORKFLOW_URL"
curl -s -X POST https://api.twilio.com/2010-04-01/Accounts/${{ secrets.TWILIO_ACCOUNT_SID }}/Messages.json \
--data-urlencode "To=${{ secrets.TWILIO_TO_NUMBER }}" \
--data-urlencode "From=${{ secrets.TWILIO_FROM_NUMBER }}" \
--data-urlencode "Body=$MESSAGE" \
-u "${{ secrets.TWILIO_ACCOUNT_SID }}:${{ secrets.TWILIO_AUTH_TOKEN }}" > /dev/null
@@ -1,50 +0,0 @@
# A sample workflow which checks out the code, builds a container
# image using Docker and scans that image for vulnerabilities using
# Snyk. The results are then uploaded to GitHub Security Code Scanning
#
# For more examples, including how to limit scans to only high-severity
# issues, monitor images for newly disclosed vulnerabilities in Snyk and
# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/
name: Snyk Container
on:
push:
branches:
- launch
pull_request:
# The branches below must be a subset of the branches above
branches:
- master
- launch
schedule:
- cron: '18 4 * * 3'
jobs:
snyk:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build a Docker image
run: docker build -t frontend .
- name: Run Snyk to check Docker image for vulnerabilities
# Snyk can be used to break the build when it detects vulnerabilities.
# In this case we want to upload the issues to GitHub Code Scanning
continue-on-error: true
uses: snyk/actions/docker@master
env:
# In order to use the Snyk Action you will need to have a Snyk API token.
# More details in https://github.com/snyk/actions#getting-your-snyk-token
# or you can signup for free at https://snyk.io/login
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: your/image-to-test
args: --file=Dockerfile
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: snyk.sarif
@@ -1,46 +0,0 @@
# A sample workflow which checks out your Infrastructure as Code Configuration files,
# such as Kubernetes, Helm & Terraform and scans them for any security issues.
# The results are then uploaded to GitHub Security Code Scanning
#
# For more examples, including how to limit scans to only high-severity issues
# and fail PR checks, see https://github.com/snyk/actions/
name: Snyk Infrastructure as Code
on:
push:
branches:
- master
- launch
pull_request:
# The branches below must be a subset of the branches above
branches:
- master
- launch
schedule:
- cron: '41 16 * * 2'
jobs:
snyk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Snyk to check configuration files for security issues
# Snyk can be used to break the build when it detects security issues.
# In this case we want to upload the issues to GitHub Code Scanning
continue-on-error: true
uses: snyk/actions/iac@master
env:
# In order to use the Snyk Action you will need to have a Snyk API token.
# More details in https://github.com/snyk/actions#getting-your-snyk-token
# or you can signup for free at https://snyk.io/login
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
# Add the path to the configuration file that you would like to test.
# For example `deployment.yaml` for a Kubernetes deployment manifest
# or `main.tf` for a Terraform configuration file
file: your-file-to-test.yaml
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: snyk.sarif
+3 -4
View File
@@ -1,4 +1,4 @@
FROM golang:1.22 as builder FROM golang:1.24 as builder
# Add files # Add files
RUN mkdir /app RUN mkdir /app
@@ -14,9 +14,8 @@ ADD ./go-app/go.mod /app
RUN wget -O /app_sdk/app_base.py https://raw.githubusercontent.com/Shuffle/app_sdk/refs/heads/main/shuffle_sdk/shuffle_sdk.py RUN wget -O /app_sdk/app_base.py https://raw.githubusercontent.com/Shuffle/app_sdk/refs/heads/main/shuffle_sdk/shuffle_sdk.py
ADD ./app_gen /app_gen ADD ./app_gen /app_gen
RUN go get -v RUN go mod download
#RUN go mod tidy RUN go mod tidy
#RUN go clean -modcache
# From November 2022, CGO is enabled due to packages # From November 2022, CGO is enabled due to packages
# that we use requiring it. This is a temporary fix # that we use requiring it. This is a temporary fix
@@ -1,3 +1,11 @@
# No extra requirements needed # No extra requirements needed
requests requests==2.32.3
urllib3 urllib3==2.3.0
liquidpy==0.8.2
MarkupSafe==3.0.2
flask[async]==3.1.0
python-dateutil==2.9.0.post0
PyJWT==2.10.1
cryptography==44.0.2
shufflepy==0.1.0
shuffle-sdk==0.0.25
-9
View File
@@ -1,9 +0,0 @@
urllib3==1.26.19
requests==2.31.0
MarkupSafe==2.0.1
liquidpy==0.8.1
flask[async]==2.0.2
waitress==2.1.0
#flask==1.1.2
python-dateutil==2.8.1
+134 -94
View File
@@ -211,7 +211,7 @@ func fixTags(tags []string) []string {
func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error { func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error {
ctx := context.Background() ctx := context.Background()
client, err := client.NewEnvClient() client, err := client.NewEnvClient()
defer client.Close() defer client.Close()
if err != nil { if err != nil {
log.Printf("Unable to create docker client: %s", err) log.Printf("Unable to create docker client: %s", err)
return err return err
@@ -473,73 +473,84 @@ func buildImage(tags []string, dockerfileLocation string) error {
} }
} }
} }
} else {
ctx := context.Background()
client, err := client.NewEnvClient()
defer client.Close()
if err != nil {
log.Printf("Unable to create docker client: %s", err)
return err
}
log.Printf("[INFO] Docker Tags: %s", tags)
dockerfileSplit := strings.Split(dockerfileLocation, "/")
// Create a buffer
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
defer tw.Close()
baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/")
// Builds the entire folder into buf
err = getParsedTar(tw, baseDir, "")
if err != nil {
log.Printf("Tar issue: %s", err)
}
dockerFileTarReader := bytes.NewReader(buf.Bytes())
buildOptions := types.ImageBuildOptions{
Remove: true,
Tags: tags,
BuildArgs: map[string]*string{},
}
//NetworkMode: "host",
httpProxy := os.Getenv("HTTP_PROXY")
if len(httpProxy) > 0 {
buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy
}
httpsProxy := os.Getenv("HTTPS_PROXY")
if len(httpProxy) > 0 {
buildOptions.BuildArgs["https_proxy"] = &httpsProxy
}
// Build the actual image
imageBuildResponse, err := client.ImageBuild(
ctx,
dockerFileTarReader,
buildOptions,
)
if err != nil {
return err
}
// Read the STDOUT from the build process
defer imageBuildResponse.Body.Close()
buildBuf := new(strings.Builder)
_, err = io.Copy(buildBuf, imageBuildResponse.Body)
if err != nil {
return err
} else {
if strings.Contains(buildBuf.String(), "errorDetail") {
log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n"))
return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ",")))
}
}
return nil
} }
ctx := context.Background()
client, err := client.NewEnvClient()
defer client.Close()
if err != nil {
log.Printf("Unable to create docker client: %s", err)
return err
}
log.Printf("[INFO] Docker Tags: %s", tags)
dockerfileSplit := strings.Split(dockerfileLocation, "/")
// Create a buffer
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
defer tw.Close()
baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/")
// Builds the entire folder into buf
err = getParsedTar(tw, baseDir, "")
if err != nil {
log.Printf("[ERROR] Tar issue during app build: %s", err)
}
dockerFileTarReader := bytes.NewReader(buf.Bytes())
buildOptions := types.ImageBuildOptions{
Remove: true,
Tags: tags,
BuildArgs: map[string]*string{},
}
//NetworkMode: "host",
httpProxy := os.Getenv("HTTP_PROXY")
if len(httpProxy) > 0 {
buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy
}
httpsProxy := os.Getenv("HTTPS_PROXY")
if len(httpProxy) > 0 {
buildOptions.BuildArgs["https_proxy"] = &httpsProxy
}
// Print the actual file content from dockerFileTarReader
/*
data, err := ioutil.ReadAll(dockerFileTarReader)
if err != nil {
log.Printf("[ERROR] Failed reading Dockerfile TAR reader: %s", err)
} else {
log.Printf("[DEBUG] Dockerfile TAR reader content: %s", string(data))
}
*/
// Build the actual image
imageBuildResponse, err := client.ImageBuild(
ctx,
dockerFileTarReader,
buildOptions,
)
if err != nil {
return err
}
// Read the STDOUT from the build process
defer imageBuildResponse.Body.Close()
buildBuf := new(strings.Builder)
_, err = io.Copy(buildBuf, imageBuildResponse.Body)
if err != nil {
return err
} else {
if strings.Contains(buildBuf.String(), "errorDetail") {
log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n"))
return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ",")))
}
}
return nil return nil
} }
@@ -604,11 +615,27 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
// return // return
//} //}
body, err := ioutil.ReadAll(request.Body) var err error
if err != nil { body := []byte{}
resp.WriteHeader(401) //log.Printf("IMAGE REQUEST BODY: %#v", request.Body)
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`)) if request.Body == nil || request.Body == http.NoBody {
return // Check for the image query, otherwise we skip everything
imageQuery := request.URL.Query().Get("image")
if len(imageQuery) == 0 {
resp.WriteHeader(400)
resp.Write([]byte(`{"success": false, "reason": "No image query found"}`))
return
}
body = []byte(fmt.Sprintf(`{"name": "%s"}`, imageQuery))
} else {
body, err = ioutil.ReadAll(request.Body)
if err != nil {
resp.WriteHeader(400)
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`))
return
}
} }
// This has to be done in a weird way because Datastore doesn't // This has to be done in a weird way because Datastore doesn't
@@ -630,28 +657,48 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
return return
} }
img := image.Summary{}
img2 := image.Summary{}
tagFound := ""
tagFound2 := ""
// Old way of doing it
//alternativeNameSplit := strings.Split(version.Name, "/")
//alternativeName := version.Name
//if len(alternativeNameSplit) == 3 {
// alternativeName = strings.Join(alternativeNameSplit[1:3], "/")
//}
appname, baseAppname, appnameSplit2, err := shuffle.GetAppNameSplit(version)
if err != nil {
log.Printf("[ERROR] Failed getting appname split: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't get the right docker image name"}`)))
return
}
if len(version.Name) == 0 {
log.Printf("[ERROR] No image name provided for download: %s", version.Name)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "No image name"}`)))
return
}
log.Printf("[INFO] Trying to download image: '%s'. Appname: '%s'. BaseAppname: '%s', Split2: %s", version.Name, appname, baseAppname, appnameSplit2)
alternativeName := appname
ctx := context.Background() ctx := context.Background()
images, err := dockercli.ImageList(ctx, image.ListOptions{ images, err := dockercli.ImageList(ctx, image.ListOptions{
All: true, All: true,
}) })
img := image.Summary{}
tagFound := ""
img2 := image.Summary{}
tagFound2 := ""
alternativeNameSplit := strings.Split(version.Name, "/")
alternativeName := version.Name
if len(alternativeNameSplit) == 3 {
alternativeName = strings.Join(alternativeNameSplit[1:3], "/")
}
log.Printf("[INFO] Trying to download image: %s. Alt: %s", version.Name, alternativeName)
for _, image := range images { for _, image := range images {
for _, tag := range image.RepoTags { for _, tag := range image.RepoTags {
//log.Printf("[DEBUG] Tag: %s", tag) if strings.Contains(tag, "<none>") {
continue
}
if strings.ToLower(tag) == strings.ToLower(version.Name) { if strings.ToLower(tag) == strings.ToLower(version.Name) {
img = image img = image
tagFound = tag tagFound = tag
@@ -834,7 +881,7 @@ func handleRemoteDownloadApp(resp http.ResponseWriter, ctx context.Context, user
type tmpapp struct { type tmpapp struct {
Success bool `json:"success"` Success bool `json:"success"`
OpenAPI string `json:"openapi"` OpenAPI string `json:"openapi"`
App string `json:"app"` App string `json:"app"`
} }
app := tmpapp{} app := tmpapp{}
@@ -984,11 +1031,4 @@ func activateWorkflowAppDocker(resp http.ResponseWriter, request *http.Request)
log.Printf("[INFO] User %s (%s) is activating %s. Public: %t, Shared: %t", user.Username, user.Id, app.Name, app.Public, app.Sharing) log.Printf("[INFO] User %s (%s) is activating %s. Public: %t, Shared: %t", user.Username, user.Id, app.Name, app.Public, app.Sharing)
buildSwaggerApp(resp, []byte(openApiApp.Body), user, true) buildSwaggerApp(resp, []byte(openApiApp.Body), user, true)
//app.Active = true
//app.Generated = true
//app, err := shuffle.SetApp(ctx, app)
//resp.WriteHeader(200)
//resp.Write([]byte(`{"success": true}`))
} }
+101 -76
View File
@@ -1,91 +1,109 @@
module shuffle module shuffle
go 1.22.2 go 1.24.0
toolchain go1.24.3
//replace github.com/frikky/schemaless => ../../../schemaless
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared //replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
require ( require (
cloud.google.com/go/datastore v1.15.0 cloud.google.com/go/datastore v1.20.0
cloud.google.com/go/storage v1.40.0 cloud.google.com/go/storage v1.55.0
github.com/basgys/goxml2json v1.1.0 github.com/basgys/goxml2json v1.1.0
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
github.com/docker/docker v27.5.0+incompatible github.com/docker/docker v28.2.2+incompatible
github.com/frikky/kin-openapi v0.42.0 github.com/frikky/kin-openapi v0.42.0
github.com/fsouza/go-dockerclient v1.11.0 github.com/fsouza/go-dockerclient v1.12.1
github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.6.0 github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.13.0 github.com/go-git/go-git/v5 v5.16.1
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/h2non/filetype v1.1.3 github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.8.0 github.com/shuffle/shuffle-shared v0.8.84
golang.org/x/crypto v0.31.0 golang.org/x/crypto v0.38.0
google.golang.org/api v0.176.1 google.golang.org/api v0.236.0
google.golang.org/grpc v1.68.1 google.golang.org/grpc v1.72.2
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.30.2 k8s.io/api v0.33.1
k8s.io/apimachinery v0.30.2 k8s.io/apimachinery v0.33.1
k8s.io/client-go v0.30.2 k8s.io/client-go v0.33.1
) )
require ( require (
cloud.google.com/go v0.112.1 // indirect cel.dev/expr v0.20.0 // indirect
cloud.google.com/go/auth v0.3.0 // indirect cloud.google.com/go v0.121.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/iam v1.1.7 // indirect cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/scheduler v1.10.6 // indirect cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/scheduler v1.11.7 // indirect
dario.cat/mergo v1.0.0 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/adrg/strutil v0.2.3 // indirect github.com/adrg/strutil v0.3.1 // indirect
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect github.com/algolia/algoliasearch-client-go/v3 v3.31.4 // indirect
github.com/bitly/go-simplejson v0.5.1 // indirect github.com/bitly/go-simplejson v0.5.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
github.com/cloudflare/circl v1.3.7 // indirect github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/containerd/containerd v1.6.26 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units 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/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/frikky/schemaless v0.0.13 // indirect github.com/frikky/schemaless v0.0.16 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-github/v28 v28.1.1 // indirect github.com/google/go-github/v28 v28.1.1 // indirect
github.com/google/go-querystring v1.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.15.9 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -93,51 +111,58 @@ require (
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opensearch-project/opensearch-go 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/opensearch-project/opensearch-go/v2 v2.3.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/sashabaranov/go-openai v1.19.2 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/sashabaranov/go-openai v1.40.1 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect github.com/skeema/knownhosts v1.3.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/src-d/gcfg v1.4.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opencensus.io v0.24.0 // indirect github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.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/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect go.opentelemetry.io/otel/metric v1.36.0 // indirect
golang.org/x/mod v0.17.0 // indirect go.opentelemetry.io/otel/sdk v1.36.0 // indirect
golang.org/x/net v0.33.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect go.opentelemetry.io/otel/trace v1.36.0 // indirect
golang.org/x/sync v0.10.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect
golang.org/x/sys v0.28.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/term v0.27.0 // indirect golang.org/x/net v0.40.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/sync v0.14.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/protobuf v1.35.2 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
) )
+238 -274
View File
@@ -1,3 +1,5 @@
cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -7,58 +9,65 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs= cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.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.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
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.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.15.0 h1:0P9WcsQeTWjuD1H14JIY7XQscIPQ4Laje8ti96IC5vg= cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM=
cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew=
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.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.11.7 h1:zkMEJ0UbEJ3O7NwEUlKLIp6eXYv1L7wHjbxyxznajKM=
cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE= cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 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.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw= cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krnnfGj4pV65YLUm3C0/8bl7V5Nry2Pwvy3ru/wLc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
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 h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 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/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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.9.10 h1:TxXGNmcbQxBKVWvjvTocNb6jrPyeHlk5EiDhhgHgggs= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/Microsoft/hcsshim v0.9.10/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= github.com/algolia/algoliasearch-client-go/v3 v3.31.4 h1:UJhx6AhZCYf0qZygDz2c1x1+1q2q2sfzsRaQM6yswWk=
github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= github.com/algolia/algoliasearch-client-go/v3 v3.31.4/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ=
github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -81,107 +90,115 @@ github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUK
github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw=
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y=
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w=
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 h1:9bAydALqAjBfPHd/eAiJBHnMZUYov8m2PkXVr+YGQeI= github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 h1:9bAydALqAjBfPHd/eAiJBHnMZUYov8m2PkXVr+YGQeI=
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82/go.mod h1:tyA14J0sA3Hph4dt+AfCjPrYR13+vVodshQSM7km9qw= github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82/go.mod h1:tyA14J0sA3Hph4dt+AfCjPrYR13+vVodshQSM7km9qw=
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
github.com/containerd/containerd v1.6.26 h1:VVfrE6ZpyisvB1fzoY8Vkiq4sy+i5oF4uk7zu03RaHs= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/containerd/containerd v1.6.26/go.mod h1:I4TRdsdoo5MlKob5khDJS2EPT1l1oMNaE2MBm6FrwxM= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 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 v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.2.2+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 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 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 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0= github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0=
github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4=
github.com/frikky/schemaless v0.0.13 h1:ARiN9V7wr2VZXAr9JK5wvTbyPgpGrgeiL1VhR5MlgaQ= github.com/frikky/schemaless v0.0.16 h1:4d2ZktB9xGsAusbbKliOI8TuriSrdIMzD/6ToY3wkz8=
github.com/frikky/schemaless v0.0.13/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= github.com/frikky/schemaless v0.0.16/go.mod h1:jT48kTcmr1q3o8i+8qe7g+eCsbwaz2Q9CjOJevQQzQs=
github.com/fsouza/go-dockerclient v1.11.0 h1:4ZAk6W7rPAtPXm7198EFqA5S68rwnNQORxlOA5OurCA= github.com/fsouza/go-dockerclient v1.12.1 h1:FMoLq+Zhv9Oz/rFmu6JWkImfr6CBgZOPcL+bHW4gS0o=
github.com/fsouza/go-dockerclient v1.11.0/go.mod h1:0I3TQCRseuPTzqlY4Y3ajfsg2VAdMQoazrkxJTiJg8s= github.com/fsouza/go-dockerclient v1.12.1/go.mod h1:OqsgJJcpCwqyM3JED7TdfM9QVWS5O7jSYwXxYKmOooY=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= github.com/go-git/go-git/v5 v5.16.1 h1:TuxMBWNL7R05tXsUGi0kh1vi4tq0WfXNLlIrAkXG1k8=
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= github.com/go-git/go-git/v5 v5.16.1/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 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= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -190,74 +207,61 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -266,19 +270,17 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -286,19 +288,20 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= 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/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 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 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-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -310,59 +313,61 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
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 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M=
github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo=
github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ= github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ=
github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8= github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sashabaranov/go-openai v1.19.2 h1:+dkuCADSnwXV02YVJkdphY8XD9AyHLUWwk6V7LB6EL8= github.com/sashabaranov/go-openai v1.40.1 h1:bJ08Iwct5mHBVkuvG6FEcb9MDTfsXdTYPGjYLRdeTEU=
github.com/sashabaranov/go-openai v1.19.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sashabaranov/go-openai v1.40.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA= github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/shuffle/shuffle-shared v0.8.84 h1:ElIMQYjKBVOiadbiGkSzt/lPU5xqaQwRxQvk9wx/xYM=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/shuffle/shuffle-shared v0.8.84/go.mod h1:RdfNxqCPI+zU4jQKy3E/p4Io2injm7LpSKQUCDHNtLk=
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=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -372,63 +377,59 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.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 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 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/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 h1:R/OBkMoGgfy2fLhs2QhkCI1w4HLEQX92GCcJB6SSdNk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
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/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 h1:giGm8w67Ja7amYNfYMdme7xSp2pIxThWopw8+QP51Yk= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0 h1:Ydage/P0fRrSPpZeCVxzjqGcI6iVmG2xb43+IR8cjqM= go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
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=
go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -437,8 +438,6 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -458,8 +457,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -473,27 +470,23 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-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.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -502,11 +495,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -518,7 +510,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -530,14 +521,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -548,12 +539,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -565,9 +556,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -583,14 +572,12 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -599,8 +586,8 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.176.1 h1:DJSXnV6An+NhJ1J+GWtoF2nHEuqB1VNoTfnIbjNvwD4= google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0=
google.golang.org/api v0.176.1/go.mod h1:j2MaSDYcvYV1lkZ1+SMW4IeF90SrEyFA+tluDYWRrFg= google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -621,95 +608,72 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
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-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
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/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw=
k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4=
k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
+189 -101
View File
@@ -11,17 +11,18 @@ import (
"crypto/md5" "crypto/md5"
"strconv" "strconv"
"os"
"io"
"log"
"fmt"
"errors"
"net/url"
"os/exec"
"net/http"
"io/ioutil"
"math/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
@@ -31,11 +32,12 @@ import (
"github.com/frikky/kin-openapi/openapi2conv" "github.com/frikky/kin-openapi/openapi2conv"
"github.com/frikky/kin-openapi/openapi3" "github.com/frikky/kin-openapi/openapi3"
//"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
gitProxy "github.com/go-git/go-git/v5/plumbing/transport" gitProxy "github.com/go-git/go-git/v5/plumbing/transport"
http2 "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage/memory" "github.com/go-git/go-git/v5/storage/memory"
// Random // Random
@@ -46,7 +48,8 @@ import (
// Web // Web
"github.com/gorilla/mux" "github.com/gorilla/mux"
http2 "gopkg.in/src-d/go-git.v4/plumbing/transport/http" //http2 "gopkg.in/src-d/go-git.v5/plumbing/transport/http"
//http2 "github.com/go-git/go-git/plumbing/transport/http"
) )
// This is used to handle onprem vs offprem databases etc // This is used to handle onprem vs offprem databases etc
@@ -59,6 +62,7 @@ var registryName = "registry.hub.docker.com"
var runningEnvironment = "onprem" var runningEnvironment = "onprem"
var syncUrl = "https://shuffler.io" var syncUrl = "https://shuffler.io"
//var syncUrl = "http://localhost:5002"
type retStruct struct { type retStruct struct {
Success bool `json:"success"` Success bool `json:"success"`
@@ -445,7 +449,7 @@ func checkGitProxy(cloneOptions *git.CloneOptions) *git.CloneOptions {
func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) error { func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) error {
// Returns false if there is an issue // Returns false if there is an issue
// Use this for register // Use this for register
err := shuffle.CheckPasswordStrength(password) err := shuffle.CheckPasswordStrength(username, password)
if err != nil { if err != nil {
log.Printf("[WARNING] Bad password strength: %s", err) log.Printf("[WARNING] Bad password strength: %s", err)
return err return err
@@ -458,8 +462,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
} }
ctx := context.Background() ctx := context.Background()
//users, err := FindUser(ctx context.Context, username string) ([]User, error) {
users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(username))) users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(username)))
if err != nil && len(users) == 0 { if err != nil && len(users) == 0 {
log.Printf("[WARNING] Failed getting user %s: %s", username, err) log.Printf("[WARNING] Failed getting user %s: %s", username, err)
@@ -484,7 +486,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
newUser.Active = true newUser.Active = true
newUser.Orgs = []string{org.Id} newUser.Orgs = []string{org.Id}
// FIXME - Remove this later
if role == "admin" { if role == "admin" {
newUser.Role = "admin" newUser.Role = "admin"
newUser.Roles = []string{"admin"} newUser.Roles = []string{"admin"}
@@ -637,7 +638,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
} else { } else {
log.Printf("[DEBUG] Successfully created the default org!") log.Printf("[DEBUG] Successfully created the default org!")
defaultEnv := os.Getenv("ORG_ID") defaultEnv := os.Getenv("ENVIRONMENT_NAME")
if len(defaultEnv) == 0 { if len(defaultEnv) == 0 {
defaultEnv = "Shuffle" defaultEnv = "Shuffle"
log.Printf("[DEBUG] Setting default environment for org to %s", defaultEnv) log.Printf("[DEBUG] Setting default environment for org to %s", defaultEnv)
@@ -1022,6 +1023,11 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
} }
} }
if parsedAdmin == "true" {
userInfo.Role = "admin"
userInfo.ActiveOrg.Role = "admin"
}
chatDisabled := false chatDisabled := false
if os.Getenv("SHUFFLE_CHAT_DISABLED") == "true" { if os.Getenv("SHUFFLE_CHAT_DISABLED") == "true" {
chatDisabled = true chatDisabled = true
@@ -1084,6 +1090,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
Priorities: orgPriorities, Priorities: orgPriorities,
Licensed: licensed, Licensed: licensed,
ActiveApps: activatedAppIds, ActiveApps: activatedAppIds,
Theme: userInfo.Theme,
} }
returnData, err := json.Marshal(returnValue) returnData, err := json.Marshal(returnValue)
@@ -1844,6 +1851,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
// return // return
//} //}
log.Printf("[DEBUG] HOOKS: webhook callback: %s", request.URL.String())
if request.Method != "POST" { if request.Method != "POST" {
request.Method = "POST" request.Method = "POST"
} }
@@ -1855,6 +1864,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
path := strings.Split(request.URL.String(), "/") path := strings.Split(request.URL.String(), "/")
if len(path) < 4 { if len(path) < 4 {
log.Printf("[DEBUG] HOOKS: Invalid webhook path: %s", request.URL.String())
resp.WriteHeader(403) resp.WriteHeader(403)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
@@ -1870,7 +1880,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
if location[1] == "api" { if location[1] == "api" {
if len(location) <= 4 { if len(location) <= 4 {
log.Printf("[INFO] Couldn't handle location. Too short in webhook: %d", len(location)) log.Printf("[INFO] Couldn't handle location. Too short in webhook: %d", len(location))
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
} }
@@ -1887,6 +1897,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
} }
} }
log.Printf("[DEBUG] HOOKS: Pre user agent check")
// Find user agent header // Find user agent header
userAgent := request.Header.Get("User-Agent") userAgent := request.Header.Get("User-Agent")
if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") { if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") {
@@ -1909,8 +1921,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
//log.Printf("HookID: %s", hookId) //log.Printf("HookID: %s", hookId)
hook, err := shuffle.GetHook(ctx, hookId) hook, err := shuffle.GetHook(ctx, hookId)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed getting hook %s (callback): %s", hookId, err) log.Printf("[WARNING] HOOKS: Failed getting hook %s (callback): %s", hookId, err)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
} }
@@ -1922,21 +1934,21 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
//resp.WriteHeader(200) //resp.WriteHeader(200)
//resp.Write([]byte(`{"success": true}`)) //resp.Write([]byte(`{"success": true}`))
if hook.Status == "stopped" { if hook.Status == "stopped" {
log.Printf("[WARNING] Not running %s because hook status is stopped", hook.Id) log.Printf("[WARNING] HOOKS: Not running %s because hook status is stopped", hook.Id)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Is it running?"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Is it running?"}`)))
return return
} }
if len(hook.Workflows) == 0 { if len(hook.Workflows) == 0 {
log.Printf("[DEBUG] Not running because hook isn't connected to any workflows") log.Printf("[DEBUG] HOOKS: Not running because hook isn't connected to any workflows")
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`)))
return return
} }
if hook.Environment == "cloud" { if hook.Environment == "cloud" {
log.Printf("[DEBUG] This should trigger in the cloud. Duplicate action allowed onprem.") log.Printf("[DEBUG] HOOKS: This should trigger in the cloud. Duplicate action allowed onprem.")
} }
// Check auth // Check auth
@@ -1952,7 +1964,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
body, err := ioutil.ReadAll(request.Body) body, err := ioutil.ReadAll(request.Body)
if err != nil { if err != nil {
log.Printf("[DEBUG] Body data error: %s", err) log.Printf("[DEBUG] HOOKS: data read error: %s", err)
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
@@ -1993,7 +2005,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
b, err := json.Marshal(newBody) b, err := json.Marshal(newBody)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed newBody marshaling for webhook: %s", err) log.Printf("[ERROR] HOOKS: Failed newBody marshaling for webhook: %s", err)
resp.WriteHeader(500) resp.WriteHeader(500)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
@@ -2009,7 +2021,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
} }
if len(hook.Start) == 0 { if len(hook.Start) == 0 {
log.Printf("[WARNING] No start node for hook %s - running with workflow default.", hook.Id) log.Printf("[ERROR] HOOKS: No start node for hook %s - running with workflow default.", hook.Id)
//bodyWrapper = string(parsedBody) //bodyWrapper = string(parsedBody)
} }
@@ -2021,7 +2033,6 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
// OrgId: activeOrgs[0].Id, // OrgId: activeOrgs[0].Id,
workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest, hook.OrgId) workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest, hook.OrgId)
if err == nil { if err == nil {
if hook.Version == "v2" { if hook.Version == "v2" {
timeout := 15 timeout := 15
@@ -2056,6 +2067,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
} else { } else {
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId))) resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId)))
} }
return return
} }
@@ -2063,6 +2075,10 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
} }
log.Printf("[ERROR] HOOKS: END OF FUNCTION FOR '%s'. IF this is reached, something went wrong.", hook.Id)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Failed to run workflow. Check logs."}`))
} }
func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
@@ -3079,8 +3095,9 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
return return
} }
// FIXME: Check whether it's in use. if user.Id == app.Owner || (user.Role == "admin" && user.ActiveOrg.Id == app.ReferenceOrg) || shuffle.ArrayContains(app.Contributors, user.Id) {
if user.Id != app.Owner && user.Role != "admin" { 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) log.Printf("[WARNING] Wrong user (%s) for app %s when verifying swagger", user.Username, app.Name)
resp.WriteHeader(403) 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."}`)) resp.Write([]byte(`{"success": false, "reason": "You don't have permissions to edit this app. Contact support@shuffler.io if this persists."}`))
@@ -3154,7 +3171,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) err = shuffle.DumpApi(basePath, api)
if err != nil { if err != nil {
@@ -3279,6 +3307,12 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
Body: string(body), Body: string(body),
} }
if !shuffle.ArrayContains(api.Contributors, user.Id) {
api.Contributors = append(api.Contributors, user.Id)
}
shuffle.SetAppRevision(ctx, api)
log.Printf("[INFO] API LENGTH FOR %s: %d, ID: %s", api.Name, len(parsed.Body), newmd5) 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: Might cause versioning issues if we re-use the same!!
// FIXME: Need a way to track different versions of the same app properly. // FIXME: Need a way to track different versions of the same app properly.
@@ -3354,6 +3388,21 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
resp.WriteHeader(200) resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "id": "%s"}`, api.ID))) resp.Write([]byte(fmt.Sprintf(`{"success": true, "id": "%s"}`, api.ID)))
} }
org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id)
if err != nil {
log.Printf("[ERROR] Failed getting org during image build (%s): %s", user.ActiveOrg.Id, err)
} else {
imagenames := []string{
fmt.Sprintf("%s_%s", api.Name, api.AppVersion),
fmt.Sprintf("%s_%s", api.Name, api.ID),
}
err = shuffle.DistributeAppToEnvironments(ctx, *org, imagenames)
if err != nil {
log.Printf("[ERROR] Failed distributing app to environments: %s", err)
}
}
} }
// Creates an app from the app builder // Creates an app from the app builder
@@ -3507,7 +3556,12 @@ func handleCloudJob(job shuffle.CloudSyncJob) error {
return err return err
} }
redirectDomain := "localhost:5001" backendPort := os.Getenv("BACKEND_PORT")
if backendPort == "" {
backendPort = "5001"
}
redirectDomain := fmt.Sprintf("localhost:%s", backendPort)
redirectUrl := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain) redirectUrl := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain)
outlookClient, _, err := shuffle.GetOutlookClient(ctx, "", hook.OauthToken, redirectUrl) outlookClient, _, err := shuffle.GetOutlookClient(ctx, "", hook.OauthToken, redirectUrl)
if err != nil { if err != nil {
@@ -3752,30 +3806,57 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error {
} }
} }
if org.SyncConfig.WorkflowBackup { // Check if it's 1/20 times (600 seconds - 10 min on average)
workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "") // Only problem: May take time to sync the first time, which is annoying
if err != nil { // This is to ensure that we don't spam the shuffle cloud servers with a lot of data
log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err) shouldBackupData := false
} else { randomNumber := rand.Intn(20)
backupJob.Workflows = workflows if randomNumber == 0 {
} shouldBackupData = true
} }
if org.SyncConfig.AppBackup && len(org.Users) > 0 { // Just to prevent it from spamming large outbound requests
if shouldBackupData {
apps, err := shuffle.GetPrioritizedApps(ctx, foundUser) if org.SyncConfig.WorkflowBackup {
if err != nil { workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "")
log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err) if err != nil {
} else { log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err)
backupJob.Apps = apps } else {
backupJob.Workflows = workflows
}
} }
}
info, err := shuffle.GetOrgStatistics(ctx, org.Id) if org.SyncConfig.AppBackup && len(org.Users) > 0 {
if err != nil { foundUser.ActiveOrg.Id = org.Id
log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err) apps, err := shuffle.GetPrioritizedApps(ctx, foundUser)
} else { if err != nil {
backupJob.Stats = *info log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err)
} else {
parsedApps := []shuffle.WorkflowApp{}
for _, app := range apps {
if len(app.Actions) == 0 {
continue
}
if !app.Generated {
continue
}
parsedApps = append(parsedApps, app)
}
backupJob.Apps = parsedApps
}
}
// Send stats once every 10 times or so..?
// For now, just send every time
info, err := shuffle.GetOrgStatistics(ctx, org.Id)
if err != nil {
log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err)
} else {
backupJob.Stats = *info
}
} }
backupJobData, err := json.Marshal(backupJob) backupJobData, err := json.Marshal(backupJob)
@@ -3812,6 +3893,7 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error {
//log.Printf("[ERROR] Failed cloud sync job controller run for '%s': %s", respBody, err) //log.Printf("[ERROR] Failed cloud sync job controller run for '%s': %s", respBody, err)
return err return err
} }
return nil return nil
} }
@@ -3843,7 +3925,7 @@ func runInitEs(ctx context.Context) {
log.Printf("[INFO] Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy) log.Printf("[INFO] Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy)
} }
defaultEnv := os.Getenv("ORG_ID") defaultEnv := os.Getenv("ENVIRONMENT_NAME")
if len(defaultEnv) == 0 { if len(defaultEnv) == 0 {
defaultEnv = "Shuffle" defaultEnv = "Shuffle"
log.Printf("[DEBUG] Setting default environment for org to %s", defaultEnv) log.Printf("[DEBUG] Setting default environment for org to %s", defaultEnv)
@@ -3949,6 +4031,8 @@ func runInitEs(ctx context.Context) {
time.Sleep(30 * time.Second) time.Sleep(30 * time.Second)
} }
// FIXME: This should ONLY run on one backend instance
schedules, err := shuffle.GetAllSchedules(ctx, "ALL") schedules, err := shuffle.GetAllSchedules(ctx, "ALL")
if err != nil { if err != nil {
log.Printf("[WARNING] Failed getting schedules during service init: %s", err) log.Printf("[WARNING] Failed getting schedules during service init: %s", err)
@@ -4092,7 +4176,7 @@ func runInitEs(ctx context.Context) {
} }
//interval := int(org.SyncConfig.Interval) //interval := int(org.SyncConfig.Interval)
interval := 15 interval := 30
if interval == 0 { if interval == 0 {
log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id) log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id)
continue continue
@@ -4161,20 +4245,26 @@ func runInitEs(ctx context.Context) {
cleanupJob := func() func() { cleanupJob := func() func() {
return func() { return func() {
log.Printf("[INFO] Running schedule for cleaning up or re-running unfinished workflows in %d environments.", len(environments)) //log.Printf("[INFO] Running schedule for cleaning up or re-running unfinished workflows in %d environments.", len(environments))
backendPort := os.Getenv("BACKEND_PORT")
if backendPort == "" {
backendPort = "5001"
}
for _, environment := range environments { for _, environment := range environments {
// Allowed without PROXY management as it's localhost // Allowed without PROXY management as it's localhost
// client := shuffle.GetExternalClient(syncUrl) // client := shuffle.GetExternalClient(syncUrl)
httpClient := &http.Client{} httpClient := &http.Client{}
url := fmt.Sprintf("http://localhost:5001/api/v1/environments/%s/stop", environment) url := fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/stop", backendPort, environment)
req, err := http.NewRequest( req, err := http.NewRequest(
"GET", "GET",
url, url,
nil, nil,
) )
// FIXME: This will stop working of the user rotates their key lol
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, parsedApikey)) req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, parsedApikey))
if err != nil { if err != nil {
log.Printf("[ERROR] Failed CREATING environment request for %s: %s", environment, err) log.Printf("[ERROR] Failed CREATING environment request for %s: %s", environment, err)
@@ -4188,14 +4278,22 @@ func runInitEs(ctx context.Context) {
continue continue
} }
respBody, err := ioutil.ReadAll(newresp.Body) respBody, err := ioutil.ReadAll(newresp.Body)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed setting respbody %s", err) log.Printf("[ERROR] Failed setting respbody %s for execution stop. Status: %d", err, newresp.StatusCode)
continue continue
} }
log.Printf("[DEBUG] Successfully ran workflow cleanup request for %s. Body: %s", environment, string(respBody))
url = fmt.Sprintf("http://localhost:5001/api/v1/environments/%s/rerun", environment) if newresp.StatusCode != 200 {
if !strings.Contains(string(respBody), "is active") {
log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d. Body: %s", environment, newresp.StatusCode, string(respBody))
}
continue
}
url = fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/rerun", backendPort, environment)
req, err = http.NewRequest( req, err = http.NewRequest(
"GET", "GET",
url, url,
@@ -4215,12 +4313,17 @@ func runInitEs(ctx context.Context) {
continue continue
} }
respBody, err = ioutil.ReadAll(newresp.Body) if newresp.StatusCode != 200 {
if err != nil { log.Printf("[WARNING] Failed rerunning environment %s. Status code: %d", environment, newresp.StatusCode)
log.Printf("[ERROR] Failed setting respbody %s", err)
continue
} }
log.Printf("[DEBUG] Successfully ran workflow RERUN request for %s. Body: %s", environment, string(respBody))
//respBody, err := ioutil.ReadAll(newresp.Body)
//if err != nil {
// log.Printf("[ERROR] Failed setting respbody %s", err)
// continue
//}
//log.Printf("[DEBUG] Ran workflow RERUN request for %s with the response. Body: %s", environment, string(respBody))
} }
} }
} }
@@ -4313,45 +4416,16 @@ func runInitEs(ctx context.Context) {
log.Printf("[DEBUG] Skipping download of default apps as %d were found", len(workflowapps)) log.Printf("[DEBUG] Skipping download of default apps as %d were found", len(workflowapps))
} }
log.Printf("[INFO] Downloading OpenAPI data for search - EXTRA APPS")
apis := "https://github.com/shuffle/security-openapis"
// THis gets memory problems hahah
//apis := "https://github.com/APIs-guru/openapi-directory"
fs := memfs.New()
storer := memory.NewStorage()
cloneOptions := &git.CloneOptions{
URL: apis,
}
cloneOptions = checkGitProxy(cloneOptions)
_, err = git.Clone(storer, fs, cloneOptions)
if err != nil {
log.Printf("[ERROR] Failed loading repo %s into memory: %s", apis, err)
} else if err == nil && len(workflowapps) < 10 {
log.Printf("[INFO] Finished git clone. Looking for updates to the repo.")
dir, err := fs.ReadDir("")
if err != nil {
log.Printf("Failed reading folder: %s", err)
}
iterateOpenApiGithub(fs, dir, "", "")
log.Printf("[INFO] Finished downloading extra API samples")
} else {
log.Printf("[INFO] Skipping download of extra API samples as %d were found", len(workflowapps))
}
if os.Getenv("SHUFFLE_HEALTHCHECK_DISABLED") != "true" { if os.Getenv("SHUFFLE_HEALTHCHECK_DISABLED") != "true" {
healthcheckInterval := 30 healthcheckInterval := 60
log.Printf("[INFO] Starting healthcheck job every %d minute. Stats available on /api/v1/health/stats. Disable with SHUFFLE_HEALTHCHECK_DISABLED=true", healthcheckInterval) log.Printf("[INFO] Starting healthcheck job every %d minute. Stats available on /api/v1/health/stats, and dashboard on /health. Disable with SHUFFLE_HEALTHCHECK_DISABLED=true", healthcheckInterval)
job := func() { job := func() {
// Prepare a fake http.responsewriter // Prepare a fake http.responsewriter
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
request := http.Request{} request := http.Request{}
// Add the "force=true" query to the fake request // Add the "force=true" query to the fake request
request.URL, err = url.Parse("/api/v1/health/stats?force=true") request.URL, err = url.Parse("/api/v1/health?force=true")
if err != nil { if err != nil {
log.Printf("[ERROR] Failed to parse test url for healthstats: %s", err) log.Printf("[ERROR] Failed to parse test url for healthstats: %s", err)
} }
@@ -4422,7 +4496,7 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error)
return &org, errors.New(fmt.Sprintf("Couldn't find any sync key to disable org %s", org.Id)) return &org, errors.New(fmt.Sprintf("Couldn't find any sync key to disable org %s", org.Id))
} }
log.Printf("[INFO] Should run cloud sync disable for org %s with URL %s and sync key %s", org.Id, syncUrl, org.SyncConfig.Apikey) log.Printf("[INFO] Should run cloud sync disable for org %s with URL %s", org.Id, syncUrl)
client := shuffle.GetExternalClient(syncUrl) client := shuffle.GetExternalClient(syncUrl)
req, err := http.NewRequest( req, err := http.NewRequest(
@@ -4635,7 +4709,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
// If you want to disable cloud sync, see previous section. // If you want to disable cloud sync, see previous section.
if org.CloudSync { if org.CloudSync {
log.Printf("[WARNING] Org %s is already syncing. Skip", org.Id) log.Printf("[WARNING] Org %s is already syncing. Skip", org.Id)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Your org is already syncing. Nothing to set up."}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Your org is already syncing. Nothing to set up."}`)))
return return
} }
@@ -4712,6 +4786,9 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
org.SyncConfig = shuffle.SyncConfig{ org.SyncConfig = shuffle.SyncConfig{
Apikey: responseData.SessionKey, Apikey: responseData.SessionKey,
Interval: responseData.IntervalSeconds, Interval: responseData.IntervalSeconds,
WorkflowBackup: true,
AppBackup: true,
} }
interval := int(responseData.IntervalSeconds) interval := int(responseData.IntervalSeconds)
@@ -5092,8 +5169,8 @@ func initHandlers() {
// Changed from workflows/streams to streams, as appengine was messing up // Changed from workflows/streams to streams, as appengine was messing up
// This does not increase the API counter // This does not increase the API counter
// Used by frontend // Used by frontend
r.HandleFunc("/api/v1/streams", handleWorkflowQueue).Methods("POST") r.HandleFunc("/api/v1/streams", handleSetWorkflowExecution).Methods("POST")
r.HandleFunc("/api/v1/streams/results", handleGetStreamResults).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/streams/results", handleGetWorkflowExecutionResult).Methods("POST", "OPTIONS")
// Used by orborus // Used by orborus
r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET", "POST") r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET", "POST")
@@ -5104,7 +5181,7 @@ func initHandlers() {
r.HandleFunc("/api/v1/apps/{key}/execute", executeSingleAction).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps/{key}/execute", executeSingleAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/{key}/run", executeSingleAction).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps/{key}/run", executeSingleAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/categories", shuffle.GetActiveCategories).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/apps/categories", shuffle.GetActiveCategories).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/apps/categories/run", shuffle.RunCategoryAction).Methods("POST", "OPTIONS") //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/upload", handleAppZipUpload).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/{appId}/activate", activateWorkflowAppDocker).Methods("GET", "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}/deactivate", activateWorkflowAppDocker).Methods("GET", "OPTIONS")
@@ -5185,6 +5262,16 @@ func initHandlers() {
r.HandleFunc("/api/v1/hooks/{key}/delete", shuffle.HandleDeleteHook).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/hooks/{key}/delete", shuffle.HandleDeleteHook).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/hooks/{key}", 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 // OpenAPI configuration
r.HandleFunc("/api/v1/verify_swagger", verifySwagger).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/verify_swagger", verifySwagger).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/verify_openapi", verifySwagger).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/verify_openapi", verifySwagger).Methods("POST", "OPTIONS")
@@ -5327,6 +5414,7 @@ func main() {
if innerPort == "" { if innerPort == "" {
log.Printf("[DEBUG] Running on %s:5001", hostname) log.Printf("[DEBUG] Running on %s:5001", hostname)
log.Fatal(http.ListenAndServe(":5001", nil)) log.Fatal(http.ListenAndServe(":5001", nil))
os.Setenv("BACKEND_PORT", "5001")
} else { } else {
log.Printf("[DEBUG] Running on %s:%s", hostname, innerPort) log.Printf("[DEBUG] Running on %s:%s", hostname, innerPort)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", innerPort), nil)) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", innerPort), nil))
+228 -808
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -1,6 +1,6 @@
services: services:
frontend: frontend:
image: ghcr.io/shuffle/shuffle-frontend:latest image: ghcr.io/shuffle/shuffle-frontend:nightly
container_name: shuffle-frontend container_name: shuffle-frontend
hostname: shuffle-frontend hostname: shuffle-frontend
ports: ports:
@@ -14,7 +14,7 @@ services:
depends_on: depends_on:
- backend - backend
backend: backend:
image: ghcr.io/shuffle/shuffle-backend:latest image: ghcr.io/shuffle/shuffle-backend:nightly
container_name: shuffle-backend container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME} hostname: ${BACKEND_HOSTNAME}
# Here for debugging: # Here for debugging:
@@ -33,7 +33,7 @@ services:
- SHUFFLE_FILE_LOCATION=/shuffle-files - SHUFFLE_FILE_LOCATION=/shuffle-files
restart: unless-stopped restart: unless-stopped
orborus: orborus:
image: ghcr.io/shuffle/shuffle-orborus:latest image: ghcr.io/shuffle/shuffle-orborus:nightly
container_name: shuffle-orborus container_name: shuffle-orborus
hostname: shuffle-orborus hostname: shuffle-orborus
networks: networks:
@@ -53,15 +53,15 @@ services:
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY} - SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
- SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY} - SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY}
- SHUFFLE_STATS_DISABLED=true - SHUFFLE_STATS_DISABLED=true
- SHUFFLE_WORKER_SCALE=run
- SHUFFLE_LOGS_DISABLED=true - SHUFFLE_LOGS_DISABLED=true
- SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:latest - SHUFFLE_SWARM_CONFIG=run
- SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:nightly
env_file: .env env_file: .env
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
- seccomp:unconfined - seccomp:unconfined
opensearch: opensearch:
image: opensearchproject/opensearch:2.14.0 image: opensearchproject/opensearch:3.0.0
hostname: shuffle-opensearch hostname: shuffle-opensearch
container_name: shuffle-opensearch container_name: shuffle-opensearch
environment: environment:
+74
View File
@@ -0,0 +1,74 @@
SERVER_URL="http://localhost:3000"
declare -a Routes=($(grep -oP '(?<=path=")[^"]*' src/App.jsx | grep -E '^[a-zA-Z0-9/_:-]+$' | grep -vE '^/$'))
for i in "${!Routes[@]}"; do
Routes[$i]="${Routes[$i]#/}"
done
# # Add all tab routes and unique routes here
Routes+=(
'workflows?tab=org_workflows'
'workflows?tab=my_workflows'
'workflows?tab=all_workflows'
'apps/gmail'
'apis/gmail'
'apps?tab=my_apps'
'apps?tab=all_apps'
'search?tab=org_apps'
'search?tab=my_apps'
'search?tab=workflows'
'search?tab=docs'
'search?tab=creators'
'search?tab=discord'
"admin?tab=organization"
"admin?tab=users"
"admin?tab=app_auth"
"admin?tab=datastore"
"admin?tab=files"
"admin?tab=triggers"
"admin?tab=locations"
"admin?tab=tenants"
"admin?admin_tab=org_config"
"admin?admin_tab=sso"
"admin?admin_tab=notifications"
"admin?admin_tab=billingstats"
"admin?admin_tab=branding(beta)"
)
# Stop frontend container so it test unpushed changes
docker stop shuffle-frontend
# # Install all frontend dependencies
yarn add selenium-webdriver --dev && yarn install
echo "Starting frontend..."
BROWSER=none yarn start &
SERVER_PID=$!
echo "Frontend started with PID: $SERVER_PID"
echo "Waiting for 1 minute to ensure the server is fully up..."
sleep 60
echo "Server is up! Starting Selenium tests..."
echo "Starting frontend tests..."
node selenium-test.js "$SERVER_URL" "${Routes[@]}"
TEST_EXIT_CODE=$?
if [[ $TEST_EXIT_CODE -ne 0 ]]; then
echo "Selenium tests failed. Exiting..."
kill $SERVER_PID
exit 1
fi
kill $SERVER_PID
echo "Testing complete. See above logs for errors if any."
# Starting shuffle-frontend container
echo "Starting shuffle-frontend container..."
docker start shuffle-frontend
echo "shuffle-frontend started successfully."
exit 0
Executable → Regular
+2 -1
View File
@@ -21,7 +21,7 @@
"@uiw/codemirror-themes": "^4.21.9", "@uiw/codemirror-themes": "^4.21.9",
"@uiw/react-codemirror": "^4.21.21", "@uiw/react-codemirror": "^4.21.21",
"algoliasearch": "^4.8.3", "algoliasearch": "^4.8.3",
"class-transformer": "^0.2.0", "class-transformer": "^0.5.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"cpx": "^1.5.0", "cpx": "^1.5.0",
"create-react-app": "^5.0.1", "create-react-app": "^5.0.1",
@@ -128,6 +128,7 @@
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"promise-window": "^1.2.1", "promise-window": "^1.2.1",
"selenium-webdriver": "^4.29.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
} }
} }
@@ -0,0 +1,6 @@
<svg width="45" height="56" viewBox="0 0 45 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.866 31.366L22.2583 37.366L13.3454 52.8036L11.2901 46.9667C10.9625 46.0363 10.0053 45.4837 9.03569 45.6651L2.95314 46.8036L11.866 31.366Z" stroke="#2BC07E" stroke-width="2"/>
<path d="M33.759 31.366L23.3667 37.366L32.2796 52.8036L34.3349 46.9667C34.6625 46.0363 35.6197 45.4837 36.5893 45.6651L42.6719 46.8036L33.759 31.366Z" stroke="#2BC07E" stroke-width="2"/>
<path d="M18.6381 6.27038C20.6383 3.8382 24.3617 3.8382 26.3619 6.27038L26.7961 6.79841C28.1024 8.38685 30.0411 9.32049 32.0974 9.35141L32.781 9.36168C35.9296 9.40902 38.2511 12.3201 37.5966 15.4003L37.4546 16.069C37.0271 18.0807 37.5059 20.1786 38.7639 21.8056L39.182 22.3464C41.1082 24.8376 40.2796 28.4677 37.4634 29.8765L36.852 30.1823C35.0127 31.1024 33.671 32.7847 33.1833 34.7826L33.0212 35.4468C32.2744 38.5059 28.9197 40.1215 26.0624 38.798L25.442 38.5106C23.5759 37.6463 21.4241 37.6463 19.558 38.5106L18.9376 38.798C16.0803 40.1215 12.7256 38.5059 11.9788 35.4468L11.8167 34.7826C11.329 32.7847 9.98734 31.1024 8.14805 30.1823L7.53663 29.8765C4.72036 28.4677 3.89182 24.8376 5.81795 22.3464L6.23611 21.8056C7.49405 20.1786 7.97288 18.0807 7.54544 16.069L7.40335 15.4003C6.74887 12.3201 9.07037 9.40902 12.219 9.36168L12.9026 9.35141C14.9589 9.32049 16.8976 8.38685 18.2039 6.79841L17.4316 6.16323L18.2039 6.79841L18.6381 6.27038Z" fill="#2F2F2F" stroke="#2BC07E" stroke-width="2"/>
<path d="M30.5 18.5L21 28L16 23" stroke="#2BC07E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,5 @@
<svg width="54" height="52" viewBox="0 0 54 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.9999 31.4723C38.007 34.1853 37.3731 36.8617 36.1499 39.2834C34.6996 42.1853 32.47 44.6261 29.7108 46.3324C26.9517 48.0387 23.7719 48.9432 20.5277 48.9444C17.9537 48.9512 15.4126 48.3809 13.0908 47.2778C12.8499 47.1634 12.5744 47.1419 12.3214 47.2262L2.89737 50.3675C2.11561 50.6281 1.37187 49.8844 1.63246 49.1026L4.77381 39.6786C4.85812 39.4256 4.83665 39.1501 4.72223 38.9092C3.6191 36.5874 3.04884 34.0463 3.05555 31.4723C3.05681 28.2281 3.96125 25.0483 5.66758 22.2892C7.37391 19.53 9.81473 17.3004 12.7166 15.8501C15.1383 14.6269 17.8147 13.993 20.5277 14.0001H21.5555C25.84 14.2364 29.8868 16.0448 32.921 19.079C35.9552 22.1132 37.7636 26.16 37.9999 30.4445V31.4723Z" stroke="#4FB1E8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M39 32L38.0926 33.7823C38.6627 34.0726 39.3373 34.0726 39.9074 33.7823L39 32ZM39 32C39.9074 33.7823 39.9079 33.782 39.9085 33.7817L39.91 33.781L39.9136 33.7791L39.9239 33.7739L39.9562 33.7571C39.9829 33.7432 40.0199 33.7237 40.0664 33.6989C40.1593 33.6492 40.2906 33.5778 40.4547 33.4853C40.7826 33.3004 41.2432 33.0306 41.7925 32.6811C42.8869 31.9846 44.3541 30.9586 45.8304 29.6433C48.7043 27.083 52 23.078 52 18V8.2C52 7.37067 51.4882 6.62739 50.7134 6.33156L39.7134 2.13156C39.254 1.95615 38.746 1.95615 38.2866 2.13156L27.2866 6.33156C26.5118 6.62739 26 7.37067 26 8.2V18C26 23.078 29.2957 27.083 32.1696 29.6433C33.6459 30.9586 35.1131 31.9846 36.2075 32.6811C36.7568 33.0306 37.2174 33.3004 37.5453 33.4853C37.7094 33.5778 37.8407 33.6492 37.9336 33.6989C37.9801 33.7237 38.0171 33.7432 38.0438 33.7571L38.0761 33.7739L38.0864 33.7791L38.09 33.781L38.0915 33.7817C38.0921 33.782 38.0926 33.7823 39 32Z" fill="#2F2F2F" stroke="#2F2F2F" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40.1819 30.1501C39.6936 30.4608 39.2864 30.6997 39 30.8615C38.7136 30.6997 38.3064 30.4608 37.8181 30.1501C36.8185 29.5139 35.4896 28.5832 34.1652 27.4033C31.4771 25.0085 29 21.761 29 18V8.8886L39 5.07041L49 8.8886V18C49 21.761 46.5229 25.0085 43.8348 27.4033C42.5104 28.5832 41.1815 29.5139 40.1819 30.1501Z" stroke="#4FB1E8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.2326 41.4787H19.2326L19.2326 41.47C19.2313 40.1671 19.6607 38.9 20.4546 37.865C21.2485 36.8301 22.3625 36.0851 23.6242 35.7458C24.8858 35.4065 26.2244 35.4919 27.4325 35.9887C28.6405 36.4855 29.6503 37.3659 30.3056 38.4931L30.5302 38.8796L30.9393 38.6995C33.4578 37.591 35.237 36.1436 36.3734 34.1074C37.5026 32.084 37.9652 29.5351 37.9652 26.2827V19.274C37.9652 14.5199 36.6216 10.455 34.1879 7.56945C31.7474 4.6759 28.2394 3.00436 24.0012 3.00436C19.7629 3.00436 16.2549 4.67653 13.8145 7.57034C11.3808 10.4561 10.0371 14.521 10.0371 19.274V27.4501C10.0367 27.7819 9.90438 28.1002 9.66889 28.3351C9.43337 28.5701 9.11382 28.7025 8.78025 28.703H6.43988C4.86506 28.7013 3.35543 28.0765 2.242 26.966C1.1287 25.8556 0.502381 24.3503 0.5 22.7803V20.4447C0.501784 18.874 1.12781 17.3678 2.24116 16.2566C3.35447 15.1454 4.86421 14.5199 6.43943 14.5174C6.43964 14.5174 6.43985 14.5174 6.44006 14.5174L7.55861 14.5174H7.96354L8.04769 14.1213C9.77814 5.97693 15.6661 0.5 24.0012 0.5C32.3361 0.5 38.2218 5.97684 39.9523 14.1213L40.0365 14.5174H40.4414L41.5599 14.5174C41.5602 14.5174 41.5604 14.5174 41.5606 14.5174C43.1354 14.5199 44.6448 15.1451 45.7581 16.2558C46.8714 17.3667 47.4977 18.8724 47.5 20.4428V22.7778C47.4983 24.3483 46.8722 25.8543 45.7588 26.9652C44.6453 28.0762 43.1353 28.7013 41.5601 28.703H40.8371H40.3532L40.3374 29.1866C40.2503 31.8522 39.354 34.4287 37.7668 36.5749C36.1796 38.7212 33.9764 40.3361 31.4486 41.2057L31.1171 41.3197L31.1114 41.6704C31.0859 43.2413 30.436 44.7381 29.3042 45.8313C28.1724 46.9246 26.6514 47.5247 25.0758 47.4992C23.5002 47.4738 21.9995 46.8249 20.9038 45.6957C19.8081 44.5666 19.2071 43.0496 19.2326 41.4787ZM21.745 41.4701V41.4705C21.745 42.1473 21.9461 42.8087 22.3228 43.3712C22.6995 43.9337 23.2348 44.3719 23.8609 44.6306C24.4869 44.8894 25.1758 44.9571 25.8403 44.8252C26.5049 44.6933 27.1154 44.3677 27.5948 43.8895C28.0741 43.4112 28.4007 42.8017 28.533 42.138C28.6654 41.4743 28.5974 40.7864 28.3379 40.1613C28.0784 39.5362 27.639 39.0021 27.0754 38.6264C26.5119 38.2507 25.8495 38.0502 25.172 38.0502H25.1716C24.2634 38.051 23.3923 38.4112 22.7498 39.0523C22.1072 39.6934 21.7457 40.563 21.745 41.4701ZM40.4776 25.7009V26.2009H40.9776H41.5607C42.4693 26.2009 43.341 25.841 43.9842 25.1999C44.6273 24.5587 44.9892 23.6887 44.99 22.781V22.7806V20.4444C44.99 19.5368 44.6288 18.6665 43.9861 18.0248C43.3435 17.3831 42.4721 17.0225 41.5634 17.0218H41.563H40.9776H40.4776V17.5218V25.7009ZM3.01237 22.7806L3.01237 22.7815C3.01382 23.6881 3.37566 24.557 4.01816 25.1975L4.37117 24.8434L4.01816 25.1975C4.66062 25.838 5.53122 26.1979 6.43894 26.1986H6.43934H7.02473H7.52473V25.6986V17.5218V17.0218H7.02473H6.43934H6.43894C5.53027 17.0225 4.65884 17.3831 4.01622 18.0248C3.37357 18.6665 3.01237 19.5368 3.01237 20.4444L3.01237 22.7806Z" fill="#FF8544" stroke="#2F2F2F"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg width="65" height="45" viewBox="0 0 65 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.5445 38.9971V36.0579C28.5445 34.4989 27.9252 33.0037 26.8228 31.9013C25.7204 30.7989 24.2253 30.1796 22.6662 30.1796H10.9096C9.35055 30.1796 7.85537 30.7989 6.75297 31.9013C5.65057 33.0037 5.03125 34.4989 5.03125 36.0579V38.9971" stroke="#806BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.7885 25.8205C20.035 25.8205 22.6668 23.1887 22.6668 19.9422C22.6668 16.6957 20.035 14.0638 16.7885 14.0638C13.542 14.0638 10.9102 16.6957 10.9102 19.9422C10.9102 23.1887 13.542 25.8205 16.7885 25.8205Z" stroke="#806BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1 43.9362H32.575" stroke="#806BFF" stroke-width="2" stroke-linecap="round"/>
<path d="M16.5742 8.16418V5C16.5742 2.79086 18.3651 1 20.5742 1H59.5742C61.7834 1 63.5742 2.79086 63.5742 5V29C63.5742 31.2091 61.7834 33 59.5742 33H33.3907" stroke="#806BFF" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="66" height="28" viewBox="0 0 66 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.25391 27.1562C5.13411 27.0521 3.82552 27 2.32812 27H0.785156L9.28125 3.19141C9.65885 2.1237 9.84766 1.50521 9.84766 1.33594C9.84766 1.15365 9.84115 1.04297 9.82812 1.00391L9.84766 0.964844C10.694 1.00391 11.5924 1.02344 12.543 1.02344C13.3242 1.02344 14.2617 1.01042 15.3555 0.984375L15.375 1.02344C15.349 1.07552 15.3359 1.14714 15.3359 1.23828C15.3359 1.48568 15.5312 2.14323 15.9219 3.21094L24.5938 27.0586C24.3333 27.0456 24.0273 27.0391 23.6758 27.0391C23.6758 27.0391 23.3372 27.026 22.6602 27C22.3346 27 22.0417 27 21.7812 27H18.7539L16.918 21.375H8.08984L6.25391 27.1562ZM15.3945 16.6875L12.4453 7.60547L9.57422 16.6875H15.3945ZM27.3867 0.886719C29.1966 0.652344 31.0521 0.535156 32.9531 0.535156C36.4948 0.535156 39.1836 1.36849 41.0195 3.03516C42.8685 4.6888 43.793 7.16927 43.793 10.4766C43.793 13.4323 42.7839 15.7695 40.7656 17.4883C38.8776 19.1029 36.4492 19.9102 33.4805 19.9102C33.1419 19.9102 32.7708 19.8906 32.3672 19.8516V27H27.3867V0.886719ZM33.5391 5.24219C33.1484 5.24219 32.7578 5.25521 32.3672 5.28125V15.5547C32.7839 15.6068 33.2917 15.6328 33.8906 15.6328C34.4896 15.6328 35.1146 15.5091 35.7656 15.2617C36.4167 15.0013 36.9635 14.6432 37.4062 14.1875C38.3307 13.237 38.793 11.987 38.793 10.4375C38.793 6.97396 37.0417 5.24219 33.5391 5.24219ZM48.4023 0.945312C49.7044 0.997396 50.4922 1.02344 50.7656 1.02344H52.1719C52.862 1.02344 53.4154 0.971354 53.832 0.867188L53.8125 27H48.4023V0.945312ZM59.9258 27C60.043 26.2057 60.1016 25.5026 60.1016 24.8906C60.1016 24.2786 60.0951 23.862 60.082 23.6406C60.082 23.4062 60.0755 23.1784 60.0625 22.957C60.0495 22.7357 60.0365 22.5273 60.0234 22.332L59.9648 21.8438H65.6289C65.5898 22.3385 65.5703 22.7422 65.5703 23.0547L65.5312 23.875C65.5312 24.1224 65.5312 24.5911 65.5312 25.2812C65.5312 25.9583 65.5638 26.5312 65.6289 27H59.9258Z" fill="#2BC07E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1,6 @@
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.9263 14.5851V13.0757C8.9263 12.275 9.24436 11.5071 9.81052 10.941C10.3767 10.3748 11.1445 10.0568 11.9452 10.0568H17.983C18.7837 10.0568 19.5516 10.3748 20.1177 10.941C20.6839 11.5071 21.002 12.275 21.002 13.0757V14.5851" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.9635 7.03783C13.2962 7.03783 11.9446 5.68621 11.9446 4.01891C11.9446 2.35161 13.2962 1 14.9635 1C16.6308 1 17.9824 2.35161 17.9824 4.01891C17.9824 5.68621 16.6308 7.03783 14.9635 7.03783Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.99923 12.887V11.6292C0.99923 10.9619 1.26428 10.3221 1.73608 9.85025C2.20788 9.37846 2.84777 9.1134 3.51499 9.1134L7.98047 9.1134" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.03111 6.59763C4.6417 6.59763 3.51535 5.47128 3.51535 4.08186C3.51535 2.69245 4.6417 1.5661 6.03111 1.5661C7.42053 1.5661 8.54688 2.69245 8.54688 4.08186C8.54688 5.47128 7.42053 6.59763 6.03111 6.59763Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,6 @@
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.9263 14.5851V13.0757C8.9263 12.275 9.24436 11.5071 9.81052 10.941C10.3767 10.3748 11.1445 10.0568 11.9452 10.0568H17.983C18.7837 10.0568 19.5516 10.3748 20.1177 10.941C20.6839 11.5071 21.002 12.275 21.002 13.0757V14.5851" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.9635 7.03783C13.2962 7.03783 11.9446 5.68621 11.9446 4.01891C11.9446 2.35161 13.2962 1 14.9635 1C16.6308 1 17.9824 2.35161 17.9824 4.01891C17.9824 5.68621 16.6308 7.03783 14.9635 7.03783Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.99923 12.887V11.6292C0.99923 10.9619 1.26428 10.3221 1.73608 9.85025C2.20788 9.37846 2.84777 9.1134 3.51499 9.1134L7.98047 9.1134" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.03111 6.59763C4.6417 6.59763 3.51535 5.47128 3.51535 4.08186C3.51535 2.69245 4.6417 1.5661 6.03111 1.5661C7.42053 1.5661 8.54688 2.69245 8.54688 4.08186C8.54688 5.47128 7.42053 6.59763 6.03111 6.59763Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,7 @@
<svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.3214 5H11.4351H12.5488" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="0.501953" y="0.5" width="15" height="19" rx="1.5" stroke="#9E9E9E"/>
<path d="M3.63903 12H12.5488" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.63903 15.6666H12.5488" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3214 8.33331H11.4351H12.5488" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 590 B

@@ -0,0 +1,7 @@
<svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.3214 5H11.4351H12.5488" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="0.501953" y="0.5" width="15" height="19" rx="1.5" stroke="#FF8544"/>
<path d="M3.63903 12H12.5488" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.63903 15.6666H12.5488" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3214 8.33331H11.4351H12.5488" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 590 B

@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.502 8.50003L17.502 8.50133C17.5052 9.74245 17.2152 10.9668 16.6557 12.0746L16.6547 12.0765C15.9906 13.4052 14.9698 14.5228 13.7064 15.3041C12.4431 16.0853 10.9872 16.4995 9.50176 16.5L9.50065 16.5C8.25953 16.5033 7.0352 16.2133 5.92738 15.6537C5.80838 15.5936 5.67031 15.5835 5.54384 15.6257L0.792523 17.2095L2.37629 12.4581C2.41845 12.3317 2.40836 12.1936 2.34825 12.0746C1.78869 10.9668 1.49872 9.74245 1.50195 8.50133L1.50195 8.50022C1.50253 7.01482 1.91665 5.55891 2.69792 4.29556C3.4792 3.03222 4.59678 2.01134 5.92548 1.34728L5.92548 1.34728L5.92738 1.34633C7.0352 0.786764 8.25953 0.496791 9.50065 0.500028H9.50195H9.98796C11.9444 0.611298 13.7917 1.43858 15.1775 2.82444C16.5634 4.21031 17.3907 6.05759 17.502 8.01402L17.502 8.50003Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 925 B

@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.502 8.50003L17.502 8.50133C17.5052 9.74245 17.2152 10.9668 16.6557 12.0746L16.6547 12.0765C15.9906 13.4052 14.9698 14.5228 13.7064 15.3041C12.4431 16.0853 10.9872 16.4995 9.50176 16.5L9.50065 16.5C8.25953 16.5033 7.0352 16.2133 5.92738 15.6537C5.80838 15.5936 5.67031 15.5835 5.54384 15.6257L0.792523 17.2095L2.37629 12.4581C2.41845 12.3317 2.40836 12.1936 2.34825 12.0746C1.78869 10.9668 1.49872 9.74245 1.50195 8.50133L1.50195 8.50022C1.50253 7.01482 1.91665 5.55891 2.69792 4.29556C3.4792 3.03222 4.59678 2.01134 5.92548 1.34728L5.92548 1.34728L5.92738 1.34633C7.0352 0.786764 8.25953 0.496791 9.50065 0.500028H9.50195H9.98796C11.9444 0.611298 13.7917 1.43858 15.1775 2.82444C16.5634 4.21031 17.3907 6.05759 17.502 8.01402L17.502 8.50003Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 925 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 62 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="18" height="22" viewBox="0 0 18 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.002 1H3.00195C2.47152 1 1.96281 1.21071 1.58774 1.58579C1.21267 1.96086 1.00195 2.46957 1.00195 3V19C1.00195 19.5304 1.21267 20.0391 1.58774 20.4142C1.96281 20.7893 2.47152 21 3.00195 21H15.002C15.5324 21 16.0411 20.7893 16.4162 20.4142C16.7912 20.0391 17.002 19.5304 17.002 19V8L10.002 1Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.002 1V8H17.002" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 568 B

@@ -0,0 +1,4 @@
<svg width="18" height="22" viewBox="0 0 18 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.002 1H3.00195C2.47152 1 1.96281 1.21071 1.58774 1.58579C1.21267 1.96086 1.00195 2.46957 1.00195 3V19C1.00195 19.5304 1.21267 20.0391 1.58774 20.4142C1.96281 20.7893 2.47152 21 3.00195 21H15.002C15.5324 21 16.0411 20.7893 16.4162 20.4142C16.7912 20.0391 17.002 19.5304 17.002 19V8L10.002 1Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.002 1V8H17.002" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 568 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.002 19C14.9725 19 19.002 14.9706 19.002 10C19.002 5.02944 14.9725 1 10.002 1C5.03139 1 1.00195 5.02944 1.00195 10C1.00195 14.9706 5.03139 19 10.002 19Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.38281 7.3C7.59441 6.6985 8.01205 6.1913 8.56177 5.86822C9.11149 5.54514 9.75782 5.42704 10.3863 5.53484C11.0147 5.64264 11.5847 5.96937 11.9954 6.45718C12.406 6.94498 12.6308 7.56237 12.6298 8.2C12.6298 10 9.92981 10.9 9.92981 10.9" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.002 14.5H10.011" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 742 B

@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.002 19C14.9725 19 19.002 14.9706 19.002 10C19.002 5.02944 14.9725 1 10.002 1C5.03139 1 1.00195 5.02944 1.00195 10C1.00195 14.9706 5.03139 19 10.002 19Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.38281 7.3C7.59441 6.6985 8.01205 6.1913 8.56177 5.86822C9.11149 5.54514 9.75782 5.42704 10.3863 5.53484C11.0147 5.64264 11.5847 5.96937 11.9954 6.45718C12.406 6.94498 12.6308 7.56237 12.6298 8.2C12.6298 10 9.92981 10.9 9.92981 10.9" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.002 14.5H10.011" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 742 B

+11
View File
@@ -0,0 +1,11 @@
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="56" height="56" rx="4" fill="#2F2F2F"/>
<g clip-path="url(#clip0_3970_2549)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M28 12C19.16 12 12 19.16 12 28C12 35.08 16.58 41.06 22.94 43.18C23.74 43.32 24.04 42.84 24.04 42.42C24.04 42.04 24.02 40.78 24.02 39.44C20 40.18 18.96 38.46 18.64 37.56C18.46 37.1 17.68 35.68 17 35.3C16.44 35 15.64 34.26 16.98 34.24C18.24 34.22 19.14 35.4 19.44 35.88C20.88 38.3 23.18 37.62 24.1 37.2C24.24 36.16 24.66 35.46 25.12 35.06C21.56 34.66 17.84 33.28 17.84 27.16C17.84 25.42 18.46 23.98 19.48 22.86C19.32 22.46 18.76 20.82 19.64 18.62C19.64 18.62 20.98 18.2 24.04 20.26C25.32 19.9 26.68 19.72 28.04 19.72C29.4 19.72 30.76 19.9 32.04 20.26C35.1 18.18 36.44 18.62 36.44 18.62C37.32 20.82 36.76 22.46 36.6 22.86C37.62 23.98 38.24 25.4 38.24 27.16C38.24 33.3 34.5 34.66 30.94 35.06C31.52 35.56 32.02 36.52 32.02 38.02C32.02 40.16 32 41.88 32 42.42C32 42.84 32.3 43.34 33.1 43.18C39.42 41.06 44 35.06 44 28C44 19.16 36.84 12 28 12Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_3970_2549">
<rect width="32" height="32" fill="white" transform="translate(12 12)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 188 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0L-1.48522e-08 13.3913L4.40052 13.3913L4.40052 4.46465L22 4.46465L22 2.44001e-08L0 0Z" fill="#FF8444"/>
<path d="M17.5995 8.60864L17.5995 17.5353L-9.90052e-09 17.5353L-1.48522e-08 22L22 22L22 8.60864L17.5995 8.60864Z" fill="#FF8444"/>
<path d="M13.3915 8.60864L8.60889 8.60864L8.60889 13.3913L13.3915 13.3913L13.3915 8.60864Z" fill="#FF8444"/>
</svg>

After

Width:  |  Height:  |  Size: 459 B

@@ -0,0 +1,6 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.22417 1H1.00195V7.22222H7.22417V1Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.0015 1H10.7793V7.22222H17.0015V1Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.0015 10.7778H10.7793V17H17.0015V10.7778Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.22417 10.7778H1.00195V17H7.22417V10.7778Z" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

@@ -0,0 +1,6 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.22417 1H1.00195V7.22222H7.22417V1Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.0015 1H10.7793V7.22222H17.0015V1Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.0015 10.7778H10.7793V17H17.0015V10.7778Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.22417 10.7778H1.00195V17H7.22417V10.7778Z" stroke="#FF8544" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 61 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4467_7657)">
<path d="M6.54065 20.7133C6.54065 18.9072 5.07648 17.443 3.27033 17.443C1.46417 17.443 0 18.9072 0 20.7133C0 22.5195 1.46417 23.9837 3.27033 23.9837C5.07648 23.9837 6.54065 22.5195 6.54065 20.7133Z" fill="#17BCAF"/>
<path d="M23.9838 16.3516C23.9838 16.7226 23.958 17.0851 23.9062 17.441C23.3841 21.0997 20.2648 23.9213 16.4767 23.9817C16.4358 23.9817 16.3948 23.9817 16.3538 23.9817H11.994C10.1884 23.9817 8.72378 22.5169 8.72378 20.7113C8.72378 19.8075 9.09048 18.9921 9.68154 18.3988C10.2748 17.8056 11.0902 17.441 11.994 17.441H16.3171C16.8974 17.441 17.4043 17.0031 17.4432 16.4228C17.4841 15.808 17.0118 15.2924 16.4121 15.2622C16.3948 15.2622 16.3754 15.2622 16.3559 15.2622H7.63439C3.78805 15.2622 0.606175 12.419 0.0798164 8.72161C0.0258865 8.36564 0 8.00109 0 7.63222C0 7.26331 0.0258865 6.89875 0.0776594 6.5428C0.60186 2.88203 3.72118 0.062559 7.50708 0C7.54807 0 7.58907 0 7.63006 0H11.9897C13.7975 0 15.2622 1.46475 15.2622 3.27033C15.2622 4.1742 14.8955 4.98961 14.3044 5.58285C13.7112 6.17608 12.8958 6.54065 11.9919 6.54065H7.66889C7.08858 6.54065 6.58163 6.97856 6.5428 7.55888C6.50182 8.17364 6.97424 8.68919 7.57394 8.71945C7.59123 8.71945 7.61061 8.71945 7.63006 8.71945H16.3516C20.1958 8.71945 23.3755 11.5626 23.904 15.26C23.9558 15.616 23.9817 15.9806 23.9817 16.3494L23.9838 16.3516Z" fill="#18FFCD"/>
<path d="M23.984 3.27033C23.984 1.46417 22.5199 0 20.7137 0C18.9076 0 17.4434 1.46417 17.4434 3.27033C17.4434 5.07648 18.9076 6.54065 20.7137 6.54065C22.5199 6.54065 23.984 5.07648 23.984 3.27033Z" fill="#17BCAF"/>
<path d="M20.6855 19.622C20.6855 21.9884 18.8002 23.917 16.4488 23.9817C16.4078 23.9817 16.3669 23.9817 16.3259 23.9817H11.9661C10.1605 23.9817 8.6958 22.517 8.6958 20.7114C8.6958 19.8075 9.06251 18.9921 9.65357 18.3989C10.2469 17.8057 11.0623 17.441 11.9661 17.441H16.2892C16.8694 17.441 17.3764 17.0031 17.4153 16.4229C17.4562 15.8081 16.9838 15.2925 16.3841 15.2623C18.7657 15.2904 20.6898 17.2318 20.6898 19.622H20.6855Z" fill="url(#paint0_linear_4467_7657)"/>
<path d="M3.27237 4.35971C3.27237 1.99326 5.15778 0.0668734 7.50916 0C7.55015 0 7.59107 0 7.63206 0H11.9918C13.7974 0 15.2621 1.46475 15.2621 3.27032C15.2621 4.1742 14.8954 4.98961 14.3044 5.58285C13.7111 6.17608 12.8957 6.54065 11.9918 6.54065H7.66874C7.08847 6.54065 6.58154 6.97856 6.5427 7.55888C6.50172 8.17364 6.97414 8.68919 7.57385 8.71945C5.19229 8.69135 3.26807 6.7499 3.26807 4.35971H3.27237Z" fill="url(#paint1_linear_4467_7657)"/>
</g>
<defs>
<linearGradient id="paint0_linear_4467_7657" x1="8.72017" y1="15.2623" x2="20.7103" y2="15.2623" gradientUnits="userSpaceOnUse">
<stop stop-color="#18C2B0"/>
<stop offset="1" stop-color="#0B5C53"/>
</linearGradient>
<linearGradient id="paint1_linear_4467_7657" x1="15.2601" y1="0" x2="3.26995" y2="0" gradientUnits="userSpaceOnUse">
<stop stop-color="#18C2B0"/>
<stop offset="1" stop-color="#0B5C53"/>
</linearGradient>
<clipPath id="clip0_4467_7657">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

+253
View File
@@ -0,0 +1,253 @@
const { Builder, By, until } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const fs = require('fs');
const path = require('path');
console.log('Starting Selenium script for testing pages...');
(async () => {
const userDataDir = path.join(__dirname, 'chrome-user-data', `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`);
fs.mkdirSync(userDataDir, { recursive: true });
let options = new chrome.Options();
options.addArguments('--no-sandbox');
options.addArguments('--headless');
let driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
const SuccessfullyLoadedPath = [];
const FailedToLoadPath = [];
try {
await driver.manage().window().setRect({ width: 1600, height: 1200 });
await driver.manage().window().maximize()
await driver.manage().setTimeouts({ implicit: 15000 });
const frontendURL = process.argv[2];
const routes = process.argv.slice(3);
console.log('Frontend URL:', frontendURL);
const isCloud = frontendURL === 'http://localhost:3002' || frontendURL === 'https://sandbox.shuffler.io' || frontendURL === 'https://shuffler.io';
if (isCloud) {
// Login Credentials
const LOGIN_URL = `${frontendURL}/login`;
// put your login credentials here
const USERNAME = '';
const PASSWORD = '';
console.log('Logging in...');
await driver.get(LOGIN_URL);
try {
await driver.wait(until.elementLocated(By.css('#emailfield')), 10000);
await driver.findElement(By.css('#emailfield')).sendKeys(USERNAME);
await driver.wait(until.elementLocated(By.css('#outlined-password-input')), 10000);
await driver.findElement(By.css('#outlined-password-input')).sendKeys(PASSWORD);
await driver.wait(until.elementLocated(By.css('#loginButton')), 10000);
await driver.findElement(By.css('#loginButton')).click();
// Ensure login success by checking URL change
await driver.wait(async () => {
const url = await driver.getCurrentUrl();
return url.includes('welcome');
}, 20000);
console.log('Successfully logged in!');
} catch (error) {
console.error('Login failed:', error.message);
FailedToLoadPath.push(LOGIN_URL);
await driver.quit();
process.exit(1);
}
}else {
// Steps for onprem testing
// 1. Login
// 2. Create new workflow
// Write your own login credentials here if you are testing onprem locally
const USERNAME = 'demo@demo.io';
const PASSWORD = 'supercoolpassword';
const WORKFLOW_URL = `${frontendURL}/workflows`;
try {
// Login
console.log('Logging in...');
await driver.get(`${frontendURL}/login`);
await driver.wait(until.elementLocated(By.css('#emailfield')), 10000);
await driver.findElement(By.css('#emailfield')).sendKeys(USERNAME);
await driver.wait(until.elementLocated(By.css('#outlined-password-input')), 10000);
await driver.findElement(By.css('#outlined-password-input')).sendKeys(PASSWORD);
const loginButton = await driver.findElement(By.css('#loginButton'));
await driver.executeScript("arguments[0].scrollIntoView(true);", loginButton);
await loginButton.click();
// Ensure signup success by checking URL change
await driver.wait(async () => {
const url = await driver.getCurrentUrl();
return url.includes('welcome') || url.includes('workflows');
}, 20000);
const isParentPresent = await driver.wait(async () => {
return await driver.executeScript(
"return document.querySelector('.parent-component') !== null;"
);
}, 5000).catch(() => false);
if (!isParentPresent) {
const logs = await driver.manage().logs().get('browser');
const severeErrors = logs.filter(log => log.level.name === 'SEVERE');
if (severeErrors.length > 0) {
const crashCausingErrors = severeErrors.filter(err => {
return !err.message.includes('Warning:') &&
!err.message.includes('MUI:')
});
console.error(`Page (${frontendURL}/login) did not load correctly:`, crashCausingErrors);
FailedToLoadPath.push(`${frontendURL}/login`);
}
}
console.log('Successfully logged in!');
} catch (error) {
console.error('Failed to login:', error.message);
FailedToLoadPath.push(WORKFLOW_URL);
await driver.quit();
process.exit(1);
}
try {
// Create new workflow
console.log('Creating new workflow...');
await driver.get(WORKFLOW_URL);
await driver.sleep(1000);
await driver.wait(until.elementLocated(By.css('#create_workflow_button')), 10000);
const create_workflow_button = await driver.findElement(By.css('#create_workflow_button'));
await driver.sleep(1000);
await create_workflow_button.click();
await driver.sleep(1500);
await driver.wait(until.elementLocated(By.css('#Enter-Workflow-Name')), 10000);
const enter_workflow_name_field = await driver.findElement(By.css('#Enter-Workflow-Name'));
await driver.sleep(1000);
await enter_workflow_name_field.sendKeys("Test Workflow");
// Wait before saving
await driver.sleep(1500);
await driver.wait(until.elementLocated(By.css('#save_workflow_button')), 10000);
const save_workflow_button = await driver.findElement(By.css('#save_workflow_button'));
await driver.sleep(1000); // Delay before clicking
await save_workflow_button.click();
// Wait for the parent component to appear
await driver.sleep(2000);
const isParentPresent = await driver.wait(async () => {
return await driver.executeScript(
"return document.querySelector('.parent-component') !== null;"
);
}, 5000).catch(() => false);
if (!isParentPresent) {
const logs = await driver.manage().logs().get('browser');
const severeErrors = logs.filter(log => log.level.name === 'SEVERE');
if (severeErrors.length > 0) {
const crashCausingErrors = severeErrors.filter(err => {
return !err.message.includes('Warning:') &&
!err.message.includes('MUI:');
});
console.error(`Page (${WORKFLOW_URL}) did not load correctly:`, crashCausingErrors);
FailedToLoadPath.push(`${WORKFLOW_URL}`);
}
}
} catch (error) {
console.error('Failed to create new workflow:', error.message);
FailedToLoadPath.push(WORKFLOW_URL);
await driver.quit();
process.exit(1);
}
}
// Get routes from command line arguments
if (routes.length === 0) {
console.error('No routes found to test.');
await driver.quit();
process.exit(1);
}
console.log(`Found ${routes.length} routes to test.`);
for (const route of routes) {
const url = `${frontendURL}/${route}`;
console.log(`Testing route: ${url}`);
try {
await driver.get(url);
await driver.sleep(3000);
// Wait for document readiness
await driver.wait(async () => {
return await driver.executeScript('return document.readyState') === 'complete';
}, 10000);
const isParentPresent = await driver.wait(async () => {
return await driver.executeScript(
"return document.querySelector('.parent-component') !== null;"
);
}, 5000).catch(() => false);
if (!isParentPresent) {
const logs = await driver.manage().logs().get('browser');
const severeErrors = logs.filter(log => log.level.name === 'SEVERE');
if (severeErrors.length > 0) {
const crashCausingErrors = severeErrors.filter(err => {
return !err.message.includes('Warning:') &&
!err.message.includes('MUI:')
});
console.error(`Page (${url}) did not load correctly:`, crashCausingErrors);
FailedToLoadPath.push(url);
}
continue;
}
console.log(`Successfully loaded: ${url}`);
SuccessfullyLoadedPath.push(url);
} catch (error) {
console.error(`Failed to load ${url}:`, error.message);
}
}
} finally {
console.log("Total pages tested: ", SuccessfullyLoadedPath.length + FailedToLoadPath.length);
console.log("Successfully loaded pages: ", SuccessfullyLoadedPath.length);
if (FailedToLoadPath.length > 0) {
console.log("Failed to load pages: ", FailedToLoadPath.length);
console.log("Failed to load pages paths: ", FailedToLoadPath);
process.exit(1);
}else {
console.log("No pages failed to load. Congrats!");
}
await driver.quit();
fs.rmSync(userDataDir, { recursive: true, force: true });
}
})();
+270 -39
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useContext } from "react";
import { Link, Route, Routes, BrowserRouter, useNavigate } from "react-router-dom"; import { Link, Route, Routes, BrowserRouter, useNavigate } from "react-router-dom";
import { CookiesProvider } from "react-cookie"; import { CookiesProvider } from "react-cookie";
@@ -12,12 +12,16 @@ import Header from "./components/NewHeader.jsx";
import HealthPage from "./components/HealthPage.jsx"; import HealthPage from "./components/HealthPage.jsx";
//import Header from "./components/Header.jsx"; //import Header from "./components/Header.jsx";
import theme from "./theme.jsx"; import theme, { getTheme } from "./theme.jsx";
import Apps from "./views/Apps.jsx"; import Apps from "./views/Apps.jsx";
import Apps2 from "./views/Apps2.jsx"; import Apps2 from "./views/Apps2.jsx";
import AppCreator from "./views/AppCreator.jsx"; import AppCreator from "./views/AppCreator.jsx";
import DetectionDashBoard from "./views/DetectionDashboard.jsx"; import DetectionDashBoard from "./views/DetectionDashboard.jsx";
// LLM related tests
import ChatBot from "./components/ChatBot.jsx";
import AgentUI from "./views/AgentUI.jsx";
import Welcome from "./views/Welcome.jsx"; import Welcome from "./views/Welcome.jsx";
import Dashboard from "./views/Dashboard.jsx"; import Dashboard from "./views/Dashboard.jsx";
import DashboardView from "./views/DashboardViews.jsx"; import DashboardView from "./views/DashboardViews.jsx";
@@ -25,6 +29,7 @@ import AdminSetup from "./views/AdminSetup.jsx";
import Admin from "./views/Admin.jsx"; import Admin from "./views/Admin.jsx";
import Docs from "./views/Docs.jsx"; import Docs from "./views/Docs.jsx";
import Usecases2 from "./views/Usecases2.jsx"; import Usecases2 from "./views/Usecases2.jsx";
import DashboardViews from "./views/DashboardViews.jsx";
//import Introduction from "./views/Introduction"; //import Introduction from "./views/Introduction";
import SetAuthentication from "./views/SetAuthentication.jsx"; import SetAuthentication from "./views/SetAuthentication.jsx";
import SetAuthenticationSSO from "./views/SetAuthenticationSSO.jsx"; import SetAuthenticationSSO from "./views/SetAuthenticationSSO.jsx";
@@ -33,9 +38,10 @@ import RunWorkflow from "./views/RunWorkflow.jsx";
import Admin2 from "./views/Admin2.jsx"; import Admin2 from "./views/Admin2.jsx";
import LoginPage from "./views/LoginPage.jsx"; import LoginPage from "./views/LoginPage.jsx";
import LoginPageOld from "./views/LoginPageOld.jsx";
import SettingsPage from "./views/SettingsPage.jsx"; import SettingsPage from "./views/SettingsPage.jsx";
import KeepAlive from "./views/KeepAlive.jsx"; import KeepAlive from "./views/KeepAlive.jsx";
import { ThemeProvider } from "@mui/material/styles"; import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from '@mui/material/CssBaseline'; import CssBaseline from '@mui/material/CssBaseline';
@@ -57,7 +63,8 @@ import 'react-toastify/dist/ReactToastify.css';
import Drift from "react-driftjs"; import Drift from "react-driftjs";
import { AppContext } from './context/ContextApi.jsx'; import { Context } from './context/ContextApi.jsx';
import Navbar from "./components/Navbar.jsx";
import Workflows2 from "./views/Workflows2.jsx"; import Workflows2 from "./views/Workflows2.jsx";
import AppExplorer from "./views/AppExplorer.jsx"; import AppExplorer from "./views/AppExplorer.jsx";
@@ -85,7 +92,10 @@ const App = (message, props) => {
const [dataset, setDataset] = useState(false) const [dataset, setDataset] = useState(false)
const [isLoaded, setIsLoaded] = useState(false) const [isLoaded, setIsLoaded] = useState(false)
const [curpath, setCurpath] = useState(typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname) const [curpath, setCurpath] = useState(typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname)
const { themeMode, handleThemeChange, setBrandColor, brandColor,setThemeMode} = useContext(Context);
const currentTheme = getTheme(themeMode, brandColor);
const mainColor = currentTheme?.palette?.backgroundColor
const [isPreviousThemeLight, setIsPreviousThemeLight] = useState(false)
useEffect(() => { useEffect(() => {
if (dataset === false) { if (dataset === false) {
@@ -95,6 +105,29 @@ const App = (message, props) => {
} }
}, []); }, []);
useEffect(() => {
const isDarkPath = curpath === "/" || curpath === "/pricing" || curpath === "/partners" || curpath === "/faq" || curpath === "/professional-services" || curpath === "/contact" || curpath === "/training";
if (curpath && isDarkPath) {
if (themeMode === "light") {
setIsPreviousThemeLight(true)
}
handleThemeChange("dark")
}else if ( !isDarkPath && isPreviousThemeLight && userdata?.active_org?.branding?.theme === "light") {
handleThemeChange("light")
setIsPreviousThemeLight(false)
}
if (isDarkPath && userdata && userdata?.active_org?.branding?.brand_color !== "#ff8544") {
setBrandColor("#ff8544")
}else if(!isDarkPath && userdata && userdata?.active_org?.branding?.brand_color !== "#ff8544" && userdata?.active_org?.branding?.brand_color?.length > 0) {
const brandColor = localStorage.getItem("brandColor")
if (brandColor !== null && brandColor !== undefined && brandColor.length > 0) {
setBrandColor(brandColor)
}
}
}, [themeMode, curpath, userdata])
if ( if (
isLoaded && isLoaded &&
!isLoggedIn && !isLoggedIn &&
@@ -159,7 +192,16 @@ const App = (message, props) => {
{ path: "/" } { path: "/" }
); );
} }
} if (responseJson?.theme?.length > 0) {
handleThemeChange(responseJson.theme)
}else{
handleThemeChange("dark")
}
}else {
handleThemeChange("dark")
setThemeMode("dark")
localStorage.removeItem("theme");
}
// Handling Ethereum update // Handling Ethereum update
@@ -182,10 +224,11 @@ const App = (message, props) => {
const includedData = const includedData =
<div <div
style={{ style={{
backgroundColor: theme.palette.backgroundColor, backgroundColor: mainColor,
color: "rgba(255, 255, 255, 0.65)", color: "rgba(255, 255, 255, 0.65)",
minHeight: "100vh", minHeight: "100vh",
}} }}
className='parent-component'
> >
<ScrollToTop <ScrollToTop
getUserNotifications={getUserNotifications} getUserNotifications={getUserNotifications}
@@ -211,7 +254,20 @@ const App = (message, props) => {
{ window?.location?.pathname === "/" || window?.location?.pathname === "/training" || !(isLoggedIn && isLoaded) ? ( { window?.location?.pathname === "/" || window?.location?.pathname === "/training" || !(isLoggedIn && isLoaded) ? (
<div style={{ minHeight: 68, maxHeight: 68 }}> <div style={{ minHeight: 68, maxHeight: 68 }}>
<Header {/* <Header
notifications={notifications}
setNotifications={setNotifications}
userdata={userdata}
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
globalUrl={globalUrl}
setIsLoggedIn={setIsLoggedIn}
isLoggedIn={isLoggedIn}
curpath={curpath}
{...props}
/> */}
<Navbar
notifications={notifications} notifications={notifications}
setNotifications={setNotifications} setNotifications={setNotifications}
userdata={userdata} userdata={userdata}
@@ -235,6 +291,7 @@ const App = (message, props) => {
<div style={{ height: 60 }} /> <div style={{ height: 60 }} />
*/} */}
<Routes> <Routes>
<Route <Route
exact exact
path="/login" path="/login"
@@ -242,7 +299,8 @@ const App = (message, props) => {
<LoginPage <LoginPage
isLoggedIn={isLoggedIn} isLoggedIn={isLoggedIn}
setIsLoggedIn={setIsLoggedIn} setIsLoggedIn={setIsLoggedIn}
register={true} register={false}
inregister={false}
isLoaded={isLoaded} isLoaded={isLoaded}
globalUrl={globalUrl} globalUrl={globalUrl}
setCookie={setCookie} setCookie={setCookie}
@@ -252,6 +310,63 @@ const App = (message, props) => {
/> />
} }
/> />
<Route
exact
path="/login2"
element={
<LoginPageOld
isLoggedIn={isLoggedIn}
setIsLoggedIn={setIsLoggedIn}
register={false}
inregister={false}
isLoaded={isLoaded}
globalUrl={globalUrl}
setCookie={setCookie}
cookies={cookies}
checkLogin={checkLogin}
{...props}
/>
}
/>
<Route
exact
path="/loginsetup"
element={
<LoginPageOld
isLoggedIn={isLoggedIn}
setIsLoggedIn={setIsLoggedIn}
register={false}
inregister={false}
isLoaded={isLoaded}
globalUrl={globalUrl}
setCookie={setCookie}
cookies={cookies}
checkLogin={checkLogin}
{...props}
/>
}
/>
<Route
exact
path="/register"
element={
<LoginPage
isLoggedIn={isLoggedIn}
setIsLoggedIn={setIsLoggedIn}
inregister={true}
isLoaded={isLoaded}
globalUrl={globalUrl}
setCookie={setCookie}
cookies={cookies}
checkLogin={checkLogin}
{...props}
/>
}
/>
<Route <Route
exact exact
path="/admin2" path="/admin2"
@@ -289,13 +404,14 @@ const App = (message, 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 path="/search" element={<Search serverside={false} isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.palette.inputColor} {...props} /> } />
<Route <Route
exact exact
path="/admin/:key" path="/admin/:key"
element={ element={
<Admin <Admin
isLoggedIn={isLoggedIn} isLoggedIn={isLoggedIn}
userdata={userdata}
setIsLoggedIn={setIsLoggedIn} setIsLoggedIn={setIsLoggedIn}
register={true} register={true}
isLoaded={isLoaded} isLoaded={isLoaded}
@@ -353,9 +469,10 @@ const App = (message, props) => {
} }
/> />
) : null} ) : null}
<Route <Route
exact exact
path="/AdminSetup" path="/adminsetup"
element={ element={
<AdminSetup <AdminSetup
isLoaded={isLoaded} isLoaded={isLoaded}
@@ -365,6 +482,7 @@ const App = (message, props) => {
/> />
} }
/> />
<Route <Route
exact exact
path="/detectionframework" path="/detectionframework"
@@ -458,8 +576,8 @@ const App = (message, props) => {
checkLogin={checkLogin} checkLogin={checkLogin}
userdata={userdata} userdata={userdata}
globalUrl={globalUrl} globalUrl={globalUrl}
surfaceColor={theme.palette.surfaceColor} surfaceColor={currentTheme.palette.surfaceColor}
inputColor={theme.palette.inputColor} inputColor={currentTheme.palette.inputColor}
{...props} {...props}
/> />
} }
@@ -476,8 +594,8 @@ const App = (message, 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="/apps/:appid" element={<AppExplorer userdata={userdata} isLoggedIn={isLoggedIn} isLoaded={isLoaded} globalUrl={globalUrl} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.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="/apis/:appid" element={<ApiExplorerWrapper serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} isMobile={false} selectedApp={undefined} isLoaded={isLoaded}globalUrl={globalUrl} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.palette.inputColor} checkLogin={checkLogin} {...props} />} />
<Route <Route
exact exact
path="/detections/sigma" path="/detections/sigma"
@@ -549,14 +667,40 @@ const App = (message, props) => {
/> />
} }
/> />
<Route exact path="/workflows/:key/code" element={<CodeWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} /> <Route exact path="/workflows/:key/code" element={<CodeWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.palette.inputColor}{...props} />} />
<Route exact path="/workflows/:key/run" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } /> <Route exact path="/workflows/:key/run" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.palette.inputColor}{...props} /> } />
<Route exact path="/workflows/:key/execute" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} /> } /> <Route exact path="/workflows/:key/execute" element={<RunWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.palette.inputColor}{...props} /> } />
<Route exact path="/forms" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} /> <Route exact path="/forms" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.palette.inputColor}{...props} />} />
<Route exact path="/forms/:key/run" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} /> <Route exact path="/forms/:key/run" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.palette.inputColor}{...props} />} />
<Route exact path="/forms/:key" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} /> <Route exact path="/forms/:key" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={currentTheme.palette.surfaceColor} inputColor={currentTheme.palette.inputColor}{...props} />} />
<Route
exact
path="/legal/:key"
element={
<Docs
isMobile={isMobile}
isLoaded={isLoaded}
globalUrl={globalUrl}
isLoggedIn={isLoggedIn}
{...props}
/>
}
/>
<Route
exact
path="/legal"
element={
<Docs
isMobile={isMobile}
isLoaded={isLoaded}
globalUrl={globalUrl}
isLoggedIn={isLoggedIn}
{...props}
/>
}
/>
<Route <Route
exact exact
path="/docs/:key" path="/docs/:key"
@@ -574,7 +718,6 @@ const App = (message, props) => {
exact exact
path="/docs" path="/docs"
element={ element={
//navigate(`/docs/about`)
<Docs <Docs
isMobile={isMobile} isMobile={isMobile}
isLoaded={isLoaded} isLoaded={isLoaded}
@@ -614,7 +757,7 @@ const App = (message, props) => {
/> />
} }
/> />
<Route exact path="/login/:key/mfa-setup" element={<MFASetUp setCookie={setCookie} serverside={false} mainColor={theme.palette.backgroundColor} userdata={userdata} stripeKey={undefined} globalUrl={globalUrl} inputColor={theme.palette.inputColor} isLoaded={isLoaded} {...props} />} /> <Route exact path="/login/:key/mfa-setup" element={<MFASetUp setCookie={setCookie} serverside={false} mainColor={currentTheme.palette.backgroundColor} userdata={userdata} stripeKey={undefined} globalUrl={globalUrl} inputColor={currentTheme.palette.inputColor} isLoaded={isLoaded} {...props} />} />
<Route <Route
exact exact
path="/login_sso" path="/login_sso"
@@ -648,18 +791,92 @@ const App = (message, props) => {
/> />
} }
/> />
<Route <Route
exact exact
path="/dashboards" path="/dashboard"
element={ element={
<DashboardView <DashboardViews
isLoaded={isLoaded} serverside={false}
isLoggedIn={isLoggedIn} isLoaded={isLoaded}
globalUrl={globalUrl} isLoggedIn={isLoggedIn}
{...props} globalUrl={globalUrl}
/> wut={userdata}
} />
/> }
/>
<Route
exact
path="/dashboards"
element={
<DashboardViews
serverside={false}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
wut={userdata}
/>
}
/>
<Route
exact
path="/dashboard/:key"
element={
<DashboardViews
serverside={false}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
wut={userdata}
/>
}
/>
<Route
exact
path="/chat"
element={
<ChatBot
serverside={false}
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
cookies={cookies}
userdata={userdata}
{...props}
/>
}
/>
<Route
exact
path="/conversation"
element={
<ChatBot
serverside={false}
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
cookies={cookies}
userdata={userdata}
{...props}
/>
}
/>
<Route
exact
path="/dashboards/:key"
element={
<DashboardViews
serverside={false}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
wut={userdata}
/>
}
/>
<Route <Route
exact exact
path="/welcome" path="/welcome"
@@ -677,6 +894,22 @@ const App = (message, props) => {
/> />
} }
/> />
<Route
exact
path="/agents"
element={
<AgentUI
serverside={false}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
userdata={userdata}
{...props}
/>
}
/>
<Route <Route
exact exact
path="/" path="/"
@@ -705,8 +938,7 @@ const App = (message, props) => {
</div> </div>
return ( return (
<AppContext> <ThemeProvider theme={currentTheme} defaultMode="dark">
<ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
<CookiesProvider> <CookiesProvider>
<BrowserRouter> <BrowserRouter>
@@ -722,11 +954,10 @@ const App = (message, props) => {
pauseOnFocusLoss pauseOnFocusLoss
draggable draggable
pauseOnHover pauseOnHover
theme="dark" theme={themeMode}
/> />
</CookiesProvider> </CookiesProvider>
</ThemeProvider> </ThemeProvider>
</AppContext>
); );
}; };
+305 -24
View File
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useContext, memo } from 'react'; import React, { useState, useEffect, useContext, memo } from 'react';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import OrganizationTab from '../components/OrganizationTab.jsx'; import OrganizationTab from '../components/OrganizationTab.jsx';
import PartnerTab from '../components/PartnerTab.jsx';
import UserManagmentTab from '../components/UserManagmentTab.jsx'; import UserManagmentTab from '../components/UserManagmentTab.jsx';
import CacheView from "../components/CacheView.jsx"; import CacheView from "../components/CacheView.jsx";
import Files from "../components/Files.jsx"; import Files from "../components/Files.jsx";
@@ -19,24 +20,124 @@ import {
FmdGoodOutlined as FmdGoodOutlinedIcon, FmdGoodOutlined as FmdGoodOutlinedIcon,
GroupOutlined as GroupOutlinedIcon GroupOutlined as GroupOutlinedIcon
} from '@mui/icons-material'; } from '@mui/icons-material';
import theme from '../theme.jsx'; import theme, { getTheme } from '../theme.jsx';
import { Button, Tooltip } from '@mui/material'; import { Button, Skeleton, Tooltip } from '@mui/material';
import { Index } from 'react-instantsearch-dom'; import { Index } from 'react-instantsearch-dom';
import { Context } from '../context/ContextApi.jsx'; import { Context } from '../context/ContextApi.jsx';
import { toast } from 'react-toastify';
const PartnerIcon = ({ strokeColor, fillColor = 'transparent', width = 22, height = 22 }) => (
<svg
width={width}
height={height}
viewBox="0 0 18 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5327 4.49465C14.0102 4.60513 13.5779 4.90684 13.2905 5.31071L12.382 1L1.7665 3.23791C1.24516 3.34838 0.911462 3.86034 1.02072 4.38181L3.25807 15L7.57601 14.0901C7.16749 13.8026 6.85992 13.3679 6.74948 12.8393C6.51672 11.7334 7.22331 10.6477 8.32892 10.4149C9.43453 10.1821 10.52 10.8889 10.7527 11.9947C10.8643 12.5221 10.7587 13.0448 10.501 13.4724L14.8189 12.5625L13.9104 8.25182C14.3368 8.50484 14.8533 8.60699 15.3759 8.49652C16.4815 8.2637 17.1893 7.17801 16.9553 6.07212C16.7225 4.96623 15.6371 4.25827 14.5315 4.49228L14.5327 4.49465Z"
fill={fillColor}
stroke={strokeColor}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
const AdminNavBar = (props) => { const AdminNavBar = (props) => {
const location = useLocation(); const location = useLocation();
const { globalUrl, userdata, isCloud, isLoaded,removeCookie, handleStatusChange, selectedStatus, setSelectedStatus, handleEditOrg, serverside, notifications, handleGetOrg, orgId, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } = props; const { globalUrl, userdata, isCloud,isOrgLoaded, isLoaded,removeCookie, handleStatusChange, selectedStatus, setSelectedStatus, handleEditOrg, serverside, notifications, handleGetOrg, orgId, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } = props;
const [selectedItem, setSelectedItem] = useState("Organization"); const [selectedItem, setSelectedItem] = useState("Organization");
const [isSelectedFiles, setIsSelectedFiles] = useState(true); const [isSelectedFiles, setIsSelectedFiles] = useState(true);
const [isSelectedDataStore, setIsSelectedDataStore] = useState(true); const [isSelectedDataStore, setIsSelectedDataStore] = useState(true);
const [isIntegrationPartner, setIsIntegrationPartner] = useState(false);
const [isChildOrg, setIsChildOrg] = useState(false);
const [isGlobalUser, setIsGlobalUser] = useState(false);
const [visibleItems, setVisibleItems] = useState([]);
const [isUserDataLoaded, setIsUserDataLoaded] = useState(false);
useEffect(() => {
if (userdata && userdata?.active_org?.id?.length > 0) {
setIsUserDataLoaded(true);
}
}, [userdata]);
const { themeMode, brandColor } = React.useContext(Context);
const theme = getTheme(themeMode, brandColor);
const HandlePartnerChange = () => {
if (userdata?.id?.length > 0) {
const isIntegrationPartner = userdata?.org_status?.includes("integration_partner") || false;
setIsIntegrationPartner(isIntegrationPartner);
const isChildOrg = userdata?.org_status?.includes("sub_org") || false;
setIsChildOrg(isChildOrg);
const isGlobalUser = userdata?.active_org?.branding?.global_user || false;
setIsGlobalUser(isGlobalUser);
} else {
setIsIntegrationPartner(false);
setIsChildOrg(false);
setIsGlobalUser(false);
}
}
useEffect(() => {
if (userdata && userdata?.id?.length > 0) {
HandlePartnerChange();
}
}, [userdata]);
const HandleVisibleTabs = () => {
if (userdata?.id?.length > 0) {
if (userdata?.active_org?.role === "admin" || userdata?.support) {
setVisibleItems(items);
}else {
const filteredItems = items.filter(item => item.text !== "Users" && item.text !== "Files" && item.text !== "Datastore" && item.text !== "Triggers" && item.text !== "Locations");
setVisibleItems(filteredItems);
}
}
}
useEffect(() => {
if (isIntegrationPartner && isChildOrg && !isGlobalUser) {
// Filter out Users and Tenants tabs
if (userdata?.active_org?.role === "admin" || userdata?.support) {
const filteredItems = items.filter(item =>
item.text !== "Users" && item.text !== "Tenants"
);
setVisibleItems(filteredItems);
}else {
const filteredItems = items.filter(item =>
item.text !== "Users" && item.text !== "Tenants" && item.text !== "Files" && item.text !== "Datastore" && item.text !== "Triggers" && item.text !== "Locations"
);
setVisibleItems(filteredItems);
}
} else {
HandleVisibleTabs();
}
}, [isIntegrationPartner, isChildOrg, isGlobalUser, selectedOrganization, userdata]);
const navigate = useNavigate(); const navigate = useNavigate();
//const leadinfo = selectedOrganization.lead_info === undefined || selectedOrganization.lead_info === null || selectedOrganization.lead_info === "" ? "" : JSON.stringify(selectedOrganization.lead_info)
//const isPartner = leadinfo.includes("partner")
useEffect(() => { useEffect(() => {
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const tabName = queryParams.get('tab'); const tabName = queryParams.get('tab');
const partnerTab = queryParams.get('partner_tab');
// if(!isCloud){
// if(tabName === "partner" || partnerTab !== null) {
// setSelectedItem("Organization");
// navigate(`?tab=organization`, { replace: true });
// }
// }
if (partnerTab) {
setSelectedItem("Partner");
}
if (tabName === "environments") { if (tabName === "environments") {
setSelectedItem("Locations"); setSelectedItem("Locations");
} else if (tabName === "suborgs") { } else if (tabName === "suborgs") {
@@ -45,14 +146,15 @@ const AdminNavBar = (props) => {
setSelectedItem("Datastore"); setSelectedItem("Datastore");
}else if (tabName) { }else if (tabName) {
setSelectedItem(tabName.charAt(0).toUpperCase() + tabName.slice(1)); setSelectedItem(tabName.charAt(0).toUpperCase() + tabName.slice(1));
} else { }else if (tabName === "partner") {
setSelectedItem("Organization"); setSelectedItem("Partner");
} }
}, [location.search]); }, [location.search]);
const items = [ 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: <BusinessIcon />, alt: "Organization Icon", text: "Organization", component: OrganizationTab, props: { isIntegrationPartner, isChildOrg, isGlobalUser,globalUrl,removeCookie, selectedStatus, isLoaded, setSelectedStatus, handleStatusChange, handleEditOrg, handleGetOrg, userdata, isCloud, serverside, notifications, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } },
{ iconSrc: undefined, alt: "Partner Icon", text: "Partner", component: PartnerTab, props: { globalUrl,removeCookie, isLoaded, handleGetOrg, userdata, isCloud, serverside, checkLogin, setSelectedOrganization, selectedOrganization } },
{ iconSrc: <PermIdentityIcon />, alt: "Users Icon", text: "Users", component: UserManagmentTab, props: { globalUrl, userdata, serverside, isCloud, selectedOrganization, setSelectedOrganization, handleEditOrg } }, { 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: <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: <StorageOutlinedIcon />, alt: "Datastore Icon", text: "Datastore", component: CacheView, props: { globalUrl, userdata, selectedOrganization, serverside, isSelectedDataStore, orgId , isCloud} },
@@ -60,7 +162,7 @@ const AdminNavBar = (props) => {
{ iconSrc: <AccessTimeOutlinedIcon />, alt: "Trigger Icon", text: "Triggers", component: SchedulesTab, props: { globalUrl, userdata, isCloud, serverside } }, { 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: <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 } } { iconSrc: <GroupOutlinedIcon />, alt: "Tenants Icon", text: "Tenants", component: TenantsTab, props: {isCloud, globalUrl, userdata, serverside, selectedOrganization, setSelectedOrganization, checkLogin } }
]; ].filter(Boolean);
const setConfig = (newValue) => { const setConfig = (newValue) => {
setSelectedItem(newValue); setSelectedItem(newValue);
@@ -73,19 +175,80 @@ const AdminNavBar = (props) => {
} }
}; };
useEffect(() => {
if (isIntegrationPartner && isChildOrg && !isGlobalUser && isOrgLoaded && isUserDataLoaded) {
const queryParams = new URLSearchParams(location.search);
const tabName = queryParams?.get('admin_tab')?.toLowerCase();
if (tabName === "sso" || tabName === "branding") {
toast.info("You are not allowed to access this tab. Please contact your admin for more information. Redirecting to Organization Configuration tab.");
setTimeout(() => {
setSelectedItem("Organization");
navigate(`?admin_tab=org_config`, { replace: true });
window.location.reload();
}
, 3000);
}
const params = new URLSearchParams(location.search);
const tab = params?.get('tab')?.toLowerCase();
if (tab === "users" || tab === "tenants") {
toast.info("You are not allowed to access this tab. Please contact your admin for more information. Redirecting to Organization Configuration tab.");
setTimeout(() => {
setSelectedItem("Organization");
navigate(`?admin_tab=org_config`, { replace: true });
window.location.reload();
}
, 3000);
}
} else if (userdata && isOrgLoaded && isUserDataLoaded && userdata?.active_org?.role !== "admin" && !userdata?.support) {
const queryParams = new URLSearchParams(location.search);
const tabName = queryParams?.get('admin_tab')?.toLowerCase();
if (tabName === "sso") {
toast.info("You are not allowed to access this tab. Please contact your admin for more information. Redirecting to Organization Configuration tab.");
setTimeout(() => {
setSelectedItem("Organization");
navigate(`?admin_tab=org_config`, { replace: true });
window.location.reload();
}
, 3000);
}
const params = new URLSearchParams(location.search);
const tab = params?.get('tab')?.toLowerCase();
if (tab === "users" || tab === "locations" || tab === "environments" || tab === "files" || tab === "datastore" || tab === "triggers") {
toast.info("You are not allowed to access this tab. Please contact your admin for more information. Redirecting to Organization Configuration tab.");
setTimeout(() => {
setSelectedItem("Organization");
navigate(`?admin_tab=org_config`, { replace: true });
window.location.reload();
}
, 3000);
}
}
}, [isIntegrationPartner, isChildOrg, isGlobalUser, location.search, userdata, isOrgLoaded, isUserDataLoaded]);
const renderComponent = () => { const renderComponent = () => {
const selectedItemData = items.find(item => item.text === selectedItem); const selectedItemData = visibleItems.find(item => item.text === selectedItem);
if (!selectedItemData) { if (!selectedItemData) {
setSelectedItem("Organization"); setSelectedItem("Organization");
// If no tab is specified, default to "Organization" tab // 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}/>; return <OrganizationTab isIntegrationPartner={isIntegrationPartner} isChildOrg={isChildOrg} isGlobalUser={isGlobalUser} 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 ComponentToRender = selectedItemData.component;
const componentProps = selectedItemData.props; const componentProps = selectedItemData.props;
return <ComponentToRender {...componentProps} />; const updatedProps = {
}; ...componentProps,
notifications: notifications,
setNotifications: setNotifications,
userdata: userdata,
selectedOrganization: selectedOrganization
};
return <ComponentToRender {...updatedProps} />;
};
const defaultImage = "/images/logos/orange_logo.svg" const defaultImage = "/images/logos/orange_logo.svg"
const imageData = const imageData =
@@ -94,15 +257,16 @@ const AdminNavBar = (props) => {
: selectedOrganization?.image; : selectedOrganization?.image;
return ( return (
!isOrgLoaded && !isUserDataLoaded ? <Loader /> :
<Wrapper> <Wrapper>
<div style={{ flexDirection: 'column', width: 220, }}> <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' }}> <nav style={{ padding: '25px 25px 3px 25px', height: isCloud ? "calc(100% - 60px)" : "calc(100% - 30px)" , fontSize: '16px', borderTopLeftRadius: 8, borderBottomLeftRadius: 8, background: theme.palette.platformColor, color: '#9CA3AF' }}>
<div style={{ display: 'flex', alignItems: 'center', }}> <div style={{ display: 'flex', alignItems: 'center', }}>
<img loading="lazy" src={imageData} alt="Logo" style={{ width: '30px', borderRadius: 8, height: '30px', marginRight: '8px' }} /> <img loading="lazy" src={imageData} alt="Logo" style={{ width: '30px', borderRadius: 8, height: '30px', marginRight: '8px' }} />
<div style={{ <div style={{
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
fontSize: '16px', fontSize: '16px',
color: "#FFFFFF", color: theme.palette.text.primary,
fontWeight: 400, fontWeight: 400,
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
@@ -111,8 +275,8 @@ const AdminNavBar = (props) => {
marginLeft: 5, marginLeft: 5,
}}>{selectedOrganization?.name}</div> }}>{selectedOrganization?.name}</div>
</div> </div>
<div style={{ borderTop: '1px solid #494949', marginTop: 23 }} /> <div style={{ borderTop: theme.palette.defaultBorder, marginTop: 23 }} />
{items.map((item, index) => ( {visibleItems.map((item, index) => (
<Tooltip <Tooltip
key={index} key={index}
title={ title={
@@ -129,11 +293,10 @@ const AdminNavBar = (props) => {
color="primary" color="primary"
sx={{ sx={{
gap: 1, gap: 1,
"&:hover": {
backgroundColor: "#323232 !important",
},
"&.MuiButton-root": { "&.MuiButton-root": {
color: selectedItem === item.text ? "#FFFFFF" : "#9E9E9E", color: selectedItem === item.text
? theme.palette.text.primary
: theme.palette.text.secondary,
fontSize: 16, fontSize: 16,
backgroundColor: "transparent", backgroundColor: "transparent",
textTransform: "none", textTransform: "none",
@@ -146,13 +309,16 @@ const AdminNavBar = (props) => {
justifyContent: "flex-start", justifyContent: "flex-start",
borderLeft: borderLeft:
selectedItem === item.text selectedItem === item.text
? "3px solid rgba(255, 132, 68, 1)" ? `3px solid ${theme.palette.primary.main}`
: "none", : "none",
borderTopLeftRadius: selectedItem === item.text ? "2.5px" : null, borderTopLeftRadius: selectedItem === item.text ? "2.5px" : null,
borderBottomLeftRadius: selectedItem === item.text ? "2.5px" : null, borderBottomLeftRadius: selectedItem === item.text ? "2.5px" : null,
paddingLeft: selectedItem === item.text ? "15px" : "10px", paddingLeft: selectedItem === item.text ? "15px" : "10px",
fontWeight: selectedItem === item.text ? 200 : "normal", fontWeight: selectedItem === item.text ? 200 : "normal",
flex: 1, flex: 1,
"&:hover": {
backgroundColor: theme.palette.hoverColor,
},
}, },
"&.Mui-disabled": { "&.Mui-disabled": {
color: "#6F6F6F", color: "#6F6F6F",
@@ -161,7 +327,7 @@ const AdminNavBar = (props) => {
disabled={ disabled={
((item.text === "Users") || (item.text === "Files") || (item.text === "Triggers") || (item.text === "Locations")) && !(userdata?.support || userdata?.active_org?.role === "admin") ((item.text === "Users") || (item.text === "Files") || (item.text === "Triggers") || (item.text === "Locations")) && !(userdata?.support || userdata?.active_org?.role === "admin")
} }
startIcon={item.iconSrc} startIcon={item?.iconSrc === undefined ? <PartnerIcon strokeColor={selectedItem === item.text ? theme.palette.text.primary : theme.palette.text.secondary} /> : item?.iconSrc}
onClick={() => setConfig(item.text)} onClick={() => setConfig(item.text)}
> >
{item.text.replace(/_/g, " ")} {item.text.replace(/_/g, " ")}
@@ -173,17 +339,132 @@ const AdminNavBar = (props) => {
</nav> </nav>
</div> </div>
<Wrapper2>{renderComponent()}</Wrapper2>
<Wrapper2>
{renderComponent()}
</Wrapper2>
</Wrapper> </Wrapper>
); );
}; };
export default AdminNavBar; export default AdminNavBar;
const Loader = () => {
const dummyItems = Array.from({ length: 6 });
const dummyNavItems = Array.from({ length: 6 });
const dummyTabItems = ['Org Configuration', 'SSO', 'Notifications', 'Billing & Stats', 'Branding'];
const { leftSideBarOpenByClick, windowWidth, themeMode } = useContext(Context);
const theme = getTheme(themeMode);
return (
<div style={{
display: 'flex',
width: '100%',
height: '100%',
minHeight: '100vh',
maxWidth: '1200px',
fontFamily: 'Arial, sans-serif',
paddingLeft: leftSideBarOpenByClick ? windowWidth <= 1300 ? 220 : 200 : 80,
transition: "padding-left 0.3s ease",
}}>
<div style={{
width: '220px',
backgroundColor: theme.palette.platformColor,
borderTopLeftRadius: '8px',
borderBottomLeftRadius: '8px',
padding: '25px 25px 3px 25px',
display: 'flex',
flexDirection: 'column'
}}>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}>
<Skeleton
width="30px"
height="30px"
sx={{ borderRadius: '8px', marginRight: '8px', }}
/>
<Skeleton width="120px" height="24px" />
</div>
{/* Divider */}
<Skeleton
width="100%"
height="1px"
sx={{ marginBottom: '15px' }}
/>
{/* Nav Items */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px', width: '100%' }}>
{dummyNavItems.map((_, index) => (
<div key={index} style={{ display: 'flex', alignItems: 'center', padding: '5px 0' }}>
<Skeleton
width="18px"
height="18px"
sx={{ marginRight: '10px' }}
/>
<Skeleton width={`${100 + Math.random() * 40}px`} height="36px" />
</div>
))}
</div>
</div>
<div style={{
flex: 1,
backgroundColor: theme.palette.platformColor,
borderTopRightRadius: '8px',
borderBottomRightRadius: '8px',
borderLeft: theme.palette.defaultBorder,
display: 'flex',
flexDirection: 'column'
}}>
<div style={{
display: 'flex',
borderBottom: theme.palette.defaultBorder,
padding: '0 16px'
}}>
{dummyTabItems.map((_, index) => (
<div
key={index}
style={{
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '28px 0',
borderBottom: index === 0 ? '2px solid #FF8444' : 'none'
}}
>
<Skeleton width={`${80 + Math.random() * 30}px`} height="24px" />
</div>
))}
</div>
<div style={{ flex: 1, padding: '24px' }}>
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', margin: '0 auto', alignItems: 'flex-start' }}>
<Skeleton variant='square' width="200px" height="200px" sx={{ marginBottom: '20px' }} />
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px', width: '100%', marginTop: '50px' }}>
{dummyItems.map((_, index) => (
<div key={index} style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Skeleton width={'400px'} height="36px" />
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
const PaddingWrapper2 = memo(({ children }) => { const PaddingWrapper2 = memo(({ children }) => {
return ( return (
<div div style={{marginBottom: 30, width: "75%" , maxWidth: 1200, height: "100%", boxSizing: 'border-box'}}> <div style={{
marginBottom: 30,
width: "75%" ,
maxWidth: 1200,
height: "100%",
boxSizing: 'border-box',
}}>
{children} {children}
</div> </div>
) )
+267 -264
View File
@@ -1,275 +1,278 @@
import React, { useEffect, useState } from 'react'; // import React, { useEffect, useState } from 'react';
import Switch from '@mui/material/Switch'; // import Switch from '@mui/material/Switch';
import { Typography, Button } from '@mui/material'; // import { Typography, Button } from '@mui/material';
import { useNavigate, Link, useParams } from "react-router-dom"; // import { useNavigate, Link, useParams } from "react-router-dom";
import { Bar } from 'react-chartjs-2'; // import { Bar } from 'react-chartjs-2';
import Grid from '@mui/material/Grid'; // import Grid from '@mui/material/Grid';
import SearchIcon from '@mui/icons-material/Search'; // import SearchIcon from '@mui/icons-material/Search';
import NewReleasesIcon from '@mui/icons-material/NewReleases'; // import NewReleasesIcon from '@mui/icons-material/NewReleases';
import MailOutlineIcon from '@mui/icons-material/MailOutline'; // import MailOutlineIcon from '@mui/icons-material/MailOutline';
const AnalyticsTab = (props) => { const AnalyticsTab = (props) => {
const { userdata, globalUrl, serverside } = 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) => { console.log("if you need this, work without react-rechartjs-2")
setSelectedOption(option);
// You can perform actions based on the selected option, such as filtering data
};
const handleChange = (event) => { // const [checked, setChecked] = useState(false);
setChecked(event.target.checked); // 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 allData = { // const handleOptionChange = (option) => {
labels: ['Email analysis', 'Email Management', 'EDR to Ticket', 'Ticket Analysis'], // setSelectedOption(option);
datasets: [ // // You can perform actions based on the selected option, such as filtering data
{ // };
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; // const handleChange = (event) => {
if (selectedOption === 'all') { // setChecked(event.target.checked);
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 allData = {
const options = { // labels: ['Email analysis', 'Email Management', 'EDR to Ticket', 'Ticket Analysis'],
scales: { // datasets: [
yAxes: [ // {
{ // label: 'Revisions',
ticks: { // data: [12, 19, 3, 5], // Data for revisions
beginAtZero: true // backgroundColor: '#FF8444',
} // borderWidth: 1,
} // // borderRadius: 60,
] // barPercentage: 0.7,
}, // categoryPercentage: 0.5,
}; // },
return ( // {
<div style={{ width: 1030, marginLeft: 20, marginTop:10, paddingRight: 17, paddingLeft: 17}}> // label: 'Runs',
<div style={{ width: 985, display: 'flex', alignItems: 'start', paddingTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}> // data: [8, 15, 5, 8], // Data for runs
<div style={{ marginLeft: 18 }}>Timeline</div> // backgroundColor: '#9747FF',
</div> // borderWidth: 1,
<div style={{ display: "flex", width: "100%", marginTop: 16 }}> // // borderRadius: 60,
<div> // barPercentage: 0.7,
<div style={{ width: 595, alignItems: 'start', paddingTop: 16, paddingBottom: checked ? 28 : 20, marginRight: 20, fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}> // categoryPercentage: 0.5
<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 }} /> // let data;
<Link onClick={() => { setExpand(prevExpand => !prevExpand); }} style={{ color: "#FF8444" }}>Expand</Link> // if (selectedOption === 'all') {
</div> // data = allData;
{expand ? null : // } else if (selectedOption === 'revisions') {
<div style={{ marginTop: 8, display: "flex" }}> // data = {
<div style={{ marginLeft: 19, }}> // labels: allData.labels,
<Typography style={{ fontSize: 13, color: "#9E9E9E", textAlign: "start" }}>Onboarding</Typography> // datasets: [allData.datasets[0]] // Show only revisions data
<Grid container spacing={2} style={{ marginTop: 1 }} > // };
<Grid item xs={4}> // } else if (selectedOption === 'run') {
<div style={{ position: 'relative' }}> // data = {
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} /> // labels: allData.labels,
{checked ? // datasets: [allData.datasets[1]] // Show only runs data
<div style={{ // };
position: 'absolute', // }
bottom: 0,
left: '50%', // // Options for the chart
top: 30, // const options = {
transform: 'translateX(-50%)', // scales: {
backgroundColor: '#2f2f2f', // yAxes: [
borderRadius: '50%', // {
height: 24, // ticks: {
width: 24, // beginAtZero: true
transition: 'transform 0.5s ease', // }
}}><SearchIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> : // }
null // ]
} // },
</div> // };
</Grid> // return (
<Grid item xs={4}> // <div style={{ width: 1030, marginLeft: 20, marginTop:10, paddingRight: 17, paddingLeft: 17}}>
<div style={{ position: 'relative' }}> // <div style={{ width: 985, display: 'flex', alignItems: 'start', paddingTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} /> // <div style={{ marginLeft: 18 }}>Timeline</div>
{checked ? // </div>
<div style={{ // <div style={{ display: "flex", width: "100%", marginTop: 16 }}>
position: 'absolute', // <div>
bottom: 0, // <div style={{ width: 595, alignItems: 'start', paddingTop: 16, paddingBottom: checked ? 28 : 20, marginRight: 20, fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
left: '50%', // <div style={{ display: "flex", alignItems: "center" }}>
top: 30, // <div style={{ marginLeft: 18, marginRight: 310 }}>Apps</div>
transform: 'translateX(-50%)', // <Switch checked={checked}
backgroundColor: '#2f2f2f', // onChange={handleChange} /> Category
borderRadius: '50%', // <div style={{ marginLeft: 16, borderLeft: "1px solid #D9D9D9", width: 10, height: 15 }} />
height: 24, // <Link onClick={() => { setExpand(prevExpand => !prevExpand); }} style={{ color: "#FF8444" }}>Expand</Link>
width: 24, // </div>
transition: 'transform 0.5s ease', // {expand ? null :
}}><MailOutlineIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> : // <div style={{ marginTop: 8, display: "flex" }}>
null // <div style={{ marginLeft: 19, }}>
} // <Typography style={{ fontSize: 13, color: "#9E9E9E", textAlign: "start" }}>Onboarding</Typography>
</div> // <Grid container spacing={2} style={{ marginTop: 1 }} >
</Grid> // <Grid item xs={4}>
<Grid item xs={4}> // <div style={{ position: 'relative' }}>
<div style={{ position: 'relative' }}> // <img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} /> // {checked ?
{checked ? // <div style={{
<div style={{ // position: 'absolute',
position: 'absolute', // bottom: 0,
bottom: 0, // left: '50%',
left: '50%', // top: 30,
top: 30, // transform: 'translateX(-50%)',
transform: 'translateX(-50%)', // backgroundColor: '#2f2f2f',
backgroundColor: '#2f2f2f', // borderRadius: '50%',
borderRadius: '50%', // height: 24,
height: 24, // width: 24,
width: 24, // transition: 'transform 0.5s ease',
transition: 'transform 0.5s ease', // }}><SearchIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
}}><NewReleasesIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> : // null
null // }
} // </div>
</div> // </Grid>
</Grid> // <Grid item xs={4}>
</Grid> // <div style={{ position: 'relative' }}>
</div> // <img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
<div style={{ borderLeft: "1px solid #494949", justifyContent: "center", alignItems: "center", marginTop: 40, marginLeft: 20, marginRight: 20, height: 40 }}></div> // {checked ?
<div style={{}}> // <div style={{
<Typography style={{ fontSize: 13, color: "#9E9E9E", textAlign: "start" }}>Other</Typography> // position: 'absolute',
<Grid container spacing={2} style={{ marginTop: 1 }} > // bottom: 0,
<Grid item xs={4}> // left: '50%',
<div style={{ position: 'relative' }}> // top: 30,
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} /> // transform: 'translateX(-50%)',
{checked ? // backgroundColor: '#2f2f2f',
<div style={{ // borderRadius: '50%',
position: 'absolute', // height: 24,
bottom: 0, // width: 24,
left: '50%', // transition: 'transform 0.5s ease',
top: 30, // }}><MailOutlineIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
transform: 'translateX(-50%)', // null
backgroundColor: '#2f2f2f', // }
borderRadius: '50%', // </div>
height: 24, // </Grid>
width: 24, // <Grid item xs={4}>
transition: 'transform 0.5s ease', // <div style={{ position: 'relative' }}>
}}><SearchIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> : // <img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
null // {checked ?
} // <div style={{
</div> // position: 'absolute',
</Grid> // bottom: 0,
<Grid item xs={4}> // left: '50%',
<div style={{ position: 'relative' }}> // top: 30,
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} /> // transform: 'translateX(-50%)',
{checked ? // backgroundColor: '#2f2f2f',
<div style={{ // borderRadius: '50%',
position: 'absolute', // height: 24,
bottom: 0, // width: 24,
left: '50%', // transition: 'transform 0.5s ease',
top: 30, // }}><NewReleasesIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
transform: 'translateX(-50%)', // null
backgroundColor: '#2f2f2f', // }
borderRadius: '50%', // </div>
height: 24, // </Grid>
width: 24, // </Grid>
transition: 'transform 10s ease-in-out', // </div>
}}><NewReleasesIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> : // <div style={{ borderLeft: "1px solid #494949", justifyContent: "center", alignItems: "center", marginTop: 40, marginLeft: 20, marginRight: 20, height: 40 }}></div>
null // <div style={{}}>
} // <Typography style={{ fontSize: 13, color: "#9E9E9E", textAlign: "start" }}>Other</Typography>
</div> // <Grid container spacing={2} style={{ marginTop: 1 }} >
</Grid> // <Grid item xs={4}>
<Grid item xs={4}> // <div style={{ position: 'relative' }}>
<div style={{ position: 'relative' }}> // <img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} /> // {checked ?
{checked ? // <div style={{
<div style={{ // position: 'absolute',
position: 'absolute', // bottom: 0,
bottom: 0, // left: '50%',
left: '50%', // top: 30,
top: 30, // transform: 'translateX(-50%)',
transform: 'translateX(-50%)', // backgroundColor: '#2f2f2f',
backgroundColor: '#2f2f2f', // borderRadius: '50%',
borderRadius: '50%', // height: 24,
height: 24, // width: 24,
width: 24, // transition: 'transform 0.5s ease',
transition: 'transform 10s ease-in-out', // }}><SearchIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
}}><MailOutlineIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> : // null
null // }
} // </div>
</div> // </Grid>
</Grid> // <Grid item xs={4}>
</Grid> // <div style={{ position: 'relative' }}>
</div> // <img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
</div>} // {checked ?
</div> // <div style={{
<div style={{ width: 595, height: 322, marginTop: 16, paddingTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}> // position: 'absolute',
<div style={{ marginLeft: 18, textAlign: "start" }}>Workflows</div> // bottom: 0,
<div style={{textAlign:"center"}}> // left: '50%',
<Button style={{ textTransform: "capitalize", background: selectedOption === 'all' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'all' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('all')}>All</Button> // top: 30,
<Button style={{ textTransform: "capitalize", background: selectedOption === 'revisions' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'revisions' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('revisions')}>Revisions</Button> // transform: 'translateX(-50%)',
<Button style={{ textTransform: "capitalize", background: selectedOption === 'run' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'run' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('run')}>Runs</Button> // backgroundColor: '#2f2f2f',
</div> // borderRadius: '50%',
<Bar data={data} options={options} style={{ width: 400, marginLeft: 25, padding:20,marginTop: 10 }} /> // height: 24,
</div> // width: 24,
</div> // transition: 'transform 10s ease-in-out',
<div style={{ width: 375, height: 440, display: 'flex', alignItems: 'start', paddingTop: '16px', paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}> // }}><NewReleasesIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
<div style={{ marginLeft: 18 }}>Insights</div> // null
</div> // }
</div> // </div>
<div style={{ width: 985, paddingTop: 16, marginTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}> // </Grid>
<div style={{ marginLeft: 18, textAlign: 'start', marginBottom: 16 }}>Sessions Overview</div> // <Grid item xs={4}>
<div style={{ display: "flex", width: "100%", justifyContent: "center" }}> // <div style={{ position: 'relative' }}>
<div style={{ fontSize: '16px',width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}> // <img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
<div style={{ marginTop: 21, color: "#ffffff", marginLeft:20,fontSize: 16, fontWeight: "bold", }}> // {checked ?
2.8 Hours // <div style={{
</div> // position: 'absolute',
<div style={{ marginTop: 13, marginBottom: 16, marginLeft:20, }}> // bottom: 0,
Avg. Activity per session // left: '50%',
</div> // top: 30,
</div> // transform: 'translateX(-50%)',
<div style={{ marginLeft: 16, fontSize: '16px', width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}> // backgroundColor: '#2f2f2f',
<div style={{ marginTop: 21, marginLeft:20, color: "#ffffff", fontSize: 16, fontWeight: "bold", }}> // borderRadius: '50%',
/usercases/edr to ticket // height: 24,
</div> // width: 24,
<div style={{ marginTop: 13, marginLeft:20, marginBottom: 16, }}> // transition: 'transform 10s ease-in-out',
Last visited page // }}><MailOutlineIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
</div> // null
</div> // }
<div style={{ marginLeft: 16, fontSize: '16px', width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}> // </div>
<div style={{ marginTop: 21, marginLeft:20,color: "#ffffff", fontSize: 16, fontWeight: "bold", }}> // </Grid>
/workflow/email management // </Grid>
</div> // </div>
<div style={{ marginTop: 13, marginLeft:20, marginBottom: 16, }}> // </div>}
Most visited page // </div>
</div> // <div style={{ width: 595, height: 322, marginTop: 16, paddingTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
</div> // <div style={{ marginLeft: 18, textAlign: "start" }}>Workflows</div>
</div> // <div style={{textAlign:"center"}}>
</div> // <Button style={{ textTransform: "capitalize", background: selectedOption === 'all' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'all' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('all')}>All</Button>
</div> // <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; export default AnalyticsTab;
+49 -35
View File
@@ -29,7 +29,7 @@ import {
Tooltip, Tooltip,
} from "@mui/material"; } from "@mui/material";
import throttle from "lodash/throttle"; import throttle from "lodash/throttle";
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
import { validateJson, collapseField, } from "../views/Workflows.jsx"; import { validateJson, collapseField, } from "../views/Workflows.jsx";
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
@@ -37,6 +37,8 @@ import { Context } from "../context/ContextApi.jsx";
function CustomTabPanel(props) { function CustomTabPanel(props) {
const { children, value, index, ...other } = props; const { children, value, index, ...other } = props;
const {themeMode} = useContext(Context);
const theme = getTheme(themeMode)
return ( return (
<div <div
@@ -47,7 +49,7 @@ function CustomTabPanel(props) {
{...other} {...other}
> >
{value === index && ( {value === index && (
<Box sx={{ p: 3, padding: 0, backgroundColor: "#1a1a1a" }}> <Box sx={{ p: 3, padding: 0, backgroundColor: theme.palette.backgroundColor}}>
{children} {children}
</Box> </Box>
)} )}
@@ -100,6 +102,8 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se
const [selectedActionIndex, setSelectedActionIndex] = useState(0); const [selectedActionIndex, setSelectedActionIndex] = useState(0);
const [ExampleBody, setExampleBody] = useState({}); const [ExampleBody, setExampleBody] = useState({});
const [filteredActions, setFilteredActions] = useState([]); const [filteredActions, setFilteredActions] = useState([]);
const {themeMode} = useContext(Context);
const theme = getTheme(themeMode)
const [firstSendDone, setFirstSendDone] = useState(false) const [firstSendDone, setFirstSendDone] = useState(false)
@@ -1391,6 +1395,8 @@ const ActionsList = memo(({
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [visibleActions, setVisibleActions] = useState([]); const [visibleActions, setVisibleActions] = useState([]);
const {themeMode} = useContext(Context);
const theme = getTheme(themeMode);
useEffect(() => { useEffect(() => {
@@ -1457,13 +1463,13 @@ const ActionsList = memo(({
<Tooltip title="Go to app" placement="right"> <Tooltip title="Go to app" placement="right">
<div style={{ display: "flex", alignItems: "center" }}> <div style={{ display: "flex", alignItems: "center" }}>
<img <img
src={openapi?.info["x-logo"]} src={openapi?.info["x-logo"]?.length > 0 ? openapi?.info["x-logo"] : theme?.palette?.defaultImage}
width={48} width={48}
height={48} height={48}
alt="app logo" alt="app logo"
style={{ marginLeft: 20, borderRadius: 8 }} style={{ marginLeft: 20, borderRadius: 8 }}
/> />
<Typography style={{ fontSize: 24, fontWeight: 'bold', marginLeft: 20, overflow: 'hidden', color: '#F1F1F1' <Typography style={{ fontSize: 24, fontWeight: 'bold', marginLeft: 20, overflow: 'hidden', color: theme.palette.textColor
}}> }}>
{info.title} {info.title}
</Typography> </Typography>
@@ -1471,7 +1477,7 @@ const ActionsList = memo(({
</Tooltip> </Tooltip>
</a> </a>
) : ( ) : (
<Typography style={{ fontSize: 24, fontWeight: 'bold', marginLeft: 20, overflow: 'hidden', color: '#F1F1F1' <Typography style={{ fontSize: 24, fontWeight: 'bold', marginLeft: 20, overflow: 'hidden', color: theme.palette.textColor
}}> }}>
Api Explorer Api Explorer
</Typography> </Typography>
@@ -1504,7 +1510,7 @@ const ActionsList = memo(({
style={{ style={{
marginLeft: 20, marginLeft: 20,
marginTop: 15, marginTop: 15,
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
overflowY: "auto", overflowY: "auto",
height: (isLoaded && isLoggedIn) ? "calc(100vh - 190px)" : "calc(100vh - 260px)", height: (isLoaded && isLoggedIn) ? "calc(100vh - 190px)" : "calc(100vh - 260px)",
paddingRight: 5, paddingRight: 5,
@@ -1522,7 +1528,7 @@ const ActionsList = memo(({
textTransform: "none", textTransform: "none",
backgroundColor: backgroundColor:
selectedActionIndex === actionIndex selectedActionIndex === actionIndex
? "#3f3f3f" ? theme.palette.hoverColor
: "transparent", : "transparent",
border: "none", border: "none",
justifyContent: "flex-start", justifyContent: "flex-start",
@@ -1535,7 +1541,7 @@ const ActionsList = memo(({
textWrap: "nowrap", textWrap: "nowrap",
textOverflow: "ellipsis", textOverflow: "ellipsis",
"&:hover": { "&:hover": {
backgroundColor: "#2f2f2f", backgroundColor: theme.palette.hoverColor,
}, },
}} }}
onClick={() => handleActionClick(actionIndex, action)} onClick={() => handleActionClick(actionIndex, action)}
@@ -1554,7 +1560,7 @@ const ActionsList = memo(({
</span> </span>
<span <span
style={{ style={{
color: "white", color: theme.palette.textColor,
textOverflow: "ellipsis", textOverflow: "ellipsis",
overflow: "hidden", overflow: "hidden",
}} }}
@@ -1564,7 +1570,7 @@ const ActionsList = memo(({
</Button> </Button>
)) ))
) : ( ) : (
<div style={{ padding: "15px", color: "white", textAlign: "center" }}> <div style={{ padding: "15px", color: theme.palette.textColor, textAlign: "center" }}>
No actions found No actions found
</div> </div>
)} )}
@@ -1602,6 +1608,8 @@ const Action = memo((
const [disableExecuteButton, setDisableExecuteButton] = useState(false); const [disableExecuteButton, setDisableExecuteButton] = useState(false);
const [showResponseLoader, setShowResponseLoader] = useState(false); const [showResponseLoader, setShowResponseLoader] = useState(false);
const [appAuthentication, setAppAuthentication] = useState([]) const [appAuthentication, setAppAuthentication] = useState([])
const {themeMode} = useContext(Context);
const theme = getTheme(themeMode);
const parseHeaders = (headersString) => { const parseHeaders = (headersString) => {
if (headersString?.length > 0) { if (headersString?.length > 0) {
const headersArray = headersString.split("\n"); const headersArray = headersString.split("\n");
@@ -2082,7 +2090,7 @@ const Action = memo((
fontWeight: 700, fontWeight: 700,
marginLeft: 40, marginLeft: 40,
marginBottom: 5, marginBottom: 5,
color: "rgba(241, 241, 241, 1)", color: theme.palette.textColor
}} }}
> >
{actionname} {actionname}
@@ -2095,7 +2103,7 @@ const Action = memo((
borderRadius: 6, borderRadius: 6,
marginLeft: "40px", marginLeft: "40px",
marginTop: 2, marginTop: 2,
backgroundColor: "#212121", backgroundColor: theme.palette.textFieldStyle.backgroundColor,
height: 51, height: 51,
alignItems: 'center', alignItems: 'center',
}} }}
@@ -2109,7 +2117,7 @@ const Action = memo((
backgroundColor: "transparent", backgroundColor: "transparent",
"& .MuiSelect-select": { "& .MuiSelect-select": {
color: RequestMethods.find((method) => method.value === selectedMethod) color: RequestMethods.find((method) => method.value === selectedMethod)
?.color || "#212121", ?.color || theme.palette.textFieldStyle.backgroundColor,
}, },
}} }}
MenuProps={{ MenuProps={{
@@ -2117,14 +2125,14 @@ const Action = memo((
sx: { sx: {
padding: 0, padding: 0,
margin: 0, margin: 0,
backgroundColor: "#212121", backgroundColor: theme.palette.textFieldStyle.backgroundColor,
}, },
}, },
MenuListProps: { MenuListProps: {
sx: { sx: {
padding: 0, padding: 0,
margin: 0, margin: 0,
backgroundColor: "#212121", backgroundColor: theme.palette.textFieldStyle.backgroundColor,
}, },
}, },
}} }}
@@ -2135,20 +2143,20 @@ const Action = memo((
value={method.value} value={method.value}
sx={{ sx={{
color: method.color, color: method.color,
backgroundColor: "#212121", backgroundColor: theme.palette.textFieldStyle.backgroundColor,
border: "none", border: "none",
marginBottom: 0.25, marginBottom: 0.25,
"&:hover": { "&:hover": {
backgroundColor: method.color, backgroundColor: method.color,
color: "#f9fcf5", color: theme.palette.textFieldStyle.color,
}, },
"&.Mui-selected": { "&.Mui-selected": {
backgroundColor: method.color, backgroundColor: method.color,
color: "#f9fcf5", color: theme.palette.textFieldStyle.color,
border: "none", border: "none",
"&:hover": { "&:hover": {
backgroundColor: method.color, backgroundColor: method.color,
color: "#f9fcf5", color: theme.palette.textFieldStyle.color,
}, },
}, },
"&.Mui-focusVisible": { "&.Mui-focusVisible": {
@@ -2167,9 +2175,8 @@ const Action = memo((
inputProps={{ inputProps={{
style: { style: {
margin: "auto", margin: "auto",
backgroundColor: "transparent",
border: "none", border: "none",
color: "rgba(241, 241, 241, 1)", color: theme.palette.textFieldStyle.color,
display: 'flex', display: 'flex',
height: '100%', height: '100%',
alignItems: 'center', alignItems: 'center',
@@ -2331,7 +2338,7 @@ const Action = memo((
style={{ style={{
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
background: "rgba(26, 26, 26, 1)", background: theme.palette.backgroundColor,
}} }}
> >
<Tabs <Tabs
@@ -2386,7 +2393,7 @@ const Action = memo((
<TableContainer <TableContainer
style={{ style={{
minWidth: 696, minWidth: 696,
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
borderRadius: 6, borderRadius: 6,
border: "1px solid rgba(73, 73, 73, 1)", border: "1px solid rgba(73, 73, 73, 1)",
}} }}
@@ -2436,7 +2443,7 @@ const Action = memo((
} }
inputProps={{ inputProps={{
style: { style: {
backgroundColor: "rgba(33, 33, 33, 1)", backgroundColor: theme.palette.platformColor,
padding: "4px 8px", padding: "4px 8px",
}, },
}} }}
@@ -2494,7 +2501,7 @@ const Action = memo((
</InputAdornment> </InputAdornment>
), ),
style: { style: {
backgroundColor: "rgba(33, 33, 33, 1)", backgroundColor: theme.palette.platformColor,
padding: "4px 8px", padding: "4px 8px",
}, },
}} }}
@@ -2600,7 +2607,7 @@ const Action = memo((
<TableContainer <TableContainer
style={{ style={{
minWidth: 696, minWidth: 696,
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
borderRadius: 6, borderRadius: 6,
border: "1px solid rgba(73, 73, 73, 1)", border: "1px solid rgba(73, 73, 73, 1)",
}} }}
@@ -2640,7 +2647,7 @@ const Action = memo((
type="text" type="text"
inputProps={{ inputProps={{
style: { style: {
backgroundColor: "rgba(33, 33, 33, 1)", backgroundColor: theme.palette.platformColor ,
padding: "4px 8px", padding: "4px 8px",
}, },
}} }}
@@ -2694,7 +2701,7 @@ const Action = memo((
value={row.value} value={row.value}
inputProps={{ inputProps={{
style: { style: {
backgroundColor: "rgba(33, 33, 33, 1)", backgroundColor: theme.palette.platformColor,
padding: "4px 8px", padding: "4px 8px",
}, },
}} }}
@@ -2794,10 +2801,10 @@ const Action = memo((
marginLeft: 10 marginLeft: 10
}} }}
> >
<span style={{ fontSize: 16, fontWeight: 600 }}> <span style={{ fontSize: 16, fontWeight: 600, color: theme.palette.textColor }}>
{action.name.replaceAll("_", " ")} {action.name.replaceAll("_", " ")}
</span> </span>
<p > <p style={{color: theme.palette.textColor}}>
{action.description {action.description
? action.description ? action.description
: ""} : ""}
@@ -2813,6 +2820,8 @@ const ActionResponse = memo(({ apiResponse, ExampleBody, isLoggedIn, isLoaded })
const [responseTabIndex, setResponseTabIndex] = useState(0) const [responseTabIndex, setResponseTabIndex] = useState(0)
const [oldResponse, setOldResponse] = useState(apiResponse) const [oldResponse, setOldResponse] = useState(apiResponse)
const [highlight, setHighlight] = useState(false) const [highlight, setHighlight] = useState(false)
const {themeMode } = useContext(Context)
const theme = getTheme(themeMode)
const MIN_HEIGHT = 50 const MIN_HEIGHT = 50
@@ -2934,7 +2943,7 @@ const ActionResponse = memo(({ apiResponse, ExampleBody, isLoggedIn, isLoaded })
style={{ style={{
width: '100%', width: '100%',
height: height, height: height,
backgroundColor: '#1a1a1a', backgroundColor: theme.palette.backgroundColor,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
borderTop: '1px solid rgba(255,255,255,0.2)', borderTop: '1px solid rgba(255,255,255,0.2)',
@@ -2988,7 +2997,7 @@ const ActionResponse = memo(({ apiResponse, ExampleBody, isLoggedIn, isLoaded })
<ReactJson <ReactJson
src={formData(ExampleBody)} src={formData(ExampleBody)}
theme={theme.palette.jsonTheme} theme={theme.palette.jsonTheme}
style={{ backgroundColor: '#1a1a1a', padding: 5 }} style={{...theme.palette.reactJsonStyle, border: "none"}}
collapsed={false} collapsed={false}
iconStyle={theme.palette.jsonIconStyle} iconStyle={theme.palette.jsonIconStyle}
collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength} collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
@@ -3005,6 +3014,10 @@ const ActionResponse = memo(({ apiResponse, ExampleBody, isLoggedIn, isLoaded })
}); });
const ResponseTabWrapper = memo(({ apiResponse }) => { const ResponseTabWrapper = memo(({ apiResponse }) => {
const {themeMode } = useContext(Context)
const theme = getTheme(themeMode)
const handleReactJsonClipboard = (copy) => { const handleReactJsonClipboard = (copy) => {
const elementName = "copy_element_shuffle"; const elementName = "copy_element_shuffle";
let copyText = document.getElementById(elementName); let copyText = document.getElementById(elementName);
@@ -3036,7 +3049,7 @@ const ResponseTabWrapper = memo(({ apiResponse }) => {
<ReactJson <ReactJson
src={apiResponse} src={apiResponse}
theme={theme.palette.jsonTheme} theme={theme.palette.jsonTheme}
style={{ backgroundColor: "#1a1a1a", padding: 5 }} style={{...theme.palette.reactJsonStyle, border: "none"}}
shouldCollapse={(jsonField) => { shouldCollapse={(jsonField) => {
return collapseField(jsonField) return collapseField(jsonField)
}} }}
@@ -3049,7 +3062,8 @@ const ResponseTabWrapper = memo(({ apiResponse }) => {
)}) )})
const PaddingWrapper = memo(({ isLoggedIn, isLoaded, children }) => { const PaddingWrapper = memo(({ isLoggedIn, isLoaded, children }) => {
const { leftSideBarOpenByClick, windowWidth } = useContext(Context); const { leftSideBarOpenByClick, windowWidth, themeMode } = useContext(Context);
const theme = getTheme(themeMode)
return ( return (
<div <div
style={{ style={{
@@ -3058,7 +3072,7 @@ const PaddingWrapper = memo(({ isLoggedIn, isLoaded, children }) => {
? windowWidth >= 1920 ? "calc(100% - 630px)" : "calc(100% - 570px)" ? windowWidth >= 1920 ? "calc(100% - 630px)" : "calc(100% - 570px)"
: windowWidth >= 1920 ? "calc(100vw - 460px)": "calc(100% - 410px)" : windowWidth >= 1920 ? "calc(100vw - 460px)": "calc(100% - 410px)"
: windowWidth >= 1920 ? "calc(100% - 370px)" : "calc(100% - 320px)", : windowWidth >= 1920 ? "calc(100% - 370px)" : "calc(100% - 320px)",
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
position: "fixed", position: "fixed",
bottom: 0, bottom: 0,
right: 0, right: 0,
+284 -75
View File
@@ -13,7 +13,7 @@ import {
} from "@mui/icons-material"; } from "@mui/icons-material";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
import { isMobile } from "react-device-detect" import { isMobile } from "react-device-detect"
@@ -66,7 +66,7 @@ import { Context } from '../context/ContextApi.jsx';
const searchClient = algoliasearch( const searchClient = algoliasearch(
"JNSS5CFDZZ", "JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240" "c8f882473ff42d41158430be09ec2b4e"
) )
const AppAuthTab = memo((props) => { const AppAuthTab = memo((props) => {
@@ -83,16 +83,17 @@ const AppAuthTab = memo((props) => {
const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState(""); const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState("");
const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]); const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]);
const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState(""); const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState("");
const [selectedSubOrg, setSelectedSubOrg] = useState([]);
const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState(""); const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState("");
const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]); const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]);
const [searchQuery, setSearchQuery] = React.useState(""); const [searchQuery, setSearchQuery] = React.useState("");
const [showAppModal, setShowAppModal] = useState(false) const [showAppModal, setShowAppModal] = useState(false)
const [selectedAuthId, setSelectedAuthId] = useState("");
const [showDistributionPopup, setShowDistributionPopup] = useState(false);
const [showAuthenticationLoader, setShowAuthenticationLoader] = useState(true) const [showAuthenticationLoader, setShowAuthenticationLoader] = useState(true)
const [showAppAuthGroupLoader, setShowAppAuthGroupLoader] = useState(true) const [showAppAuthGroupLoader, setShowAppAuthGroupLoader] = useState(true)
const changeDistribution = (data) => { const { themeMode, supportEmail, brandColor } = useContext(Context)
//changeDistributed(data, !isDistributed) const theme = getTheme(themeMode, brandColor)
editAuthenticationConfig(data.id, "suborg_distribute")
}
useEffect(() => { useEffect(() => {
getAppAuthentication(); getAppAuthentication();
@@ -212,11 +213,39 @@ const AppAuthTab = memo((props) => {
}); });
}; };
const editAuthenticationConfig = (id, parentAction) => { 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 editAuthenticationConfig = (id, parentAction, selectedSuborgs) => {
const data = { const data = {
id: id, id: id,
action: parentAction !== undefined && parentAction !== null ? parentAction : "assign_everywhere", action: parentAction !== undefined && parentAction !== null ? parentAction : "assign_everywhere",
} selected_suborgs: selectedSuborgs !== undefined && selectedSuborgs !== null ? selectedSuborgs : [],
}
const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config"; const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config";
@@ -238,6 +267,7 @@ const AppAuthTab = memo((props) => {
} else { } else {
toast("Successfully updated auth!"); toast("Successfully updated auth!");
setSelectedUserModalOpen(false); setSelectedUserModalOpen(false);
setShowDistributionPopup(false);
setTimeout(() => { setTimeout(() => {
getAppAuthentication(); getAppAuthentication();
}, 1000); }, 1000);
@@ -249,6 +279,106 @@ const AppAuthTab = memo((props) => {
}); });
}; };
const changeDistribution = (id, selectedSubOrg) => {
editAuthenticationConfig(id, "suborg_distribute", [...new Set(selectedSubOrg)])
}
const cacheDistributionModal = showDistributionPopup ? (
<Dialog
open={showDistributionPopup}
onClose={() => {setShowDistributionPopup(false);setSelectedAuthId("")}}
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>
<Typography variant="h5" color="textPrimary">
Select sub-org to distribute Datastore key
</Typography>
</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: theme.palette.primary.main }}
onClick={() => {setShowDistributionPopup(false); setSelectedAuthId("")}}
color="primary"
>
Cancel
</Button>
<Button
variant="contained"
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, marginLeft: 10 }}
onClick={() => {
changeDistribution(selectedAuthId, selectedSubOrg);
}}
color="primary"
>
Submit
</Button>
</div>
</DialogContent>
</Dialog>
) : null;
const editAuthenticationModal = selectedAuthenticationModalOpen ? ( const editAuthenticationModal = selectedAuthenticationModalOpen ? (
<Dialog <Dialog
open={selectedAuthenticationModalOpen} open={selectedAuthenticationModalOpen}
@@ -277,7 +407,7 @@ const AppAuthTab = memo((props) => {
}} }}
> >
<DialogTitle> <DialogTitle>
<span style={{ color: "white" }}> <span style={{ color: theme.palette.textColor }}>
Edit authentication for {selectedAuthentication.app.name.replaceAll("_", " ")} ( Edit authentication for {selectedAuthentication.app.name.replaceAll("_", " ")} (
{selectedAuthentication.label}) {selectedAuthentication.label})
</span> </span>
@@ -297,7 +427,7 @@ const AppAuthTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: 50, height: 50,
color: "white", color: theme.palette.textColor,
}, },
}} }}
color="primary" color="primary"
@@ -349,7 +479,7 @@ const AppAuthTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: 50, height: 50,
color: "white", color: theme.palette.textColor,
}, },
}} }}
color="primary" color="primary"
@@ -568,7 +698,7 @@ const AppAuthTab = memo((props) => {
}) })
.then((responseJson) => { .then((responseJson) => {
if (responseJson.success === false) { if (responseJson.success === false) {
toast("Failed to create. Please try again, or contact support@shuffler.io") toast(`Failed to create. Please try again, or contact ${supportEmail}`)
} else { } else {
// Close the modal // Close the modal
setAppAuthenticationGroupModalOpen(false) setAppAuthenticationGroupModalOpen(false)
@@ -660,7 +790,7 @@ const AppAuthTab = memo((props) => {
}} }}
> >
<DialogTitle> <DialogTitle>
<span style={{ color: "white" }}>App Authentication Groups</span> <span style={{ color: theme.palette.textColor }}>App Authentication Groups</span>
</DialogTitle> </DialogTitle>
<DialogContent style={{marginLeft: 0, paddingLeft: 0, }}> <DialogContent style={{marginLeft: 0, paddingLeft: 0, }}>
@@ -677,7 +807,7 @@ const AppAuthTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: "50px", height: "50px",
color: "white", color: theme.palette.textColor,
fontSize: "1em", fontSize: "1em",
}, },
}} }}
@@ -842,7 +972,7 @@ const AppAuthTab = memo((props) => {
hitsPerPage={5} hitsPerPage={5}
searchQuery={searchQuery} searchQuery={searchQuery}
globalUrl={globalUrl} globalUrl={globalUrl}
isCloud={isCloud} isCloud={isCloud !== true}
userdata={userdata} userdata={userdata}
getAppAuthentication={getAppAuthentication} getAppAuthentication={getAppAuthentication}
/> />
@@ -853,35 +983,40 @@ const AppAuthTab = memo((props) => {
) : null; ) : null;
return ( return (
<div style={{width: "100%", minHeight: 1100, maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin',boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121',borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", }}> <div style={{width: "100%", minHeight: 1100, maxHeight: 1700, overflowY: "auto", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin',boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor,borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: theme.palette.defaultBorder, }}>
{appModal} {appModal}
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}> {cacheDistributionModal}
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
<div style={{ width: 'auto', display:'flex',}}> <div style={{ width: 'auto', display:'flex',}}>
<div style={{display: 'flex', flexDirection: 'column'}}> <div style={{display: 'flex', flexDirection: 'column'}}>
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#FFFFFF" }}>App Authentication</h2> <Typography variant='h5' style={{ marginBottom: 8, marginTop: 0, }}>App Authentication</Typography>
<div> <div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', }}>
<span style={{}}> <Typography variant='body2' color="textSecondary">
Control the authentication options for individual apps. Control the authentication options for individual apps.
</span> </Typography>
&nbsp; &nbsp;
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="/docs/organizations#app_authentication" href="/docs/organizations#app_authentication"
style={{ color: "#FF8444" }} style={{ color: theme.palette.linkColor }}
> >
Learn more about App Authentication Learn more about App Authentication
</a> </a>
</div> </div>
</div> </div>
<Button
style={{ color: '#1a1a1a', textTransform: 'none', backgroundColor: "#FF8444", marginLeft:"auto", borderRadius: 4,fontSize: 16, minWidth: 162, height: 40, boxShadow:'none', }} {isCloud ?
variant="contained" <Button
color="primary" style={{ textTransform: 'none', marginLeft:"auto", borderRadius: 4,fontSize: 16, minWidth: 162, height: 40, boxShadow:'none', }}
onClick={() => setShowAppModal(true)} variant="contained"
> color="primary"
Add App Auth disabled={!isCloud}
</Button> onClick={() => setShowAppModal(true)}
>
Add App Auth
</Button>
: null}
</div> </div>
{/* <Divider {/* <Divider
style={{ style={{
@@ -895,7 +1030,7 @@ const AppAuthTab = memo((props) => {
style={{ style={{
borderRadius: 4, borderRadius: 4,
marginTop: 24, marginTop: 24,
border: "1px solid #494949", border: theme.palette.defaultBorder,
width: "100%", width: "100%",
overflowX: "auto", overflowX: "auto",
paddingBottom: 0, paddingBottom: 0,
@@ -913,7 +1048,7 @@ const AppAuthTab = memo((props) => {
}}> }}>
<ListItem style={{ width: "100%", paddingTop: 10, paddingBottom: 10, paddingRight: 10, borderBottom: "1px solid #494949", display: 'table-row'}}> <ListItem style={{ width: "100%", paddingTop: 10, paddingBottom: 10, paddingRight: 10, borderBottom: theme.palette.defaultBorder, display: 'table-row'}}>
{["Valid", "Label", "App Name", "Workflows", "Fields", "Edited", "Actions", "Distribution"].map((header, index) => ( {["Valid", "Label", "App Name", "Workflows", "Fields", "Edited", "Actions", "Distribution"].map((header, index) => (
<ListItemText <ListItemText
@@ -924,7 +1059,7 @@ const AppAuthTab = memo((props) => {
padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px", padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px",
whiteSpace: "nowrap", whiteSpace: "nowrap",
textOverflow: "ellipsis", textOverflow: "ellipsis",
borderBottom: "1px solid #494949", borderBottom: theme.palette.defaultBorder,
position: "sticky", position: "sticky",
}} }}
/> />
@@ -938,7 +1073,7 @@ const AppAuthTab = memo((props) => {
key={rowIndex} key={rowIndex}
style={{ style={{
display: "table-row", display: "table-row",
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
}} }}
> >
{Array(8) {Array(8)
@@ -955,7 +1090,7 @@ const AppAuthTab = memo((props) => {
variant="text" variant="text"
animation="wave" animation="wave"
sx={{ sx={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.loaderColor,
height: "20px", height: "20px",
borderRadius: "4px", borderRadius: "4px",
}} }}
@@ -966,14 +1101,14 @@ const AppAuthTab = memo((props) => {
)) ))
: authentication?.length === 0 ? ( : authentication?.length === 0 ? (
<div style={{ textAlign: 'center'}}> <div style={{ textAlign: 'center'}}>
<Typography style={{ color: "#FFFFFF", textAlign: 'center', padding: 20}}> <Typography color="textPrimary" style={{ textAlign: 'center', padding: 20}}>
No authentication found. No authentication found.
</Typography> </Typography>
</div> </div>
):authentication.map((data, index) => { ):authentication.map((data, index) => {
var bgColor = "#212121"; var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF";
if (index % 2 === 0) { if (index % 2 === 0) {
bgColor = "#1A1A1A"; bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA";
} }
//console.log("Auth data: ", data) //console.log("Auth data: ", data)
@@ -998,7 +1133,7 @@ const AppAuthTab = memo((props) => {
]; ];
} }
const isDistributed = data.suborg_distributed === true ? true : false; const isDistributed = data?.suborg_distribution?.length > 0 || data?.suborg_distributed ? true : false;
var validIcon = <CheckCircleIcon style={{ color: "green" }} /> var validIcon = <CheckCircleIcon style={{ color: "green" }} />
if (data.validation !== null && data.validation !== undefined && data.validation.valid === false) { if (data.validation !== null && data.validation !== undefined && data.validation.valid === false) {
@@ -1055,7 +1190,7 @@ const AppAuthTab = memo((props) => {
<ListItemText <ListItemText
primary={ primary={
<Tooltip title={"Try the app in our API explorer"} placement="top"> <Tooltip title={"Try the app in our API explorer"} placement="top">
<a href={`/apis/${data.app.id}`} style={{ color: "#FF8444", textDecoration: "none", cursor: "pointer", }} target="_blank" rel="noopener noreferrer"> <a href={`/apis/${data.app.id}`} style={{ color: theme.palette.linkColor, textDecoration: "none", cursor: "pointer", }} target="_blank" rel="noopener noreferrer">
{data?.app?.name?.replaceAll("_", " ")} {data?.app?.name?.replaceAll("_", " ")}
</a> </a>
</Tooltip> </Tooltip>
@@ -1108,14 +1243,14 @@ const AppAuthTab = memo((props) => {
style={{ style={{
overflow: "hidden", overflow: "hidden",
display: "table-cell", display: "table-cell",
verticalAlign: 'middle' verticalAlign: 'middle',
}} }}
primaryTypographyProps={{ primaryTypographyProps={{
style: { style: {
padding: 8 padding: 8
} }
}} }}
primary={new Date(data.edited * 1000).toISOString()} primary={new Date(data.edited * 1000).toISOString()}
/> />
<ListItemText <ListItemText
style={{ style={{
@@ -1125,13 +1260,24 @@ const AppAuthTab = memo((props) => {
primaryTypographyProps={{ style: { display: "flex", flexDirection: 'row', padding: 8 } }} primaryTypographyProps={{ style: { display: "flex", flexDirection: 'row', padding: 8 } }}
> >
<IconButton <IconButton
onClick={() => { onClick={() => updateAppAuthentication(data)}
updateAppAuthentication(data); disabled={data.org_id !== selectedOrganization.id}
}} >
disabled={data.org_id !== selectedOrganization.id} <svg
> width="24"
<img src="/icons/editIcon.svg" alt="Edit icon" color="secondary" /> height="24"
</IconButton> viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.1038 4.66848C16.3158 4.45654 16.5674 4.28843 16.8443 4.17373C17.1212 4.05903 17.418 4 17.7177 4C18.0174 4 18.3142 4.05903 18.5911 4.17373C18.868 4.28843 19.1196 4.45654 19.3315 4.66848C19.5435 4.88041 19.7116 5.13201 19.8263 5.40891C19.941 5.68582 20 5.9826 20 6.28232C20 6.58204 19.941 6.87882 19.8263 7.15573C19.7116 7.43263 19.5435 7.68423 19.3315 7.89617L8.43807 18.7896L4 20L5.21038 15.5619L16.1038 4.66848Z"
stroke={themeMode=== "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</IconButton>
{data.defined ? ( {data.defined ? (
<Tooltip <Tooltip
color="primary" color="primary"
@@ -1147,7 +1293,7 @@ const AppAuthTab = memo((props) => {
editAuthenticationConfig(data.id); editAuthenticationConfig(data.id);
}} }}
> >
<SelectAllIcon color="secondary" /> <SelectAllIcon color="textSecondary" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
) : ( ) : (
@@ -1164,6 +1310,47 @@ const AppAuthTab = memo((props) => {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}
<Tooltip
title={"Copy Auth ID"}
style={{}}
aria-label={"copy"}
>
<IconButton
style = {{padding: "6px"}}
onClick={() => {
navigator.clipboard.writeText(data.id);
document.execCommand("copy");
toast(data.id + " copied to clipboard");
}}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="24"
height="24"
fillOpacity="1"
/>
<path
d="M14 4H7.6C7.17565 4 6.76869 4.16857 6.46863 4.46863C6.16857 4.76869 6 5.17565 6 5.6V18.4C6 18.8243 6.16857 19.2313 6.46863 19.5314C6.76869 19.8314 7.17565 20 7.6 20H17.2C17.6243 20 18.0313 19.8314 18.3314 19.5314C18.6314 19.2313 18.8 18.8243 18.8 18.4V8.8L14 4Z"
stroke={themeMode === "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14 4V8.8H18.8"
stroke={themeMode === "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</IconButton>
</Tooltip>
<IconButton <IconButton
style={{ }} style={{ }}
disabled={data.org_id !== selectedOrganization.id} disabled={data.org_id !== selectedOrganization.id}
@@ -1202,7 +1389,23 @@ const AppAuthTab = memo((props) => {
style={{ }} style={{ }}
color="secondary" color="secondary"
onClick={() => { onClick={() => {
changeDistribution(data, !isDistributed) setShowDistributionPopup(true)
if(data?.suborg_distribution?.length > 0){
setSelectedSubOrg(data.suborg_distribution)
}else{
setSelectedSubOrg([])
}
setSelectedAuthId(data.id)
if (data?.suborg_distributed) {
const allSuborg = userdata?.orgs?.map((data, index) => {
if (data.creator_org !== userdata.active_org.id) {
return null;
}
return data.id;
})
setSelectedSubOrg(allSuborg.filter((data) => data !== null))
}
}} }}
/> />
</Tooltip> </Tooltip>
@@ -1216,6 +1419,8 @@ const AppAuthTab = memo((props) => {
{editAuthenticationModal} {editAuthenticationModal}
{authenticationView} {authenticationView}
<div style={{marginTop: 50, }}> <div style={{marginTop: 50, }}>
{/*
<div style={{ marginTop: 150, marginBottom: 20 }}> <div style={{ marginTop: 150, marginBottom: 20 }}>
<h2 style={{ color: "#FFFFFF" }}>App Authentication Groups</h2> <h2 style={{ color: "#FFFFFF" }}>App Authentication Groups</h2>
<span style={{ marginLeft: 0 }}> <span style={{ marginLeft: 0 }}>
@@ -1414,13 +1619,14 @@ const AppAuthTab = memo((props) => {
); );
} }
)} )}
</List> </List>
</div> </div>
</div> </div>
</div> */}
</div> </div>
</div> </div>
</div>
); );
}); });
@@ -1432,6 +1638,9 @@ const SearchBox = ({ currentRefinement, refine, isSearchStalled, searchQuery, se
refine(searchQuery.trim()); refine(searchQuery.trim());
}; };
const { themeMode, supportEmail } = useContext(Context);
const theme = getTheme(themeMode);
return ( return (
<form noValidate action="" role="search"> <form noValidate action="" role="search">
<TextField <TextField
@@ -1445,7 +1654,7 @@ const SearchBox = ({ currentRefinement, refine, isSearchStalled, searchQuery, se
}} }}
InputProps={{ InputProps={{
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontSize: "1em", fontSize: "1em",
height: 50, height: 50,
borderRadius: 4, borderRadius: 4,
@@ -1461,7 +1670,7 @@ const SearchBox = ({ currentRefinement, refine, isSearchStalled, searchQuery, se
{searchQuery?.length > 0 && ( {searchQuery?.length > 0 && (
<ClearIcon <ClearIcon
style={{ style={{
color: "white", color: theme.palette.textColor,
cursor: "pointer", cursor: "pointer",
marginRight: 10 marginRight: 10
}} }}
@@ -1478,7 +1687,7 @@ const SearchBox = ({ currentRefinement, refine, isSearchStalled, searchQuery, se
style={{ style={{
backgroundImage: backgroundImage:
"linear-gradient(to right, rgb(248, 106, 62), rgb(243, 64, 121))", "linear-gradient(to right, rgb(248, 106, 62), rgb(243, 64, 121))",
color: "white", color: theme.palette.textColor,
border: "none", border: "none",
padding: "10px 20px", padding: "10px 20px",
width: 100, width: 100,
@@ -1516,8 +1725,6 @@ const SearchBox = ({ currentRefinement, refine, isSearchStalled, searchQuery, se
const Hits = ({ const Hits = ({
hits, hits,
insights,
setIsAnyAppActivated,
searchQuery, searchQuery,
isCloud, isCloud,
globalUrl, globalUrl,
@@ -1541,6 +1748,8 @@ const Hits = ({
} }
) )
const navigate = useNavigate(); const navigate = useNavigate();
const { themeMode, supportEmail } = useContext(Context);
const theme = getTheme(themeMode);
const normalizedString = (name) => { const normalizedString = (name) => {
if (typeof name === 'string') { if (typeof name === 'string') {
@@ -1725,7 +1934,7 @@ const Hits = ({
}) })
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
toast.error("Failed to get app data or App doesn't. Please contact support@shuffler.io"); toast.error(`Failed to get app data or App doesn't. Please contact ${supportEmail}`);
return; return;
} }
return response.json(); return response.json();
@@ -1943,7 +2152,7 @@ const Hits = ({
return ( return (
<div> <div>
<DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}> <DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
<div style={{ color: "white" }}> <div style={{ color: theme.palette.textColor }}>
Authentication for {selectedApp.name.replaceAll("_", " ", -1)} Authentication for {selectedApp.name.replaceAll("_", " ", -1)}
</div> </div>
</DialogTitle> </DialogTitle>
@@ -2013,7 +2222,7 @@ const Hits = ({
}} }}
style={{ style={{
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.surfaceColor,
color: "white", color: theme.palette.textColor,
height: 50, height: 50,
}} }}
> >
@@ -2021,7 +2230,7 @@ const Hits = ({
key={"false"} key={"false"}
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.textColor,
}} }}
value={"false"} value={"false"}
> >
@@ -2031,7 +2240,7 @@ const Hits = ({
key={"true"} key={"true"}
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.textColor,
}} }}
value={"true"} value={"true"}
> >
@@ -2110,7 +2319,7 @@ const Hits = ({
PaperProps={{ PaperProps={{
style: { style: {
pointerEvents: "auto", pointerEvents: "auto",
color: "white", color: theme.palette.textColor,
minWidth: 1100, minWidth: 1100,
minHeight: 700, minHeight: 700,
maxHeight: 700, maxHeight: 700,
@@ -2392,7 +2601,7 @@ const Hits = ({
href={selectedMeta.link} href={selectedMeta.link}
style={{ textDecoration: "none", color: "#f85a3e" }} style={{ textDecoration: "none", color: "#f85a3e" }}
> >
<Button style={{ color: "white", }} variant="outlined" color="secondary"> <Button style={{ color: theme.palette.textColor, }} variant="outlined" color="secondary">
<EditIcon /> &nbsp;&nbsp;Edit <EditIcon /> &nbsp;&nbsp;Edit
</Button> </Button>
</a> </a>
@@ -2403,7 +2612,7 @@ const Hits = ({
style={{ style={{
height: "100%", height: "100%",
width: 1, width: 1,
backgroundColor: "white", backgroundColor: theme.palette.textColor,
marginLeft: 50, marginLeft: 50,
marginRight: 50, marginRight: 50,
}} }}
@@ -2498,7 +2707,7 @@ const Hits = ({
height: 480, height: 480,
overflowY: "auto", overflowY: "auto",
scrollbarWidth: "thin", scrollbarWidth: "thin",
scrollbarColor: "#494949 #2f2f2f", scrollbarColor: theme.palette.scrollbarColor,
width: "100%", width: "100%",
}} }}
> >
@@ -2529,7 +2738,7 @@ const Hits = ({
elevation={0} elevation={0}
style={{ style={{
...paperStyle, ...paperStyle,
backgroundColor: mouseHoverIndex === index ? "#2F2F2F" : "rgba(26, 26, 26, 1)", backgroundColor: mouseHoverIndex === index ? theme.palette.cardHoverColor : theme.palette.cardBackgroundColor,
width: "100%", width: "100%",
}} }}
onMouseEnter={() => setMouseHoverIndex(index)} onMouseEnter={() => setMouseHoverIndex(index)}
@@ -2588,7 +2797,7 @@ const Hits = ({
gap: 8, gap: 8,
textOverflow: "ellipsis", textOverflow: "ellipsis",
whiteSpace: "nowrap", whiteSpace: "nowrap",
color: "#F1F1F1", color: theme.palette.textColor,
}} }}
> >
{normalizedString(data.name)} {normalizedString(data.name)}
@@ -2631,7 +2840,7 @@ const Hits = ({
))} ))}
</div> </div>
</div> </div>
<Button style={{position: 'relative', borderRadius: 6, bottom: 10, marginRight: 10, fontSize: 16, backgroundColor: '#ff8544', color: "#1a1a1a", textTransform:'none', marginLeft: 'auto'}} onClick={(e)=> {e.preventDefault();e.stopPropagation();handleAppAuthenticationNew(data)()}}> <Button variant='contained' color='primary' style={{position: 'relative', borderRadius: 6, bottom: 10, marginRight: 10, fontSize: 16, textTransform:'none', marginLeft: 'auto'}} onClick={(e)=> {e.preventDefault();e.stopPropagation();handleAppAuthenticationNew(data)()}}>
Authenticate app Authenticate app
</Button> </Button>
</div> </div>
+41 -40
View File
@@ -22,7 +22,6 @@ import CreateIcon from '@mui/icons-material/Create'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import YAML from "yaml"; import YAML from "yaml";
const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
const [openApiModal, setOpenApiModal] = useState(false) const [openApiModal, setOpenApiModal] = useState(false)
const [generateAppModal, setGenerateAppModal] = useState(false) const [generateAppModal, setGenerateAppModal] = useState(false)
@@ -43,9 +42,8 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
const parsedStyle = { const parsedStyle = {
flex: 1, flex: 1,
padding: 20, padding: "30px 20px 20px",
margin: 12, margin: 12,
paddingTop: 30,
backgroundColor: hover && !makeFancy ? theme.palette.surfaceColor : "transparent", backgroundColor: hover && !makeFancy ? theme.palette.surfaceColor : "transparent",
cursor: hover ? "pointer" : "default", cursor: hover ? "pointer" : "default",
textAlign: "center", textAlign: "center",
@@ -59,6 +57,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
: "1px solid rgba(255,255,255,0.3)", : "1px solid rgba(255,255,255,0.3)",
borderImage: makeFancy ? "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1" : "none", borderImage: makeFancy ? "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1" : "none",
transition: 'all 0.2s ease-in-out', transition: 'all 0.2s ease-in-out',
paddingBottom: isCloud ? 0 : 175,
} }
return ( return (
@@ -256,26 +255,27 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
body: openApidata, body: openApidata,
credentials: "include", credentials: "include",
}) })
.then((response) => { .then((response) => {
setValidation(false); setValidation(false);
return response.json(); return response.json();
}) })
.then((responseJson) => { .then((responseJson) => {
if (responseJson.success) { if (responseJson?.success === true) {
setAppValidation(responseJson.id); setAppValidation(responseJson?.id)
} else { navigate(`/apps/new?id=${responseJson?.id}`)
if (responseJson.reason !== undefined) { } else {
setOpenApiError(responseJson.reason); if (responseJson.reason !== undefined) {
} setOpenApiError(responseJson.reason)
toast("An error occurred in the response"); }
} toast("An error occurred in the response");
}) }
.catch((error) => { })
setValidation(false); .catch((error) => {
toast(error.toString()); setValidation(false);
setOpenApiError(error.toString()); toast(error.toString());
}); setOpenApiError(error.toString());
});
}; };
const redirectOpenApi = () => { const redirectOpenApi = () => {
@@ -345,23 +345,23 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
// Common dialog styles // Common dialog styles
const dialogStyle = { const dialogStyle = {
borderRadius: 2, borderRadius: 2,
border: "1px solid #494949", border: theme.palette.DialogStyle.border,
minWidth: '500px', minWidth: '500px',
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
backgroundColor: "#1A1A1A", backgroundColor: theme.palette.DialogStyle.backgroundColor,
zIndex: 1000, zIndex: 1000,
'& .MuiDialogContent-root': { '& .MuiDialogContent-root': {
backgroundColor: "#1A1A1A", backgroundColor: theme.palette.DialogStyle.backgroundColor,
padding: '24px', padding: '24px',
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
}, },
'& .MuiDialogTitle-root': { '& .MuiDialogTitle-root': {
backgroundColor: "#1A1A1A", backgroundColor: theme.palette.DialogStyle.backgroundColor,
padding: '24px', padding: '24px',
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
}, },
'& .MuiDialogActions-root': { '& .MuiDialogActions-root': {
backgroundColor: "#1A1A1A", backgroundColor: theme.palette.DialogStyle.backgroundColor,
padding: '16px 24px', padding: '16px 24px',
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
}, },
@@ -396,7 +396,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
pl: 4, pl: 4,
pr: 3, pr: 3,
}}> }}>
<Typography variant="h5" sx={{ fontWeight: 500, color: "#F1F1F1" }}> <Typography variant="h5"color="textPrimary" sx={{ fontWeight: 500, }}>
Create New App Create New App
</Typography> </Typography>
<IconButton <IconButton
@@ -408,15 +408,15 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
onClose() onClose()
}} }}
sx={{ sx={{
color: 'rgba(255, 255, 255, 0.7)', color: theme.palette.text.primary,
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' } '&:hover': { bgcolor: theme.palette.hoverColor },
}} }}
> >
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent sx={{ p: 3 }}> <DialogContent sx={{ p: 3 }}>
<div style={{ display: "flex", gap: '16px' }}> <div style={{ display: "flex", gap: 16, }}>
<AppCreateButton <AppCreateButton
text="Upload OpenAPI or Swagger" text="Upload OpenAPI or Swagger"
func={() => { func={() => {
@@ -476,7 +476,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
px: 4, px: 4,
}}> }}>
<Typography variant="h6" sx={{ <Typography variant="h6" sx={{
color: '#F1F1F1', color: theme.palette.text.primary,
fontWeight: 500, fontWeight: 500,
fontFamily: theme?.typography?.fontFamily fontFamily: theme?.typography?.fontFamily
}}> }}>
@@ -501,7 +501,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
</DialogTitle> </DialogTitle>
<DialogContent sx={{ px: 4, py: 3 }}> <DialogContent sx={{ px: 4, py: 3 }}>
<div style={{ display: "flex", fontSize: '14px', gap: '5px', alignItems: 'center', marginBottom: '10px', fontFamily: theme?.typography?.fontFamily, marginTop: '15px' }}> <div style={{ display: "flex", fontSize: '14px', gap: '5px', alignItems: 'center', marginBottom: '10px', fontFamily: theme?.typography?.fontFamily, marginTop: '15px' }}>
<Typography sx={{ color: 'rgba(255,255,255,0.85)', fontSize: '16px' }}> <Typography sx={{ color: theme.palette.text.primary, fontSize: '16px' }}>
Paste in the URI for the OpenAPI or find out Paste in the URI for the OpenAPI or find out
</Typography> </Typography>
<Link style={{ <Link style={{
@@ -582,6 +582,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
py: 1, py: 1,
'&:hover': { '&:hover': {
borderColor: '#FF8544', borderColor: '#FF8544',
color: '#FF8544',
bgcolor: 'rgba(255,133,68,0.1)' bgcolor: 'rgba(255,133,68,0.1)'
}, },
textTransform: 'none', textTransform: 'none',
@@ -664,7 +665,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
px: 4, px: 4,
}}> }}>
<Typography variant="h6" sx={{ <Typography variant="h6" sx={{
color: '#F1F1F1', color: theme.palette.text.primary,
fontWeight: 500, fontWeight: 500,
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
}}> }}>
@@ -680,8 +681,8 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
setValidation(false) setValidation(false)
}} }}
sx={{ sx={{
color: 'rgba(255,255,255,0.7)', color: theme.palette.text.primary,
'&:hover': { bgcolor: 'rgba(255,255,255,0.1)' } '&:hover': { bgcolor: theme.palette.hoverColor }
}} }}
> >
<CloseIcon /> <CloseIcon />
@@ -689,7 +690,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
</DialogTitle> </DialogTitle>
<DialogContent sx={{ px: 4, py: 3, pt: 0 }}> <DialogContent sx={{ px: 4, py: 3, pt: 0 }}>
<Typography sx={{ <Typography sx={{
color: 'rgba(255,255,255,0.85)', color: theme.palette.text.primary,
mb: 2, mb: 2,
fontSize: '14px', fontSize: '14px',
mt: 2, mt: 2,
@@ -705,10 +706,10 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
variant="outlined" variant="outlined"
placeholder="API Documentation URL" placeholder="API Documentation URL"
sx={{ sx={{
bgcolor: theme.palette.platformColor, bgcolor: theme.palette.textFieldStyle.backgroundColor,
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
height: '40px', height: '40px',
color: 'white', color: theme.palette.text.primary,
'& fieldset': { '& fieldset': {
borderWidth: '1px', borderWidth: '1px',
borderImage: "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1", borderImage: "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1",
@@ -756,7 +757,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
{circularLoader} {circularLoader}
{ {
!validation && !validation &&
<Typography sx={{ color: '#c5c5c5', fontSize: '14px', fontFamily: theme?.typography?.fontFamily, }}> <Typography color="textSecondary" sx={{ fontSize: '14px', fontFamily: theme?.typography?.fontFamily, }}>
This may take multiple minutes based on the size of the documentation. This may take multiple minutes based on the size of the documentation.
</Typography> </Typography>
} }
+1 -1
View File
@@ -198,7 +198,7 @@ export const findSpecificApp = (framework, inputcategory) => {
id: "", id: "",
} }
} else { } else {
console.log("findSpecificApp: unknown category: ", category) //console.log("findSpecificApp: unknown category: ", category)
} }
return null return null
+1 -1
View File
@@ -53,7 +53,7 @@ import {
const searchClient = algoliasearch( const searchClient = algoliasearch(
"JNSS5CFDZZ", "JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240" "c8f882473ff42d41158430be09ec2b4e"
); );
//const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6")
+85 -44
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { memo, useCallback, useEffect, useState, useContext } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { import {
@@ -25,12 +25,20 @@ import LaunchIcon from '@mui/icons-material/Launch';
import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { CloudDownloadOutlined, Delete } from '@mui/icons-material'; import { CloudDownloadOutlined, Delete } from '@mui/icons-material';
import { findSpecificApp } from '../components/AppFramework.jsx'; import { findSpecificApp } from '../components/AppFramework.jsx';
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
import YAML from 'yaml'; import YAML from 'yaml';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { InstantSearch, connectHits, connectSearchBox } from 'react-instantsearch-dom';
import algoliasearch from "algoliasearch/lite";
import { Context } from '../context/ContextApi.jsx';
const AppModal = ({ open, onClose, app, globalUrl, getApps }) => { const searchClient = algoliasearch(
"JNSS5CFDZZ",
"c8f882473ff42d41158430be09ec2b4e"
);;
const AppModal = ({ open, onClose, app, globalUrl, getApps}) => {
const [frameworkData, setFrameworkData] = useState({}) const [frameworkData, setFrameworkData] = useState({})
const [userdata, setUserdata] = useState({}) const [userdata, setUserdata] = useState({})
@@ -44,6 +52,9 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
const [deleteModalOpen, setDeleteModalOpen] = useState(false) const [deleteModalOpen, setDeleteModalOpen] = useState(false)
const [sharingConfiguration, setSharingConfiguration] = React.useState("you"); const [sharingConfiguration, setSharingConfiguration] = React.useState("you");
const navigate = useNavigate(); const navigate = useNavigate();
const {themeMode} = useContext(Context);
const theme = getTheme(themeMode);
const parseUsecase = (subcase) => { const parseUsecase = (subcase) => {
const srcdata = findSpecificApp(frameworkData, subcase.type) const srcdata = findSpecificApp(frameworkData, subcase.type)
const dstdata = findSpecificApp(frameworkData, subcase.last) const dstdata = findSpecificApp(frameworkData, subcase.last)
@@ -79,8 +90,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
}); });
}, [app]); }, [app]);
const getFramework = () => {
const getFramework = () => {
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
method: "GET", method: "GET",
headers: { headers: {
@@ -295,8 +305,6 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
}) })
} }
useEffect(() => { useEffect(() => {
setUsecaseLoading(true) setUsecaseLoading(true)
getAvailableWorkflows() getAvailableWorkflows()
@@ -329,6 +337,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
//delete apps from local storage //delete apps from local storage
localStorage.removeItem("apps"); localStorage.removeItem("apps");
getApps(); getApps();
onClose();
}, 1000); }, 1000);
} else { } else {
toast("Failed deleting app. Does it still exist?"); toast("Failed deleting app. Does it still exist?");
@@ -511,12 +520,12 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
border: "1px solid #494949", border: "1px solid #494949",
minWidth: '440px', minWidth: '440px',
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
backgroundColor: "#212121", backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
'& .MuiDialogContent-root': { '& .MuiDialogContent-root': {
backgroundColor: "#212121", backgroundColor: theme.palette.DialogStyle.backgroundColor,
}, },
'& .MuiDialogTitle-root': { '& .MuiDialogTitle-root': {
backgroundColor: "#212121", backgroundColor: theme.palette.DialogStyle.backgroundColor,
}, },
'& .MuiTypography-root': { '& .MuiTypography-root': {
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
@@ -548,7 +557,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
<IconButton <IconButton
onClick={onClose} onClick={onClose}
sx={{ sx={{
color: 'rgba(255, 255, 255, 0.7)', color: theme.palette.textColor,
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' } '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' }
}} }}
style={{ style={{
@@ -628,16 +637,14 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
> >
<Button <Button
variant="contained" variant="contained"
color="secondary"
sx={{ sx={{
bgcolor: '#494949',
'&:hover': { bgcolor: '#494949' },
textTransform: 'none', textTransform: 'none',
borderRadius: 1, borderRadius: 1,
minWidth: '45px', minWidth: '45px',
width: '45px', width: '45px',
height: '40px', height: '40px',
padding: 2, padding: 2,
color: "#fff",
fontFamily: theme?.typography?.fontFamily fontFamily: theme?.typography?.fontFamily
}} }}
onClick={(event) => { onClick={(event) => {
@@ -653,19 +660,16 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
{(userdata?.id === app?.owner)? ( {(userdata?.id === app?.owner)? (
<Tooltip title={"Delete app (confirm box will show)"}> <Tooltip title={"Delete app (confirm box will show)"}>
<Button <Button
variant="outlined" variant="contained"
component="label" component="label"
color="primary" color="secondary"
sx={{ sx={{
bgcolor: '#494949',
'&:hover': { bgcolor: '#494949', border: 'none' },
textTransform: 'none', textTransform: 'none',
borderRadius: 1, borderRadius: 1,
minWidth: '45px', minWidth: '45px',
width: '45px', width: '45px',
height: '40px', height: '40px',
padding: 2, padding: 2,
color: "#fff",
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
border: 'none' border: 'none'
}} }}
@@ -682,15 +686,13 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
{(canEditApp && app?.generated) && ( {(canEditApp && app?.generated) && (
<Button <Button
variant="contained" variant="contained"
color="secondary"
sx={{ sx={{
bgcolor: "#494949",
'&:hover': { bgcolor: '#494949' },
textTransform: 'none', textTransform: 'none',
borderRadius: 1, borderRadius: 1,
py: 1, py: 1,
px: 3, px: 3,
height: '40px', height: '40px',
color: "#fff",
fontFamily: theme?.typography?.fontFamily fontFamily: theme?.typography?.fontFamily
}} }}
startIcon={canEditApp ? <EditIcon /> : <ForkRightIcon />} startIcon={canEditApp ? <EditIcon /> : <ForkRightIcon />}
@@ -720,22 +722,11 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
textAlign: "start", textAlign: "start",
flex: 1, flex: 1,
}}> }}>
<Typography <WorkflowCard app={app}/>
variant="h6"
sx={{
fontFamily: theme?.typography?.fontFamily,
fontSize: '24px',
fontWeight: 600,
mb: 0.3,
color: '#fff'
}}
>
20
</Typography>
<Typography <Typography
variant="body2" variant="body2"
sx={{ sx={{
color: 'rgba(255, 255, 255, 0.7)', color: theme.palette.textColor,
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
fontSize: '14px' fontSize: '14px'
}} }}
@@ -746,7 +737,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
<div style={{ <div style={{
flex: 1, flex: 1,
textAlign: "start", textAlign: "start",
borderLeft: "1px solid rgba(255, 255, 255, 0.12)", borderLeft: theme.palette.defaultBorder,
paddingLeft: "10px", paddingLeft: "10px",
height: "100%", height: "100%",
}}> }}>
@@ -758,7 +749,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
}}> }}>
{Array.isArray(app?.actions) ? app.actions.length : app?.actions} {Array.isArray(app?.actions) ? app.actions.length : app?.actions}
</Typography> </Typography>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}> <Typography variant="body2" sx={{ color: theme.palette.textColor }}>
Actions Actions
</Typography> </Typography>
</div> </div>
@@ -768,14 +759,14 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
paddingLeft: "10px", paddingLeft: "10px",
paddingTop: "5px" paddingTop: "5px"
}}> }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: "5px", fontFamily: theme?.typography?.fontFamily, fontSize: "14px", fontWeight: 600, color: 'white' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: "5px", fontFamily: theme?.typography?.fontFamily, fontSize: "14px", fontWeight: 600, color: theme.palette.text.primary }}>
{ {
app?.collection ? ( app?.collection ? (
<> <>
<CheckCircleIcon sx={{ color: '#4CAF50' }} /> <CheckCircleIcon sx={{ color: '#4CAF50' }} />
<Typography variant="body1" sx={{ <Typography variant="body1" sx={{
fontWeight: 500, fontWeight: 500,
color: '#fff', color: theme.palette.textColor,
marginTop: "1px", marginTop: "1px",
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
fontSize: "16px" fontSize: "16px"
@@ -789,7 +780,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
fontSize: "16px", fontSize: "16px",
fontWeight: 500, fontWeight: 500,
marginTop: "1px", marginTop: "1px",
color: 'rgba(255, 255, 255, 0.7)', color: theme.palette.textColor,
fontFamily: theme?.typography?.fontFamily fontFamily: theme?.typography?.fontFamily
}}> }}>
No collection yet No collection yet
@@ -838,7 +829,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
)} )}
<Box sx={{ <Box sx={{
bgcolor: '#2F2F2F', bgcolor: themeMode === "dark" ? "#1E1E1E" : "#F5F5F5",
p: 2, p: 2,
borderRadius: 2, borderRadius: 2,
display: 'flex', display: 'flex',
@@ -901,16 +892,14 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
<div style={{ display: "flex", justifyContent: "center", fontFamily: theme?.typography?.fontFamily }}> <div style={{ display: "flex", justifyContent: "center", fontFamily: theme?.typography?.fontFamily }}>
<Button <Button
variant="contained" variant="contained"
color="primary"
sx={{ sx={{
bgcolor: '#FF8544',
'&:hover': { bgcolor: '#FF8544' },
textTransform: 'none', textTransform: 'none',
borderRadius: "4px", borderRadius: "4px",
py: 1, py: 1,
px: 7, px: 7,
fontSize: "14px", fontSize: "14px",
letterSpacing: "0.5px", letterSpacing: "0.5px",
color: "black",
fontFamily: theme?.typography?.fontFamily, fontFamily: theme?.typography?.fontFamily,
minWidth: '200px' minWidth: '200px'
}} }}
@@ -930,3 +919,55 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
}; };
export default AppModal; export default AppModal;
const WorkflowCard = memo(({ app }) => {
const [name, setName] = useState("");
const [relatedWorkflows, setRelatedWorkflows] = useState(0);
const WorkflowHits = ({ hits }) => {
useEffect(() => {
setRelatedWorkflows(hits?.length || 0);
}, [hits]);
return null;
};
const CustomWorkflowHits = connectHits(WorkflowHits);
const SearchBox = ({ refine, currentRefinement }) => {
useEffect(() => {
if (name.length > 0 && currentRefinement !== name) {
refine(name?.split(" ")[0]);
}
}, [name, refine, currentRefinement]);
return null;
};
const CustomSearchBox = connectSearchBox(SearchBox);
useEffect(() => {
if (app?.name && app.name.trim().length > 0 && name !== app.name) {
const formattedName = app.name
.charAt(0)
.toUpperCase() + app.name.substring(1)
.replaceAll("_", " ");
setName(formattedName);
}
}, [app?.name]);
return (
<Typography variant="h4">
{name.length > 0 ? (
<InstantSearch key={name} searchClient={searchClient} indexName="workflows">
<CustomSearchBox defaultRefinement={name?.split(" ")[0]}/>
<CustomWorkflowHits />
{relatedWorkflows}
</InstantSearch>
) : (
relatedWorkflows
)}
</Typography>
);
});
+1 -1
View File
@@ -13,7 +13,7 @@ import {
InputAdornment, InputAdornment,
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const Appsearch = props => { const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp, placeholder, const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp, placeholder,
+12 -9
View File
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef, useContext } from "react";
import theme from '../theme.jsx'; import {getTheme} from '../theme.jsx';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
import { useNavigate, Link } from 'react-router-dom'; import { useNavigate, Link } from 'react-router-dom';
import { isMobile } from 'react-device-detect'; import { isMobile } from 'react-device-detect';
@@ -36,9 +36,12 @@ import {
IconButton, IconButton,
} from '@mui/material'; } from '@mui/material';
import { Context } from "../context/ContextApi.jsx";
const AppSearchButtons = (props) => { const AppSearchButtons = (props) => {
const { userdata, globalUrl, appFramework, moreButton, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, getAppFramework, } = props const { userdata, globalUrl, appFramework, moreButton, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, getAppFramework, } = props
const { themeMode } = useContext(Context);
const theme = getTheme(themeMode);
const ref = useRef() const ref = useRef()
let navigate = useNavigate(); let navigate = useNavigate();
@@ -88,7 +91,7 @@ const AppSearchButtons = (props) => {
const foundApp = findSpecificApp(appFramework, appType) const foundApp = findSpecificApp(appFramework, appType)
if (foundApp === undefined || foundApp === null) { 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 return null
} }
@@ -195,13 +198,13 @@ const AppSearchButtons = (props) => {
zIndex: 100, zIndex: 100,
borderRadius: 6, borderRadius: 6,
border: "1px solid var(--Container-Stroke, #494949)", border: "1px solid var(--Container-Stroke, #494949)",
background: "var(--Container, #212121)", background: theme.palette.platformColor,
boxShadow: "8px 8px 32px 24px rgba(0, 0, 0, 0.16)", boxShadow: "8px 8px 32px 24px rgba(0, 0, 0, 0.16)",
}} }}
> >
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<div style={{ display: "flex", textAlign: "center", textTransform: "capitalize" }}> <div style={{ display: "flex", textAlign: "center", textTransform: "capitalize" }}>
<Typography style={{ padding: 16, color: "#FFFFFF", textTransform: "capitalize" }}> {discoveryData} </Typography> <Typography color="textPrimary" style={{ padding: 16, textTransform: "capitalize" }}> {discoveryData} </Typography>
</div> </div>
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<Tooltip <Tooltip
@@ -288,17 +291,17 @@ const AppSearchButtons = (props) => {
</div> </div>
) : null} ) : null}
<div style={{ <div style={{
display: "flex", height: 70, border: isHover ? "1px solid #f85a3e" : "var(--Container, #212121)", borderRadius: 8, background: isHover ? "var(--Container, #212121)" : "var(--Container, #212121)", display: "flex", height: 70, border: isHover ? "1px solid #f85a3e" : "var(--Container, #212121)", borderRadius: 4, background: isHover ? "var(--Container, #212121)" : "var(--Container, #212121)",
alignItems: "center", justifyContent: "center", alignItems: "center", justifyContent: "center",
}} }}
> >
<Button <Button
fullWidth fullWidth
color="secondary"
style={{ style={{
height: "100%", height: "100%",
width: "100%", width: "100%",
display: "grid" display: "grid",
backgroundColor: themeMode === "dark" ? "#212121" : "#F5F5F5",
}} }}
onClick={(event) => { onClick={(event) => {
if (onNodeSelect !== undefined) { if (onNodeSelect !== undefined) {
+8 -3
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useContext } from 'react';
import classNames from "classnames"; import classNames from "classnames";
import theme from '../theme.jsx'; import {getTheme} from '../theme.jsx';
import { import {
Tooltip, Tooltip,
@@ -14,6 +14,7 @@ import {
Chip, Chip,
Checkbox, Checkbox,
} from "@mui/material"; } from "@mui/material";
import { Context } from '../context/ContextApi.jsx';
import { import {
BarChart, BarChart,
@@ -73,6 +74,8 @@ const inputdata = {
const LineChartWrapper = ({keys, inputname, height, width}) => { const LineChartWrapper = ({keys, inputname, height, width}) => {
const [hovered, setHovered] = useState(""); const [hovered, setHovered] = useState("");
const {themeMode} = useContext(Context);
const theme = getTheme(themeMode)
//console.log("Date: ", new Date("2019-11-14T08:00:00.000Z")) //console.log("Date: ", new Date("2019-11-14T08:00:00.000Z"))
//var inputdata = keys.data //var inputdata = keys.data
@@ -166,6 +169,8 @@ const AppStats = (defaultprops) => {
const [searches, setSearches] = useState([]); const [searches, setSearches] = useState([]);
const [clickData, setClickData] = useState(undefined); const [clickData, setClickData] = useState(undefined);
const [conversionData, setConversionData] = useState(undefined); const [conversionData, setConversionData] = useState(undefined);
const {themeMode} = useContext(Context);
const theme = getTheme(themeMode)
const handleDataSetting = (inputdata, grouping) => { const handleDataSetting = (inputdata, grouping) => {
var newlist = [] var newlist = []
@@ -292,7 +297,7 @@ const AppStats = (defaultprops) => {
textAlign: "center", textAlign: "center",
padding: 40, padding: 40,
margin: 5, margin: 5,
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.surfaceColor,
} }
console.log("Widget: ", widgetData) console.log("Widget: ", widgetData)
+9 -8
View File
@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useContext } from 'react';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
import theme from '../theme.jsx'; import {getTheme} from '../theme.jsx';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material'; import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { Context } from '../context/ContextApi.jsx';
//import algoliasearch from 'algoliasearch/lite'; //import algoliasearch from 'algoliasearch/lite';
import algoliasearch from 'algoliasearch'; import algoliasearch from 'algoliasearch';
import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom';
@@ -20,10 +20,11 @@ import {
} from '@mui/material'; } from '@mui/material';
import aa from 'search-insights' import aa from 'search-insights'
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const Appsearch = props => { const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props
const { themeMode } = useContext(Context)
const theme = getTheme(themeMode)
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true"); const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs
@@ -54,10 +55,10 @@ const Appsearch = props => {
autoComplete="off" autoComplete="off"
autocomplete="off" autocomplete="off"
fullWidth fullWidth
style={{backgroundColor: "#2F2F2F", borderRadius: borderRadius, width: "100%",}} style={{backgroundColor: theme.palette.textFieldStyle.backgroundColor, borderRadius: borderRadius, width: "100%",}}
InputProps={{ InputProps={{
style:{ style:{
color: "white", color: theme.palette.textFieldStyle.color,
fontSize: "1em", fontSize: "1em",
height: 50, height: 50,
}, },
@@ -93,7 +94,7 @@ const Appsearch = props => {
<Grid container spacing={0} style={{border: "1px solid rgba(255,255,255,0.2)", maxHeight: 250, minHeight: 250, overflowY: "auto", overflowX: "hidden", }}> <Grid container spacing={0} style={{border: "1px solid rgba(255,255,255,0.2)", maxHeight: 250, minHeight: 250, overflowY: "auto", overflowX: "hidden", }}>
{hits.map((data, index) => { {hits.map((data, index) => {
const paperStyle = { const paperStyle = {
backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : "#2F2F2F", backgroundColor: index === mouseHoverIndex ? theme.palette.hoverColor : theme.palette.textFieldStyle.backgroundColor,
color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)", color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)",
// border: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e", // border: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e",
textAlign: "left", textAlign: "left",
File diff suppressed because it is too large Load Diff
+197 -120
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useContext, memo, useMemo } from 'react'; import React, { useState, useEffect, useContext, memo, useMemo } from 'react';
import theme from '../theme.jsx'; import {getTheme} from '../theme.jsx';
import classNames from "classnames"; import classNames from "classnames";
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid' import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'
@@ -28,12 +28,20 @@ import {
Paper, Paper,
Chip, Chip,
Checkbox, Checkbox,
Box,
} from "@mui/material"; } from "@mui/material";
import { import {
BarChart, BarChart,
BarSeries,
Bar,
BarLabel,
GridlineSeries, GridlineSeries,
Gridline, Gridline,
TooltipArea,
ChartTooltip,
TooltipTemplate,
} from 'reaviz'; } from 'reaviz';
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
@@ -42,31 +50,54 @@ import { Context } from '../context/ContextApi.jsx';
const LineChartWrapper = ({keys, inputname, height, width}) => { const LineChartWrapper = ({keys, inputname, height, width}) => {
const [hovered, setHovered] = useState(""); const [hovered, setHovered] = useState("");
const inputdata = keys.data === undefined ? keys : keys.data const inputdata = keys.data === undefined ? keys : keys.data
const {themeMode} = useContext(Context)
const theme = getTheme(themeMode)
return ( return (
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, padding: 30, marginTop: 15, backgroundColor: theme.palette.platformColor, overflow: "hidden", }}> <div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, padding: 30, marginTop: 15, backgroundColor: theme.palette.platformColor, overflow: "hidden", }}>
<Typography variant="h6" style={{marginBotton: 15, }}> <Typography variant="h6" style={{marginBotton: 30, }}>
{inputname} {inputname}
</Typography> </Typography>
<BarChart <BarChart
style={{marginTop: 100, }}
width={"100%"} width={"100%"}
height={height} height={height}
data={inputdata} data={inputdata}
series={
<BarSeries
bar={
<Bar />
}
/>
}
gridlines={ gridlines={
<GridlineSeries line={<Gridline direction="all" />} /> <GridlineSeries line={<Gridline direction="all" />} />
} }
/> />
</div> </div>
) )
} }
const AppStats = (defaultprops) => { const AppStats = (defaultprops) => {
const { globalUrl, selectedOrganization, userdata, isCloud, inputWorkflows,clickedFromOrgTab } = defaultprops; const {
globalUrl,
selectedOrganization,
userdata,
isCloud,
inputWorkflows,
clickedFromOrgTab,
syncStats,
} = defaultprops;
const [keys, setKeys] = useState([]) const [keys, setKeys] = useState([])
const [searches, setSearches] = useState([]); const [searches, setSearches] = useState([]);
const [appRuns, setAppruns] = useState(undefined); const [appRuns, setAppruns] = useState(undefined);
const [childOrgsAppRuns, setChildOrgsAppRuns] = useState(undefined);
const [appRunCosts, setApprunCosts] = useState(undefined); const [appRunCosts, setApprunCosts] = useState(undefined);
const [workflowRuns, setWorkflowRuns] = useState(undefined); const [workflowRuns, setWorkflowRuns] = useState(undefined);
const [subflowRuns, setSubflowRuns] = useState(undefined); const [subflowRuns, setSubflowRuns] = useState(undefined);
@@ -83,6 +114,8 @@ const AppStats = (defaultprops) => {
const [workflows, setWorkflows] = useState(inputWorkflows === undefined ? [] : inputWorkflows) const [workflows, setWorkflows] = useState(inputWorkflows === undefined ? [] : inputWorkflows)
const [resultRows, setResultRows] = useState([]) const [resultRows, setResultRows] = useState([])
const [resultLoading, setResultLoading] = useState(true) const [resultLoading, setResultLoading] = useState(true)
const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor)
const includedExecutions = selectedOrganization?.sync_features?.app_executions !== undefined ? selectedOrganization?.sync_features?.app_executions?.limit : 0 const includedExecutions = selectedOrganization?.sync_features?.app_executions !== undefined ? selectedOrganization?.sync_features?.app_executions?.limit : 0
@@ -94,9 +127,6 @@ const AppStats = (defaultprops) => {
const getWorkflowStats = async (workflow, startTime, endTime) => { const getWorkflowStats = async (workflow, startTime, endTime) => {
if (!userdata.support) {
return workflow
}
if (workflow.id === undefined || workflow.id === null || workflow.id === "") { if (workflow.id === undefined || workflow.id === null || workflow.id === "") {
return workflow return workflow
@@ -161,12 +191,8 @@ const AppStats = (defaultprops) => {
} }
const loadWorkflowStats = (foundWorkflows, startTime, endTime) => { const loadWorkflowStats = (foundWorkflows, startTime, endTime) => {
if (!userdata.support) {
return
}
if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) { if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) {
console.log("Not workflows") setResultLoading(false)
return return
} }
@@ -175,6 +201,9 @@ const AppStats = (defaultprops) => {
const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime)); const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime));
const allData = Promise.all(promises); const allData = Promise.all(promises);
if (allData === undefined || allData === null) {
setResultLoading(false)
}
allData.then((data) => { allData.then((data) => {
var total = 0 var total = 0
@@ -234,15 +263,16 @@ const AppStats = (defaultprops) => {
return return
} }
if (statistics["daily_statistics"] === undefined || statistics["daily_statistics"] === null) { const statKey = syncStats === true ? "onprem_stats" : "daily_statistics"
if (statistics[statKey] === undefined || statistics[statKey] === null) {
setFilteredStatistics(statistics) setFilteredStatistics(statistics)
return return
} }
// Calculate month to date cost // Calculate month to date cost
var mtd_cost = 0 var mtd_cost = 0
for (let key in statistics["daily_statistics"]) { for (let key in statistics[statKey]) {
const item = statistics["daily_statistics"][key] const item = statistics[statKey][key]
if (item["date"] === undefined) { if (item["date"] === undefined) {
continue continue
} }
@@ -300,8 +330,8 @@ const AppStats = (defaultprops) => {
// Check if start time is before the daily statistics["date"] string // Check if start time is before the daily statistics["date"] string
var newlist = [] var newlist = []
for (let key in statistics["daily_statistics"]) { for (let key in statistics[statKey]) {
const item = statistics["daily_statistics"][key] const item = statistics[statKey][key]
if (item["date"] === undefined) { if (item["date"] === undefined) {
continue continue
} }
@@ -332,7 +362,7 @@ const AppStats = (defaultprops) => {
var appexecutions = 0 var appexecutions = 0
var estimatedcost = 0 var estimatedcost = 0
if (newlist.length > 0) { if (newlist.length > 0) {
tmpstats["daily_statistics"] = newlist tmpstats[statKey] = newlist
for (let key in newlist) { for (let key in newlist) {
const item = newlist[key] const item = newlist[key]
@@ -386,7 +416,8 @@ const AppStats = (defaultprops) => {
return return
} }
const dailyStats = inputdata.daily_statistics const statKey = syncStats === true ? "onprem_stats" : "daily_statistics"
const dailyStats = inputdata[statKey]
if (dailyStats === undefined || dailyStats === null) { if (dailyStats === undefined || dailyStats === null) {
return return
} }
@@ -396,6 +427,11 @@ const AppStats = (defaultprops) => {
"data": [] "data": []
} }
var childorgappRuns = {
"key": "Child Org App Runs",
"data": []
}
var workflowRuns = { var workflowRuns = {
"key": "Workflow Runs (includes subflows)", "key": "Workflow Runs (includes subflows)",
"data": [] "data": []
@@ -437,6 +473,13 @@ const AppStats = (defaultprops) => {
}) })
} }
if (item["child_app_executions"] !== undefined && item["child_app_executions"] !== null) {
childorgappRuns["data"].push({
key: new Date(item["date"]),
data: inputdata["child_app_executions"]
})
}
// Check if workflow_executions key in item // Check if workflow_executions key in item
if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) { if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) {
workflowRuns["data"].push({ workflowRuns["data"].push({
@@ -466,6 +509,15 @@ const AppStats = (defaultprops) => {
}) })
} }
if (inputdata["daily_child_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) {
childorgappRuns["data"].push({
key: new Date(),
data: inputdata["daily_child_app_executions"]
})
//setApprunCosts(appcostRuns)
}
if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) { if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) {
workflowRuns["data"].push({ workflowRuns["data"].push({
key: new Date(), key: new Date(),
@@ -480,6 +532,11 @@ const AppStats = (defaultprops) => {
}) })
} }
// Only for parent orgs
if (childorgappRuns["data"].length > 0) {
setChildOrgsAppRuns(childorgappRuns)
}
setSubflowRuns(subflowRuns) setSubflowRuns(subflowRuns)
setWorkflowRuns(workflowRuns) setWorkflowRuns(workflowRuns)
setAppruns(appRuns) setAppruns(appRuns)
@@ -529,11 +586,14 @@ const AppStats = (defaultprops) => {
const paperStyle = { const paperStyle = {
textAlign: "center", textAlign: "center",
padding: 40, padding: "40px",
margin: 5, margin: "5px",
backgroundColor: theme.palette.platformColor, backgroundColor: theme.palette.cardBackgroundColor,
border: "1px solid rgba(255,255,255,0.3)", border: theme.palette.defaultBorder,
maxWidth: 300, maxWidth: "300px",
"&:hover": {
backgroundColor: theme.palette.cardHoverColor,
},
} }
const columns: GridColDef[] = [ const columns: GridColDef[] = [
@@ -646,85 +706,94 @@ const AppStats = (defaultprops) => {
<div className="content" style={{width: "100%", margin: "auto", }}> <div className="content" style={{width: "100%", margin: "auto", }}>
<Typography style={{margin: "auto", marginLeft: 10, marginBottom: 20, fontSize: 16}} color="textSecondary"> <Typography style={{margin: "auto", marginLeft: 10, marginBottom: 20, fontSize: 16}} color="textSecondary">
All shown statistics are gathered from <a All shown statistics are gathered from <a
href={`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`} href={`${globalUrl}/api/v1/orgs/${selectedOrganization?.id}/stats`}
target="_blank" target="_blank"
style={{ textDecoration: "none", color: "#FF8444",}} style={{ textDecoration: "none", color: theme.palette.linkColor,}}
>Your Organisation Statistics. </a> >Your Organisation Statistics. </a>
It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. <b>The billing tracker is in Beta, and is always calculated manually before being invoiced.</b> It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. <b>The billing tracker is in Beta, and is always calculated manually before being invoiced.</b>
<br style={{}}/>
{syncStats !== true ? null :
"PS: You are currently looking at data from your onprem synced org"}
</Typography> </Typography>
<div style={{display: "flex", flexDirection: "column", textAlign: "center",}}> <div style={{display: "flex", flexDirection: "column", textAlign: "center",}}>
<div style={{flexDirection: "row", }}> <div style={{flexDirection: "row", }}>
{filteredStatistics !== undefined ? {filteredStatistics !== undefined ?
<div style={{flex: 1, display: "flex", textAlign: "center",}}> <div style={{flex: 1, display: "flex", textAlign: "center",}}>
<Tooltip title={
<Typography variant="body1" style={{padding: 10, }}> {syncStats == true ? null :
The cost of app runs in the selected period based on {filteredStatistics.monthly_app_executions} App Runs. These numbers do not exclude your included 10.000/month or {includedExecutions} App Runs per month. App Run cost: ${invocationCost}. <Tooltip title={
</Typography> <Typography variant="body1" style={{padding: 10, }}>
}> The cost of app runs in the selected period based on {filteredStatistics.monthly_app_executions} App Runs. These numbers do not exclude your included 10.000/month or {includedExecutions} App Runs per month. App Run cost: ${invocationCost}.
<Paper style={paperStyle}>
<Typography variant="h4">
${selectedOrganization.lead_info.customer === false && selectedOrganization.lead_info.pov === false ?
0
:
apprunCost
}
</Typography> </Typography>
<Typography variant="h6"> }>
Period Cost <Box sx={paperStyle}>
</Typography> <Typography variant="h4">
</Paper> ${selectedOrganization?.lead_info?.customer === false && selectedOrganization?.lead_info?.pov === false ?
</Tooltip> 0
:
apprunCost
}
</Typography>
<Typography variant="h6">
Period Cost
</Typography>
</Box>
</Tooltip>
}
{syncStats === true ? null :
<Tooltip title={ <Tooltip title={
<Typography variant="body1" style={{padding: 10, }}> <Typography variant="body1" style={{padding: 10, }}>
App runs in the selected period App runs in the selected period
</Typography> </Typography>
}> }>
<Paper style={paperStyle}> <Box sx={paperStyle}>
<Typography variant="h4"> <Typography variant="h4">
{filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions} {filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions}
</Typography> </Typography>
<Typography variant="h6"> <Typography variant="h6">
App Runs App Runs
</Typography> </Typography>
</Paper> </Box>
</Tooltip> </Tooltip>
}
{syncStats === true ? null :
<Tooltip title={ <Tooltip title={
<Typography variant="body1" style={{padding: 10, }}> <Typography variant="body1" style={{padding: 10, }}>
Workflow runs in the selected period Workflow runs in the selected period
</Typography> </Typography>
}> }>
<Paper style={paperStyle}> <Box sx={paperStyle}>
<Typography variant="h4"> <Typography variant="h4">
{filteredStatistics.monthly_workflow_executions === null || filteredStatistics.monthly_workflow_executions === undefined ? 0 : filteredStatistics.monthly_workflow_executions} {filteredStatistics.monthly_workflow_executions === null || filteredStatistics.monthly_workflow_executions === undefined ? 0 : filteredStatistics.monthly_workflow_executions}
</Typography> </Typography>
<Typography variant="h6"> <Typography variant="h6">
Workflow Runs Workflow Runs
</Typography> </Typography>
</Paper> </Box>
</Tooltip> </Tooltip>
<Tooltip title={ }
<Typography variant="body1" style={{padding: 10, }}>
Estimated cost to be billed at the end of the current month. Subtracted contractually included app runs. Actual cost month to date: ${monthToDateCost}. App Run cost: ${invocationCost}. {syncStats === true ? null :
</Typography> <Tooltip title={
}> <Typography variant="body1" style={{padding: 10, }}>
<Paper style={{ Estimated cost to be billed at the end of the current month. Subtracted contractually included app runs. Actual cost month to date: ${monthToDateCost}. App Run cost: ${invocationCost}.
textAlign: "center",
padding: 40,
margin: 5,
marginLeft: clickedFromOrgTab? null:90,
backgroundColor: theme.palette.platformColor,
border: "1px solid rgba(255,255,255,0.3)",
maxWidth: 300,
}}>
<Typography variant="h4">
${monthTotalCost}
</Typography> </Typography>
<Typography variant="h6"> }>
Estimated cost <Box sx={paperStyle}>
</Typography> <Typography variant="h4">
</Paper> ${monthTotalCost}
</Tooltip> </Typography>
<Typography variant="h6">
Estimated cost
</Typography>
</Box>
</Tooltip>
}
</div> </div>
: null} : null}
</div> </div>
@@ -895,7 +964,13 @@ const AppStats = (defaultprops) => {
{appRuns === undefined ? {appRuns === undefined ?
null null
: :
<LineChartWrapper keys={appRuns} height={300} width={"100%"} inputname={"Daily App Runs"}/> <LineChartWrapper keys={appRuns} height={300} width={"100%"} inputname={"App Runs - Current Org"}/>
}
{childOrgsAppRuns === undefined ?
null
:
<LineChartWrapper keys={childOrgsAppRuns} height={300} width={"100%"} inputname={"Child Org App Runs"}/>
} }
{workflowRuns === undefined ? {workflowRuns === undefined ?
@@ -916,56 +991,58 @@ const AppStats = (defaultprops) => {
<LineChartWrapper keys={appRunCosts} height={300} width={"100%"} inputname={"Apprun cost - Cost per day"}/> <LineChartWrapper keys={appRunCosts} height={300} width={"100%"} inputname={"Apprun cost - Cost per day"}/>
*/} */}
{syncStats === true ? null :
<div style={{height: 150+resultRows.length * 25, padding: "10px 0px 10px 0px", }}>
{resultLoading ?
<div style={{margin: "auto", alignItems: "center", width: 350, height: "100%", }}>
<Typography variant="body2" color="textSecondary" component="p" style={{textAlign: "center", marginTop: 50, marginBottom: 15, }}>
Loading usage for selected period (may take a while)
<div style={{height: 150+resultRows.length * 25, padding: "10px 0px 10px 0px", }}> <CircularProgress style={{marginTop: 15, }} />
{resultLoading ? </Typography>
<div style={{margin: "auto", alignItems: "center", width: 350, height: "100%", }}> </div>
<Typography variant="body2" color="textSecondary" component="p" style={{textAlign: "center", marginTop: 50, marginBottom: 15, }}> :
Loading usage for selected period (may take a while) <DataGrid
</Typography> rows={resultRows}
<CircularProgress style={{}} /> columns={columns}
</div> pageSize={100}
: rowsPerPageOptions={[10, 20, 50, 100]}
<DataGrid checkboxSelection
rows={resultRows} disableSelectionOnClick
columns={columns} onPageSizeChange={(newPageSize) => {
pageSize={100} //setRowsPerPage(newPageSize)
rowsPerPageOptions={[10, 20, 50, 100]} //submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize)
checkboxSelection }}
disableSelectionOnClick // event for when clicking next page
onPageSizeChange={(newPageSize) => { // Hide page changer
//setRowsPerPage(newPageSize) onPageChange={(params) => {
//submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize) console.log("page params: ", params)
}} }}
// event for when clicking next page onSelectionModelChange={(newSelection) => {
// Hide page changer console.log("newSelection: ", newSelection)
onPageChange={(params) => { //console.log("newSelection: ", newSelection)
console.log("page params: ", params) //setSelectedWorkflowExecutionsIndexes(newSelection)
}} //var found = []
onSelectionModelChange={(newSelection) => { //for (var i = 0; i < newSelection.length; i++) {
console.log("newSelection: ", newSelection) // // Find the workflow in the resultRows
//console.log("newSelection: ", newSelection) // var selected = resultRows.find((workflow) => {
//setSelectedWorkflowExecutionsIndexes(newSelection) // return workflow.id === newSelection[i]
//var found = [] // })
//for (var i = 0; i < newSelection.length; i++) {
// // Find the workflow in the resultRows
// var selected = resultRows.find((workflow) => {
// return workflow.id === newSelection[i]
// })
// if (selected === undefined || selected === null) { // if (selected === undefined || selected === null) {
// continue // continue
// } // }
// found.push(selected) // found.push(selected)
//} //}
//setSelectedWorkflowExecutions(found) //setSelectedWorkflowExecutions(found)
}} }}
// Track which items are selected // Track which items are selected
/> />
} }
</div> </div>
}
</div> </div>
) )
+443 -22
View File
@@ -1,8 +1,7 @@
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext } from "react";
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
import { ToastContainer, toast } from "react-toastify" import { ToastContainer, toast } from "react-toastify"
import { import {
CheckCircle as CheckCircleIcon, CheckCircle as CheckCircleIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
@@ -13,8 +12,11 @@ import {
Divider, Divider,
Button, Button,
Tooltip, Tooltip,
Grid, ToggleButtonGroup,
Card, ToggleButton,
useMediaQuery,
TextField,
Box,
} from "@mui/material"; } from "@mui/material";
import { import {
@@ -27,18 +29,98 @@ import { Context } from "../context/ContextApi.jsx";
const Branding = (props) => { const Branding = (props) => {
const { globalUrl, userdata, serverside, billingInfo,clickedFromOrgTab, stripeKey, selectedOrganization, handleGetOrg, } = props; const { globalUrl, userdata, serverside, billingInfo,clickedFromOrgTab, stripeKey, selectedOrganization, handleGetOrg, } = props;
const { themeMode, handleThemeChange, supportEmail, setSupportEmail, logoutUrl, setLogoutUrl, brandColor, setBrandColor, setBrandName } = useContext(Context)
//const alert = useAlert(); //const alert = useAlert();
const [publishingInfo, setPublishingInfo] = useState(""); const [publishingInfo, setPublishingInfo] = useState("");
const [publishRequirements, setPublishRequirements] = useState([]) const [publishRequirements, setPublishRequirements] = useState([])
const [currentSelectedTheme, setCurrentSelectedTheme] = useState(themeMode);
const [integrationPartner, setIntegrationPartner] = useState(false);
const [changingTheme, setChangingTheme] = useState(false);
const theme = getTheme(themeMode, brandColor)
const [selectedBrandColor, setSelectedBrandColor] = useState(theme?.palette?.main || "#FF8544")
const [selectedBrandName, setSelectedBrandName] = useState(selectedOrganization?.branding?.brand_name || "")
const { leftSideBarOpenByClick } = useContext(Context) const [isLoading,setIsLoading] = useState(false);
const handleEditOrg = (joinStatus) => { const handleEditOrg = (joinStatus) => {
setIsLoading(true)
const data = { const data = {
"org_id": selectedOrganization.id, "org_id": selectedOrganization.id,
"creator_config": joinStatus,
}; };
if (joinStatus === "join" || joinStatus === "leave") {
data["creator_config"] = joinStatus
}
if (joinStatus === "light" || joinStatus === "dark" || joinStatus === "system") {
data["branding"] = {
"theme": joinStatus,
"enable_chat": selectedOrganization?.branding?.enable_chat || false,
"home_url": selectedOrganization?.branding?.home_url || "",
"brand_color": selectedOrganization?.branding?.brand_color || theme.palette.primary.main,
"brand_name": selectedOrganization?.branding?.brand_name || "",
"logout_url": selectedOrganization?.branding?.logout_url || "",
"support_email": selectedOrganization?.branding?.support_email || "",
}
data["editing_branding"] = true;
}
if (joinStatus === "brand_color") {
data["branding"] = {
"theme": selectedOrganization?.branding?.theme || "dark",
"enable_chat": selectedOrganization?.branding?.enable_chat || false,
"home_url": selectedOrganization?.branding?.home_url || "",
"brand_color": selectedBrandColor,
"brand_name": selectedOrganization?.branding?.brand_name || "",
"logout_url": selectedOrganization?.branding?.logout_url || "",
"support_email": selectedOrganization?.branding?.support_email || "",
}
data["editing_branding"] = true;
}
if (joinStatus === "brand_name") {
data["branding"] = {
"theme": selectedOrganization?.branding?.theme || "dark",
"enable_chat": selectedOrganization?.branding?.enable_chat || false,
"home_url": selectedOrganization?.branding?.home_url || "",
"brand_color": selectedOrganization?.branding?.brand_color || theme.palette.primary.main,
"brand_name": selectedBrandName,
"logout_url": selectedOrganization?.branding?.logout_url || "",
"support_email": selectedOrganization?.branding?.support_email || "",
}
toast.info("Updating brand name to " + selectedBrandName + ". Please wait a moment.")
data["editing_branding"] = true;
}
if (joinStatus === "support_email") {
data["branding"] = {
"theme": selectedOrganization?.branding?.theme || "dark",
"enable_chat": selectedOrganization?.branding?.enable_chat || false,
"home_url": selectedOrganization?.branding?.home_url || "",
"brand_color": selectedOrganization?.branding?.brand_color || theme.palette.primary.main,
"brand_name": selectedOrganization?.branding?.brand_name || "",
"support_email": supportEmail,
"logout_url": selectedOrganization?.branding?.logout_url || "",
}
data["editing_branding"] = true;
}
if (joinStatus === "logout_url") {
data["branding"] = {
"theme": selectedOrganization?.branding?.theme || "dark",
"enable_chat": selectedOrganization?.branding?.enable_chat || false,
"home_url": selectedOrganization?.branding?.home_url || "",
"brand_color": selectedOrganization?.branding?.brand_color || theme.palette.primary.main,
"brand_name": selectedOrganization?.branding?.brand_name || "",
"support_email": selectedOrganization?.branding?.support_email || "",
"logout_url": logoutUrl,
}
data["editing_branding"] = true;
}
const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
fetch(url, { fetch(url, {
mode: "cors", mode: "cors",
@@ -56,20 +138,65 @@ const Branding = (props) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
toast("Failed updating org: ", responseJson.reason); toast("Failed updating org: ", responseJson.reason);
} else { } else {
if (joinStatus == "join") { if (joinStatus === "join" || joinStatus === "leave") {
setPublishingInfo("Your organization is now part of the Partner Program. You can now create, publish and manage content for your organization's public page.") if (joinStatus === "join") {
} else { setPublishingInfo("Your organization is now part of the Partner Program. You can now create, publish and manage content for your organization's public page.")
setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.") } else {
setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.")
}
} }
if (joinStatus === "light" || joinStatus === "dark" || joinStatus === "system") {
handleThemeChange(joinStatus)
setChangingTheme(false)
setCurrentSelectedTheme(joinStatus)
}
if (joinStatus === "support_email") {
toast.info("Support email updated successfully.")
if (supportEmail?.length > 0) {
setSupportEmail(supportEmail)
}else {
setSupportEmail("support@shuffler.io")
}
}
if (joinStatus === "logout_url") {
toast.info("Logout URL updated successfully.")
setLogoutUrl(logoutUrl)
}
if (joinStatus === "brand_color") {
toast.info("Brand color updated successfully.")
setBrandColor(selectedBrandColor)
localStorage.setItem("brandColor", selectedBrandColor)
}
if (joinStatus === "brand_name") {
toast.info("Brand name updated successfully.")
setBrandName(selectedBrandName)
localStorage.setItem("brandName", selectedBrandName)
}
handleGetOrg(selectedOrganization.id); handleGetOrg(selectedOrganization.id);
}
setIsLoading(false)
}
}) })
) )
.catch((error) => { .catch((error) => {
toast("Err: " + error.toString()); toast("Err: " + error.toString());
}); setIsLoading(false)
})
}; };
useEffect(() => {
if (userdata && userdata?.active_org && userdata?.active_org?.branding?.theme?.length > 0) {
console.log("Setting current selected theme from userdata", userdata?.active_org?.branding?.theme);
setCurrentSelectedTheme(userdata?.active_org?.branding?.theme);
}
},[userdata]);
// Should enable / disable org branding // Should enable / disable org branding
const handleChangePublishing = () => { const handleChangePublishing = () => {
console.log("Handle change publishing"); console.log("Handle change publishing");
@@ -118,15 +245,55 @@ const Branding = (props) => {
const isPartner = leadinfo.includes("partner") const isPartner = leadinfo.includes("partner")
useEffect(() => {
if (selectedOrganization?.branding?.theme && selectedOrganization?.creator_org?.length === 0) {
setCurrentSelectedTheme(selectedOrganization.branding.theme);
}
if (selectedOrganization?.creator_org?.length > 0 && userdata?.active_org?.branding.theme) {
setCurrentSelectedTheme(userdata?.active_org?.branding.theme);
}
if (
selectedOrganization &&
selectedOrganization?.branding?.brand_color &&
selectedOrganization?.branding?.brand_color !== selectedBrandColor
) {
setSelectedBrandColor(selectedOrganization.branding.brand_color);
}
if (selectedOrganization?.branding?.brand_name && selectedOrganization?.branding?.brand_name !== selectedBrandName) {
setSelectedBrandName(selectedOrganization.branding.brand_name);
}
}, [selectedOrganization, userdata]);
useEffect(() => {
if ((userdata && userdata?.org_status?.includes("integration_partner") && !integrationPartner && !userdata?.org_status?.includes("sub_org")) || userdata?.support) {
setIntegrationPartner(true)
}
},[userdata, integrationPartner]);
const handleColorChange = (e) => {
setSelectedBrandColor(e.target.value);
};
const saveColorChanges = () => {
toast.info("Updating brand color to " + selectedBrandColor + ". Please wait a moment.")
handleEditOrg("brand_color");
};
return ( return (
<div style={{ width: clickedFromOrgTab? "100%": "auto", height: "100%", minHeight: 1100, boxSizing: 'border-box', transition: "width 0.3s ease", padding: "27px 10px 19px 27px", height: "auto", backgroundColor: '#212121', borderRadius: '16px', }}> <div style={{ width: clickedFromOrgTab? "100%": "auto", height: "100%", minHeight: 1100, boxSizing: 'border-box', transition: "width 0.3s ease", padding: "27px 10px 19px 27px", backgroundColor: theme.palette.platformColor, borderRadius: '16px', scrollbarWidth: "thin", scrollbarColor: theme.palette.scrollbarColorTransparent, }}>
<div style={{height: 843, overflowY: "auto",}}> <div style={{ overflowY: "auto",}}>
<div style={{width: "100%", overflowX: 'hidden', }}> <div style={{width: "100%", overflowX: 'hidden', }}>
<Typography style={{fontSize: 24, fontWeight: "bold", marginTop: clickedFromOrgTab ?0:null,}}> <Typography style={{fontSize: 24, fontWeight: "bold", marginTop: clickedFromOrgTab ?0:null,}}>
Partner Status & Branding Partner Status & Branding
</Typography> </Typography>
<Typography variant="body1" color="textSecondary" style={{ marginTop: 10, marginBottom: 10, fontSize: 16 }}> <Typography variant="body1" color="textSecondary" style={{ marginTop: 10, marginBottom: 10, fontSize: 16 }}>
You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more. Please note that same theme settings are applied to all sub organizations for partners.
</Typography> </Typography>
<Typography variant="body1" color="textSecondary" style={{display: 'flex', marginTop: 20, marginBottom: 10 }}> <Typography variant="body1" color="textSecondary" style={{display: 'flex', marginTop: 20, marginBottom: 10 }}>
@@ -150,7 +317,8 @@ const Branding = (props) => {
style={{ textDecoration: "none" }} // Optional: remove underline style={{ textDecoration: "none" }} // Optional: remove underline
> >
<Button <Button
variant="contained" variant="contained"
color="primary"
style={{ style={{
marginTop: 20, marginTop: 20,
marginBottom: 10, marginBottom: 10,
@@ -176,15 +344,15 @@ const Branding = (props) => {
</Button> </Button>
)} )}
<Divider style={{marginTop: 50, marginBottom: 50, }} /> <Divider style={{marginTop: 50, marginBottom: 50, color: theme.palette.defaultBorder}} />
<Typography style={{fontSize: 24, fontWeight: "bold"}}> <Typography style={{fontSize: 24, fontWeight: "bold"}}>
Partner Program Public Partner Program
</Typography> </Typography>
<div style={{ display: "flex", width: 900, marginTop: 10}}> <div style={{ display: "flex", width: 900, marginTop: 10}}>
<div> <div>
<span> <span>
<Typography variant="body1" color="textSecondary" style={{fontSize: 16}}> <Typography variant="body2" color="textSecondary">
By changing publishing settings, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Terms of Service</a>, and acknowledge that your organization's non-sensitive data will be added as a <a target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}} href="https://shuffler.io/creators">creator account</a>. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization IS reversible.<div/>Support: <a href="mailto:support@shuffler.io"target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>support@shuffler.io</a> By changing publishing settings, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: theme.palette.linkColor}}>Terms of Service</a>, and acknowledge that your organization's non-sensitive data will be added as a <a target="_blank" style={{ textDecoration: "none", color: theme.palette.linkColor}} href="https://shuffler.io/creators">creator account</a>. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization IS reversible.<div/>Support: <a href={`mailto:${supportEmail}`} target="_blank" style={{ textDecoration: "none", color: theme.palette.linkColor}}>{supportEmail}</a>
</Typography> </Typography>
{selectedOrganization.creator_id == "" ? {selectedOrganization.creator_id == "" ?
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}> <Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
@@ -195,7 +363,9 @@ const Branding = (props) => {
} }
<Button <Button
style={{ height: 40, marginTop: 10, width: 300, textTransform: 'none', fontSize: 18, backgroundColor: "#ff8544", color: "#1a1a1a" }} variant="contained"
color="primary"
style={{ height: 40, marginTop: 10, width: 300, textTransform: 'none', fontSize: 18, }}
variant={selectedOrganization.creator_id == "" ? "contained" : "outlined"} variant={selectedOrganization.creator_id == "" ? "contained" : "outlined"}
color={selectedOrganization.creator_id == "" ? "primary" : "secondary"} color={selectedOrganization.creator_id == "" ? "primary" : "secondary"}
disabled={!isOrganizationReady()} disabled={!isOrganizationReady()}
@@ -222,6 +392,257 @@ const Branding = (props) => {
</div> </div>
</div> </div>
</div> </div>
{integrationPartner ? (
<>
<Divider style={{marginTop: 50, marginBottom: 50, color: theme.palette.defaultBorder}} />
<Typography style={{fontSize: 24, fontWeight: "bold"}}>
Parent & Sub Organization Branding
</Typography>
<Typography variant="body1" color="textSecondary" style={{ marginTop: 10, marginBottom: 10, fontSize: 16 }}>
Sub organizations are not allowed to change their branding. The branding is inherited from the parent organization.
</Typography>
<ToggleButtonGroup
value={currentSelectedTheme}
exclusive
disabled={isLoading}
onChange={(event, newTheme) => {
if (newTheme === null) {
return;
}
if (newTheme === currentSelectedTheme) {
return;
}
if (changingTheme === true) {
return
}
setChangingTheme(true)
handleEditOrg(newTheme);
}}
aria-label="theme"
style={{ justifyContent: "flex-start", marginBottom: 10, marginTop: 20 }}
>
<ToggleButton value="light" aria-label="light theme" style={{ backgroundColor: currentSelectedTheme === "light" ? theme.palette.hoverColor : theme.palette.backgroundColor }}>
Light
</ToggleButton>
<ToggleButton value="dark" aria-label="dark theme" style={{ backgroundColor: currentSelectedTheme === "dark" ? theme.palette.hoverColor : theme.palette.backgroundColor }}>
Dark
</ToggleButton>
<ToggleButton value="system" aria-label="system theme" style={{ backgroundColor: currentSelectedTheme === "system" ? theme.palette.hoverColor : theme.palette.backgroundColor }}>
System
</ToggleButton>
</ToggleButtonGroup>
</>
): null}
{integrationPartner ? <>
<Divider style={{marginTop: 50, marginBottom: 50, color: theme.palette.defaultBorder}} />
<Typography variant="text" style={{color: theme.palette.text.primary, fontFamily: theme.typography.fontFamily,}}>Brand Name</Typography>
<div style={{ display: "flex", alignItems: 'center', justifyContent: 'flex-start', marginBottom: 10, gap: 15, maxWidth: 434 }}>
<TextField
type="text"
id="brandName"
value={selectedBrandName}
onChange={(e) => {
const value = e.target.value;
setSelectedBrandName(value);
}}
size="small"
PaperProps={{ style: { backgroundColor: theme.palette.backgroundColor, borderRadius: 4, border: `1px solid ${theme.palette.defaultBorder}` } }}
style={{
height: 36,
borderRadius: 4,
border: `1px solid ${theme.palette.defaultBorder}`,
width: 300
}}
/>
<Button
onClick={()=>{
if (selectedBrandName !== selectedOrganization.branding.brandName) {
handleEditOrg("brand_name");
}
}}
disabled={isLoading}
variant="contained"
color="primary"
style={{ fontSize: 16, textTransform: 'capitalize', boxShadow: "none", borderRadius: 4, width: 110, height: 35 }}
>
Update
</Button>
</div>
</>: null}
{integrationPartner ? <>
<Typography variant="text" style={{color: theme.palette.text.primary, fontFamily: theme.typography.fontFamily}}>Brand Color</Typography>
<div style={{ display: "flex", alignItems: 'center', justifyContent: 'flex-start', marginBottom: 10, gap: 15, maxWidth: 434 }}>
<input
type="color"
id="color"
name="color"
value={selectedBrandColor}
onChange={handleColorChange}
style={{
width: 50,
height: 50,
minWidth: 50,
minHeight: 50,
cursor: "pointer",
borderRadius: 5,
border: "none"
}}
/>
<TextField
type="text"
id="colorCode"
value={selectedBrandColor}
size="small"
fullWidth={true}
PaperProps={{ style: { backgroundColor: theme.palette.backgroundColor, borderRadius: 4, border: `1px solid ${theme.palette.defaultBorder}` } }}
style={{
height: 36,
padding: "0 10px",
borderRadius: 4,
border: `1px solid ${theme.palette.defaultBorder}`,
width: 260
}}
/>
<Button
onClick={()=>{saveColorChanges();}}
disabled={isLoading}
variant="contained"
color="primary"
style={{ fontSize: 16, textTransform: 'capitalize', boxShadow: "none", borderRadius: 4, width: 110, height: 35 }}
>
Update
</Button>
</div>
</>: null}
{integrationPartner ? (
<div style={{ width: "100%", maxWidth: 434, marginRight: 10, marginTop: 20 }}>
<Typography variant="text" style={{color: theme.palette.text.primary, fontFamily: theme.typography.fontFamily}}>Support Email</Typography>
<Box
sx={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<TextField
required
style={{
flex: "1",
display: "flex",
height: 35,
width: "100%",
maxWidth: 434,
fontFamily: theme.typography.fontFamily,
marginTop: "5px",
marginRight: "15px",
color: theme.palette.textFieldStyle.color,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
}}
fullWidth={true}
placeholder="Support Email"
type="email"
id="standard-required"
margin="normal"
variant="outlined"
value={supportEmail}
onChange={(e) => {
setSupportEmail(e.target.value)
}}
color="primary"
InputProps={{
style: {
color: theme.palette.textFieldStyle.color,
height: "35px",
fontSize: "1em",
borderRadius: 4,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
},
}}
/>
<Button
style={{ fontSize: 16, textTransform: 'capitalize', boxShadow: "none", borderRadius: 4, width: 110, height: 35 }}
variant="contained"
color="primary"
disabled={isLoading}
onClick={() => {
toast.info("Updating support email..")
handleEditOrg("support_email")
}}
>
Update
</Button>
</Box>
</div>
) : null}
{integrationPartner ? (
<div style={{ width: "100%", maxWidth: 434, marginRight: 10, marginTop: 20 }}>
<Typography variant="text" style={{color: theme.palette.text.primary, fontFamily: theme.typography.fontFamily}}>Logout URL</Typography>
<Box
sx={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<TextField
required
style={{
flex: "1",
display: "flex",
height: 35,
width: "100%",
maxWidth: 434,
fontFamily: theme.typography.fontFamily,
marginTop: "5px",
marginRight: "15px",
color: theme.palette.textFieldStyle.color,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
}}
fullWidth={true}
placeholder="Logout URL"
type="text"
id="standard-required"
margin="normal"
variant="outlined"
value={logoutUrl}
onChange={(e) => {
setLogoutUrl(e.target.value)
}}
color="primary"
InputProps={{
style: {
color: theme.palette.textFieldStyle.color,
height: "35px",
fontSize: "1em",
borderRadius: 4,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
},
}}
/>
<Button
style={{ fontSize: 16, textTransform: 'capitalize', boxShadow: "none", borderRadius: 4, width: 110, height: 35 }}
variant="contained"
color="primary"
disabled={isLoading}
onClick={() => {
toast.info("Updating logout url..")
handleEditOrg("logout_url")
}}
>
Update
</Button>
</Box>
</div>
) : null}
</div> </div>
</div> </div>
) )
File diff suppressed because it is too large Load Diff
+846
View File
@@ -0,0 +1,846 @@
import React, { useState, useEffect, useContext } from "react";
import { useParams, useNavigate, Link } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import theme from '../theme.jsx';
import Markdown from 'react-markdown'
import { isMobile } from "react-device-detect";
import { Context } from '../context/ContextApi.jsx';
import AppSearch from "../components/AppSearch1.jsx";
import {
Divider,
ButtonGroup,
TextField,
Button,
IconButton,
Typography,
CircularProgress,
Card,
CardContent,
} from "@mui/material";
import {
Send as SendIcon,
} from "@mui/icons-material";
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
import AuthenticationWindow from "../components/AuthenticationWindow.jsx";
const ChatBot = (props) => {
const { globalUrl } = props
const { supportEmail } = useContext(Context)
const [messages, setMessages] = useState([])
const [message, setMessage] = useState("");
const [loading, setLoading] = useState(false);
const [appAuthentication, setAppAuthentication] = React.useState([]);
const [inputAuth, setInputAuth] = useState([])
const [forceReauthentication, setForceReauthentication] = useState(false);
const [selectedType, setSelectedType] = useState("atomic");
const [appname, setAppname] = useState("");
const [threadId, setThreadId] = useState("");
const [runId, setRunId] = useState("");
const [showAppSearch, setShowAppSearch] = useState(false);
const waitingMsg = "Processing..."
const viewWidth = isMobile ? "92%" : 800
useEffect(() => {
// Check if loading and remove Waiting... from messages
const newmessages = messages
const foundmessages = messages.filter((msg) => msg.message !== waitingMsg)
if (foundmessages.length < newmessages.length) {
setMessages(foundmessages);
}
// Wait 0.5 second
const objDiv = document.getElementById("messages-window");
if (objDiv !== undefined && objDiv !== null) {
setTimeout(() => {
objDiv.scrollTop = objDiv.scrollHeight;
}, 250);
}
}, [messages]);
useEffect(() => {
if (appname === undefined || appname === null || appname === "") {
return
}
// Find the last message that was sent by us and reuse the same message content
// with added app stuff only
for (var i = messages.length-1; i >= 0; i--) {
const msg = messages[i]
if (msg.status === "sent") {
handleSubmit(undefined, msg.message)
break
}
}
}, [appname])
window.title = "Shuffle - New Chat"
let navigate = useNavigate();
// Automatic submit handler based on a lot of stuff :)
const handleSubmit = (e, inputmsg) => {
if (e !== undefined) {
e.preventDefault();
e.stopPropagation();
}
setLoading(true)
setMessage("");
const sentId = uuidv4();
var parsedData = {
"query": inputmsg,
"thread_id": threadId,
"run_id": runId,
}
if (appname !== undefined && appname !== null && appname !== "") {
parsedData["app_name"] = appname
}
if (inputAuth !== undefined && inputAuth.length > 0) {
// Forcing first auth app to be used in request
try {
parsedData["app_name"] = inputAuth[0].name
parsedData["app_id"] = inputAuth[0].id
parsedData["category"] = inputAuth[0].category
parsedData["action_name"] = inputAuth[0].action_name
} catch (e) {
}
try {
parsedData["app_name"] = inputAuth.apps[0].name
parsedData["app_id"] = inputAuth.apps[0].id
parsedData["category"] = inputAuth.apps[0].category
parsedData["action_name"] = inputAuth.apps[0].action_name
} catch (e) {
}
}
if (selectedType !== "default") {
if (selectedType == "workflow") {
parsedData["output_format"] = "workflow_suggestion"
} else {
parsedData["output_format"] = selectedType
}
}
console.log("INPUT: ", parsedData)
setInputAuth([])
var newmessages = messages;
newmessages.push({
"id": sentId,
"status": "sent",
"message": inputmsg,
})
newmessages.push({
"id": sentId,
"status": "received",
"message": waitingMsg,
})
setMessages(newmessages);
//fetch(`http://localhost:8080/api/v1/conversation`, {
fetch(`${globalUrl}/api/v1/conversation`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
body: JSON.stringify(parsedData),
})
.then((res) => res.text())
.then((resText) => {
setLoading(false)
var data = {}
// JSON parse
try {
data = JSON.parse(resText);
} catch (e) {
console.log("Error parsing response as JSON: ", e);
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg);
newmessages.push({
"status": "received",
"message": resText,
"id": uuidv4(),
});
setMessages(newmessages);
return;
}
if (data.run_id !== undefined && data.run_id !== null && data.run_id !== "") {
setRunId(data.run_id)
}
if (data.thread_id !== undefined && data.thread_id !== null && data.thread_id !== "") {
setThreadId(data.thread_id)
}
if (data.success === undefined) {
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg);
newmessages.push({
"status": "received",
"message": resText,
"id": uuidv4(),
});
setMessages(newmessages);
return;
}
// authentication for app
// app validation (choose one)
const defaultMessage = `Default output. The feature you're interacting with may not have been implemented yet. Contact ${supportEmail} with a screenshot of this and your input please.`
var outputmessage = defaultMessage;
var status = "received";
var action = ""
if (data.success === false) {
if (data.reason !== undefined) {
outputmessage = data.reason
}
status = "error"
} else {
if (data.reason !== undefined) {
outputmessage = data.reason
}
}
if (data.action !== undefined) {
//console.log("Action is defined: ", data.action);
action = data.action
if (data.action === "app_authentication") {
// If success & app auth -> say auth success and show available labels
// If !success & app auth -> do authentication
if (data.success === true) {
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg);
// No action for this.
// "action": action,
var appname = ""
if (data.apps !== undefined && data.apps !== null && data.apps.length > 0) {
appname = data.apps[0].name.replaceAll("_", " ")
}
var outputmessage = `**Please specify which ${appname} action you want to use**: \n`
if (data.available_labels !== undefined && data.available_labels !== null && data.available_labels.length > 0) {
for (var i = 0; i < data.available_labels.length; i++) {
outputmessage += "* " + data.available_labels[i] + "\n"
}
outputmessage += "* Reauthenticate ([see auth](/admin?tab=app_auth))"
}
//Some opavailable actions: " + data.apps.map((app) => app.name).join(", ")
const parsedmessage = {
"status": status,
"message": outputmessage,
"id": uuidv4(),
"category": data.category,
"thread_id": data.thread_id,
"run_id": data.run_id,
}
newmessages.push(parsedmessage);
setMessages(newmessages);
return
} else {
if (data.apps !== undefined) {
setInputAuth(data.apps)
setMessage(inputmsg);
setForceReauthentication(true)
}
}
} else if (data.action === "select_category" || data.action === "select_app") {
console.log("[DEBUG] APP SELECTION! Should help them choose an app to use")
// Show a search field
setShowAppSearch(true)
}
}
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg);
const parsedmessage = {
"status": status,
"message": outputmessage,
"id": uuidv4(),
"action": action,
"category": data.category,
"thread_id": data.thread_id,
"run_id": data.run_id,
}
newmessages.push(parsedmessage);
setMessages(newmessages);
console.log("New message: ", parsedmessage)
})
.catch((err) => {
setLoading(false)
console.log("Problem: ", err);
setMessage(message);
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg);
// Find the message with the sentId and change the status to error
newmessages.push({
"status": "error",
"message": message,
"error_message": "Failed to send: "+err,
"id": sentId,
});
setMessages(newmessages);
});
};
// Used to verify if the user is logged in after auth is done
const getAppAuthentication = () => {
console.log("Continue chat from the previous stage!");
fetch(globalUrl + "/api/v1/apps/authentication", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for app auth :O!");
}
return response.json();
})
.then((responseJson) => {
if (!responseJson.success) {
console.log("Failed to get app auth!");
return;
}
var newauth = [];
for (let authkey in responseJson.data) {
if (responseJson.data[authkey].defined === false) {
continue;
}
newauth.push(responseJson.data[authkey]);
}
if (newauth.length > appAuthentication.length) {
console.log("New auth is longer than old auth. Set new auth!");
setForceReauthentication(false)
// Check if last message contains "reauth"
if (messages.length > 0) {
const lastmessage = messages[messages.length-1];
if (lastmessage.message.toLowerCase().includes("re-auth")) {
console.log("Skipping resend due to reauth")
var newmessages = messages
newmessages.push({
"status": "received",
"message": "Authentication done. What do you want to do?",
"id": uuidv4(),
});
setMessages(newmessages);
} else {
handleSubmit(undefined, message)
}
} else {
handleSubmit(undefined, message)
}
}
setAppAuthentication(newauth)
})
.catch((err) => {
console.log("Error in getAppAuthentication: ", err);
})
}
const AuthWrapper = (props) => {
const { app } = props;
const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false);
console.log("AUTH: ", app)
return (
<div style={{position: "absolute", right: 250, bottom: 150, }}>
{app.authentication.type === "oauth2" || app.authentication.type === "oauth2-app" ?
<AuthenticationOauth2
selectedApp={app}
selectedAction={{
"app_name": app.name,
"app_id": app.id,
"app_version": app.version,
"large_image": app.large_image,
}}
authenticationType={app.authentication}
isCloud={true}
authButtonOnly={true}
getAppAuthentication={getAppAuthentication}
/>
:
<Button
fullWidth
variant="contained"
style={{
marginBottom: 20,
marginTop: 20,
flex: 1,
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: "#ffffff",
color: "#2f2f2f",
borderRadius: theme.palette?.borderRadius,
minWidth: 300,
maxWidth: 300,
maxHeight: 50,
overflow: "hidden",
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
fullWidth
color="primary"
onClick={(e) => {
console.log("Click? ")
e.preventDefault();
setAuthenticationModalOpen(true);
}}
>
<span style={{display: "flex"}}>
<img
alt={app.name}
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette?.borderRadius, }}
src={app.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10, marginTop: 5,}} variant="body1">
Authenticate
</Typography>
</span>
</Button>
}
{app.authentication.type !== "oauth2" && authenticationModalOpen ?
<AuthenticationWindow
selectedApp={app}
globalUrl={globalUrl}
getAppAuthentication={getAppAuthentication}
appAuthentication={appAuthentication}
authenticationModalOpen={authenticationModalOpen}
setAuthenticationModalOpen={setAuthenticationModalOpen}
/>
: null}
</div>
)
}
var amountfinished = 0;
const showAuthentication = inputAuth.map((app, index) => {
const authexists = appAuthentication.find((auth) => auth.app.id === app.id);
if (authexists !== undefined && forceReauthentication === false) {
console.log("Auth exists: ", authexists);
amountfinished += 1
return null
}
return (
<div key={index}>
<AuthWrapper app={app} />
</div>
)
})
if (amountfinished === inputAuth.length && amountfinished > 0) {
setInputAuth([])
setAppAuthentication([])
}
const showSamples =
<div style={{marginLeft: 10, marginRight: 10, }}>
<Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius,}}>
<CardContent>
<Typography variant="h6">
How many incidents did we get last week?
</Typography>
</CardContent>
</Card>
<Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius, marginTop: 10, }}>
<CardContent>
<Typography variant="h6">
Answer the last email from Jim about the new project, and say we're on it
</Typography>
</CardContent>
</Card>
<Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius, marginTop: 10, }}>
<CardContent>
<Typography variant="h6">
Is the IP 1.2.3.4 blocked? If not, block it.
</Typography>
</CardContent>
</Card>
</div>
function OuterLink(props) {
return (
<a
target="_blank"
rel="noopener noreferrer"
href={props.href}
style={{ color: "#f85a3e", textDecoration: "none" }}
>
{props.children}
</a>
);
}
function Img(props) {
return <img style={{ borderRadius: theme.palette?.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />;
}
function CodeHandler(props) {
//console.log("Codehandler PROPS: ", props)
const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : ""
return (
<div
style={{
minWidth: "50%",
maxWidth: "100%",
backgroundColor: theme.palette.inputColor,
overflowY: "auto",
// Check if props.inline === true, then do it inline
padding: props.inline ? 0 : 15,
display: props.inline ? "inline" : "block",
}}
>
<code
style={{
// Wrap if larger than X
whiteSpace: "pre-wrap",
overflow: "auto",
}}
>{propvalue}</code>
</div>
);
}
const markdownStyle = {
color: "rgba(255, 255, 255, 0.65)",
overflow: "hidden",
paddingBottom: 100,
margin: "auto",
maxWidth: "100%",
minWidth: "100%",
overflow: "hidden",
fontSize: isMobile ? "1.3rem" : "1.0rem",
}
const Heading = (props) => {
const element = React.createElement(
`h${props.level}`,
{ style: { marginTop: props.level === 1 ? 20 : 50 } },
props.children
);
const [hover, setHover] = useState(false);
var extraInfo = "";
return (
<Typography
onMouseOver={() => {
setHover(true);
}}
>
{props.level !== 1 ? (
<Divider
style={{
width: "90%",
marginTop: 40,
backgroundColor: theme.palette.inputColor,
}}
/>
) : null}
{element}
{/*hover ? <LinkIcon onMouseOver={() => {setHover(true)}} style={{cursor: "pointer", display: "inline", }} onClick={() => {
window.location.href += "#hello"
console.log(window.location)
//window.history.pushState('page2', 'Title', '/page2.php');
//window.history.replaceState('page2', 'Title', '/page2.php');
}} />
: ""
*/}
{extraInfo}
</Typography>
);
}
const OrderedList = (props) => {
var parsedchildren = []
for (var i = 0; i < props.children.length; i++) {
const child = props.children[i]
if (child === "\n") {
continue
}
if (child.props !== undefined && child.props.children !== undefined) {
// Remove <p> from the child wrapper
var parsedchild = []
for (var j = 0; j < child.props.children.length; j++) {
const childchild = child.props.children[j]
// print the raw childchild bytes, not string
if (childchild === "\n") {
continue
}
// If the childchild has <p> around it, remove it
parsedchild.push(childchild)
/*
// Not doing this as it breaks links
if (childchild.props !== undefined && childchild.props.children !== undefined) {
parsedchild.push(childchild.props.children)
} else {
parsedchild.push(childchild)
}
*/
}
parsedchildren.push(parsedchild)
}
}
return (
<ol style={{marginTop: 0, }}>
{parsedchildren.map((child, index) => {
return (
<li key={index} style={{minHeight: 0, display: "block", }}>
<p style={{marginTop: 0, marginBottom: 10, }}>
{index+1}. {child}
</p>
</li>
)
})}
</ol>
)
}
const Paragraph = (props) => {
return (
<p style={{marginTop: 15, marginBottom: 15, }}>
{props.children}
</p>
)
}
const markdownComponents = {
ol: OrderedList,
ul: OrderedList,
img: Img,
code: CodeHandler,
h1: Heading,
h2: Heading,
h3: Heading,
h4: Heading,
h5: Heading,
h6: Heading,
a: OuterLink,
p: Paragraph,
}
const chatWindow =
<div style={{minWidth: viewWidth, maxWidth: viewWidth, margin: "auto", textAlign: "left", minHeight: 1500, }}>
{messages.length === 0 ?
<span>
<h1>Shuffle AI</h1>
{showSamples}
</span>
: null}
<div
id="messages-window"
style={{
marginTop: 50,
display: "flex",
flexDirection: "column",
minHeight: 1500,
maxHeight: isMobile ? "85%" : "85%",
overflow: "auto", paddingBottom: 200,
}}
>
{messages.map((message, index) => {
const float = message.status === "sent" ? "left" : "right";
const border = message.status === "error" ? "red" : "rgba(255,255,255,0.3)"
const hasAction = message.action !== undefined && message.action !== null && message.action !== ""
return (
// Make a chat bubble component
<div key={index} style={{position: "relative", width: "100%", marginTop: 15, marginLeft: isMobile ? 10 : 0, }}>
<Typography variant="body1" style={{display: "flex", backgroundColor: theme.palette.surfaceColor, color: "white", padding: "0px 10px 0px 10px", borderRadius: theme.palette?.borderRadius, float: float, border: `1px solid ${border}`, "cursor": hasAction ? "pointer" : "default", maxWidth: viewWidth-30, overflowWrap: "break-word", whiteSpace: "pre-line" }} onClick={() => {
if (!hasAction) {
return
}
if (message.action === "login") {
navigate("/login?view=/conversation&message=You must log in to use ShuffleGPT")
} else if (message.action === "app_authentication") {
console.log("App auth action!")
//setAuthenticationModalOpen(true)
} else {
console.log("\n\nUnknown click action: ", message.action)
}
}}>
{message.message === waitingMsg ? <CircularProgress style={{height: 20, width: 20, marginTop: 20, marginRight: 10, }} /> : null}
<span>
<Markdown
components={markdownComponents}
id="markdown_wrapper"
style={{
minHeight: 20,
marginTop: 0,
display: "flex",
flexDirection: "row",
}}
>
{message.message}
</Markdown>
{message.thread_id !== undefined && message.thread_id !== null && message.thread_id !== "" ?
<Typography variant="body2" style={{color: "rgba(255,255,255,0.5)", marginTop: 5, }}>
Thread: {message.thread_id}
</Typography>
: null
}
</span>
</Typography>
{message.status === "error" && message.error_message ?
<Typography variant="body2" style={{color: "red", }}>
{message.error_message}
</Typography>
: null}
{(message.action === "select_category" || message.action === "select_app") && showAppSearch && index === messages.length-1 ?
<div style={{position: "absolute", right: 0, bottom: -100, }}>
<AppSearch
placeholder={"Find your "+message.category+" app"}
setNewSelectedApp={setAppname}
/>
</div>
: null}
</div>
)
})}
</div>
{showAuthentication}
<div style={{position: "fixed", bottom: 0, left: 0, width: "100%", zIndex: 100, backgroundColor: theme.palette.platformColor, }}>
<div style={{width: viewWidth, margin: "auto", }}>
{messages.length === 0 ?
<span>
<Typography variant="body2" color="textSecondary">
Query Type
</Typography>
<ButtonGroup
fullWidth
color="secondary"
style={{display: "flex", marginTop: 10, }}
>
{/*
<Button
fullWidth
disabled
variant={selectedType === "default" ? "contained" : "outlined"}
onClick={() => setSelectedType("default")}
>
Auto
</Button>
*/}
<Button
fullWidth
variant={selectedType === "atomic" ? "contained" : "outlined"}
onClick={() => setSelectedType("atomic")}
>
Auto-run action
</Button>
<Button
fullWidth
variant={selectedType === "support" ? "contained" : "outlined"}
onClick={() => setSelectedType("support")}
>
Support
</Button>
</ButtonGroup>
</span>
: null}
<form onSubmit={(e) => handleSubmit(e, message)} style={{bottom: 20, marginTop: 10, marginBottom: isMobile ? 0 : 10, maxWidth: viewWidth, minWidth: viewWidth, }}>
<TextField
id="message"
fullWidth
disabled={loading}
label="Send a message"
value={message}
onChange={(e) => setMessage(e.target.value)}
variant="outlined"
autoFocus
InputProps={{
endAdornment: (
<IconButton
aria-label="send message"
onClick={(e) => handleSubmit(e, message)}
>
<SendIcon color="primary" />
</IconButton>
)
}}
/>
</form>
{isMobile ? null :
<Typography variant="body2" color="textSecondary" align="center" style={{marginTop: 0,}} >
{`The Shuffle AI is a test system for automatic workflow generation and atomic functions for the future of Shuffle. Shuffle AI may use your organization info in the query, and attempts to auto-correct any failed behavior. If you have any questions, please contact us at ${supportEmail}`}
</Typography>
}
</div>
</div>
</div>
return (
<div style={{width: isMobile ? "100%" : 1000, margin: "auto", paddingTop: 50, }}>
{chatWindow}
</div>
)
}
export default ChatBot;
+51 -28
View File
@@ -26,7 +26,7 @@ import {
Visibility as VisibilityIcon, Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon, VisibilityOff as VisibilityOffIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import theme from "../theme.jsx"; import { getTheme } from "../theme.jsx";
import { styled } from '@mui/styles'; import { styled } from '@mui/styles';
import { Context } from "../context/ContextApi.jsx"; import { Context } from "../context/ContextApi.jsx";
@@ -48,13 +48,21 @@ const CloudSyncTab = (props) => {
const [, forceUpdate] = React.useState(); const [, forceUpdate] = React.useState();
const itemColor = "white"; const itemColor = "white";
const isCloud = window?.location?.host === "localhost:3002" || window?.location?.host === "shuffler.io"; const isCloud = window?.location?.host === "localhost:3002" || window?.location?.host === "shuffler.io";
const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
useEffect(() => { getSettings(); }, []); useEffect(() => { getSettings(); }, []);
const GridItem = (props) => { const GridItem = (props) => {
const [expanded, setExpanded] = React.useState(false); const [expanded, setExpanded] = React.useState(false);
const [showEdit, setShowEdit] = React.useState(false); const [showEdit, setShowEdit] = React.useState(false);
const [newValue, setNewValue] = React.useState(-100); const [newValue, setNewValue] = React.useState(-100);
const primary = props.data.primary; var primary = props.data.primary
const shownName = props.data.newname !== undefined && props.data.newname !== null && props.data.newname !== primary ? props.data.newname : primary
const secondary = props.data.secondary; const secondary = props.data.secondary;
const primaryIcon = props.data.icon; const primaryIcon = props.data.icon;
const secondaryIcon = props.data.active ? const secondaryIcon = props.data.active ?
@@ -167,9 +175,9 @@ const CloudSyncTab = (props) => {
<div <div
style={{ style={{
margin: 4, margin: 4,
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
borderRadius: 8, borderRadius: 8,
color: "white", color: theme.palette.text.primary,
minHeight: expanded ? 250 : "inherit", minHeight: expanded ? 250 : "inherit",
maxHeight: expanded ? 300 : "inherit", maxHeight: expanded ? 300 : "inherit",
boxShadow: "none", boxShadow: "none",
@@ -188,13 +196,13 @@ const CloudSyncTab = (props) => {
<Avatar>{primaryIcon}</Avatar> <Avatar>{primaryIcon}</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
style={{ textTransform: "capitalize", color: "#F1F1F1", fontSize: 14, fontWeight: 400, }} style={{ textTransform: "capitalize", color: theme.palette.text.primary, fontSize: 14, fontWeight: 400, }}
primary={primary} primary={shownName}
/> />
{isCloud && userdata.support === true ? {isCloud && userdata.support === true ?
<Tooltip title="Edit features (support users only)"> <Tooltip title="Edit features (support users only)">
<EditIcon <EditIcon
color="secondary" color="textPrimary"
style={{ marginRight: 10, cursor: "pointer", }} style={{ marginRight: 10, cursor: "pointer", }}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@@ -475,7 +483,7 @@ const CloudSyncTab = (props) => {
} else { } else {
toast("Cloud Syncronization successfully set up!"); toast("Cloud Syncronization successfully set up!");
setOrgSyncResponse( setOrgSyncResponse(
"Successfully started syncronization. Cloud features you now have access to can be seen below." "Successfully started syncronization. Cloud/Hybrid features are available below."
); );
} }
@@ -527,20 +535,20 @@ const CloudSyncTab = (props) => {
return ( return (
<div style={{padding: "27px 10px 19px 27px",}}> <div style={{padding: "27px 10px 19px 27px",}}>
<div style={{ marginBottom: 20 }}> <div style={{ marginBottom: 20 }}>
<h2 <Typography variant="h5"
style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }} style={{ marginBottom: 8, marginTop: 0, fontWeight: 500}}
> >
Cloud syncronization Cloud syncronization
</h2> </Typography>
<span style={{ color: "#C8C8C8", fontSize: 16, fontWeight: 400, }}> <Typography variant="body2" style={{ color: theme.palette.text.secondary, 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. What does <a href="/docs/organizations#cloud_sync" target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.linkColor, 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. This will by default back up apps and workflows.
</span> </Typography>
</div> </div>
{isCloud ? ( {isCloud ? (
<div style={{ marginTop: 15, display: "flex" }}> <div style={{ marginTop: 15, display: "flex" }}>
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<Typography style={{fontWeight: 400, fontSize: 16, color: "#F1F1F1"}}> <Typography style={{fontWeight: 400, fontSize: 16, color: theme.palette.text.secondary}}>
Currently syncronizing:{" "} Currently syncronizing:{" "}
{selectedOrganization.cloud_sync_active === true {selectedOrganization.cloud_sync_active === true
? <span style={{ color: "#4CFD72", fontSize: 16, marginLeft: 16}}>True</span> ? <span style={{ color: "#4CFD72", fontSize: 16, marginLeft: 16}}>True</span>
@@ -561,28 +569,31 @@ const CloudSyncTab = (props) => {
marginRight: 10, marginRight: 10,
fontSize: 16, fontSize: 16,
fontWeight: 400, fontWeight: 400,
color: theme.palette.text.primary,
fontFamily: theme.typography.fontFamily, fontFamily: theme.typography.fontFamily,
}} }}
> >
Your Api key Your Api key
</Typography> </Typography>
{userSettings?.apikey === undefined || userSettings?.apikey === null || userSettings?.apikey?.length <=0 ? ( {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 }}/> <Skeleton variant="rectangular" animation="wave" sx={{backgroundColor: theme.palette.loaderColor, border: '1px solid #646464', width: 500, height: 50, marginTop: 2 }}/>
): ):
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<TextField <TextField
color="primary"
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.textFieldStyle.backgroundColor,
borderRadius: theme.palette.textFieldStyle.borderRadius,
color: theme.palette.textFieldStyle.color,
maxWidth: 500, maxWidth: 500,
height: 35 height: 35
}} }}
InputProps={{ InputProps={{
sx: { sx: {
height: "35px", height: "35px",
color: "white", color: theme.palette.textFieldStyle.color,
fontSize: "1em", fontSize: "1em",
backgroundColor: '#212121', backgroundColor: theme.palette.textFieldStyle.backgroundColor,
borderRadius: theme.palette.textFieldStyle.borderRadius,
}, },
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
@@ -639,14 +650,15 @@ const CloudSyncTab = (props) => {
<TextField <TextField
color="primary" color="primary"
style={{ style={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.textFieldStyle.backgroundColor,
marginRight: 10, marginRight: 10,
height: 35, height: 35,
}} }}
InputProps={{ InputProps={{
style: { style: {
height: "35px", height: "35px",
color: "white", color: theme.palette.textFieldStyle.color,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
fontSize: "1em", fontSize: "1em",
}, },
}} }}
@@ -701,17 +713,19 @@ const CloudSyncTab = (props) => {
</div> </div>
)} )}
<h2 style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}> <Typography variant="h5" style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}>
Features {isCloud ? "Cloud" : "Hybrid"} Features
</h2> </Typography>
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 24, fontSize: 16, fontWeight: 400, marginLeft: 5, color: "#C8C8C8" }}> <Typography variant="body2" color="textSecondary" style={{ marginBottom: 24, fontSize: 16, fontWeight: 400, marginLeft: 5, color: theme.palette.text.secondary }}>
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> 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, }}> <Grid container style={{ width: "100%", marginBottom: 15, }}>
{selectedOrganization.sync_features === undefined || {selectedOrganization.sync_features === undefined ||
selectedOrganization.sync_features === null selectedOrganization.sync_features === null
? <Grid container spacing={2} justifyContent="center"> ? <Grid container spacing={2} justifyContent="center">
{[...Array(18)].map((_, i) => ( {[...Array(18)].map((_, i) => (
<Grid item xs={12} sm={6} md={4} key={i}> <Grid item xs={12} sm={6} md={4} key={i}>
<div <div
style={{ style={{
@@ -728,7 +742,7 @@ const CloudSyncTab = (props) => {
variant="rectangular" variant="rectangular"
height={50} height={50}
width={343} width={343}
sx={{ backgroundColor: '#1a1a1a', display: 'flex', borderRadius: 1 }} sx={{ backgroundColor: theme.palette.loaderColor, display: 'flex', borderRadius: 1 }}
animation="wave" animation="wave"
/> />
</div> </div>
@@ -750,6 +764,13 @@ const CloudSyncTab = (props) => {
} }
const newkey = key.replaceAll("_", " "); const newkey = key.replaceAll("_", " ");
// Rewrites to frontend names
var newname = newkey
if (newkey === "app executions") {
newname = "app runs"
}
const griditem = { const griditem = {
primary: newkey, primary: newkey,
secondary: secondary:
@@ -764,6 +785,8 @@ const CloudSyncTab = (props) => {
data_collection: "None", data_collection: "None",
active: item.active, active: item.active,
icon: <PolylineIcon style={{ color: "#1a1a1a" }} />, icon: <PolylineIcon style={{ color: "#1a1a1a" }} />,
newname: newname,
}; };
return ( return (
@@ -0,0 +1,205 @@
import React, { useState, useEffect, useContext, memo } from "react";
import { Context } from "../context/ContextApi.jsx";
import { getTheme } from "../theme.jsx";
import { GetIconInfo } from "../views/Workflows2.jsx";
import { toast } from 'react-toastify';
import {
Dialog,
DialogTitle,
DialogContent,
Typography,
Paper,
LinearProgress,
Grid,
Button,
} from '@mui/material';
import {
Rocket as RocketIcon,
FilterAlt as FilterAltIcon,
} from '@mui/icons-material';
const CollectIngestModal = (props) => {
const { globalUrl, open, setOpen } = props;
const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
if (open === undefined || open === null) {
console.error("CollectIngestModal: 'open' prop is required.");
return null
}
if (setOpen === undefined || setOpen === null) {
console.error("CollectIngestModal: 'setOpen' prop is required.");
return null
}
const startIngestion = (appname, index) => {
console.log("APPNAME:", appname, "INDEX:", index)
const body = {
"app_name": appname,
"label": appname,
}
const url = `${globalUrl}/api/v2/workflows/generate`
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
credentials: "include",
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log("Ingestion started successfully:", data);
toast.success(`Ingestion for ${appname} started successfully!`);
})
.catch((error) => {
console.error("Error starting ingestion:", error);
toast.error(`Failed to start ingestion for ${appname}. Please try again.`);
});
}
const IngestItem = (props) => {
const { type, index } = props
const [hovering, setHovering] = useState(false);
const [isFinished, setIsFinished] = useState(false);
const appname = type
const ingestedAmount = 20
const iconDetails = GetIconInfo({
"app_name": appname,
"name": appname,
})
return (
//<Grid item xs={hovering ? 12 : 5.9}
<Grid item xs={12}
style={{
minHeight: hovering ? 225 : 145,
maxHeight: hovering ? 225 : 145,
cursor: "pointer",
position: "relative",
transition: "all 0.3s ease-in-out",
borderRadius: theme.palette.borderRadius,
border: hovering ? `2px solid ${theme.palette.primary.main}` : isFinished ? `2px solid ${theme.palette.success.main}` : `2px solid ${theme.palette.secondary.main}`,
textAlign: "center",
marginBottom: 5,
overflow: "hidden",
}}
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
>
<div style={{marginTop: 35, marginBottom: 35, }}>
{iconDetails?.originalIcon && (
iconDetails?.originalIcon
)}
<Typography variant="h4" style={{marginTop: 10, }}>
{appname}
</Typography>
</div>
<Button variant="contained" onClick={() => {
toast.info("Starting ingest for relevant apps")
startIngestion(appname, index)
}}>
Start Ingestion
</Button>
{hovering ?
<div>
</div>
: null}
{isFinished ?
<div>
<Typography variant="body1" style={{
position: "absolute",
bottom: 10,
left: 10,
color: theme.palette.text.secondary,
}}>
{ingestedAmount} / X
</Typography>
<LinearProgress
style={{
width: "100%",
position: "absolute",
bottom: 0,
}}
variant="determinate"
fullWidth value={{ingestedAmount}}
/>
</div>
: null}
</Grid>
)
}
return (
<Dialog
PaperProps={{
sx: {
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
border: theme?.palette?.DialogStyle?.border,
minWidth: 500,
minHeight: 700,
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={open}
onClose={() => {
setOpen(false)
}}
>
<DialogTitle>
</DialogTitle>
<DialogContent style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: theme.palette.DialogStyle.backgroundColor, }}>
<Typography variant="h6" style={{ color: theme.palette.text.primary, marginBottom: 20 }}>
<FilterAltIcon style={{ verticalAlign: "middle", marginRight: 10 }} />
Collection and Ingestion
</Typography>
<Grid container>
<IngestItem type="Ingest Tickets" index={1} />
<IngestItem type="Enable Threat feeds" index={2} />
<IngestItem type="Track Assets" index={2} />
<IngestItem type="Enable Search" index={2} />
<IngestItem type="Enable Mitre Att&ck techniques" index={2} />
<IngestItem type="Enable Detection Rules" index={2} />
<IngestItem type="Ingest Logs" index={2} />
</Grid>
</DialogContent>
</Dialog>
)
}
export default CollectIngestModal
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useContext } from "react";
import { useInterval } from "react-powerhooks"; import { useInterval } from "react-powerhooks";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx" import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
import { import {
@@ -20,7 +20,7 @@ import {
Collapse, Collapse,
IconButton, IconButton,
} from "@mui/material"; } from "@mui/material";
import { Context } from "../context/ContextApi.jsx";
import { import {
FavoriteBorder as FavoriteBorderIcon, FavoriteBorder as FavoriteBorderIcon,
Error as ErrorIcon, Error as ErrorIcon,
@@ -76,6 +76,8 @@ const ConfigureWorkflow = (props) => {
const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false); const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false);
const [loopRunning, setLoopRunning] = useState(false) const [loopRunning, setLoopRunning] = useState(false)
const [checkStarted, setCheckStarted] = React.useState(false); const [checkStarted, setCheckStarted] = React.useState(false);
const { themeMode } = useContext(Context);
const theme = getTheme(themeMode);
useEffect(() => { useEffect(() => {
if (requiredActions.length === 0) { if (requiredActions.length === 0) {
@@ -631,7 +633,7 @@ const ConfigureWorkflow = (props) => {
if (aa !== undefined) { if (aa !== undefined) {
aa('init', { aa('init', {
appId: "JNSS5CFDZZ", appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240", apiKey: "c8f882473ff42d41158430be09ec2b4e",
}) })
const timestamp = new Date().getTime() const timestamp = new Date().getTime()
@@ -799,7 +801,7 @@ const ConfigureWorkflow = (props) => {
> >
<div <div
style={{ style={{
border: filled ? `1px solid ${theme.palette.green}` : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, width: "100%", padding: 12, cursor: filled ? "default" : "pointer", border: filled ? `1px solid ${theme.palette.green}` : theme.palette.textFieldStyle.border, borderRadius: theme.palette?.borderRadius, width: "100%", padding: 12, cursor: filled ? "default" : "pointer",
}} }}
id="app-config" id="app-config"
> >
-8
View File
@@ -142,7 +142,6 @@ const countries = [
suggested: true, suggested: true,
}, },
{ code: 'GA', label: 'Gabon', phone: '241' }, { code: 'GA', label: 'Gabon', phone: '241' },
{ code: 'GB', label: 'United Kingdom', phone: '44' },
{ code: 'GD', label: 'Grenada', phone: '1-473' }, { code: 'GD', label: 'Grenada', phone: '1-473' },
{ code: 'GE', label: 'Georgia', phone: '995' }, { code: 'GE', label: 'Georgia', phone: '995' },
{ code: 'GF', label: 'French Guiana', phone: '594' }, { code: 'GF', label: 'French Guiana', phone: '594' },
@@ -178,7 +177,6 @@ const countries = [
{ code: 'IE', label: 'Ireland', phone: '353' }, { code: 'IE', label: 'Ireland', phone: '353' },
{ code: 'IL', label: 'Israel', phone: '972' }, { code: 'IL', label: 'Israel', phone: '972' },
{ code: 'IM', label: 'Isle of Man', phone: '44' }, { code: 'IM', label: 'Isle of Man', phone: '44' },
{ code: 'IN', label: 'India', phone: '91' },
{ {
code: 'IO', code: 'IO',
label: 'British Indian Ocean Territory', label: 'British Indian Ocean Territory',
@@ -390,12 +388,6 @@ const countries = [
}, },
{ code: 'UA', label: 'Ukraine', phone: '380' }, { code: 'UA', label: 'Ukraine', phone: '380' },
{ code: 'UG', label: 'Uganda', phone: '256' }, { code: 'UG', label: 'Uganda', phone: '256' },
{
code: 'US',
label: 'United States',
phone: '1',
suggested: true,
},
{ code: 'UY', label: 'Uruguay', phone: '598' }, { code: 'UY', label: 'Uruguay', phone: '598' },
{ code: 'UZ', label: 'Uzbekistan', phone: '998' }, { code: 'UZ', label: 'Uzbekistan', phone: '998' },
{ {
+1 -1
View File
@@ -37,7 +37,7 @@ import {
AvatarGroup, AvatarGroup,
} from "@mui/material" } from "@mui/material"
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const CreatorGrid = props => { const CreatorGrid = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, isHeader } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, isHeader } = props
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
+75 -72
View File
@@ -65,80 +65,83 @@ export const LoadStats = (globalUrl, cachekey) => {
} }
const DashboardBarchart = (props) => { const DashboardBarchart = (props) => {
const { timelineData, title, height, } = props; // this is clearly unfinished and not worth my time.
var inputHeight = 15 // refer to health page to see how i made it work there w/o using chartjs
if (height !== undefined && height !== null) {
inputHeight = height
}
const barOptions = { // const { timelineData, title, height, } = props;
plugins: { // var inputHeight = 15
tooltip: { // if (height !== undefined && height !== null) {
enabled: true, // Ensure tooltips are enabled // inputHeight = height
}, // }
},
tooltips: {
mode: 'index',
intersect: false,
},
legend: {
display: false
},
layout: {
padding: {
top: 0, // Adjust the top padding as needed
bottom: -10, // Adjust the bottom padding as needed
left: 0, // Adjust the left padding as needed
right: 0, // Adjust the right padding as needed
},
},
scales: {
y: {
beginAtZero: false,
},
yAxes: [{
ticks: {
display: false
},
beginAtZero: false,
}],
xAxes: [{
ticks: {
display: false
},
beginAtZero: false,
}]
},
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
const label = data.labels[tooltipItem.index]
return label.split('\n')[0]
},
afterLabel: function (tooltipItem, data) {
const amount = tooltipItem.value === undefined || tooltipItem.value === null ? 0 : tooltipItem.value
return `Amount: ${amount}`
},
title: function () {
return title === undefined ? '' : title
}
}
}
}
return ( // const barOptions = {
<Bar // plugins: {
data={timelineData} // tooltip: {
options={barOptions} // enabled: true, // Ensure tooltips are enabled
height={inputHeight} // },
getElementAtEvent={(elements) => { // },
if (elements && elements.length > 0) { // tooltips: {
//toast("Click event") // mode: 'index',
console.log("Clicked: ", elements) // intersect: false,
} // },
}} // legend: {
/> // display: false
) // },
// layout: {
// padding: {
// top: 0, // Adjust the top padding as needed
// bottom: -10, // Adjust the bottom padding as needed
// left: 0, // Adjust the left padding as needed
// right: 0, // Adjust the right padding as needed
// },
// },
// scales: {
// y: {
// beginAtZero: false,
// },
// yAxes: [{
// ticks: {
// display: false
// },
// beginAtZero: false,
// }],
// xAxes: [{
// ticks: {
// display: false
// },
// beginAtZero: false,
// }]
// },
// tooltips: {
// callbacks: {
// label: function (tooltipItem, data) {
// const label = data.labels[tooltipItem.index]
// return label.split('\n')[0]
// },
// afterLabel: function (tooltipItem, data) {
// const amount = tooltipItem.value === undefined || tooltipItem.value === null ? 0 : tooltipItem.value
// return `Amount: ${amount}`
// },
// title: function () {
// return title === undefined ? '' : title
// }
// }
// }
// }
// return (
// <Bar
// data={timelineData}
// options={barOptions}
// height={inputHeight}
// getElementAtEvent={(elements) => {
// if (elements && elements.length > 0) {
// //toast("Click event")
// console.log("Clicked: ", elements)
// }
// }}
// />
// )
} }
export default DashboardBarchart; export default DashboardBarchart;
+1 -1
View File
@@ -29,7 +29,7 @@ import {
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const DocsGrid = props => { const DocsGrid = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, } = props
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
+12 -6
View File
@@ -3,6 +3,8 @@ import OrgHeaderexpanded from "../components/OrgHeaderexpandedNew.jsx";
import OrgHeader from '../components/OrgHeaderNew.jsx'; import OrgHeader from '../components/OrgHeaderNew.jsx';
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import CloudSyncTab from '../components/CloudSyncTab.jsx'; import CloudSyncTab from '../components/CloudSyncTab.jsx';
import { Context } from '../context/ContextApi.jsx';
import { getTheme } from '../theme.jsx';
import { import {
FileCopy as FileCopyIcon, FileCopy as FileCopyIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
@@ -10,6 +12,7 @@ import {
Button, Button,
Tooltip, Tooltip,
IconButton, IconButton,
Typography,
} from "@mui/material"; } from "@mui/material";
const EditOrgTab = (props) => { const EditOrgTab = (props) => {
@@ -33,6 +36,9 @@ const EditOrgTab = (props) => {
} }
}, []); }, []);
const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
const handleStatusChange = (event) => { const handleStatusChange = (event) => {
const { value } = event.target; const { value } = event.target;
setSelectedStatus(value); setSelectedStatus(value);
@@ -283,23 +289,23 @@ If you're interested, please let me know a time that works for you, or set up a
return ( return (
<div style={{ width: "100%", boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121', borderRadius: '16px', }}> <div style={{ width: "100%", boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor, borderRadius: '16px', }}>
<div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}} > <div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}} >
<div style={{ marginBottom: 20 }}> <div style={{ marginBottom: 20 }}>
<div style={{display:"flex"}}> <div style={{display:"flex"}}>
<div style={{width:'70%'}}> <div style={{width:'70%'}}>
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Organization overview</h2> <Typography variant='h3' sx={{ marginBottom: "15px", marginTop: 0, }}>Organization overview</Typography>
<span style={{ color: "#9E9E9E" }}> <Typography variant="body2" style={{ color: theme.palette.text.secondary, fontSize: 16 }}>
On this page organization admins can configure organisations, and sub-orgs (MSSP).{" "} On this page organization admins can configure organisations, and sub-orgs (MSSP).{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="/docs/organizations#organization" href="/docs/organizations#organization"
style={{ color: "#FF8444" }} style={{ color: theme.palette.linkColor, }}
> >
Learn more Learn more
</a> </a>
</span> </Typography>
</div> </div>
<div style={{display:"flex", alignItems:"center", marginLeft:50}}> <div style={{display:"flex", alignItems:"center", marginLeft:50}}>
<Tooltip <Tooltip
+40 -28
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useContext } from "react"; import React, { useEffect, useContext } from "react";
import theme from '../theme.jsx'; import { getTheme } from '../theme.jsx';
import { isMobile } from "react-device-detect" import { isMobile } from "react-device-detect"
import { MuiChipsInput } from "mui-chips-input"; import { MuiChipsInput } from "mui-chips-input";
import { toast } from "react-toastify" import { toast } from "react-toastify"
@@ -46,7 +46,7 @@ import {
Slider, Slider,
} from "@mui/material"; } from "@mui/material";
import { Context } from "../context/ContextApi.jsx";
import { import {
DatePicker, DatePicker,
LocalizationProvider, LocalizationProvider,
@@ -69,7 +69,8 @@ const EditWorkflow = (props) => {
const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, setRealtimeMarkdown, boxWidth, setBoxWidth, } = props const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, setRealtimeMarkdown, boxWidth, setBoxWidth, } = props
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
const {themeMode, brandColor} = useContext(Context)
const theme = getTheme(themeMode, brandColor)
const [submitLoading, setSubmitLoading] = React.useState(false); const [submitLoading, setSubmitLoading] = React.useState(false);
const [showMoreClicked, setShowMoreClicked] = React.useState(isEditing !== false ? true : false); const [showMoreClicked, setShowMoreClicked] = React.useState(isEditing !== false ? true : false);
@@ -187,7 +188,7 @@ const EditWorkflow = (props) => {
} }
const newWorkflow = isEditing === true ? false : true const newWorkflow = isEditing === true ? false : true
const priority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) const priority = userdata === undefined || userdata === null || userdata.priorities === null || userdata.priorities === undefined ? null : userdata?.priorities?.find(prio => prio.type === "usecase" && prio.active === true)
var upload = ""; var upload = "";
var total_count = 0 var total_count = 0
@@ -203,26 +204,24 @@ const EditWorkflow = (props) => {
setModalOpen(false); setModalOpen(false);
}} }}
PaperProps={{ PaperProps={{
style: { sx: {
color: "white", color: theme.palette.DialogStyle.color,
minWidth: isMobile ? "90%" : 650, minWidth: isMobile ? "90%" : "650px",
maxWidth: isMobile ? "90%" : 650, maxWidth: isMobile ? "90%" : "650px",
minHeight: 400, minHeight: "400px",
paddingTop: 25,
paddingLeft: 50,
//minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
//maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.DialogStyle.borderRadius,
backgroundColor: "black", backgroundColor: themeMode === "dark" ? "black" : theme?.palette?.DialogStyle?.backgroundColor,
}, },
}} }}
> >
<DialogTitle style={{ padding: 30, paddingBottom: 0, zIndex: 1000, }}> <DialogTitle style={{ padding: 30, paddingBottom: 0, zIndex: 1000, paddingTop: "25px", paddingLeft: "50px"}}>
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<div style={{ flex: 1, color: "rgba(255,255,255,0.9)" }}> <div style={{ flex: 1, color: theme.palette.textColor }}>
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<Typography variant="h4" style={{ flex: 9, }}> <Typography variant="h4" style={{ flex: 9, marginTop: 25, }}>
{newWorkflow ? "New" : "Editing"} workflow {newWorkflow ? "New" : "Editing"} Workflow
</Typography> </Typography>
{newWorkflow === true ? null : {newWorkflow === true ? null :
@@ -248,7 +247,7 @@ const EditWorkflow = (props) => {
</div> </div>
<Typography variant="body2" color="textSecondary" style={{ marginTop: 20, maxWidth: 440, }}> <Typography variant="body2" color="textSecondary" style={{ marginTop: 20, maxWidth: 440, }}>
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> Workflows can be built from scratch, or from templates. <a href="/usecases2" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: theme.palette.linkColor }}>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: theme.palette.linkColor }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color:theme.palette.linkColor }}>Learn more</a>
</Typography> </Typography>
<div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}> <div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}>
@@ -288,20 +287,21 @@ const EditWorkflow = (props) => {
</DialogTitle> </DialogTitle>
<FormControl> <FormControl>
<div style={{ <div style={{
borderTop: "1px solid rgba(255,255,255,0.5)", borderTop: theme.palette.defaultBorder,
width: 600, width: 600,
position: "fixed", position: "fixed",
right: 20, right: 20,
bottom: 0, bottom: 0,
zIndex: 1002, zIndex: 1002,
backgroundColor: theme.palette.backgroundColor,
height: 75, height: 75,
paddingTop: 20, paddingTop: 20,
paddingLeft: 75, paddingLeft: 30,
backgroundColor: themeMode === "dark" ? "#262626" : theme.palette.DialogStyle.backgroundColor,
}}> }}>
<Button <Button
variant="contained" variant="contained"
style={{}} style={{}}
id="save_workflow_button"
disabled={name.length === 0 || submitLoading === true} disabled={name.length === 0 || submitLoading === true}
onClick={() => { onClick={() => {
setSubmitLoading(true) setSubmitLoading(true)
@@ -387,7 +387,7 @@ const EditWorkflow = (props) => {
</Button> </Button>
</div> </div>
<DialogContent style={{ paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, paddingBottom: 200, }}> <DialogContent style={{ paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, paddingBottom: 400, paddingLeft: 50, }}>
<div style={{ minWidth: newWorkflow ? 500 : 550, maxWidth: newWorkflow ? 450 : 500, }}> <div style={{ minWidth: newWorkflow ? 500 : 550, maxWidth: newWorkflow ? 450 : 500, }}>
<TextField <TextField
onChange={(event) => { onChange={(event) => {
@@ -395,7 +395,7 @@ const EditWorkflow = (props) => {
}} }}
InputProps={{ InputProps={{
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
}, },
}} }}
color="primary" color="primary"
@@ -406,6 +406,7 @@ const EditWorkflow = (props) => {
label="Name" label="Name"
autoFocus autoFocus
fullWidth fullWidth
id="Enter-Workflow-Name"
/> />
<div style={{ display: "flex", marginTop: 10, }}> <div style={{ display: "flex", marginTop: 10, }}>
@@ -467,7 +468,7 @@ const EditWorkflow = (props) => {
style={{ flex: 1, maxHeight: 120, overflow: "auto", }} style={{ flex: 1, maxHeight: 120, overflow: "auto", }}
InputProps={{ InputProps={{
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
}, },
}} }}
placeholder="Tags" placeholder="Tags"
@@ -543,6 +544,8 @@ const EditWorkflow = (props) => {
}} }}
> >
<FormControlLabel value="test" control={<Radio />} label="Test" /> <FormControlLabel value="test" control={<Radio />} label="Test" />
<FormControlLabel value="staging" control={<Radio />} label="Staging" />
<FormControlLabel value="preprod" control={<Radio />} label="Pre-production" />
<FormControlLabel value="production" control={<Radio />} label="Production" /> <FormControlLabel value="production" control={<Radio />} label="Production" />
</RadioGroup> </RadioGroup>
@@ -577,12 +580,20 @@ const EditWorkflow = (props) => {
<Typography variant="h4" style={{ marginTop: 50, }}> <Typography variant="h4" style={{ marginTop: 50, }}>
Multi-Tenancy, Backups & Security Multi-Tenancy, Backups & Security
</Typography> </Typography>
<Typography variant="body2" color="textSecondary" style={{ marginTop: 30, marginBottom: 10, }}> <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!) Control mechanisms for multi-tenancy, backups, and security.
</Typography> </Typography>
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ? <Typography variant="h6" style={{ marginTop: 50, }}>
Multi-Tenant Workflows
</Typography>
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, marginBottom: 10, }}>
Make one workflow, and keep a separate, synced copy in your other tenants. Control distributed auth, runtime locations, files, datastore keys etc. Can only distribute from parent org to child org. Need help trying it? <a href="https://shuffler.io/contact" target="_blank" style={{color: "#f85a3e", textDecoration: "none",}}>Contact us for a demo</a>
</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.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 === "" ? 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)" }}> <Typography variant="body2" style={{ marginTop: 10, color: "rgba(255,255,255,0.7)" }}>
@@ -600,6 +611,7 @@ const EditWorkflow = (props) => {
multiple multiple
style={{ marginTop: 10, }} style={{ marginTop: 10, }}
value={innerWorkflow.suborg_distribution === undefined || innerWorkflow.suborg_distribution === null ? ["none"] : innerWorkflow.suborg_distribution} value={innerWorkflow.suborg_distribution === undefined || innerWorkflow.suborg_distribution === null ? ["none"] : innerWorkflow.suborg_distribution}
disabled={workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow.length > 0}
onChange={(e) => { onChange={(e) => {
var newvalue = e.target.value var newvalue = e.target.value
if (newvalue.length > 1 && newvalue[0] === "none") { if (newvalue.length > 1 && newvalue[0] === "none") {
@@ -1274,7 +1286,7 @@ const EditWorkflow = (props) => {
{newWorkflow === true ? {newWorkflow === true ?
<span style={{ marginTop: 30, }}> <span style={{ paddingTop: 30 }}>
<Typography variant="h6" style={{ marginLeft: 30, paddingBottom: 0, }}> <Typography variant="h6" style={{ marginLeft: 30, paddingBottom: 0, }}>
Relevant Workflows Relevant Workflows
</Typography> </Typography>
+64 -51
View File
@@ -1,5 +1,5 @@
import React, { memo, useContext, useEffect, useState } from 'react'; import React, { memo, useContext, useEffect, useState } from 'react';
import theme from "../theme.jsx"; import { getTheme } from "../theme.jsx";
import { import {
Tooltip, Tooltip,
Typography, Typography,
@@ -62,6 +62,9 @@ const EnvironmentTab = memo((props) => {
const [selectedSubOrg, setSelectedSubOrg] = React.useState([]); const [selectedSubOrg, setSelectedSubOrg] = React.useState([]);
const [showLocationActionModal, setShowLocationActionModal] = React.useState(undefined) const [showLocationActionModal, setShowLocationActionModal] = React.useState(undefined)
const { themeMode, supportEmail, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
useEffect(() => { useEffect(() => {
getEnvironments(); getEnvironments();
@@ -370,7 +373,7 @@ const EnvironmentTab = memo((props) => {
}) })
.catch((error) => { .catch((error) => {
toast( toast(
"Failed dismissing alert. Please contact support@shuffler.io if this persists.", `Failed dismissing alert. Please contact ${supportEmail} if this persists.`,
); );
}); });
}; };
@@ -512,11 +515,11 @@ const EnvironmentTab = memo((props) => {
}} }}
> >
<DialogTitle> <DialogTitle>
<span style={{ color: "white" }}>Add Location</span> <Typography variant='h5' color="textPrimary" >Add Location</Typography>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<div> <div>
Location Name <Typography variant='body2' color="textPrimary">Location Name</Typography>
<TextField <TextField
color="primary" color="primary"
style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor,}} style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor,}}
@@ -524,7 +527,7 @@ const EnvironmentTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: "50px", height: "50px",
color: "white", color: theme.palette.textFieldStyle.color,
fontSize: "1em", fontSize: "1em",
}, },
}} }}
@@ -539,19 +542,18 @@ const EnvironmentTab = memo((props) => {
} }
/> />
</div> </div>
{loginInfo} {/* Assuming loginInfo is part of the relevant content */} {loginInfo}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", color: "#ff8544" }} style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", color: theme.palette.primary.main, }}
onClick={() => setModalOpen(false)} onClick={() => setModalOpen(false)}
color="primary"
> >
Cancel Cancel
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", backgroundColor: "#ff8544", color: "#1a1a1a" }} style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", }}
onClick={() => { onClick={() => {
submitEnvironment(modalUser); // Assuming modalUser is available submitEnvironment(modalUser); // Assuming modalUser is available
}} }}
@@ -733,9 +735,9 @@ const EnvironmentTab = memo((props) => {
}} }}
> >
<DialogTitle> <DialogTitle>
<div style={{ color: "rgba(255,255,255,0.9)" }}> <Typography variant='h5' color="textPrimary" >
Select sub-org to distribute Environments Select sub-org to distribute Environments
</div> </Typography>
</DialogTitle> </DialogTitle>
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}> <DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
<MenuItem value="none" onClick={()=> {handleSelectSubOrg(null, "none")}}>None</MenuItem> <MenuItem value="none" onClick={()=> {handleSelectSubOrg(null, "none")}}>None</MenuItem>
@@ -778,7 +780,7 @@ const EnvironmentTab = memo((props) => {
<div style={{ display: "flex", marginTop: 20 }}> <div style={{ display: "flex", marginTop: 20 }}>
<Button <Button
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#ff8544" }} style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: theme.palette.primary.main }}
onClick={() => setShowDistributionPopup(false)} onClick={() => setShowDistributionPopup(false)}
color="primary" color="primary"
> >
@@ -786,7 +788,7 @@ const EnvironmentTab = memo((props) => {
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#1a1a1a", backgroundColor: "#ff8544", marginLeft: 10 }} style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, marginLeft: 10 }}
onClick={() => { onClick={() => {
changeDistribution(selectedEnvironment, selectedSubOrg); changeDistribution(selectedEnvironment, selectedSubOrg);
}} }}
@@ -800,27 +802,27 @@ const EnvironmentTab = memo((props) => {
) : null; ) : null;
return ( return (
<div style={{ width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121',borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", }}> <div style={{ width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor,borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: theme.palette.defaultBorder, }}>
{modalView} {modalView}
{EnvironmentDistributionModal} {EnvironmentDistributionModal}
<div style={{ height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}> <div style={{ height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin' }}>
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}> <div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
<div style={{ marginBottom: 20 }}> <div style={{ marginBottom: 20 }}>
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Runtime Locations</h2> <Typography variant='h5' color="textPrimary" style={{ marginBottom: 8, marginTop: 0,}}>Runtime Locations</Typography>
<span style={{ color: textColor }}> <Typography variant='body2' color="textSecondary">
Decides which Orborus <b>runtime location</b> to run your workflows in. Previously called Environments. <br /> If you have scale problems, <a href="https://shuffler.io/docs/configuration#high-availability" target="_blank" rel="noopener noreferrer" style={{ color: "#FF8444" }}>check the docs</a> or talk to our team: support@shuffler.io.&nbsp; Decides which Orborus <b>runtime location</b> to run your workflows in. Previously called Environments. <br /> If you have scale problems, <a href="https://shuffler.io/docs/configuration#high-availability" target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.linkColor }}>check the docs</a> or talk to our team: {supportEmail}.&nbsp;
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="/docs/organizations#locations" href="/docs/organizations#locations"
style={{ color: "#FF8444" }} style={{ color: theme.palette.linkColor, }}
> >
Learn more Learn more
</a> </a>
</span> </Typography>
</div> </div>
<Button <Button
style={{ backgroundColor: '#ff8544', color: "#1a1a1a", borderRadius: 4, textTransform: "capitalize", fontSize: 16, }} style={{ borderRadius: 4, textTransform: "capitalize", fontSize: 16, }}
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
@@ -828,9 +830,9 @@ const EnvironmentTab = memo((props) => {
Add Location Add Location
</Button> </Button>
<Button <Button
style={{ backgroundColor: "#2F2F2F", borderRadius: 4, width: 81, height: 40, marginLeft: 16, marginRight: 15 }} style={{ borderRadius: 4, width: 81, height: 40, marginLeft: 16, marginRight: 15 }}
variant="contained" variant="contained"
color="primary" color="secondary"
onClick={getEnvironments} onClick={getEnvironments}
> >
<CachedIcon /> <CachedIcon />
@@ -851,7 +853,7 @@ const EnvironmentTab = memo((props) => {
style={{ style={{
borderRadius: 4, borderRadius: 4,
marginTop: 24, marginTop: 24,
border: "1px solid #494949", border: theme.palette.defaultBorder,
width: "100%", width: "100%",
overflowX: "auto", overflowX: "auto",
paddingBottom: 0, paddingBottom: 0,
@@ -873,7 +875,7 @@ const EnvironmentTab = memo((props) => {
width: "100%", width: "100%",
minWidth: 800, minWidth: 800,
paddingBottom: 0, paddingBottom: 0,
borderBottom: "1px solid #494949", borderBottom: theme.palette.defaultBorder,
}} }}
> >
{["Type", "Status", "Scale", "Pipeline", "Name", "Type", "Queue", "Actions", "Distribution"].map((header, index) => { {["Type", "Status", "Scale", "Pipeline", "Name", "Type", "Queue", "Actions", "Distribution"].map((header, index) => {
@@ -901,7 +903,7 @@ const EnvironmentTab = memo((props) => {
style={{ style={{
display: "grid", display: "grid",
gridTemplateColumns: "80px 80px 80px 120px 120px 120px 120px 350px 150px", gridTemplateColumns: "80px 80px 80px 120px 120px 120px 120px 350px 150px",
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
height: 40, height: 40,
width: "100%", width: "100%",
boxSizing: "border-box", boxSizing: "border-box",
@@ -919,7 +921,7 @@ const EnvironmentTab = memo((props) => {
variant="text" variant="text"
animation="wave" animation="wave"
sx={{ sx={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.loaderColor,
borderRadius: "4px", borderRadius: "4px",
}} }}
/> />
@@ -941,9 +943,9 @@ const EnvironmentTab = memo((props) => {
return null; return null;
} }
var bgColor = "#212121"; var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF";
if (index % 2 === 0) { if (index % 2 === 0) {
bgColor = "#1A1A1A"; bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA";
} }
// Check if there's a notification for it in userdata.priorities // Check if there's a notification for it in userdata.priorities
@@ -1013,7 +1015,9 @@ const EnvironmentTab = memo((props) => {
environment.name === "Cloud" ? ( environment.name === "Cloud" ? (
<Tooltip title="Cloud" placement="top"> <Tooltip title="Cloud" placement="top">
<CloudIcon <CloudIcon
style={{ color: "rgba(255,255,255,0.8)" }} style={{
color: themeMode === "dark" ? "#CCCCCC" : "#333333",
}}
/> />
</Tooltip> </Tooltip>
) : environment.run_type === "docker" ? ( ) : environment.run_type === "docker" ? (
@@ -1075,7 +1079,7 @@ const EnvironmentTab = memo((props) => {
: :
<span>IP / label: {environment?.running_ip?.split(":")[0]}. May stay running up to a minute after stopping Orborus.</span> <span>IP / label: {environment?.running_ip?.split(":")[0]}. May stay running up to a minute after stopping Orborus.</span>
: :
"Cloud is automatically configured. Reachout to support@shuffler.io if you have any questions." `Cloud is automatically configured. Reachout to ${supportEmail} if you have any questions.`
} }
<br /> <br />
@@ -1169,7 +1173,7 @@ const EnvironmentTab = memo((props) => {
<ListItemText <ListItemText
primary={ primary={
environment.Type === "cloud" ? environment.Type === "cloud" ?
<Tooltip title={"Make a new environment to set up a Datalake node. Please contact support@shuffler.io if this is something you want to see on Cloud directly."} placement="top"> <Tooltip title={`Make a new environment to set up a Datalake node. Please contact ${supportEmail} if this is something you want to see on Cloud directly.`} placement="top">
<CancelIcon style={{ color: "rgba(255,255,255,0.3)" }} /> <CancelIcon style={{ color: "rgba(255,255,255,0.3)" }} />
</Tooltip> </Tooltip>
: :
@@ -1267,13 +1271,12 @@ const EnvironmentTab = memo((props) => {
/> />
<ListItemText <ListItemText
style={{ style={{
minWidth: 300, minWidth: 350,
overflow: "hidden",
}} }}
> >
<div style={{ display: "flex", flexWrap: "nowrap" }}> <div style={{ display: "flex", flexWrap: "nowrap" }}>
<ButtonGroup <ButtonGroup
style={{ borderRadius: "5px 5px 5px 5px", flexWrap: "nowrap" }} style={{ borderRadius: "5px 5px 5px 5px", flexWrap: "nowrap", width: "100%" }}
> >
<Button <Button
variant="outlined" variant="outlined"
@@ -1369,7 +1372,7 @@ const EnvironmentTab = memo((props) => {
</ButtonGroup> </ButtonGroup>
<IconButton disabled={environment.Type === "cloud"} onClick={()=> {setIsExpanded(prev => !prev)}}> <IconButton disabled={environment.Type === "cloud"} onClick={()=> {setIsExpanded(prev => !prev)}}>
{listItemExpanded === index ? <ExpandLessIcon /> : <ExpandMoreIcon />} {listItemExpanded === index ? <ExpandLessIcon sx={{color: theme.palette.text.primary}} /> : <ExpandMoreIcon sx={{color: theme.palette.text.primary}}/>}
</IconButton> </IconButton>
</div> </div>
</ListItemText> </ListItemText>
@@ -1420,7 +1423,7 @@ const EnvironmentTab = memo((props) => {
<Collapse in={listItemExpanded === index} timeout="auto" unmountOnExit> <Collapse in={listItemExpanded === index} timeout="auto" unmountOnExit>
<Grid container justifyContent="center" style={{minWidth: 850, maxWidth: 850, }}> <Grid container justifyContent="center" style={{minWidth: 850, maxWidth: 850, }}>
<Grid item xs={12} sm={8} md={6}> <Grid item xs={12} sm={8} md={6}>
<div style={{minWidth: 700, maxWidth: 700, minHeight: 350, display: 'flex', justifyContent: "center", backgroundColor: "transparent", }}> <div style={{minWidth: 750, maxWidth: 750, minHeight: 350, display: 'flex', justifyContent: "center", backgroundColor: "transparent", }}>
<div style={{ paddingTop: 50, paddingBottom: 100, }}> <div style={{ paddingTop: 50, paddingBottom: 100, }}>
<Typography variant="h6"> <Typography variant="h6">
Self-Hosted Orborus instance Self-Hosted Orborus instance
@@ -1443,7 +1446,7 @@ const EnvironmentTab = memo((props) => {
> >
<Tab <Tab
value={0} value={0}
label=<span> label=<span style={{color: theme.palette.text.secondary, }}>
<img <img
src="/icons/docker.svg" src="/icons/docker.svg"
style={{ width: 20, height: 20, marginRight: 10, }} style={{ width: 20, height: 20, marginRight: 10, }}
@@ -1452,32 +1455,32 @@ const EnvironmentTab = memo((props) => {
/> />
<Tab <Tab
value={1} value={1}
label=<span> label=<span style={{color: theme.palette.text.secondary, }}>
<img <img
src="/icons/docker.svg" src="/icons/docker.svg"
style={{ width: 20, height: 20, marginRight: 10, }} style={{ width: 20, height: 20, marginRight: 10,}}
/> Scale /> Scale
</span> </span>
/> />
<Tab <Tab
value={2} value={2}
label=<span> label=<span style={{color: theme.palette.text.secondary, }}>
<img <img
src="/icons/k8s.svg" src="/icons/k8s.svg"
style={{ width: 20, height: 20, marginRight: 10, }} style={{ width: 20, height: 20, marginRight: 10 }}
/> k8s /> k8s
</span> </span>
/> />
</Tabs> </Tabs>
<Typography variant="body1" color="textSecondary" style={{marginTop: 15, }}> <Typography variant="body1" color="textSecondary" style={{marginTop: 15, }}>
{installationTab === 2 ? {installationTab === 2 ?
<span> <Typography variant='body2' color="textSecondary">
Check our <a href="https://docs.docker.com/get-started/get-docker/" target="_blank" rel="noopener noreferrer" style={{textDecoration: "none", color: "#f85a3e",}}>Kubernetes documentation</a> for more information on how to run Shuffle on Kubernetes. The status of the node will change when connected. Check our <a href="https://docs.docker.com/get-started/get-docker/" target="_blank" rel="noopener noreferrer" style={{textDecoration: "none", color: "#f85a3e",}}>Kubernetes documentation</a> for more information on how to run Shuffle on Kubernetes. The status of the node will change when connected.
</span> </Typography>
: :
<span> <Typography variant='body2' color="textSecondary">
1. <a href="https://docs.docker.com/get-started/get-docker/" target="_blank" rel="noopener noreferrer" style={{textDecoration: "none", color: "#f85a3e",}}>Ensure Docker is installed</a> and the target server can reach '{globalUrl}' 1. <a href="https://docs.docker.com/get-started/get-docker/" target="_blank" rel="noopener noreferrer" style={{textDecoration: "none", color: "#f85a3e",}}>Ensure Docker is installed</a> and the target server can reach '{globalUrl}'
</span> </Typography>
} }
</Typography> </Typography>
@@ -1501,13 +1504,19 @@ const EnvironmentTab = memo((props) => {
> >
<div style={{ display: "flex", position: "relative", }}> <div style={{ display: "flex", position: "relative", }}>
<code <code
contenteditable="true" contentEditable="true"
id="orborus_command" id="orborus_command"
style={{ style={{
// Wrap if larger than X
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
overflow: "auto", overflow: "auto",
marginRight: 30, marginRight: 30,
backgroundColor: themeMode === "dark" ? "#1e1e1e" : "#f5f5f5",
color: themeMode === "dark" ? "#f8f8f2" : "#333",
padding: "8px",
borderRadius: "4px",
fontFamily: "monospace",
fontSize: 18,
border: themeMode === "dark" ? "1px solid #555" : "1px solid #ddd",
}} }}
> >
{getOrborusCommand(environment)} {getOrborusCommand(environment)}
@@ -1532,7 +1541,8 @@ const EnvironmentTab = memo((props) => {
</div> </div>
<Divider style={{marginTop: 25, marginBottom: 10, }}/> <Divider style={{marginTop: 25, marginBottom: 10, }}/>
Configure HTTP Proxies: <Checkbox <div style={{display: 'flex', alignItems: 'center', }}>
<Typography variant='body2' color="textSecondary">Configure HTTP Proxies:</Typography> <Checkbox
id="shuffle_skip_proxies" id="shuffle_skip_proxies"
onClick={() => { onClick={() => {
if (commandController.proxies === undefined) { if (commandController.proxies === undefined) {
@@ -1545,8 +1555,10 @@ const EnvironmentTab = memo((props) => {
setUpdate(Math.random()) setUpdate(Math.random())
}} }}
/> />
</div>
<div /> <div />
Disable Pipelines & Data Lake: <Checkbox <div style={{display: 'flex', alignItems: 'center', }}>
<Typography variant='body2' color="textSecondary">Disable Pipelines & Data Lake:</Typography> <Checkbox
id="shuffle_skip_pipelines" id="shuffle_skip_pipelines"
onClick={() => { onClick={() => {
if (commandController.pipelines === undefined) { if (commandController.pipelines === undefined) {
@@ -1559,6 +1571,7 @@ const EnvironmentTab = memo((props) => {
}} }}
/> />
</div> </div>
</div>
} }
<Typography variant="body1" color="textSecondary" style={{marginTop: 15, }}> <Typography variant="body1" color="textSecondary" style={{marginTop: 15, }}>
+107 -47
View File
@@ -43,7 +43,7 @@ import {
import Dropzone from "../components/Dropzone.jsx"; import Dropzone from "../components/Dropzone.jsx";
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
import { Context } from "../context/ContextApi.jsx"; import { Context } from "../context/ContextApi.jsx";
const Files = memo((props) => { const Files = memo((props) => {
@@ -58,6 +58,8 @@ const Files = memo((props) => {
const [openEditor, setOpenEditor] = React.useState(false); const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false);
const [loadFileModalOpen, setLoadFileModalOpen] = React.useState(false); const [loadFileModalOpen, setLoadFileModalOpen] = React.useState(false);
const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
const [field1, setField1] = React.useState(""); const [field1, setField1] = React.useState("");
const [field2, setField2] = React.useState(""); const [field2, setField2] = React.useState("");
@@ -442,7 +444,7 @@ const Files = memo((props) => {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#ff8544" }} style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: theme.palette.primary.main }}
onClick={() => setLoadFileModalOpen(false)} onClick={() => setLoadFileModalOpen(false)}
color="primary" color="primary"
> >
@@ -450,7 +452,7 @@ const Files = memo((props) => {
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#1a1a1a", backgroundColor: "#ff8544" }} style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, }}
disabled={downloadUrl.length === 0 || !downloadUrl.includes("http")} disabled={downloadUrl.length === 0 || !downloadUrl.includes("http")}
onClick={() => { onClick={() => {
handleGithubValidation(); handleGithubValidation();
@@ -517,9 +519,9 @@ const Files = memo((props) => {
}} }}
> >
<DialogTitle> <DialogTitle>
<div style={{ color: "rgba(255,255,255,0.9)" }}> <Typography variant="h5" color="textPrimary" >
Select sub-org to distribute files Select sub-org to distribute files
</div> </Typography>
</DialogTitle> </DialogTitle>
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}> <DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
<MenuItem value="none" onClick={()=> {handleSelectSubOrg(null, "none")}}>None</MenuItem> <MenuItem value="none" onClick={()=> {handleSelectSubOrg(null, "none")}}>None</MenuItem>
@@ -562,15 +564,14 @@ const Files = memo((props) => {
<div style={{ display: "flex", marginTop: 20 }}> <div style={{ display: "flex", marginTop: 20 }}>
<Button <Button
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#ff8544" }} style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: theme.palette.primary.main }}
onClick={() => setShowDistributionPopup(false)} onClick={() => setShowDistributionPopup(false)}
color="primary"
> >
Cancel Cancel
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#1a1a1a", backgroundColor: "#ff8544", marginLeft: 10 }} style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, marginLeft: 10 }}
onClick={() => { onClick={() => {
changeDistribution(fileIdSelectedForDistribution, selectedSubOrg); changeDistribution(fileIdSelectedForDistribution, selectedSubOrg);
}} }}
@@ -906,27 +907,27 @@ const Files = memo((props) => {
onDrop={uploadFile} onDrop={uploadFile}
> >
{fileDistributionModal} {fileDistributionModal}
<div style={{width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121',borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", }}> <div style={{width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor,borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: theme.palette.defaultBorder, }}>
<div style={{height: "100%", maxHeight: 1700,overflowY: 'auto', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}> <div style={{height: "100%", maxHeight: 1700,overflowY: 'auto', scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}> <div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin' }}>
<DownloadFileIcon setLoadFileModalOpen={setLoadFileModalOpen} isSelectedFiles={isSelectedFiles} /> <DownloadFileIcon setLoadFileModalOpen={setLoadFileModalOpen} isSelectedFiles={isSelectedFiles} />
{fileDownloadModal} {fileDownloadModal}
<div style={{ marginTop: isSelectedFiles ? 2: 20, marginBottom:20 }}> <div style={{ marginTop: isSelectedFiles ? 2: 20, marginBottom:20 }}>
<h2 style={{ display: isSelectedFiles ? null : "inline", marginTop: isSelectedFiles?0:null, marginBottom: isSelectedFiles?8:null, color: "#FFFFFF"}}>Files</h2> <Typography variant="h5" color="textPrimary" style={{ display: isSelectedFiles ? null : "inline", marginTop: isSelectedFiles?0:null, marginBottom: isSelectedFiles?8:null, fontWeight: 500}}>Files</Typography>
<span style={{ marginLeft: isSelectedFiles ? null : 25, color:isSelectedFiles?"#9E9E9E":null}}> <Typography variant="body2" color="textSecondary" style={{ marginLeft: isSelectedFiles ? null : 25,}}>
Files from Workflows are a way to store as well as edit files.{" "} Files from Workflows are a way to store as well as edit files.{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="https://shuffler.io/docs/organizations#files" href="https://shuffler.io/docs/organizations#files"
style={{ textDecoration: isSelectedFiles ? null:"none", color: isSelectedFiles? "#FF8444": "#f85a3e" }} style={{ textDecoration: isSelectedFiles ? null:"none", color: theme.palette.linkColor }}
> >
Learn more Learn more
</a> </a>
</span> </Typography>
</div> </div>
@@ -937,7 +938,7 @@ const Files = memo((props) => {
onClick={() => { onClick={() => {
upload.click(); upload.click();
}} }}
style={{backgroundColor: isSelectedFiles?'#ff8544':null, color:isSelectedFiles?"#212121":null, textTransform: 'none',fontSize: 16, borderRadius:isSelectedFiles?4:null, width:isSelectedFiles?143:null, height:isSelectedFiles?35:null, boxShadow: isSelectedFiles?'none':null,}} style={{ textTransform: 'none',fontSize: 16, borderRadius:isSelectedFiles?4:null, width:isSelectedFiles?143:null, height:isSelectedFiles?35:null, boxShadow: isSelectedFiles?'none':null,}}
> >
Upload files Upload files
</Button> </Button>
@@ -958,9 +959,9 @@ const Files = memo((props) => {
}} }}
/> />
<Button <Button
style={{ marginLeft: 16, marginRight: 15, backgroundColor:isSelectedFiles?"#2F2F2F":null,borderRadius:isSelectedFiles?4:null, width:isSelectedFiles?81:null, height:isSelectedFiles?35:null, boxShadow: isSelectedFiles?'none':null, }} style={{ marginLeft: 16, marginRight: 15, borderRadius:isSelectedFiles?4:null, width:isSelectedFiles?81:null, height:isSelectedFiles?35:null, boxShadow: isSelectedFiles?'none':null, }}
variant="contained" variant="contained"
color="primary" color="secondary"
onClick={() => getFiles(selectedCategory)} onClick={() => getFiles(selectedCategory)}
> >
<CachedIcon /> <CachedIcon />
@@ -976,7 +977,6 @@ const Files = memo((props) => {
labelId="input-namespace-select-label" labelId="input-namespace-select-label"
id="input-namespace-select-id" id="input-namespace-select-id"
style={{ style={{
color: "white",
minWidth: 122, minWidth: 122,
maxWidth: 122, maxWidth: 122,
height: 35, height: 35,
@@ -1013,7 +1013,6 @@ const Files = memo((props) => {
<MenuItem <MenuItem
key={index} key={index}
value={data} value={data}
style={{ color: "white" }}
> >
{data.replaceAll("_", " ")} {data.replaceAll("_", " ")}
</MenuItem> </MenuItem>
@@ -1044,8 +1043,8 @@ const Files = memo((props) => {
Please note that your selected files ({selectedFileId?.length}) will be moved to the <kbd>{updateToThisCategory}</kbd> category. Please note that your selected files ({selectedFileId?.length}) will be moved to the <kbd>{updateToThisCategory}</kbd> category.
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => setShowFileCategoryPopup(false)} style={{fontSize: 16, textTransform: 'none'}}>Close</Button> <Button onClick={() => setShowFileCategoryPopup(false)} style={{fontSize: 16, textTransform: 'none', color: theme.palette.primary.main }}>Close</Button>
<Button onClick={() => handleUpdateFileCategory(updateToThisCategory)} style={{fontSize: 16, textTransform: 'none', color: "#1a1a1a", backgroundColor: "#ff8544"}}>Update</Button> <Button variant="contained" color="primary" onClick={() => handleUpdateFileCategory(updateToThisCategory)} style={{fontSize: 16, textTransform: 'none', }}>Update</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</FormControl> </FormControl>
@@ -1055,8 +1054,9 @@ const Files = memo((props) => {
{renderTextBox ? {renderTextBox ?
<Tooltip title={"Close"} style={{}} aria-label={""}> <Tooltip title={"Close"} style={{}} aria-label={""}>
<Button <Button
style={{ marginLeft: 5, marginRight: 15, height: 35, borderRadius: 4, backgroundColor: "#494949", textTransform: 'none', fontSize: 16, color: "#f1f1f1" }} style={{ marginLeft: 5, marginRight: 15, height: 35, borderRadius: 4, textTransform: 'none', fontSize: 16, }}
color="primary" variant="contained"
color="secondary"
onClick={() => { onClick={() => {
setRenderTextBox(false); setRenderTextBox(false);
console.log(" close clicked") console.log(" close clicked")
@@ -1068,8 +1068,9 @@ const Files = memo((props) => {
: :
<Tooltip title={"Add new file category"} style={{}} aria-label={""}> <Tooltip title={"Add new file category"} style={{}} aria-label={""}>
<Button <Button
style={{ marginLeft: 5, marginRight: 15, width: 169, height: 35, borderRadius: 4, backgroundColor: "#494949", textTransform: 'none', fontSize: 16, color: "#f1f1f1" }} style={{whiteSpace: 'nowrap', textWrap: 'nowrap', marginLeft: 5, marginRight: 15, width: 169, height: 35, borderRadius: 4, textTransform: 'none', fontSize: 16, }}
color="primary" variant="contained"
color="secondary"
onClick={() => { onClick={() => {
setRenderTextBox(true); setRenderTextBox(true);
}} }}
@@ -1096,7 +1097,8 @@ const Files = memo((props) => {
}} }}
InputProps={{ InputProps={{
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
height: 35, height: 35,
fontSize: 16, fontSize: 16,
borderRadius: 4, borderRadius: 4,
@@ -1133,7 +1135,7 @@ const Files = memo((props) => {
style={{ style={{
borderRadius: 4, borderRadius: 4,
marginTop: 24, marginTop: 24,
border: "1px solid #494949", border: theme.palette.defaultBorder,
width: "100%", width: "100%",
overflowX: "auto", overflowX: "auto",
paddingBottom: 0, paddingBottom: 0,
@@ -1151,7 +1153,7 @@ const Files = memo((props) => {
> >
<ListItem <ListItem
style={{ style={{
borderBottom: "1px solid #494949" , borderBottom: theme.palette.defaultBorder ,
display: "table-row" display: "table-row"
}} }}
> >
@@ -1198,7 +1200,7 @@ const Files = memo((props) => {
padding: index === 0 ? "0px 8px 8px 15px" : "0px 8px 8px 8px", padding: index === 0 ? "0px 8px 8px 15px" : "0px 8px 8px 8px",
whiteSpace: "nowrap", whiteSpace: "nowrap",
textOverflow: "ellipsis", textOverflow: "ellipsis",
borderBottom: "1px solid #494949", borderBottom: theme.palette.defaultBorder,
verticalAlign: "middle" verticalAlign: "middle"
}} }}
primaryTypographyProps={{ primaryTypographyProps={{
@@ -1215,7 +1217,7 @@ const Files = memo((props) => {
key={rowIndex} key={rowIndex}
style={{ style={{
display: "table-row", display: "table-row",
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
}} }}
> >
{Array(8) {Array(8)
@@ -1232,7 +1234,7 @@ const Files = memo((props) => {
variant="text" variant="text"
animation="wave" animation="wave"
sx={{ sx={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.loaderColor,
height: "20px", height: "20px",
borderRadius: "4px", borderRadius: "4px",
}} }}
@@ -1257,9 +1259,9 @@ const Files = memo((props) => {
return null; return null;
} }
var bgColor = isSelectedFiles ? "#212121":"#27292d"; var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF";
if (index % 2 === 0) { if (index % 2 === 0) {
bgColor = isSelectedFiles ? "#1A1A1A":"#1f2023"; bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA";
} }
const isDistributed = file?.suborg_distribution?.length > 0 ? true : false; const isDistributed = file?.suborg_distribution?.length > 0 ? true : false;
const filenamesplit = file.filename.split(".") const filenamesplit = file.filename.split(".")
@@ -1425,9 +1427,20 @@ const Files = memo((props) => {
readFileData(file) readFileData(file)
}} }}
> >
<img src="/icons/editIcon.svg" alt="edit icon" <svg
style={{color: iseditable ? "white" : "grey", width: 24, height: 24}} width="24"
/> height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.1038 4.66848C16.3158 4.45654 16.5674 4.28843 16.8443 4.17373C17.1212 4.05903 17.418 4 17.7177 4C18.0174 4 18.3142 4.05903 18.5911 4.17373C18.868 4.28843 19.1196 4.45654 19.3315 4.66848C19.5435 4.88041 19.7116 5.13201 19.8263 5.40891C19.941 5.68582 20 5.9826 20 6.28232C20 6.58204 19.941 6.87882 19.8263 7.15573C19.7116 7.43263 19.5435 7.68423 19.3315 7.89617L8.43807 18.7896L4 20L5.21038 15.5619L16.1038 4.66848Z"
stroke={themeMode=== "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</IconButton> </IconButton>
</span> </span>
</Tooltip> </Tooltip>
@@ -1470,15 +1483,39 @@ const Files = memo((props) => {
downloadFile(file); downloadFile(file);
}} }}
> >
<img src="/icons/downloadIcon.svg" alt="download icon" <svg
style={{ width="24"
width: 24, height: 24, height="24"
color: viewBox="0 0 24 24"
file.status === "active" fill="none"
? "white" xmlns="http://www.w3.org/2000/svg"
: "grey", >
}} <rect
width="24"
height="24"
fill={themeMode === "dark" ? "#212121" : "#EDEDED"}
fillOpacity="0.02"
/> />
<path
d="M8.22595 16.4463L11.7792 19.9995L15.3324 16.4463"
stroke={file.status === "active" ? (themeMode === "dark" ? "#F1F1F1" : "black") : "grey"}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M11.7792 12.0049V19.9997"
stroke={file.status === "active" ? (themeMode === "dark" ? "#F1F1F1" : "black") : "grey"}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M19.6676 17.415C20.4399 16.8719 21.019 16.0968 21.321 15.2023C21.6229 14.3078 21.632 13.3403 21.3468 12.4403C21.0617 11.5402 20.4971 10.7545 19.7352 10.197C18.9732 9.6396 18.0534 9.33948 17.1092 9.34021H15.99C15.7228 8.299 15.2229 7.33196 14.5279 6.5119C13.8329 5.69184 12.961 5.04013 11.9777 4.60583C10.9944 4.17153 9.92534 3.96596 8.85109 4.00459C7.77684 4.04322 6.72535 4.32505 5.77578 4.82886C4.82621 5.33267 4.00331 6.04534 3.36902 6.9132C2.73474 7.78106 2.30559 8.78151 2.11391 9.83922C1.92222 10.8969 1.97297 11.9844 2.26236 13.0196C2.55174 14.0549 3.07221 15.011 3.78459 15.816"
stroke={file.status === "active" ? (themeMode === "dark" ? "#F1F1F1" : "black") : "grey"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</IconButton> </IconButton>
</span> </span>
</Tooltip> </Tooltip>
@@ -1496,7 +1533,31 @@ const Files = memo((props) => {
toast(file.id + " copied to clipboard"); toast(file.id + " copied to clipboard");
}} }}
> >
<img src="/icons/copyIcon.svg" alt="copy icon" style={{ color: "white", width: 24, height: 24 }} /> <svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="24"
height="24"
fillOpacity="1"
/>
<path
d="M14 4H7.6C7.17565 4 6.76869 4.16857 6.46863 4.46863C6.16857 4.76869 6 5.17565 6 5.6V18.4C6 18.8243 6.16857 19.2313 6.46863 19.5314C6.76869 19.8314 7.17565 20 7.6 20H17.2C17.6243 20 18.0313 19.8314 18.3314 19.5314C18.6314 19.2313 18.8 18.8243 18.8 18.4V8.8L14 4Z"
stroke={themeMode === "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14 4V8.8H18.8"
stroke={themeMode === "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip <Tooltip
@@ -1570,7 +1631,6 @@ const Files = memo((props) => {
disabled={userdata?.active_org?.role !== "admin" || (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} checked={isDistributed}
style={{ }} style={{ }}
color="secondary"
onClick={() => { onClick={() => {
setShowDistributionPopup(true) setShowDistributionPopup(true)
if(file?.suborg_distribution?.length > 0){ if(file?.suborg_distribution?.length > 0){
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -1,7 +1,8 @@
import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; import React, { useRef, useState, useEffect, useLayoutEffect, useContext } from "react";
import { Context } from "../context/ContextApi.jsx";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useParams, useNavigate, Link } from "react-router-dom"; import { useParams, useNavigate, Link } from "react-router-dom";
import theme from '../theme.jsx'; import { getTheme } from '../theme.jsx';
//import { useAlert //import { useAlert
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
@@ -112,6 +113,8 @@ const AuthenticationOauth2 = (props) => {
authenticationType.client_secret !== null && authenticationType.client_secret !== null &&
authenticationType.client_secret.length > 0 authenticationType.client_secret.length > 0
); );
const {themeMode, brandColor} = useContext(Context);
const theme = getTheme(themeMode, brandColor);
const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : ""); const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : "");
const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : ""); const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "");
@@ -724,7 +727,7 @@ const AuthenticationOauth2 = (props) => {
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette?.borderRadius, }} style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette?.borderRadius, }}
src={selectedAction.large_image} src={selectedAction.large_image}
/> />
<Typography style={{ margin: 0, marginLeft: 10, marginTop: 5,}} variant="body1"> <Typography style={{ margin: 0, marginLeft: 10, marginTop: 5, color: "#2f2f2f",}} variant="body1">
One-click Login One-click Login
</Typography> </Typography>
</span> </span>
@@ -738,7 +741,7 @@ const AuthenticationOauth2 = (props) => {
return ( return (
<div> <div>
<DialogTitle> <DialogTitle>
<div style={{ color: "white" }}> <div style={{ color: theme.palette.text.primary }}>
Authenticate {selectedApp.name.replaceAll("_", " ")} Authenticate {selectedApp.name.replaceAll("_", " ")}
</div> </div>
</DialogTitle> </DialogTitle>
@@ -749,7 +752,7 @@ const AuthenticationOauth2 = (props) => {
target="_blank" target="_blank"
rel="norefferer" rel="norefferer"
href="/docs/apps#authentication" href="/docs/apps#authentication"
style={{ textDecoration: "none", color: "#f85a3e" }} style={{ textDecoration: "none", color: theme.palette.linkColor}}
> >
{" "} {" "}
Learn more about Oauth2 with Shuffle Learn more about Oauth2 with Shuffle
+15 -13
View File
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState, useContext } from "react";
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { Context } from "../context/ContextApi.jsx";
import { import {
Tooltip, Tooltip,
TextField, TextField,
@@ -69,6 +69,8 @@ const OrgHeader = (props) => {
selectedOrganization.description selectedOrganization.description
); );
const { themeMode } = useContext(Context)
const {theme} = getTheme(themeMode)
const [file, setFile] = React.useState(""); const [file, setFile] = React.useState("");
const [fileBase64, setFileBase64] = React.useState( const [fileBase64, setFileBase64] = React.useState(
@@ -364,10 +366,10 @@ const OrgHeader = (props) => {
> >
<FormControl> <FormControl>
<DialogTitle> <DialogTitle>
<div style={{ color: "rgba(255, 255, 255, 0.9)" }}>Upload Organization Image</div> <div style={{ color: theme?.palette?.textColor}}>Upload Organization Image</div>
</DialogTitle> </DialogTitle>
{errorText} {errorText}
<DialogContent style={{ color: "rgba(255, 255, 255, 0.65)" }}> <DialogContent style={{ color: theme?.palette?.textColor }}>
<AvatarEditor <AvatarEditor
ref={setEditorRef} ref={setEditorRef}
image={croppedData} image={croppedData}
@@ -389,7 +391,7 @@ const OrgHeader = (props) => {
style={appIconStyle} style={appIconStyle}
onClick={() => { upload.click(); }} onClick={() => { upload.click(); }}
> >
<AddAPhotoOutlinedIcon style={{ color: "rgba(255, 255, 255, 0.9)" }} /> <AddAPhotoOutlinedIcon style={{ color: theme?.palette?.textColor }} />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip title="Zoom In"> <Tooltip title="Zoom In">
@@ -399,7 +401,7 @@ const OrgHeader = (props) => {
style={appIconStyle} style={appIconStyle}
onClick={zoomIn} onClick={zoomIn}
> >
<ZoomInOutlinedIcon style={{ color: "rgba(255, 255, 255, 0.9)" }} /> <ZoomInOutlinedIcon style={{ color: theme?.palette?.textColor }} />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip title="Zoom Out"> <Tooltip title="Zoom Out">
@@ -409,7 +411,7 @@ const OrgHeader = (props) => {
style={appIconStyle} style={appIconStyle}
onClick={zoomOut} onClick={zoomOut}
> >
<ZoomOutOutlinedIcon style={{ color: "rgba(255, 255, 255, 0.9)" }} /> <ZoomOutOutlinedIcon style={{ color: theme?.palette?.textColor }} />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip title="Rotate"> <Tooltip title="Rotate">
@@ -419,7 +421,7 @@ const OrgHeader = (props) => {
style={appIconStyle} style={appIconStyle}
onClick={rotation} onClick={rotation}
> >
<LoopIcon style={{ color: "rgba(255, 255, 255, 0.9)" }} /> <LoopIcon style={{ color: theme?.palette?.textColor }} />
</Button> </Button>
</Tooltip> </Tooltip>
</div> </div>
@@ -427,7 +429,7 @@ const OrgHeader = (props) => {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", color: "rgba(255, 255, 255, 0.9)" }} style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", color: theme?.palette?.textColor }}
onClick={onCancelSaveAppIcon} onClick={onCancelSaveAppIcon}
> >
Cancel Cancel
@@ -491,7 +493,7 @@ const OrgHeader = (props) => {
<div style={{ marginLeft: 16, alignContent: "center" }}> <div style={{ marginLeft: 16, alignContent: "center" }}>
<div > <div >
<Button <Button
style={{ backgroundColor: '#ff8544', fontSize: 16, textTransform: 'capitalize', color: "#212121", boxShadow: "none", borderRadius: 4, width: 128, height: 40 }} style={{ fontSize: 16, textTransform: 'capitalize', boxShadow: "none", borderRadius: 4, width: 128, height: 40 }}
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => { onClick={() => {
@@ -503,9 +505,9 @@ const OrgHeader = (props) => {
</div> </div>
<div> <div>
<Button <Button
style={{ backgroundColor: '#494949', fontSize: 16, textTransform: 'capitalize', color: "#ffffff", boxShadow: "none", marginTop: 20, borderRadius: 4, width: 128, height: 40 }}
variant="contained" variant="contained"
color="primary" color="secondary"
style={{ fontSize: 16, textTransform: 'capitalize', boxShadow: "none", marginTop: 20, borderRadius: 4, width: 128, height: 40 }}
onClick={() => removeImage()} onClick={() => removeImage()}
> >
Remove Remove
@@ -1,8 +1,9 @@
import React, { memo, useEffect, useState } from "react"; import React, { memo, useEffect, useState, useContext } from "react";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { toast } from "react-toastify" import { toast } from "react-toastify"
import theme from '../theme.jsx'; import { getTheme } from '../theme.jsx';
import { Context } from "../context/ContextApi.jsx";
//import { useAlert //import { useAlert
import { import {
@@ -89,6 +90,8 @@ const OrgHeaderexpandedNew = (props) => {
); );
const [openNotification, setOpenNotification] = React.useState(false); const [openNotification, setOpenNotification] = React.useState(false);
const { themeMode, supportEmail, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
const handleStatusChange = (event) => { const handleStatusChange = (event) => {
const { value } = event.target; const { value } = event.target;
@@ -178,7 +181,7 @@ const OrgHeaderexpandedNew = (props) => {
const [uploadRepo, setUploadRepo] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_repo === undefined || selectedOrganization.defaults.workflow_upload_repo.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_repo) const [uploadRepo, setUploadRepo] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_repo === undefined || selectedOrganization.defaults.workflow_upload_repo.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_repo)
const [uploadBranch, setUploadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch === undefined || selectedOrganization.defaults.workflow_upload_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch) const [uploadBranch, setUploadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch === undefined || selectedOrganization.defaults.workflow_upload_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch)
const [uploadUsername, setUploadUsername] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_username === undefined || selectedOrganization.defaults.workflow_upload_username.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_username) const [uploadUsername, setUploadUsername] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_username === undefined || selectedOrganization.defaults.workflow_upload_username.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_username)
const [uploadToken, setUploadToken] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_token === undefined || selectedOrganization.defaults.workflow_upload_token.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_token) const [uploadToken, setUploadToken] = React.useState("")
const [regionStatus, setRegionStatus] = useState(); const [regionStatus, setRegionStatus] = useState();
useEffect(() => { useEffect(() => {
@@ -199,9 +202,10 @@ const OrgHeaderexpandedNew = (props) => {
setUploadUsername(selectedOrganization?.defaults?.workflow_upload_username) setUploadUsername(selectedOrganization?.defaults?.workflow_upload_username)
} }
if (uploadToken !== selectedOrganization?.defaults?.workflow_upload_token) { // Not showing the token in the UI (cause it's in plain text)
setUploadToken(selectedOrganization?.defaults?.workflow_upload_token) // if (uploadToken !== selectedOrganization?.defaults?.workflow_upload_token) {
} // setUploadToken(selectedOrganization?.defaults?.workflow_upload_token)
// }
}, [selectedOrganization]) }, [selectedOrganization])
useEffect(() => { useEffect(() => {
@@ -264,7 +268,7 @@ const OrgHeaderexpandedNew = (props) => {
const handleSendChangeRegionMail = (region) => { const handleSendChangeRegionMail = (region) => {
if (selectedOrganization === undefined || selectedOrganization === null) { if (selectedOrganization === undefined || selectedOrganization === null) {
toast.error("Failed to send request for changing region. Please contact support@shuffler.io.") toast.error(`Failed to send request for changing region. Please contact ${supportEmail}`)
return return
} }
@@ -294,30 +298,29 @@ const OrgHeaderexpandedNew = (props) => {
body: JSON.stringify(data), body: JSON.stringify(data),
}).then((response) => { }).then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
toast.error("Failed to send request for changing region. Please contact support@shuffler.io.") toast.error(`Failed to send request for changing region. Please contact ${supportEmail}`)
} else { } else {
toast.success("Successfully sent request for region change. We will process the move and contact you shortly.") toast.success("Successfully sent request for region change. We will process the move and contact you shortly.")
} }
}).catch((err) => { }).catch((err) => {
console.log(err) console.log(err)
toast.error("Failed to send request for changing region. Please contact support@shuffler.io.") toast.error(`Failed to send request for changing region. Please contact ${supportEmail}`)
}) })
} }
const setSelectedRegion = (region) => { const setSelectedRegion = (region) => {
// send a POST request to /api/v1/orgs/{org_id}/region with the region as the body // send a POST request to /api/v1/orgs/{org_id}/region with the region as the body
if (region === "US") { const regionMap = {
region = "us-west2" "US": "us-west2",
} else if (region === "EU") { "EU": "europe-west2",
region = "europe-west2" "CA": "northamerica-northeast1",
} else if (region === "CA") { "UK": "europe-west2",
region = "northamerica-northeast1" "EU-2": "europe-west3",
} else if (region === "UK") { "AUS": "australia-southeast1"
region = "europe-west2" };
} else if (region === "EU-2") {
region = "europe-west3" region = regionMap[region] || region;
}
var data = { var data = {
dst_region: region dst_region: region
@@ -336,7 +339,11 @@ const OrgHeaderexpandedNew = (props) => {
timeOut: 1000 timeOut: 1000
}).then((response) => { }).then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
toast("Failed to change region!") response.json().then((reason) => {
toast.error("Failed to change region: " + reason.reason)
}).catch((err) => {
toast.error("Failed to change region")
});
} }
else { else {
toast("Region changed successfully! Reloading in 5 seconds..") toast("Region changed successfully! Reloading in 5 seconds..")
@@ -347,7 +354,7 @@ const OrgHeaderexpandedNew = (props) => {
} }
return response.json(); // return responseJson
}) })
} }
@@ -355,7 +362,7 @@ const OrgHeaderexpandedNew = (props) => {
const orgSaveButton = ( const orgSaveButton = (
<Tooltip title="Save any unsaved data" placement="bottom"> <Tooltip title="Save any unsaved data" placement="bottom">
<Button <Button
style={{ width: 244, height: 51, display: 'flex', justifyContent: 'center', textTransform: 'capitalize', padding: "16px, 24px, 16px, 24px", borderRadius: 4, backgroundColor: "#ff8544", color: "#1a1a1a", fontSize: 16, }} style={{ width: 244, height: 51, display: 'flex', justifyContent: 'center', textTransform: 'capitalize', padding: "16px, 24px, 16px, 24px", borderRadius: 4, fontSize: 16, }}
variant="contained" variant="contained"
color="primary" color="primary"
disabled={ disabled={
@@ -441,11 +448,11 @@ const OrgHeaderexpandedNew = (props) => {
<Grid item xs={12} style={{}}> <Grid item xs={12} style={{}}>
<span> <span>
<div style={{}}> <div style={{}}>
<div style={{ flex: "3", color: "white" }}> <div style={{ flex: "3", color: theme.palette.text.primary }}>
<div style={{ marginTop: 8, display: "flex" }} /> <div style={{ marginTop: 8, display: "flex" }} />
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<div style={{ width: "100%", maxWidth: 434, marginRight: 10 }}> <div style={{ width: "100%", maxWidth: 434, marginRight: 10 }}>
Name <Typography variant="text" style={{color: theme.palette.text.primary}}>Name</Typography>
<TextField <TextField
required required
style={{ style={{
@@ -456,7 +463,8 @@ const OrgHeaderexpandedNew = (props) => {
maxWidth: 434, maxWidth: 434,
marginTop: "5px", marginTop: "5px",
marginRight: "15px", marginRight: "15px",
backgroundColor: isEditOrgTab ? "#212121" : theme.palette.inputColor, color: theme.palette.textFieldStyle.color,
backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
}} }}
fullWidth={true} fullWidth={true}
placeholder="Name" placeholder="Name"
@@ -510,10 +518,11 @@ const OrgHeaderexpandedNew = (props) => {
color="primary" color="primary"
InputProps={{ InputProps={{
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
height: "35px", height: "35px",
fontSize: "1em", fontSize: "1em",
borderRadius: 4, borderRadius: 4,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
}, },
classes: { classes: {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
@@ -523,10 +532,10 @@ const OrgHeaderexpandedNew = (props) => {
</div> </div>
{userdata?.support ? ( {userdata?.support ? (
<div style={{ alignItems: 'center' }}> <div style={{ alignItems: 'center' }}>
<div style={{ marginRight: '12px', color: 'white' }}>Status</div> <div style={{ marginRight: '12px', color: theme.palette.text.primary }}>Status</div>
<FormControl style={{ width: 220, height: 35 }}> <FormControl style={{ width: 220, height: 35 }}>
<Select <Select
style={{ minWidth: 220, marginTop: 5, maxWidth: 220, height: 35, borderRadius: 4 }} style={{ minWidth: 220, marginTop: 5, maxWidth: 220, height: 35, borderRadius: 4, color: theme.palette.textFieldStyle.color}}
id="multiselect-status" id="multiselect-status"
multiple multiple
value={selectedStatus} value={selectedStatus}
@@ -535,7 +544,7 @@ const OrgHeaderexpandedNew = (props) => {
renderValue={(selected) => selected.join(', ')} renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps} MenuProps={MenuProps}
> >
{["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "old customer", "old lead"].map((name) => ( {["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "integration partner", "distribution partner", "service partner", "old customer", "old lead"].map((name) => (
<MenuItem key={name} value={name}> <MenuItem key={name} value={name}>
<Checkbox checked={selectedStatus.indexOf(name) > -1} /> <Checkbox checked={selectedStatus.indexOf(name) > -1} />
<ListItemText primary={name} /> <ListItemText primary={name} />
@@ -549,13 +558,13 @@ const OrgHeaderexpandedNew = (props) => {
{isCloud ? ( {isCloud ? (
<div style={{ marginLeft: 13, fontSize: 16, color: "#9E9E9E" }} > <div style={{ marginLeft: 13, fontSize: 16, color: "#9E9E9E" }} >
Change Region <Typography variant="text" style={{color: theme.palette.text.primary}}>Change Region</Typography>
<RegionChangeModal selectedOrganization={selectedOrganization} setSelectedRegion={setSelectedRegion} userdata={userdata} handleSendChangeRegionMail={handleSendChangeRegionMail} /> <RegionChangeModal selectedOrganization={selectedOrganization} setSelectedRegion={setSelectedRegion} userdata={userdata} handleSendChangeRegionMail={handleSendChangeRegionMail} />
</div> </div>
) : null} ) : null}
</div> </div>
<div style={{ marginTop: "10px" }} /> <div style={{ marginTop: "10px" }} />
About <Typography variant="text" style={{color: theme.palette.text.primary}}>Description</Typography>
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<TextField <TextField
required required
@@ -565,7 +574,8 @@ const OrgHeaderexpandedNew = (props) => {
flex: "1", flex: "1",
marginTop: "5px", marginTop: "5px",
marginRight: "15px", marginRight: "15px",
backgroundColor: isEditOrgTab ? "#212121" : theme.palette.inputColor, color: theme.palette.textFieldStyle.color,
backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
height: 89, height: 89,
borderRadius: 4, borderRadius: 4,
}} }}
@@ -618,7 +628,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
height: 89, height: 89,
borderRadius: 4, borderRadius: 4,
}, },
@@ -629,7 +639,7 @@ const OrgHeaderexpandedNew = (props) => {
</div> </div>
</div> </div>
<Typography variant="h5" style={{ color: "rgba(241, 241, 241, 1)", fontSize: 24, fontWeight: 600, marginTop: 40, textAlign: "left" }}> <Typography variant="h5" style={{ fontSize: 24, fontWeight: 500, marginTop: 40, textAlign: "left" }}>
Preferences Preferences
</Typography> </Typography>
@@ -650,7 +660,7 @@ const OrgHeaderexpandedNew = (props) => {
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<span> <span>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Org Documentation reference</Typography> <Typography style={{ fontWeight: 400, fontSize: 18, color: theme.palette.text.primary }}>Org Documentation reference</Typography>
<Typography variant="body2" color="textSecondary" style={{ fontWeight: 400, fontSize: 16, marginTop: 8 }}> <Typography variant="body2" color="textSecondary" style={{ fontWeight: 400, fontSize: 16, marginTop: 8 }}>
Add a URL that is added as a link, pointing to any external documentation page you want. Add a URL that is added as a link, pointing to any external documentation page you want.
@@ -665,7 +675,8 @@ const OrgHeaderexpandedNew = (props) => {
height: 35, height: 35,
fontSize: 16, fontSize: 16,
borderRadius: 4, borderRadius: 4,
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, color: theme.palette.textFieldStyle.color,
backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
}} }}
fullWidth={true} fullWidth={true}
type="name" type="name"
@@ -706,6 +717,13 @@ const OrgHeaderexpandedNew = (props) => {
auto_provision: selectedOrganization?.sso_config?.auto_provision, auto_provision: selectedOrganization?.sso_config?.auto_provision,
} }
) )
if (userdata.org_status.includes("integration_partner")) {
toast.info("Reloading page to update the changes everywhere")
setTimeout(() => {
window.location.reload()
}, 5000)
}
} }
}} }}
onChange={(e) => { onChange={(e) => {
@@ -716,8 +734,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
borderRadius: 4, borderRadius: 4,
@@ -733,9 +750,9 @@ const OrgHeaderexpandedNew = (props) => {
serverside={false} serverside={false}
/> />
<Grid item xs={12} style={{ marginTop: 20, }}> <Grid item xs={12} style={{ marginTop: 20, }}>
<Typography variant="h4" style={{ textAlign: "left", color: "rgba(241, 241, 241, 1)", fontSize: 24, fontWeight: 600, }}>Workflow Backup Repository</Typography> <Typography variant="h5" style={{ textAlign: "left", fontWeight: 500, }}>Workflow Backup Repository</Typography>
<Typography variant="body2" style={{ textAlign: "left", marginTop: 8, color: "#9E9E9E", fontSize: 16, fontWeight: 400 }}> <Typography variant="body2" style={{ textAlign: "left", marginTop: 8, color: theme.palette.text.secondary, fontSize: 16, fontWeight: 400 }}>
Decide where workflows are backed up in a Git repository. Will create logs and notifications if upload fails. The repository and branch must already have been initialized. Files will show up in the repo root in the /orgId/workflow-status/workflowId.json format. <b>MSSP:</b> If suborg exists, this will automatically be applied for them as well (not retroactive). <a href="/docs/configuration#environment-variables" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">Credentials are encrypted.</a> Decide where workflows are backed up in a Git repository. Will create logs and notifications if upload fails. The repository and branch must already have been initialized. Files will show up in the repo root in the /orgId/workflow-status/workflowId.json format. <b>MSSP:</b> If suborg exists, this will automatically be applied for them as well (not retroactive). <a href="/docs/configuration#environment-variables" style={{ textDecoration: "none", color: theme.palette.linkColor }} target="_blank">Credentials are encrypted.</a>
</Typography> </Typography>
<Grid container style={{ marginTop: 10, }} spacing={2}> <Grid container style={{ marginTop: 10, }} spacing={2}>
<Grid item xs={6} style={{}}> <Grid item xs={6} style={{}}>
@@ -748,7 +765,7 @@ const OrgHeaderexpandedNew = (props) => {
marginTop: "8px", marginTop: "8px",
marginRight: "16px", marginRight: "16px",
height: 35, height: 35,
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
}} }}
fullWidth={true} fullWidth={true}
type="name" type="name"
@@ -767,7 +784,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
@@ -788,7 +805,7 @@ const OrgHeaderexpandedNew = (props) => {
marginTop: "8px", marginTop: "8px",
marginRight: "16px", marginRight: "16px",
height: 35, height: 35,
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
}} }}
fullWidth={true} fullWidth={true}
type="name" type="name"
@@ -807,7 +824,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
@@ -830,7 +847,7 @@ const OrgHeaderexpandedNew = (props) => {
marginTop: "8px", marginTop: "8px",
marginRight: "16px", marginRight: "16px",
height: 35, height: 35,
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
}} }}
fullWidth={true} fullWidth={true}
type="name" type="name"
@@ -849,8 +866,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
borderRadius: 4, borderRadius: 4,
@@ -862,7 +878,7 @@ const OrgHeaderexpandedNew = (props) => {
</Grid> </Grid>
<Grid item xs={6} style={{}}> <Grid item xs={6} style={{}}>
<span> <span>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Git token/password</Typography> <Typography style={{ fontWeight: 400, fontSize: 16 }}>New Git token/password</Typography>
<TextField <TextField
required required
style={{ style={{
@@ -870,7 +886,7 @@ const OrgHeaderexpandedNew = (props) => {
marginTop: "8px", marginTop: "8px",
marginRight: "16px", marginRight: "16px",
height: 35, height: 35,
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
}} }}
fullWidth={true} fullWidth={true}
id="outlined-with-placeholder" id="outlined-with-placeholder"
@@ -888,7 +904,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
@@ -915,7 +931,7 @@ const OrgHeaderexpandedNew = (props) => {
marginTop: "8px", marginTop: "8px",
marginRight: "16px", marginRight: "16px",
height: 35, height: 35,
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
}} }}
fullWidth={true} fullWidth={true}
type="name" type="name"
@@ -932,7 +948,8 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
@@ -954,7 +971,7 @@ const OrgHeaderexpandedNew = (props) => {
flex: "1", flex: "1",
marginTop: "8px", marginTop: "8px",
marginRight: "15px", marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
height: 35, height: 35,
}} }}
fullWidth={true} fullWidth={true}
@@ -972,7 +989,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
@@ -994,7 +1011,7 @@ const OrgHeaderexpandedNew = (props) => {
flex: "1", flex: "1",
marginTop: "5px", marginTop: "5px",
marginRight: "15px", marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
height: 35, height: 35,
}} }}
fullWidth={true} fullWidth={true}
@@ -1012,7 +1029,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
@@ -1034,7 +1051,7 @@ const OrgHeaderexpandedNew = (props) => {
flex: "1", flex: "1",
marginTop: "5px", marginTop: "5px",
marginRight: "15px", marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor, backgroundColor: isEditOrgTab ? theme.palette.textFieldStyle.backgroundColor : theme.palette.inputColor,
height: 35, height: 35,
}} }}
fullWidth={true} fullWidth={true}
@@ -1052,7 +1069,7 @@ const OrgHeaderexpandedNew = (props) => {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline, notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
}, },
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontWeight: 400, fontWeight: 400,
fontSize: 16, fontSize: 16,
@@ -1091,6 +1108,7 @@ const RegionChangeModal = memo(({ selectedOrganization, setSelectedRegion, userd
"EU-2": "eu", "EU-2": "eu",
"CA": "ca", "CA": "ca",
"UK": "gb", "UK": "gb",
"AUS": "au",
}; };
//let regiontag = "UK"; //let regiontag = "UK";
@@ -1112,6 +1130,9 @@ const RegionChangeModal = memo(({ selectedOrganization, setSelectedRegion, userd
} else if (regiontag === "ca") { } else if (regiontag === "ca") {
regiontag = "CA"; regiontag = "CA";
regionCode = "ca"; regionCode = "ca";
} else if (regiontag === "au") {
regiontag = "AUS";
regionCode = "au"
} }
} }
@@ -1124,11 +1145,12 @@ const RegionChangeModal = memo(({ selectedOrganization, setSelectedRegion, userd
value={regiontag} value={regiontag}
style={{ minWidth: 120, height: 35, borderRadius: 4 }} style={{ minWidth: 120, height: 35, borderRadius: 4 }}
onChange={(e) => { onChange={(e) => {
if (userdata?.support) { // if (userdata?.support) {
setSelectedRegion(e.target.value) // setSelectedRegion(e.target.value)
} else { // } else {
handleSendChangeRegionMail(e.target.value) // handleSendChangeRegionMail(e.target.value)
} // }
setSelectedRegion(e.target.value)
}} }}
> >
{Object.keys(regionMapping).map((region, index) => { {Object.keys(regionMapping).map((region, index) => {
@@ -1138,6 +1160,7 @@ const RegionChangeModal = memo(({ selectedOrganization, setSelectedRegion, userd
selectedOrganization.region = "europe-west2"; selectedOrganization.region = "europe-west2";
} }
// Check if the current region matches the selected region // Check if the current region matches the selected region
if (region === selectedOrganization.region) { if (region === selectedOrganization.region) {
// If the region matches, set the MenuItem as selected // If the region matches, set the MenuItem as selected
@@ -1145,7 +1168,7 @@ const RegionChangeModal = memo(({ selectedOrganization, setSelectedRegion, userd
<MenuItem value={region} key={index} disabled> <MenuItem value={region} key={index} disabled>
{/* show region image through cdn */} {/* show region image through cdn */}
<img src={`https://flagcdn.com/48x36/${regionImageCode}.png`} alt={region} style={{ marginRight: 10 }} /> <img src={`https://flagcdn.com/48x36/${regionImageCode}.png`} alt={region} style={{ marginRight: 10 }} />
{region} {region === "AUS" ? "AUS (test)" : region}
</MenuItem> </MenuItem>
); );
} else { } else {
@@ -1155,7 +1178,7 @@ const RegionChangeModal = memo(({ selectedOrganization, setSelectedRegion, userd
alt={region} alt={region}
style={{ marginRight: 10, width: 20, height: 18, }} style={{ marginRight: 10, width: 20, height: 18, }}
/> />
{region} {region === "AUS" ? "AUS (test)" : region}
</MenuItem>; </MenuItem>;
} }
})} })}
+55 -18
View File
@@ -1,15 +1,15 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback, useContext } from 'react';
import { Link, useNavigate, useLocation } from "react-router-dom"; import { Link, useNavigate, useLocation } from "react-router-dom";
import Billing from "../components/Billing.jsx"; import Billing from "../components/Billing.jsx";
import Priorities from "../components/Priorities.jsx"; import Priorities from "../components/Priorities.jsx";
import Branding from "../components/Branding.jsx"; import Branding from "../components/Branding.jsx";
import AnalyticsTab from '../components/AnalyticsTab.jsx';
import EditOrgTab from '../components/EditOrgTab.jsx'; import EditOrgTab from '../components/EditOrgTab.jsx';
import CloudSyncTab from '../components/CloudSyncTab.jsx'; import CloudSyncTab from '../components/CloudSyncTab.jsx';
import SSOTab from "../components/ssoTab.jsx" import SSOTab from "../components/ssoTab.jsx"
import { ToastContainer, toast } from "react-toastify"; import { ToastContainer, toast } from "react-toastify";
import { Button, Tooltip } from '@mui/material'; import { Button, Tooltip } from '@mui/material';
import { getTheme } from '../theme.jsx';
import { Context } from '../context/ContextApi.jsx';
const OrganizationTab = (props) => { const OrganizationTab = (props) => {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -26,7 +26,9 @@ const OrganizationTab = (props) => {
selectedOrganization, handleGetOrg, selectedOrganization, handleGetOrg,
handleStatusChange, handleEditOrg, handleStatusChange, handleEditOrg,
isLoaded, isLoaded,
removeCookie removeCookie,
isIntegrationPartner, isChildOrg,
isGlobalUser
} = props; } = props;
const [selectedTab, setSelectedTab] = useState('org_config'); const [selectedTab, setSelectedTab] = useState('org_config');
@@ -34,10 +36,28 @@ const OrganizationTab = (props) => {
const [billingInfo, setBillingInfo] = useState({}); const [billingInfo, setBillingInfo] = useState({});
const [orgRequest, setOrgRequest] = React.useState(true); const [orgRequest, setOrgRequest] = React.useState(true);
const [curIndex, setCurIndex] = React.useState(0); const [curIndex, setCurIndex] = React.useState(0);
const items = ['Org Configuration', "SSO", "Notifications", 'Billing & Stats', 'Branding'];
const [visibleTabs, setVisibleTabs] = useState(items);
const [unreadNotifications, setUnreadNotifications] = React.useState( const [unreadNotifications, setUnreadNotifications] = React.useState(
notifications?.filter((notification) => notification.read === false)?.length notifications?.filter((notification) => notification.read === false)?.length
); );
const { themeMode, brandColor, brandName } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
useEffect(() => {
if (isIntegrationPartner && isChildOrg && !isGlobalUser) {
setVisibleTabs(items.filter((item) => item !== 'Branding' && item !== 'SSO'));
}else {
if (userdata && userdata.active_org && userdata.active_org.role === 'admin') {
setVisibleTabs(items);
}else {
setVisibleTabs(items.filter((item) => item !== 'SSO'));
}
}
},[isIntegrationPartner, isChildOrg, isGlobalUser, userdata]);
useEffect(() => { useEffect(() => {
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const tabName = queryParams.get('admin_tab'); const tabName = queryParams.get('admin_tab');
@@ -49,10 +69,26 @@ const OrganizationTab = (props) => {
} else if(decodedTabName === 'sso'){ } else if(decodedTabName === 'sso'){
setCurIndex(1) setCurIndex(1)
}else if (decodedTabName === 'notifications' || decodedTabName === 'priorities') { }else if (decodedTabName === 'notifications' || decodedTabName === 'priorities') {
setCurIndex(2); if (isIntegrationPartner && isChildOrg && !isGlobalUser) {
setCurIndex(1);
}else {
if (userdata && userdata.active_org && userdata.active_org.role === 'admin') {
setCurIndex(2);
}else {
setCurIndex(1);
}
}
} else if (decodedTabName === 'billingstats' || decodedTabName === 'billing') { } else if (decodedTabName === 'billingstats' || decodedTabName === 'billing') {
setCurIndex(3); if (isIntegrationPartner && isChildOrg && !isGlobalUser) {
} else if (decodedTabName === 'branding(beta)') { setCurIndex(2);
} else {
if (userdata && userdata?.active_org && userdata?.active_org?.role === 'admin') {
setCurIndex(3);
} else {
setCurIndex(2);
}
}
} else if (decodedTabName === 'branding') {
setCurIndex(4); setCurIndex(4);
} }
// else if (decodedTabName === 'analytics') { // else if (decodedTabName === 'analytics') {
@@ -65,7 +101,7 @@ const OrganizationTab = (props) => {
const formattedTabName = tabName.toLowerCase().replace(/[\s&]+/g, ''); const formattedTabName = tabName.toLowerCase().replace(/[\s&]+/g, '');
const encodedTabName = encodeURIComponent(formattedTabName); const encodedTabName = encodeURIComponent(formattedTabName);
setSelectedTab(formattedTabName); setSelectedTab(formattedTabName);
document.title = `Shuffle - admin - ${formattedTabName}`; document.title = brandName?.length > 0 ? `${brandName} - admin - ${formattedTabName}` : `Shuffle - admin - ${formattedTabName}`;
navigate(`?admin_tab=${encodedTabName}`); navigate(`?admin_tab=${encodedTabName}`);
}; };
@@ -121,7 +157,7 @@ const OrganizationTab = (props) => {
isLoaded={isLoaded} isLoaded={isLoaded}
/> />
); );
case 'branding(beta)': case 'branding':
return <Branding return <Branding
isCloud={isCloud} isCloud={isCloud}
userdata={userdata} userdata={userdata}
@@ -138,10 +174,11 @@ const OrganizationTab = (props) => {
} }
}; };
return ( return (
<div style={{ height: "100%", width: "100%", color: '#FFFFFF', backgroundColor: '#212121', borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", boxSizing: 'border-box' }}> <div style={{ height: "100%", width: "100%", color: theme.palette.platformColor, backgroundColor: theme.palette.platformColor, borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: theme.palette.defaultBorder, boxSizing: 'border-box' }}>
<div style={{ display: 'flex', justifyContent: 'space-around', width: "100%", borderBottom: '1px solid #494949' ,boxSizing: 'border-box' }}> <div style={{ display: 'flex', justifyContent: 'space-around', width: "100%", borderBottom: theme.palette.defaultBorder ,boxSizing: 'border-box' }}>
{['Org Configuration', "sso", "Notifications", 'Billing & Stats', 'Branding (Beta)'].map((tabName, index) => ( {visibleTabs.map((tabName, index) => (
<Tooltip <Tooltip
key={index} key={index}
title={ title={
@@ -162,10 +199,10 @@ const OrganizationTab = (props) => {
sx={{ sx={{
"&.MuiButton-root": { "&.MuiButton-root": {
padding: '28px 0', padding: '28px 0',
borderBottom: index === curIndex ? '2px solid #FF8444' : 'none', borderBottom: index === curIndex ? `2px solid ${theme.palette.primary.main}`: 'none',
cursor: 'pointer', cursor: 'pointer',
fontWeight: index === curIndex ? 'bold' : 'normal', fontWeight: index === curIndex ? 'bold' : 'normal',
color: index === curIndex ? "#FF8444" : "#FFFFFF", color: index === curIndex ? theme.palette.primary.main : theme.palette.text.primary,
textTransform: 'none', textTransform: 'none',
fontSize: 16, fontSize: 16,
width: "100%", width: "100%",
@@ -173,7 +210,7 @@ const OrganizationTab = (props) => {
borderRadius: 0, borderRadius: 0,
}, },
"&: hover": { "&: hover": {
backgroundColor: "#323232" backgroundColor: theme.palette.hoverColor
}, },
"&.Mui-disabled": { "&.Mui-disabled": {
color: "#6F6F6F", color: "#6F6F6F",
@@ -183,7 +220,7 @@ const OrganizationTab = (props) => {
((tabName === "sso")) && !(userdata?.support || userdata?.active_org?.role === "admin") ((tabName === "sso")) && !(userdata?.support || userdata?.active_org?.role === "admin")
} }
> >
{index === 2 && unreadNotifications > 0 ? ( {tabName.toLowerCase() === "notifications" && unreadNotifications > 0 ? (
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<div style={{ <div style={{
position: 'absolute', position: 'absolute',
@@ -192,7 +229,7 @@ const OrganizationTab = (props) => {
width: 20, width: 20,
height: 20, height: 20,
borderRadius: 10, borderRadius: 10,
backgroundColor: '#FF8444', backgroundColor: theme.palette.primary.main,
color: '#FFFFFF', color: '#FFFFFF',
fontSize: 12, fontSize: 12,
display: 'flex', display: 'flex',
@@ -204,7 +241,7 @@ const OrganizationTab = (props) => {
{tabName} {tabName}
</div> </div>
) : ( ) : (
<>{index === 1 ? "SSO" : tabName}</> <>{tabName}</>
)} )}
</Button> </Button>
</div> </div>
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
import { Box, Typography } from '@mui/material'
import React from 'react'
const PartnerApps = () => {
return (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
height: "100%",
minHeight: "500px",
}}
>
<Typography>Partner Apps</Typography>
</Box>
)
}
export default PartnerApps
+987
View File
@@ -0,0 +1,987 @@
import React, { memo, useEffect, useState, useContext } from "react";
import { makeStyles } from "@mui/styles";
import { toast } from "react-toastify";
import { getTheme } from "../theme.jsx";
import { Context } from "../context/ContextApi.jsx";
import countries from "./Countries.jsx";
import {
FormControl,
InputLabel,
Paper,
OutlinedInput,
Checkbox,
Card,
Tooltip,
FormControlLabel,
Chip,
Link,
Typography,
Switch,
Select,
MenuItem,
Divider,
ListItemText,
TextField,
Button,
Tabs,
Tab,
Grid,
Autocomplete,
Skeleton,
Box,
} from "@mui/material";
const useStyles = makeStyles({
notchedOutline: {
borderColor: "#f85a3e !important",
},
});
const PartnerDetails = (props) => {
const {
userdata,
selectedOrganization,
setSelectedOrganization,
globalUrl,
isCloud,
adminTab,
isEditOrgTab,
partnerData,
loadingPartnerData,
setPartnerData,
} = props;
const classes = useStyles();
const defaultBranch = "main";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 400,
borderRadius: 10,
overflowY: "scroll",
},
},
getContentAnchorEl: () => null,
};
const expertiseOptions = [
"Cybersecurity",
"Cloud Security",
"Managed Detection and Response (MDR)",
"Incident Response",
"Penetration Testing",
"Compliance and Governance",
"Network Security",
"Endpoint Security",
"Identity and Access Management (IAM)",
"SIEM",
];
const servicesOptions = [
"Cybersecurity Consulting",
"Managed Security",
"Incident Response",
"Penetration Testing",
"Cloud Security",
"Managed Detection",
"Endpoint Security",
"Network Security",
"Identity Management",
"Compliance",
"Security Training",
"Security Management",
"Threat Intelligence",
"Web Security",
"Database Security",
"Disaster Recovery",
"IT Security Audit",
"Cybersecurity Training",
"Security Automation",
"AI Security",
"IoT Security",
"Cloud Security Broker",
"Security Service",
"Managed Security Provider",
"Cybersecurity Service",
]
const solutionsOptions = [
"EDR",
"MDR",
"XDR",
"Case Management",
"SIEM",
"Vulnerability Management",
"IPS",
"IDS",
"Threat Intelligence",
"IAM",
"Data Security",
"Incident Response",
"Cloud Security",
"Network Security",
]
const { themeMode } = useContext(Context);
const theme = getTheme(themeMode);
const [isDisabled, setIsDisabled] = useState(isCloud ? userdata?.active_org?.is_partner ? false : true : true);
const handleExpertiseChange = (event) => {
const { value } = event.target;
setPartnerData({
...partnerData,
expertise: value,
});
};
useEffect(() => {
setIsDisabled(isCloud ? userdata?.active_org?.is_partner ? false : true : true);
if(userdata?.support) {
setIsDisabled(false);
}
}, [userdata, isCloud]);
const handleServicesChange = (event) => {
const { value } = event.target;
setPartnerData({
...partnerData,
services: Array.isArray(value) ? value : [],
});
};
const handleSolutionsChange = (event) => {
const { value } = event.target;
setPartnerData({
...partnerData,
solutions: Array.isArray(value) ? value : [],
});
};
const setSelectedRegion = (region) => {
// send a POST request to /api/v1/orgs/{org_id}/region with the region as the body
const regionMap = {
US: "us-west2",
EU: "europe-west2",
CA: "northamerica-northeast1",
UK: "europe-west2",
"EU-2": "europe-west3",
AUS: "australia-southeast1",
};
region = regionMap[region] || region;
var data = {
dst_region: region,
};
toast.info(
"Changing region to " + region + "...This may take a few minutes."
);
fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/change/region`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify(data),
timeOut: 1000,
}).then((response) => {
if (response.status !== 200) {
response
.json()
.then((reason) => {
toast.error("Failed to change region: " + reason.reason);
})
.catch((err) => {
toast.error("Failed to change region");
});
} else {
toast("Region changed successfully! Reloading in 5 seconds..");
// Reload the page in 2 seconds
setTimeout(() => {
window.location.reload();
}, 5000);
}
// return responseJson
});
};
if (loadingPartnerData) {
return (
<div style={{ textAlign: "left", maxWidth: "95%" }}>
<Grid container spacing={3} style={{ textAlign: "left", marginTop: 5 }}>
<Grid item xs={12}>
<span>
<div>
<div style={{ flex: "3", color: "white" }}>
<div style={{ marginTop: 8, display: "flex" }} />
<div style={{ display: "flex" }}>
<div style={{ width: "100%", maxWidth: 434, marginRight: 10 }}>
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
Name
</Typography>
<Skeleton
variant="rounded"
height={35}
width="100%"
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div>
{/* <div style={{ alignItems: "center" }}>
<div style={{ marginRight: "12px", color: theme?.palette?.text?.primary }}>
Expertise
</div>
<Skeleton
variant="rounded"
height={35}
width={220}
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div>
<div style={{ alignItems: "center", marginLeft: 12 }}>
<div style={{ marginRight: "12px", color: theme?.palette?.text?.primary }}>
Services
</div>
<Skeleton
variant="rounded"
height={35}
width={220}
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div> */}
<div style={{ alignItems: "center" }}>
<div style={{ marginRight: "12px", color: theme?.palette?.text?.primary }}>
Solutions
</div>
<Skeleton
variant="rounded"
height={35}
width={220}
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div>
<div style={{ marginLeft: 13, fontSize: 16, color: "#9E9E9E" }}>
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
Region
</Typography>
<Skeleton
variant="rounded"
height={35}
width={190}
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div>
<div style={{ alignItems: "center", marginLeft: 12 }}>
<div style={{ marginRight: "12px", color: theme?.palette?.text?.primary }}>
Country
</div>
<Skeleton
variant="rounded"
height={35}
width={220}
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div>
</div>
<div style={{ marginTop: "10px" }}>
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
Description
</Typography>
<Skeleton
variant="rounded"
height={89}
width="100%"
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div>
<div>
<div style={{ width: "100%", maxWidth: 500, marginRight: 10, marginTop: 10 }}>
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
Website URL
</Typography>
<Skeleton
variant="rounded"
height={35}
width="100%"
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div>
<div style={{ width: "100%", maxWidth: 500, marginRight: 10, marginTop: 20 }}>
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
Article URL
</Typography>
<Skeleton
variant="rounded"
height={35}
width="100%"
style={{
marginTop: 5,
borderRadius: 4
}}
animation="wave"
/>
</div>
</div>
</div>
</div>
</span>
</Grid>
</Grid>
</div>
);
}
return (
<div style={{ textAlign: "left", maxWidth: "95%" }}>
<Grid container spacing={3} style={{ textAlign: "left", marginTop: 5 }}>
<Grid item xs={12} style={{}}>
<span>
<div style={{}}>
<div style={{ flex: "3", color: "white" }}>
<div style={{ marginTop: 8, display: "flex" }} />
<div style={{ display: "flex" }}>
<div
style={{ width: "100%", maxWidth: 434, marginRight: 10 }}
>
<Typography
variant="text"
style={{ color: theme.palette.text.primary }}
>
Name
</Typography>
<TextField
required
disabled={isDisabled}
style={{
flex: "1",
display: "flex",
height: 35,
width: "100%",
maxWidth: 434,
marginTop: "5px",
marginRight: "15px",
color: theme.palette.textFieldStyle.color,
backgroundColor: isEditOrgTab
? theme.palette.textFieldStyle.backgroundColor
: theme.palette.inputColor,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
fullWidth={true}
placeholder="Name"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={partnerData?.name}
onBlur={() => {}}
onChange={(e) => {
if (e.target.value.length > 100) {
toast("Choose a shorter name.");
return;
}
setPartnerData({
...partnerData,
name: e.target.value,
});
}}
color="primary"
InputProps={{
style: {
color: theme.palette.textFieldStyle.color,
height: "35px",
fontSize: "1em",
borderRadius: 4,
backgroundColor:
theme.palette.textFieldStyle.backgroundColor,
},
classes: {
notchedOutline: isEditOrgTab
? null
: classes.notchedOutline,
},
}}
/>
</div>
{/* <div style={{ alignItems: "center" }}>
<div
style={{
marginRight: "12px",
color: theme.palette.text.primary,
}}
>
Expertise
</div>
<FormControl style={{ width: 220, height: 35 }}>
<Select
style={{
minWidth: 220,
marginTop: 5,
maxWidth: 220,
height: 35,
borderRadius: 4,
color: theme.palette.textFieldStyle.color,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
disabled={isDisabled}
id="multiselect-status"
multiple
value={partnerData?.expertise}
onChange={(event) => {
handleExpertiseChange(event);
}}
input={<OutlinedInput />}
renderValue={(selected) => selected.join(", ")}
MenuProps={MenuProps}
>
{expertiseOptions?.map((name) => (
<MenuItem key={name} value={name}>
<Checkbox
checked={partnerData?.expertise?.indexOf(name) > -1}
/>
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
<div style={{ alignItems: "center", marginLeft: 12 }}>
<div
style={{
marginRight: "12px",
color: theme.palette.text.primary,
}}
>
Services
</div>
<FormControl style={{ width: 220, height: 35 }}>
<Select
style={{
minWidth: 220,
marginTop: 5,
maxWidth: 220,
height: 35,
borderRadius: 4,
color: theme.palette.textFieldStyle.color,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
disabled={isDisabled}
id="multiselect-status"
multiple
value={partnerData?.services || []}
onChange={(event) => {
handleServicesChange(event);
}}
input={<OutlinedInput />}
renderValue={(selected) => selected.join(", ")}
MenuProps={MenuProps}
>
{servicesOptions?.map((name) => (
<MenuItem key={name} value={name}>
<Checkbox
checked={(partnerData?.services || []).indexOf(name) > -1}
/>
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
</div> */}
<div style={{ alignItems: "center" }}>
<div
style={{
marginRight: "12px",
color: theme.palette.text.primary,
}}
>
Solutions
</div>
<FormControl style={{ width: 190, height: 35 }}>
<Select
style={{
minWidth: 190,
marginTop: 5,
maxWidth: 190,
height: 35,
borderRadius: 4,
color: theme.palette.textFieldStyle.color,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
disabled={isDisabled}
id="multiselect-status"
multiple
value={partnerData?.solutions || []}
onChange={(event) => {
handleSolutionsChange(event);
}}
input={<OutlinedInput />}
renderValue={(selected) => selected.join(", ")}
MenuProps={MenuProps}
>
{solutionsOptions?.map((name) => (
<MenuItem key={name} value={name}>
<Checkbox
checked={(partnerData?.solutions || []).indexOf(name) > -1}
/>
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
<div
style={{ marginLeft: 13, fontSize: 16, color: "#9E9E9E" }}
>
<Typography
variant="text"
style={{ color: theme.palette.text.primary }}
>
Region
</Typography>
<RegionChangeModal
isDisabled={isDisabled}
selectedOrganization={selectedOrganization}
setSelectedRegion={setSelectedRegion}
partnerData={partnerData}
setPartnerData={setPartnerData}
userdata={userdata}
/>
</div>
<div style={{ alignItems: "flex-start", marginLeft: 13, display: "flex", flexDirection: "column" }}>
<Typography
variant="text"
style={{ color: theme.palette.text.primary }}
>
Country
</Typography>
<FormControl style={{ width: 210, height: 35 }}>
<Autocomplete
id="country-select"
value={countries.find(country => country.label === partnerData?.country)}
style={{
minWidth: 210,
marginTop: 5,
maxWidth: 210,
height: 35,
borderRadius: 4,
color: theme.palette.textFieldStyle.color,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
disabled={isDisabled}
options={countries}
autoHighlight
onClear={() => {
setPartnerData({
...partnerData,
country: "",
});
}}
getOptionLabel={(option) => option?.label || ""}
onChange={(event, newValue) => {
setPartnerData({
...partnerData,
country: newValue?.label,
});
}}
renderOption={(props, option) => (
<Box
component="li"
sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
{...props}
>
<img
loading="lazy"
width="20"
src={`https://flagcdn.com/48x36/${option.code.toLowerCase()}.png`}
alt=""
/>
{option.label}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
placeholder="Select country"
variant="outlined"
InputProps={{
...params.InputProps,
startAdornment: partnerData?.country ? (
<img
loading="lazy"
width="20"
src={`https://flagcdn.com/48x36/${countries.find(country => country.label === partnerData.country)?.code.toLowerCase()}.png`}
alt=""
style={{ marginRight: 8 }}
/>
) : null,
style: {
color: theme.palette.textFieldStyle.color,
height: "35px",
fontSize: "1em",
borderRadius: 4,
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
},
classes: {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
},
}}
/>
)}
/>
</FormControl>
</div>
</div>
<div style={{ marginTop: "10px", }} />
<Typography
variant="text"
style={{ color: theme.palette.text.primary }}
>
Description
</Typography>
<div style={{ display: "flex" }}>
<TextField
required
disabled={isDisabled}
multiline
rows={3}
style={{
flex: "1",
marginTop: "5px",
color: theme.palette.textFieldStyle.color,
backgroundColor: isEditOrgTab
? theme.palette.textFieldStyle.backgroundColor
: theme.palette.inputColor,
height: 89,
borderRadius: 4,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
placeholder="A description for the organization"
value={partnerData?.description}
onBlur={(e) => {
setPartnerData({
...partnerData,
description: e.target.value,
});
}}
onChange={(e) => {
setPartnerData({
...partnerData,
description: e.target.value,
});
}}
InputProps={{
classes: {
notchedOutline: isEditOrgTab
? null
: classes.notchedOutline,
},
style: {
color: theme.palette.textFieldStyle.color,
height: 89,
borderRadius: 4,
},
}}
/>
</div>
<div>
<div
style={{ width: "100%", maxWidth: 500, marginRight: 10, marginTop: 10 }}
>
<Typography
variant="text"
style={{ color: theme.palette.text.primary }}
>
Website URL
</Typography>
<TextField
required
disabled={isDisabled}
style={{
flex: "1",
display: "flex",
height: 35,
width: "100%",
maxWidth: 500,
marginTop: "5px",
marginRight: "15px",
color: theme.palette.textFieldStyle.color,
backgroundColor: isEditOrgTab
? theme.palette.textFieldStyle.backgroundColor
: theme.palette.inputColor,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
fullWidth={true}
placeholder="https://www.example.com"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={partnerData?.website_url}
onBlur={() => {}}
onChange={(e) => {
if (e.target.value.length > 100) {
toast("Choose a shorter website URL.");
return;
}
setPartnerData({
...partnerData,
website_url: e.target.value,
});
}}
color="primary"
InputProps={{
style: {
color: theme.palette.textFieldStyle.color,
height: "35px",
fontSize: "1em",
borderRadius: 4,
backgroundColor:
theme.palette.textFieldStyle.backgroundColor,
},
classes: {
notchedOutline: isEditOrgTab
? null
: classes.notchedOutline,
},
}}
/>
</div>
<div
style={{ width: "100%", maxWidth: 500, marginRight: 10, marginTop: 20 }}
>
<Typography
variant="text"
style={{ color: theme.palette.text.primary }}
>
Article URL
</Typography>
<TextField
required
disabled={isDisabled}
style={{
flex: "1",
display: "flex",
height: 35,
width: "100%",
maxWidth: 500,
marginTop: "5px",
marginRight: "15px",
color: theme.palette.textFieldStyle.color,
backgroundColor: isEditOrgTab
? theme.palette.textFieldStyle.backgroundColor
: theme.palette.inputColor,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
fullWidth={true}
placeholder="https://www.example.com"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={partnerData?.article_url}
onBlur={() => {}}
onChange={(e) => {
if (e.target.value.length > 100) {
toast("Choose a shorter article URL.");
return;
}
setPartnerData({
...partnerData,
article_url: e.target.value,
});
}}
color="primary"
InputProps={{
style: {
color: theme.palette.textFieldStyle.color,
height: "35px",
fontSize: "1em",
borderRadius: 4,
backgroundColor:
theme.palette.textFieldStyle.backgroundColor,
},
classes: {
notchedOutline: isEditOrgTab
? null
: classes.notchedOutline,
},
}}
/>
</div>
</div>
</div>
</div>
</span>
</Grid>
</Grid>
</div>
);
};
export default PartnerDetails;
const RegionChangeModal = memo(
({
isDisabled,
selectedOrganization,
setSelectedRegion,
userdata,
partnerData,
setPartnerData,
}) => {
// Show from options: "us-west2", "europe-west2", "europe-west3", "northamerica-northeast1"
// var regions = ["us-west2", "europe-west2", "europe-west3", "northamerica-northeast1"]
const regionMapping = {
"Africa": "af",
"Asia Pacific (APAC)": "",
"Europe": "eu",
"Latin America": "la",
"Middle East": "me",
"North America": "na",
"Global": ""
};
//let regiontag = "UK";
let regiontag = partnerData?.region || "UK";
let regionCode = "gb";
const regionsplit = selectedOrganization?.region_url?.split(".");
if (regionsplit?.length > 2 && !regionsplit[0]?.includes("shuffler")) {
const namesplit = regionsplit[0]?.split("/");
regiontag = namesplit[namesplit.length - 1];
if (regiontag === "california") {
regiontag = "US";
regionCode = "us";
} else if (regiontag === "frankfurt") {
regiontag = "EU-2";
regionCode = "eu";
} else if (regiontag === "ca") {
regiontag = "CA";
regionCode = "ca";
} else if (regiontag === "austrailia") {
regiontag = "AUS";
regionCode = "au";
}
}
return (
<FormControl
disabled={isDisabled}
style={{
display: "flex",
flexDirection: "column",
marginTop: 5,
alignItems: "center",
cursor: isDisabled ? "not-allowed" : "pointer",
}}
>
{/* <InputLabel id="demo-simple-select-label">Region</InputLabel> */}
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={partnerData?.region || "Europe"}
style={{ minWidth: 190, height: 35, borderRadius: 4 }}
onChange={(e) => {
// if (userdata?.support) {
// setSelectedRegion(e.target.value)
// } else {
// handleSendChangeRegionMail(e.target.value)
// }
setPartnerData({
...partnerData,
region: e.target.value,
})
// setSelectedRegion(e.target.value)
}}
>
{Object.keys(regionMapping).map((region, index) => {
const regionImageCode = regionMapping[region];
// Set the default region if selectedOrganization.region is not set
if (selectedOrganization.region === undefined) {
selectedOrganization.region = "europe-west2";
}
if (region === "AUS") {
region = "AUS (test)";
}
// Check if the current region matches the selected region
if (region === selectedOrganization.region) {
// If the region matches, set the MenuItem as selected
return (
<MenuItem value={region} key={index} disabled>
{/* show region image through cdn */}
{/* <img
src={`https://flagcdn.com/48x36/${regionImageCode}.png`}
alt={region}
style={{ marginRight: 10 }}
/> */}
{region}
</MenuItem>
);
} else {
return (
<MenuItem sx={{ display: "flex" }} key={index} value={region}>
{/* <img
src={`https://flagcdn.com/48x36/${regionImageCode}.png`}
alt={region}
style={{ marginRight: 10, width: 20, height: 18 }}
/> */}
{region}
</MenuItem>
);
}
})}
</Select>
</FormControl>
);
}
);
File diff suppressed because one or more lines are too long
+391
View File
@@ -0,0 +1,391 @@
import React, { useEffect, useState, useContext } from 'react';
import { toast } from "react-toastify";
import { Context } from '../context/ContextApi.jsx';
import { getTheme } from '../theme.jsx';
import {
Button,
Typography,
Box,
IconButton,
Tooltip,
} from "@mui/material";
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import { useNavigate } from 'react-router';
import PartnerHeader from '../components/PartnerHeader.jsx';
import PartnerDetails from '../components/PartnerDetails.jsx';
const PartnerSettings = (props) => {
const {
isCloud,
userdata,
globalUrl,
serverside,
loadingPartnerData,
selectedOrganization,
setSelectedOrganization,
handleGetOrg,
partnerData,
setPartnerData,
} = props;
const [isPartner, setIsPartner] = React.useState(isCloud ? userdata?.active_org?.is_partner ? true : false : false);
const [partnerTypes, setPartnerTypes] = React.useState({});
const [isPublishing, setIsPublishing] = React.useState(false);
const [isToggling, setIsToggling] = React.useState(false);
useEffect(() => {
setIsPartner(isCloud ? userdata?.active_org?.is_partner ? true : false : false);
if(userdata?.support){
setIsPartner(true);
}
}, [userdata, isCloud]);
// Partner Types handling : Getting from org status
useEffect(() => {
const partnerTypes = {};
userdata?.org_status.forEach(status => {
if (status.includes("_partner")) {
partnerTypes[status] = true;
}
});
setPartnerTypes(partnerTypes);
}, [selectedOrganization]);
// Partner Type Colors
const partnerTypeColors = {
"tech_partner": "#ff8544",
"distribution_partner": "#2BC07E",
"service_partner": "#a99cf9",
"integration_partner": "#fb47a0"
}
const handleSendUpdateRequest = () => {
toast("Your request has been sent to the support team. They will review your request and get back to you as soon as possible.")
}
// Open partner page in new tab
const handleOpenPartnerPage = () => {
if (partnerData?.id) {
if(partnerData?.public){
window.open(`${window.location.origin}/partners/${partnerData.name.toLowerCase().replaceAll(" ", "_")}`)
}else{
window.open(`${window.location.origin}/partners/${partnerData?.id}`, "_blank");
}
} else {
toast.error("Partner ID not found");
}
}
// Update Partner Details
const handleUpdatePartnerDetails = () => {
// Validate required fields before publishing
const requiredStringFields = [
{ field: partnerData?.name, name: "Partner Name" },
{ field: partnerData?.description, name: "Description" },
{ field: selectedOrganization?.id, name: "Organization Id" },
{ field: partnerData?.image_url, name: "Logo Image" },
{ field: partnerData?.landscape_image_url, name: "Landscape Image" },
{ field: partnerData?.website_url, name: "Website URL" },
{ field: partnerData?.article_url, name: "Article URL" },
{ field: partnerData?.country, name: "Country" },
{ field: partnerData?.region, name: "Region" },
];
// Required array fields (multi-select dropdowns)
const requiredArrayFields = [
{ field: partnerData?.solutions, name: "Solutions" },
];
// Check if any required string fields are empty
const emptyStringFields = requiredStringFields.filter(item =>
!item.field || item.field.trim() === ""
);
// Check if any required array fields are empty
const emptyArrayFields = requiredArrayFields.filter(item =>
!item.field || !Array.isArray(item.field) || item.field.length === 0
);
// Combine all empty fields
const allEmptyFields = [...emptyStringFields, ...emptyArrayFields];
// If there are empty required fields, show error and return
if (allEmptyFields.length > 0) {
const missingFields = allEmptyFields.map(item => item.name).join(", ");
toast.error(`Please fill in all required fields: ${missingFields}`);
return;
}
// Check if at least one partner type is selected
if (Object.keys(partnerTypes).length === 0) {
toast.error("There should be at least one partner type");
return;
}
setIsPublishing(true);
const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id;
const data = {
id: partnerData.id?.trim() || null,
name: partnerData.name?.trim(),
org_id: selectedOrganization?.id?.trim(),
description: partnerData.description?.trim(),
website_url: partnerData.website_url?.trim(),
article_url: partnerData.article_url?.trim(),
partner_type: partnerTypes,
expertise: partnerData?.expertise || [],
services: partnerData?.services || [],
solutions: partnerData?.solutions || [],
country: partnerData?.country || "",
region: partnerData?.region || "",
image_url: partnerData?.image_url?.trim(),
landscape_image_url: partnerData?.landscape_image_url?.trim(),
public: partnerData?.public
}
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) => {
setIsPublishing(false);
if (response.status !== 200) {
toast.error("Failed to publish partner");
}
return response.json();
})
.then((responseJson) => {
toast.success("Partner details successfully updated");
})
.catch((error) => {
setIsPublishing(false);
toast.error("Failed to update partner details: " + error?.message);
})
}
// Toggle Partner Publish Status
const handleTogglePublishStatus = () => {
setIsToggling(true);
const newPublishStatus = !partnerData?.public;
const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id;
const data = {
id: partnerData.id?.trim() || null,
name: partnerData.name?.trim(),
org_id: selectedOrganization?.id?.trim(),
description: partnerData.description?.trim(),
website_url: partnerData.website_url?.trim(),
article_url: partnerData.article_url?.trim(),
partner_type: partnerTypes,
usecases: partnerData?.usecases || [],
expertise: partnerData?.expertise || [],
services: partnerData?.services || [],
solutions: partnerData?.solutions || [],
country: partnerData?.country || "",
region: partnerData?.region || "",
image_url: partnerData?.image_url?.trim(),
landscape_image_url: partnerData?.landscape_image_url?.trim(),
public: newPublishStatus
}
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) => {
setIsToggling(false);
if (response.status !== 200) {
toast.error("Failed to update publish status");
}
return response.json();
})
.then((responseJson) => {
// Update local state
setPartnerData(prev => ({
...prev,
public: newPublishStatus
}));
toast.success(`Partner ${newPublishStatus ? 'published' : 'unpublished'} successfully`);
})
.catch((error) => {
setIsToggling(false);
toast.error("Failed to update publish status: " + error?.message);
})
}
const { themeMode } = useContext(Context);
const theme = getTheme(themeMode);
const navigate = useNavigate();
return (
<div style={{ width: "100%", boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor, borderRadius: '16px', }}>
<div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}} >
<div style={{ marginBottom: 40 }}>
<div style={{display:"flex", alignItems:"center", justifyContent:"space-between"}}>
<div style={{width:'100%'}}>
<Box
sx={{display:"flex", alignItems:"flex-start", gap:2, justifyContent:"flex-start"
}}>
<Typography variant='h3' sx={{ marginBottom: "15px", marginTop: 0, }}>Configuration</Typography>
{Object?.entries(partnerTypes)?.map(([key, value]) => (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "999px",
py: 1.2,
px: 2.5,
fontSize: "13px",
fontWeight: 500,
fontFamily: theme.typography.fontFamily,
color: "#fff",
backgroundColor: "transparent",
border: `1.5px solid ${
partnerTypeColors[key]
}`,
transition: "all 0.2s ease",
textAlign: "center",
whiteSpace: "nowrap",
color: partnerTypeColors[key],
}}
>
{key.replace("_", " ").replace(/\b\w/g, char => char.toUpperCase())}
</Box>
))}
</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
marginTop: 0.5,
}}>
<Typography variant="body2" style={{ color: theme.palette.text.secondary, fontSize: 16, maxWidth: "60%" }}>
Set up and manage partner details and information to be displayed on the Partners page.
</Typography>
<Box sx={{ display: "flex", gap: 2, alignItems: "center" }}>
{isPartner && (
<>
{/* View Partner Page Button */}
<Tooltip title="View Partner Page">
<IconButton
sx={{
color: theme.palette.primary.main,
backgroundColor: theme.palette.action.hover,
"&:hover": {
backgroundColor: theme.palette.action.selected,
}
}}
onClick={handleOpenPartnerPage}
disabled={!partnerData?.id}
>
<OpenInNewIcon />
</IconButton>
</Tooltip>
{/* Update Details Button */}
<Button
sx={{
fontSize: 16,
textTransform: 'capitalize',
boxShadow: "none",
px: 4,
}}
variant="contained"
color="primary"
disabled={isPublishing || isToggling}
onClick={handleUpdatePartnerDetails}
>
{isPublishing ? "Updating..." : "Update Details"}
</Button>
{/* Toggle Publish/Unpublish Button */}
<Button
sx={{
fontSize: 16,
textTransform: 'capitalize',
boxShadow: "none",
marginRight: 4,
px: 3,
backgroundColor: partnerData?.public ? "#FD4C62" : "#4caf50",
"&:hover": {
backgroundColor: partnerData?.public ? "#FD4C62" : "#4caf50"
}
}}
variant="contained"
disabled={isToggling || isPublishing}
onClick={handleTogglePublishStatus}
>
{isToggling ? (partnerData?.public ? "Unpublishing..." : "Publishing...") : (partnerData?.public ? "Unpublish" : "Publish")}
</Button>
</>
)}
{!isPartner && (
<Button
sx={{
fontSize: 16,
textTransform: 'capitalize',
boxShadow: "none",
px: 5,
textDecoration: "none",
marginRight: 4
}}
variant="contained"
color="primary"
onClick={() => {
if(!isCloud){
navigate("/become-partner")
}else{
window.open("https://shuffler.io/become-partner")
}
}}
>
Become a partner
</Button>
)}
</Box>
</Box>
</div>
</div>
</div>
<PartnerHeader
isCloud={isCloud}
userdata={userdata}
isPublishing={isPublishing}
setSelectedOrganization={setSelectedOrganization}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
isEditOrgTab={true}
loadingPartnerData={loadingPartnerData}
partnerData={partnerData}
setPartnerData={setPartnerData}
handleGetOrg={handleGetOrg}
/>
<PartnerDetails
isCloud={isCloud}
userdata={userdata}
partnerData={partnerData}
isPublishing={isPublishing}
loadingPartnerData={loadingPartnerData}
setPartnerData={setPartnerData}
setSelectedOrganization={setSelectedOrganization}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
isEditOrgTab={true}
handleGetOrg={handleGetOrg}
serverside={serverside}
/>
</div>
</div >
)
}
export default PartnerSettings;
+245
View File
@@ -0,0 +1,245 @@
import React, { useEffect, useState, useCallback, useContext } from 'react';
import { useNavigate, useLocation } from "react-router-dom";
import Branding from "../components/Branding.jsx";
import { ToastContainer, toast } from "react-toastify";
import { Button } from '@mui/material';
import { getTheme } from '../theme.jsx';
import { Context } from '../context/ContextApi.jsx';
import PartnerSettings from '../components/PartnerSettings.jsx';
import PartnersUsecasesTab from '../components/PartnersUsecasesTab.jsx';
import PartnersApps from '../components/PartnerApps.jsx';
import PartnerArticles from '../components/PartnersArticles.jsx';
const PartnerTab = (props) => {
const location = useLocation();
const navigate = useNavigate();
const {
userdata,
globalUrl,
serverside,
isCloud,
setSelectedOrganization,
selectedOrganization, handleGetOrg,
handleStatusChange,
isLoaded,
removeCookie
} = props;
const [selectedTab, setSelectedTab] = useState('partner_settings');
const [loadingPartnerData, setLoadingPartnerData] = useState(false);
const [curIndex, setCurIndex] = React.useState(0);
const [partnerData, setPartnerData] = React.useState({
name: "",
description: "",
image_url: "",
landscape_image_url: "",
website_url: "",
article_url: "",
expertise: [],
usecases: [],
services: [],
solutions: [],
country: "",
region: "",
partner_type: {},
});
const tabsOnPartnerTab = [ 'Partner Settings', 'Usecases', 'Apps', 'AI Agents', 'Articles', 'Branding'];
const { themeMode } = useContext(Context);
const theme = getTheme(themeMode);
useEffect(() => {
if(!isCloud || !userdata?.active_org?.is_partner) {
// If the user is not a partner or if it's not a cloud environment do not make api call :)
return;
}
setLoadingPartnerData(true);
const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id;
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
toast("Failed to get partner data")
}
return response.json();
})
.then((responseJson) => {
if(responseJson.success) {
setPartnerData(responseJson?.partner);
console.log("responseJson", responseJson)
setLoadingPartnerData(false);
}else{
setLoadingPartnerData(false);
toast(responseJson?.reason)
}
})
.catch((error) => {
setLoadingPartnerData(false);
})
}, [globalUrl]);
// Used to auto select the tab based on the url : partner_tab
useEffect(() => {
const queryParams = new URLSearchParams(location.search);
const tabName = queryParams.get('partner_tab');
if (tabName) {
const decodedTabName = decodeURIComponent(tabName);
setSelectedTab(decodedTabName);
if (decodedTabName === 'partner_settings') {
setCurIndex(0);
} else if(decodedTabName === 'usecases'){
setCurIndex(1)
}else if (decodedTabName === 'apps'){
setCurIndex(2);
} else if (decodedTabName === 'aiagents') {
setCurIndex(3);
} else if (decodedTabName === 'articles') {
setCurIndex(4);
} else if (decodedTabName === 'branding') {
setCurIndex(5);
}
}
}, [location.search]);
// Tab click on partner tab
const handleTabClick = (tabName) => {
const formattedTabName = tabName.toLowerCase().replace(/[\s&]+/g, '');
const encodedTabName = encodeURIComponent(formattedTabName);
setSelectedTab(formattedTabName);
document.title = `Shuffle - partner - ${formattedTabName}`;
navigate(`?partner_tab=${encodedTabName}`);
};
// Rendering the content based on the selected tab
const renderContent = () => {
switch (selectedTab) {
case 'partner_settings':
return <PartnerSettings isCloud={isCloud} loadingPartnerData={loadingPartnerData} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
case 'usecases':
return <PartnersUsecasesTab isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
case `apps`:
return <PartnersApps isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
case 'articles' :
return <PartnerArticles isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
case `ai_agents`:
return <PartnerArticles isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
case 'branding':
return <Branding
isCloud={isCloud}
userdata={userdata}
globalUrl={globalUrl}
handleGetOrg={handleGetOrg}
selectedOrganization={selectedOrganization}
clickedFromOrgTab={true}
setSelectedOrganization={setSelectedOrganization}
/>;
default:
return <PartnerSettings isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} />;
}
};
const isTabDisabled = (tabName) => {
// If user is a support user, enable all tabs
if (userdata?.support) {
return false;
}
// For non-support users, apply the following restrictions:
// For onPrem only partner settings and branding are enabled
if (
!isCloud &&
(tabName === "Usecases" ||
tabName === "Apps" ||
tabName === "AI Agents" ||
tabName === "Articles")
) {
return true;
}
// Disable Apps, Articles, and AI Agents for all non-support users
if (
tabName === "Apps" ||
tabName === "Articles" ||
tabName === "AI Agents"
) {
return true;
}
// Disable Usecases tab for cloud users if not a partner or is in a sub-org
if (isCloud && tabName === "Usecases") {
if (
!userdata?.active_org?.is_partner ||
userdata?.active_org?.is_sub_org
) {
return true;
}
}
// Disable Branding tab if not an integration partner
const isIntegrationPartner =
userdata &&
userdata?.org_status?.includes("integration_partner") &&
!userdata?.org_status?.includes("sub_org");
if (tabName === "Branding" && !isIntegrationPartner) {
return true;
}
// Enable the tab by default
return false;
}
return (
<div style={{ height: "100%", width: "100%", color: theme.palette.platformColor, backgroundColor: theme.palette.platformColor, borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: theme.palette.defaultBorder, boxSizing: 'border-box' }}>
<div style={{ display: 'flex', justifyContent: 'space-around', width: "100%", borderBottom: theme.palette.defaultBorder ,boxSizing: 'border-box' }}>
{tabsOnPartnerTab?.map((tabName, index) => (
<div style={{ pointerEvents: 'auto', width: '100%',}}>
<Button
key={tabName}
onClick={() => {
setCurIndex(index);
handleTabClick(index === 0 ? "partner_settings" : tabName.toLowerCase().replace(/[\s&]+/g, ''));
}}
disabled={isTabDisabled(tabName)}
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" : theme.palette.text.primary,
textTransform: 'none',
fontSize: 16,
width: "100%",
height: "100%",
borderRadius: 0,
},
"&: hover": {
backgroundColor: theme.palette.hoverColor
},
"&.Mui-disabled": {
color: "#6F6F6F",
},
}}
>
{tabName}
</Button>
</div>
))}
</div>
<div style={{ display: 'flex', justifyContent: 'center', width: "100%", height: "100%", paddingBottom: 200, boxSizing:'border-box'}}>
{renderContent()}
</div>
</div>
);
};
export default PartnerTab;
@@ -0,0 +1,21 @@
import { Box, Typography } from '@mui/material'
import React from 'react'
const PartnersArticles = () => {
return (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
height: "100%",
minHeight: "500px",
}}
>
<Typography>Partner Articles</Typography>
</Box>
)
}
export default PartnersArticles
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -1,10 +1,10 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useContext } from "react";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { getTheme } from "../theme.jsx";
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
import theme from "../theme.jsx";
import { useNavigate, Link } from "react-router-dom"; import { useNavigate, Link } from "react-router-dom";
import { findSpecificApp } from "../components/AppFramework.jsx" import { findSpecificApp } from "../components/AppFramework.jsx"
import { Context } from "../context/ContextApi.jsx";
import { import {
Paper, Paper,
Typography, Typography,
@@ -23,7 +23,8 @@ import {
const Priority = (props) => { const Priority = (props) => {
const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props; const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props;
const { themeMode, supportEmail } = useContext(Context);
const theme = getTheme(themeMode);
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true"); const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
let navigate = useNavigate(); let navigate = useNavigate();
@@ -113,7 +114,7 @@ const Priority = (props) => {
} }
}) })
.catch((error) => { .catch((error) => {
toast("Failed dismissing alert. Please contact support@shuffler.io if this persists."); toast(`Failed dismissing alert. Please contact ${supportEmail} if this persists.`);
}); });
} }
@@ -121,10 +122,10 @@ const Priority = (props) => {
const srcSize = realignedSrc ? 35 : 30 const srcSize = realignedSrc ? 35 : 30
const dstSize = realignedDst ? 35 : 30 const dstSize = realignedDst ? 35 : 30
return ( return (
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : clickedFromOrgTab ?null:"1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, marginTop: 10, marginBottom: 10, padding: clickedFromOrgTab ? 24:15, textAlign: "center", minHeight: isCloud ? 70 : 100, maxHeight: isCloud ? 70 : 100, textAlign: "left", backgroundColor: clickedFromOrgTab ? "#1A1A1A": theme.palette.surfaceColor, display: "flex", }}> <div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : clickedFromOrgTab ?null:"1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, marginTop: 10, marginBottom: 10, padding: clickedFromOrgTab ? 24:15, textAlign: "center", minHeight: isCloud ? 70 : 100, maxHeight: isCloud ? 70 : 100, textAlign: "left", backgroundColor: clickedFromOrgTab ? theme.palette.backgroundColor : theme.palette.surfaceColor, display: "flex", }}>
<div style={{flex: 2, overflow: "hidden",}}> <div style={{flex: 2, overflow: "hidden",}}>
<span style={{display: "flex", }}> <span style={{display: "flex", }}>
{priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null} {priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, color: theme.palette.text.primary}}/> : null}
<Typography variant="body1" > <Typography variant="body1" >
{priority.name} {priority.name}
</Typography> </Typography>
@@ -138,7 +139,7 @@ const Priority = (props) => {
{newdescription.split("&").length > 3 ? {newdescription.split("&").length > 3 ?
<span style={{display: "flex", }}> <span style={{display: "flex", }}>
<ArrowForwardIcon style={{marginLeft: 15, marginRight: 15, }}/> <ArrowForwardIcon style={{marginLeft: 15, marginRight: 15, color: theme.palette.text.primary }}/>
<img src={newdescription.split("&")[3]} alt={priority.name+"2"} style={{height: dstSize, width: dstSize, marginRight: realignedDst ? -5 : 10, borderRadius: theme.palette?.borderRadius-3, marginTop: realignedDst ? 5 : 0 }} /> <img src={newdescription.split("&")[3]} alt={priority.name+"2"} style={{height: dstSize, width: dstSize, marginRight: realignedDst ? -5 : 10, borderRadius: theme.palette?.borderRadius-3, marginTop: realignedDst ? 5 : 0 }} />
<Typography variant="body2" color="textSecondary" style={{marginTop: 3}}> <Typography variant="body2" color="textSecondary" style={{marginTop: 3}}>
{newdescription.split("&")[2]} {newdescription.split("&")[2]}
@@ -154,7 +155,7 @@ const Priority = (props) => {
} }
</div> </div>
<div style={{flex: 1, display: "flex", marginLeft: 30, }}> <div style={{flex: 1, display: "flex", marginLeft: 30, }}>
<Button style={{height: 50, borderRadius: 4, fontSize:16, boxShadow: clickedFromOrgTab ? "none":null,textTransform: clickedFromOrgTab ? 'capitalize':null, marginTop: 8, width: 175, marginRight: 10, color: priority.active === false ? "white" :clickedFromOrgTab ?"#ff8544": "black", backgroundColor: priority.active === false ? theme.palette.inputColor :clickedFromOrgTab?"transparent":"rgba(255,255,255,0.8)", border: "1px solid #ff8544"}} variant="contained" color="secondary" onClick={() => { <Button style={{height: 50, borderRadius: 4, fontSize:16, boxShadow: clickedFromOrgTab ? "none":null,textTransform: clickedFromOrgTab ? 'capitalize':null, marginTop: 8, width: 175, marginRight: 10, }} variant="outlined" color="primary" onClick={() => {
if (isCloud) { if (isCloud) {
ReactGA.event({ ReactGA.event({
@@ -180,10 +181,9 @@ const Priority = (props) => {
Explore Explore
</Button> </Button>
{priority.active === true ? {priority.active === true ?
<Button style={{borderRadius: 25, fontSize:16, boxShadow: clickedFromOrgTab ? "none":null,textTransform: clickedFromOrgTab ? 'capitalize':null, width: 100, height: 50, marginTop: 8, }} variant="text" color="secondary" onClick={() => { <Button style={{borderRadius: 4, fontSize:16, boxShadow: clickedFromOrgTab ? "none":null,textTransform: clickedFromOrgTab ? 'capitalize':null, width: 100, height: 50, marginTop: 8, }} variant="outlined" color="secondary" onClick={() => {
// dismiss -> get envs // dismiss -> get envs
changeRecommendation(priority, "dismiss") changeRecommendation(priority, "dismiss")
// Check window location if it's /workflows // Check window location if it's /workflows
if (window.location.pathname === "/workflows") { if (window.location.pathname === "/workflows") {
// Set local storage to hide priorities for now // Set local storage to hide priorities for now
+9 -4
View File
@@ -1,4 +1,4 @@
import React from "react" import React, {useContext} from "react"
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { import {
@@ -10,14 +10,20 @@ import {
} from "@mui/material" } from "@mui/material"
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import theme from "../theme.jsx"; import theme from "../theme.jsx";
import { getTheme } from "../theme.jsx";
import { import {
Lock as LockIcon, Lock as LockIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { Context } from "../context/ContextApi.jsx";
// onclickHandler = function override from parent onclick // onclickHandler = function override from parent onclick
const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflowId, }) => { const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflowId, }) => {
const { themeMode } = useContext(Context);
const theme = getTheme(themeMode);
const navigate = useNavigate(); const navigate = useNavigate();
const [hovered, setHovered] = React.useState(false) const [hovered, setHovered] = React.useState(false)
@@ -87,7 +93,7 @@ const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflow
transition: "opacity 0.1s", transition: "opacity 0.1s",
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
backgroundColor: hovered || currentWorkflowId === workflow.id ? "#1f1f1f" : "transparent", backgroundColor: hovered || currentWorkflowId === workflow.id ? theme.palette.hoverColor : "transparent",
}} }}
disableRipple disableRipple
> >
@@ -127,7 +133,6 @@ const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflow
))} ))}
<Typography <Typography
style={{ style={{
color: "#CDCDCD",
fontSize: 16, fontSize: 16,
marginLeft: 8, marginLeft: 8,
maxWidth: 180, maxWidth: 180,
+7 -3
View File
@@ -1,12 +1,16 @@
import React, { useState, useEffect, useLayoutEffect } from "react"; import React, { useState, useEffect, useLayoutEffect, useContext, useMemo } from "react";
import * as cytoscape from "cytoscape"; import * as cytoscape from "cytoscape";
import CytoscapeComponent from "react-cytoscapejs"; import CytoscapeComponent from "react-cytoscapejs";
import cystyle from "../defaultCytoscapeStyle.jsx"; import defaultCytoscapeStyle from "../defaultCytoscapeStyle.jsx";
import { Context } from "../context/ContextApi.jsx";
import { getTheme } from "../theme.jsx";
const surfaceColor = "#27292D"; const surfaceColor = "#27292D";
const CytoscapeWrapper = (props) => { const CytoscapeWrapper = (props) => {
const { globalUrl, inworkflow, height, width } = props; const { globalUrl, inworkflow, height, width } = props;
const {themeMode} = useContext(Context)
const theme = getTheme(themeMode)
const cystyle = useMemo(() => defaultCytoscapeStyle(theme), [themeMode]);
const [elements, setElements] = useState([]); const [elements, setElements] = useState([]);
const [workflow, setWorkflow] = useState(inworkflow); const [workflow, setWorkflow] = useState(inworkflow);
const [cy, setCy] = React.useState(); const [cy, setCy] = React.useState();
+40 -21
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useContext } from 'react';
import { import {
TextField, TextField,
@@ -17,10 +17,11 @@ import {
IconButton, IconButton,
Switch, Switch,
} from '@mui/material'; } from '@mui/material';
import { Context } from '../context/ContextApi.jsx';
import { toast } from "react-toastify" import { toast } from "react-toastify"
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import theme from '../theme.jsx'; import {getTheme} from '../theme.jsx';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import Pagination from '@mui/material/Pagination'; import Pagination from '@mui/material/Pagination';
@@ -39,7 +40,8 @@ import {
EditNote as EditNoteIcon, EditNote as EditNoteIcon,
AccountTree as AccountTreeIcon, AccountTree as AccountTreeIcon,
Cached as CachedIcon, Cached as CachedIcon,
FilterAltOff as FilterAltOffIcon FilterAltOff as FilterAltOffIcon,
Send as SendIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid' import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'
@@ -69,6 +71,8 @@ const RuntimeDebugger = (props) => {
const [endTime, setEndTime] = useState("") const [endTime, setEndTime] = useState("")
const [startTime, setStartTime] = useState("") const [startTime, setStartTime] = useState("")
const [totalCount, setTotalCount] = useState(0) const [totalCount, setTotalCount] = useState(0)
const {themeMode, supportEmail} = useContext(Context)
const theme = getTheme(themeMode)
const [workflow, setWorkflow] = useState({}) const [workflow, setWorkflow] = useState({})
const [ignoreOrg, setIgnoreOrg] = useState(false) const [ignoreOrg, setIgnoreOrg] = useState(false)
@@ -107,7 +111,7 @@ const RuntimeDebugger = (props) => {
var maxworkflows = 5 var maxworkflows = 5
console.log("Looking for MAX this amount of workflows: ", maxworkflows) //console.log("Looking for MAX this amount of workflows: ", maxworkflows)
for (let key in workflows) { for (let key in workflows) {
if (key > maxworkflows) { if (key > maxworkflows) {
break break
@@ -347,6 +351,9 @@ const RuntimeDebugger = (props) => {
source = "rerun of a previous run" source = "rerun of a previous run"
} else if (source === "form") { } else if (source === "form") {
foundSource = <EditNoteIcon style={{color: theme.palette.primary.secondary, height: imageSize, width: imageSize, }} /> foundSource = <EditNoteIcon style={{color: theme.palette.primary.secondary, height: imageSize, width: imageSize, }} />
} else if (source === "single_action" || source == "single_api" || source === "direct_api") {
foundSource = <SendIcon color="secondary" style={{height: imageSize-5, }} />
source = "Single API call"
} else { } else {
source = "manual" source = "manual"
} }
@@ -354,12 +361,12 @@ const RuntimeDebugger = (props) => {
var imageSource = ""; var imageSource = "";
if (params?.row?.org?.id?.length > 0) { if (params?.row?.org?.id?.length > 0) {
if (params?.row?.org?.image?.length > 0){ if (params?.row?.org?.image?.length > 0){
imageSource = params?.row.org?.image imageSource = params?.row?.org?.image
}else { }else {
imageSource = "/images/no_image.png" imageSource = "/images/no_image.png"
} }
}else { } else {
if (userdata.active_org.image?.length > 0){ if (userdata?.active_org?.image?.length > 0){
imageSource = userdata?.active_org?.image imageSource = userdata?.active_org?.image
}else { }else {
imageSource = "/images/no_image.png" imageSource = "/images/no_image.png"
@@ -629,7 +636,7 @@ const RuntimeDebugger = (props) => {
</Link> </Link>
</span> </span>
</Tooltip> </Tooltip>
<Tooltip arrow title="Force continue workflow. Only workflows for workflows in EXECUTING state. This is NOT a rerun, but way for Shuffle to figure out the next steps automatically. If the execution doesn't finish even after trying this, please contact support@shuffler.io"> <Tooltip arrow title={`Force continue workflow. Only workflows for workflows in EXECUTING state. This is NOT a rerun, but way for Shuffle to figure out the next steps automatically. If the execution doesn't finish even after trying this, please contact ${supportEmail}`}>
<IconButton <IconButton
style={{marginLeft: 5, }} style={{marginLeft: 5, }}
disabled={params.row.status !== "EXECUTING"} disabled={params.row.status !== "EXECUTING"}
@@ -812,7 +819,7 @@ const RuntimeDebugger = (props) => {
<div style={{display: "flex", paddingTop: 50, }}> <div style={{display: "flex", paddingTop: 50, }}>
<div style={{display: 'flex', flexDirection: 'column'}}> <div style={{display: 'flex', flexDirection: 'column'}}>
<div style={{display: "flex", width: "100%", }}> <div style={{display: "flex", width: "100%", }}>
<h1 style={{flex: 3, whiteSpace: "nowrap" }}>Workflow Run Debugger {totalCount !== 0 ? ` (~${totalCount})` : ""}</h1> <Typography variant="h3" style={{flex: 3, whiteSpace: "nowrap" }}>Workflow Run Debugger {totalCount !== 0 ? ` (~${totalCount})` : ""}</Typography>
{selectedWorkflowExecutions.length > 0 ? {selectedWorkflowExecutions.length > 0 ?
<ButtonGroup> <ButtonGroup>
<Tooltip title="Reruns ALL selected workflows. This will make a new execution for them, and not continue the existing."> <Tooltip title="Reruns ALL selected workflows. This will make a new execution for them, and not continue the existing.">
@@ -890,7 +897,8 @@ const RuntimeDebugger = (props) => {
fullWidth fullWidth
value={searchQuery} value={searchQuery}
style={{ style={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
color: theme.palette.textColor,
marginTop: 20, marginTop: 20,
marginLeft: 10, marginLeft: 10,
marginRight: 12, marginRight: 12,
@@ -901,7 +909,8 @@ const RuntimeDebugger = (props) => {
}} }}
InputProps={{ InputProps={{
style: { style: {
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
color: theme.palette.textColor,
fontSize: "1em", fontSize: "1em",
height: 51, height: 51,
width: 693, width: 693,
@@ -951,7 +960,7 @@ const RuntimeDebugger = (props) => {
} }
color="secondary" color="secondary"
/> />
<Typography variant="body2" style={{color: "white", }}>Show workflow runs from suborgs</Typography> <Typography variant="body2">Show workflow runs from suborgs</Typography>
</div> </div>
) : null} ) : null}
</div> </div>
@@ -999,8 +1008,8 @@ const RuntimeDebugger = (props) => {
classes={{ inputRoot: classes.inputRoot }} classes={{ inputRoot: classes.inputRoot }}
ListboxProps={{ ListboxProps={{
style: { style: {
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
color: "white", color: theme.palette.textColor,
}, },
}} }}
getOptionLabel={(option) => { getOptionLabel={(option) => {
@@ -1018,7 +1027,7 @@ const RuntimeDebugger = (props) => {
options={workflows} options={workflows}
fullWidth fullWidth
style={{ style={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
height: 50, height: 50,
borderRadius: 4, borderRadius: 4,
marginLeft: 5, marginLeft: 5,
@@ -1063,8 +1072,13 @@ const RuntimeDebugger = (props) => {
}> }>
<MenuItem <MenuItem
style={{ style={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
color: data.id === workflow.id ? "red" : "white", color: data.id === workflow.id ? "red" : theme.palette.textColor,
}}
sx={{
"&:hover": {
backgroundColor: theme.palette.hoverColor,
},
}} }}
value={data} value={data}
onClick={(e) => { onClick={(e) => {
@@ -1082,9 +1096,16 @@ const RuntimeDebugger = (props) => {
return ( return (
<TextField <TextField
style={{ style={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.backgroundColor,
color: theme.palette.textColor,
borderRadius: 4, borderRadius: 4,
}} }}
inputProps={{
style: {
backgroundColor: theme.palette.backgroundColor,
color: theme.palette.textColor,
},
}}
{...params} {...params}
label="Workflow" label="Workflow"
variant="outlined" variant="outlined"
@@ -1128,9 +1149,7 @@ const RuntimeDebugger = (props) => {
<Tooltip title="Clear all filters and search parameters"> <Tooltip title="Clear all filters and search parameters">
<Button <Button
style={{ marginLeft: 10, minHeight: 60, marginTop: 10, backgroundColor: "#1a1a1a", border: "1px solid #424242", boxShadow: 'none', borderRadius: 4, width: 81, height: 51, marginRight: 15 }} style={{ marginLeft: 10, minHeight: 60, marginTop: 10, backgroundColor: theme.palette.backgroundColor, border: "1px solid #424242", boxShadow: 'none', borderRadius: 4, width: 81, height: 51, marginRight: 15 }}
variant="contained"
color="primary"
onClick={() => { onClick={() => {
setWorkflowId("") setWorkflowId("")
setWorkflow({"id": "", "name": "All Workflows"}) setWorkflow({"id": "", "name": "All Workflows"})
+59 -58
View File
@@ -1,5 +1,5 @@
import React, { forwardRef, memo, useContext, useEffect } from 'react'; import React, { forwardRef, memo, useContext, useEffect } from 'react';
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
import { toast } from "react-toastify" ; import { toast } from "react-toastify" ;
import { import {
Divider, Divider,
@@ -33,6 +33,9 @@ const SchedulesTab = memo((props) => {
const [pipelineModalOpen, setPipelineModalOpen] = React.useState(false); const [pipelineModalOpen, setPipelineModalOpen] = React.useState(false);
const [newPipelineValue, setNewPipelineValue] = React.useState("export | sigma /tmp/sigma_rules | to SHUFFLE_WEBHOOK"); const [newPipelineValue, setNewPipelineValue] = React.useState("export | sigma /tmp/sigma_rules | to SHUFFLE_WEBHOOK");
const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
useEffect(() => { useEffect(() => {
if (allSchedules.length === 0 && webHooks.length === 0 && pipelines.length === 0) { if (allSchedules.length === 0 && webHooks.length === 0 && pipelines.length === 0) {
handleGetAllTriggers() handleGetAllTriggers()
@@ -142,11 +145,11 @@ const SchedulesTab = memo((props) => {
}} }}
> >
<DialogTitle> <DialogTitle>
<span style={{ color: "white" }}> <Typography variant='h5' color="textPrimary" >
Run a Tenzir pipeline Run a Tenzir pipeline
</span> </Typography>
<Typography variant="body2" color="textSecondary" style={{marginTop: 10, }}> <Typography variant="body2" color="textSecondary" style={{marginTop: 10, }}>
Alpha feature. Deploys to the first available Orborus location. <a href="https://docs.tenzir.com/pipelines" target="_blank" rel="noopener noreferrer" style={{ color: "#ff8544" }}>Explore Tenzir Pipelines</a>. The example below exports everything in the Tenzir database, runs Sigma rules on it, and forwards the results to a Shuffle webhook. Alpha feature. Deploys to the first available Orborus location. <a href="https://docs.tenzir.com/pipelines" target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.primary.main }}>Explore Tenzir Pipelines</a>. The example below exports everything in the Tenzir database, runs Sigma rules on it, and forwards the results to a Shuffle webhook.
</Typography> </Typography>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
@@ -173,17 +176,16 @@ const SchedulesTab = memo((props) => {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", color: "#ff8544" }} style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", color: theme.palette.primary.main }}
onClick={() => { onClick={() => {
setPipelineModalOpen(false) setPipelineModalOpen(false)
}} }}
color="primary"
> >
Cancel Cancel
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", backgroundColor: "#ff8544", color: "#1a1a1a" }} style={{ borderRadius: "2px", fontSize: 16, textTransform: "none", }}
onClick={() => { onClick={() => {
submitPipelineWrapper(newPipelineValue) submitPipelineWrapper(newPipelineValue)
}} }}
@@ -472,7 +474,7 @@ const SchedulesTab = memo((props) => {
height: "100%", height: "100%",
transition: 'width 0.3s ease', transition: 'width 0.3s ease',
padding: '27px 10px 27px 27px', padding: '27px 10px 27px 27px',
backgroundColor: '#212121', backgroundColor: theme.palette.platformColor,
borderTopRightRadius: 8, borderTopRightRadius: 8,
borderBottomRightRadius: 8, borderBottomRightRadius: 8,
borderLeft: '1px solid #494949', borderLeft: '1px solid #494949',
@@ -480,36 +482,36 @@ const SchedulesTab = memo((props) => {
{NewPipelineView} {NewPipelineView}
<div style={{height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}> <div style={{height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#FFFFFF" }}> <Typography variant='h5' color="textPrimary" style={{ marginBottom: 8, marginTop: 0, }}>
Triggers Triggers
</h2> </Typography>
<Typography variant="body1" style={{ marginBottom: 50, marginTop: 0, }} color="textSecondary"> <Typography variant="body2" style={{ marginBottom: 50, marginTop: 0, }} color="textSecondary">
Triggers are Automatic Workflow starters. <b>Status: Schedules ({allSchedules.length}), Webhooks ({webHooks.length}), Pipelines ({pipelines.length})</b> Triggers are Automatic Workflow starters. <b>Status: Schedules ({allSchedules.length}), Webhooks ({webHooks.length}), Pipelines ({pipelines.length})</b>
</Typography> </Typography>
<div> <div>
<h4 style={{ marginBottom: 8, marginTop: 0, color: "#FFFFFF" }}> <Typography variant='h6' color="textPrimary" style={{ marginBottom: 8, marginTop: 0, fontWeight: 500}}>
Schedules Schedules
</h4> </Typography>
<span style={{color:textColor}}> <Typography variant='body2' color="textSecondary">
Schedules used in Workflows. Makes locating and control easier.{" "} Schedules used in Workflows. Makes locating and control easier.{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="/docs/organizations#schedules" href="/docs/organizations#schedules"
style={{ color:"#FF8444" }} style={{ color: theme.palette.primary.main }}
> >
Learn more Learn more
</a> </a>
</span> </Typography>
</div> </div>
<div style={{height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}> <div style={{height: "100%", width: "calc(100% - 20px)", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
<div <div
style={{ style={{
borderRadius: 4, borderRadius: 4,
marginTop: 24, marginTop: 24,
border: "1px solid #494949", border: theme.palette.defaultBorder,
width: "100%", width: "100%",
overflowX: "auto", overflowX: "auto",
paddingBottom: 0, paddingBottom: 0,
@@ -525,7 +527,7 @@ const SchedulesTab = memo((props) => {
overflowX: "auto", overflowX: "auto",
paddingBottom: 0 paddingBottom: 0
}}> }}>
<ListItem style={{width: "100%", paddingTop: 10, paddingBottom: 10, paddingRight: 10, borderBottom: "1px solid #494949", display: 'table-row'}}> <ListItem style={{width: "100%", paddingTop: 10, paddingBottom: 10, paddingRight: 10, borderBottom: theme.palette.defaultBorder, display: 'table-row'}}>
{["Name", "Interval", "Environment", "Workflow", "Argument", "Action"].map((header, index) => ( {["Name", "Interval", "Environment", "Workflow", "Argument", "Action"].map((header, index) => (
<ListItemText <ListItemText
key={index} key={index}
@@ -535,7 +537,7 @@ const SchedulesTab = memo((props) => {
padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px", padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px",
whiteSpace: "nowrap", whiteSpace: "nowrap",
textOverflow: "ellipsis", textOverflow: "ellipsis",
borderBottom: "1px solid #494949", borderBottom: theme.palette.defaultBorder,
}} }}
/> />
))} ))}
@@ -546,7 +548,7 @@ const SchedulesTab = memo((props) => {
key={rowIndex} key={rowIndex}
style={{ style={{
display: "table-row", display: "table-row",
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
}} }}
> >
{Array(6) {Array(6)
@@ -563,7 +565,7 @@ const SchedulesTab = memo((props) => {
variant="text" variant="text"
animation="wave" animation="wave"
sx={{ sx={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.loaderColor,
height: "20px", height: "20px",
borderRadius: "4px", borderRadius: "4px",
}} }}
@@ -575,13 +577,13 @@ const SchedulesTab = memo((props) => {
):( ):(
allSchedules?.length === 0 ? ( allSchedules?.length === 0 ? (
<div style={{ textAlign: 'center'}}> <div style={{ textAlign: 'center'}}>
<Typography style={{color: "#FFFFFF", fontSize: 16, padding: 20, textAlign: 'center'}}>No schedules found</Typography> <Typography style={{color: theme.palette.text.primary, fontSize: 16, padding: 20, textAlign: 'center'}}>No schedules found</Typography>
</div> </div>
):( ):(
allSchedules.map((schedule, index) => { allSchedules.map((schedule, index) => {
var bgColor = "#212121" var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF";
if (index % 2 === 0) { if (index % 2 === 0) {
bgColor = "#1a1a1a"; bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA";
} }
return ( return (
@@ -645,7 +647,7 @@ const SchedulesTab = memo((props) => {
rel="noopener noreferrer" rel="noopener noreferrer"
style={{ style={{
textDecoration: "none", textDecoration: "none",
color: "#f85a3e", color: theme.palette.primary.main,
}} }}
href={`/workflows/${schedule.workflow_id}`} href={`/workflows/${schedule.workflow_id}`}
target="_blank" target="_blank"
@@ -658,7 +660,7 @@ const SchedulesTab = memo((props) => {
style={{ style={{
color: color:
schedule.workflow_id !== "global" schedule.workflow_id !== "global"
? "#FF8444" ? theme.palette.primary.main
: "grey", : "grey",
}} }}
/> />
@@ -698,8 +700,7 @@ const SchedulesTab = memo((props) => {
style={{ style={{
textTransform: 'none', textTransform: 'none',
fontSize: 16, fontSize: 16,
color: schedule.status === "running" ? '#1a1a1a' : null, width: 150,
backgroundColor: schedule.status === "running" ? '#ff8544' : null,
}} }}
color={schedule.status === "running" ? "secondary" : "primary"} color={schedule.status === "running" ? "secondary" : "primary"}
variant={schedule.status === "running" ? "contained" : "outlined"} variant={schedule.status === "running" ? "contained" : "outlined"}
@@ -725,23 +726,23 @@ const SchedulesTab = memo((props) => {
</div> </div>
<div style={{ marginTop: 50, marginBottom: 20 }}> <div style={{ marginTop: 50, marginBottom: 20 }}>
<h4 style={{color: "#FFFFFF"}} >Webhooks</h4> <Typography variant='h6' color="textPrimary">Webhooks</Typography>
<span> Webhooks used in Shuffle workflows.&nbsp; <Typography variant='body2' color="textSecondary"> Webhooks used in Shuffle workflows.&nbsp;
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="/docs/triggers#webhooks" href="/docs/triggers#webhooks"
style={{ textDecoration: "none", color: "#f85a3e" }} style={{ color: theme.palette.primary.main }}
> >
Learn more Learn more
</a> </a>
</span> </Typography>
</div> </div>
<div <div
style={{ style={{
borderRadius: 4, borderRadius: 4,
marginTop: 24, marginTop: 24,
border: "1px solid #494949", border: theme.palette.defaultBorder,
width: "100%", width: "100%",
overflowX: "auto", overflowX: "auto",
paddingBottom: 0, paddingBottom: 0,
@@ -757,7 +758,7 @@ const SchedulesTab = memo((props) => {
paddingBottom: 0, paddingBottom: 0,
minWidth: 600, minWidth: 600,
}}> }}>
<ListItem style={{width:"100%", borderBottom:"1px solid #494949", display: "table-row"}}> <ListItem style={{width:"100%", borderBottom:theme.palette.defaultBorder, display: "table-row"}}>
{["Name", "Environment", "Workflow", "URL", "Action"].map((header, index) => ( {["Name", "Environment", "Workflow", "URL", "Action"].map((header, index) => (
<ListItemText <ListItemText
key={index} key={index}
@@ -767,7 +768,7 @@ const SchedulesTab = memo((props) => {
padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px", padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px",
whiteSpace: "nowrap", whiteSpace: "nowrap",
textOverflow: "ellipsis", textOverflow: "ellipsis",
borderBottom: "1px solid #494949", borderBottom: theme.palette.defaultBorder,
position: "sticky", position: "sticky",
}} }}
/> />
@@ -779,7 +780,7 @@ const SchedulesTab = memo((props) => {
key={rowIndex} key={rowIndex}
style={{ style={{
display: "table-row", display: "table-row",
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
}} }}
> >
{Array(5) {Array(5)
@@ -796,7 +797,7 @@ const SchedulesTab = memo((props) => {
variant="text" variant="text"
animation="wave" animation="wave"
sx={{ sx={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.loaderColor,
height: "20px", height: "20px",
borderRadius: "4px", borderRadius: "4px",
}} }}
@@ -808,13 +809,13 @@ const SchedulesTab = memo((props) => {
):( ):(
webHooks?.length === 0 ? ( webHooks?.length === 0 ? (
<div style={{textAlign: "center"}}> <div style={{textAlign: "center"}}>
<Typography style={{color: "#FFFFFF", padding: 20, fontSize: 16, textAlign: 'center'}}>No webhooks found</Typography> <Typography style={{color: theme.palette.text.primary, padding: 20, fontSize: 16, textAlign: 'center'}}>No webhooks found</Typography>
</div> </div>
):( ):(
webHooks.map((webhook, index) => { webHooks.map((webhook, index) => {
var bgColor = "#212121" var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF";
if (index % 2 === 0) { if (index % 2 === 0) {
bgColor = "#1a1a1a"; bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA";
} }
return ( return (
@@ -840,7 +841,7 @@ const SchedulesTab = memo((props) => {
rel="noopener noreferrer" rel="noopener noreferrer"
style={{ style={{
textDecoration: "none", textDecoration: "none",
color: "#f85a3e", color: theme.palette.primary.main,
}} }}
href={`/workflows/${webhook.workflows[0]}`} href={`/workflows/${webhook.workflows[0]}`}
target="_blank" target="_blank"
@@ -853,7 +854,7 @@ const SchedulesTab = memo((props) => {
style={{ style={{
color: color:
webhook.workflows[0].workflow_id !== "global" webhook.workflows[0].workflow_id !== "global"
? "#FF8444" ? theme.palette.primary.main
: "grey", : "grey",
}} }}
/> />
@@ -904,6 +905,7 @@ const SchedulesTab = memo((props) => {
fontSize: 16, fontSize: 16,
color:webhook.status === "running" ? '#1a1a1a' : null, color:webhook.status === "running" ? '#1a1a1a' : null,
backgroundColor: webhook.status === "running" ? '#ff8544' : null, backgroundColor: webhook.status === "running" ? '#ff8544' : null,
width: 150,
}} }}
color={webhook.status === "running" ? "secondary" : "primary"} color={webhook.status === "running" ? "secondary" : "primary"}
variant={webhook.status === "running" ? "contained" : "outlined"} variant={webhook.status === "running" ? "contained" : "outlined"}
@@ -928,24 +930,24 @@ const SchedulesTab = memo((props) => {
</List> </List>
</div> </div>
<div style={{ marginTop: 50, marginBottom: 20 }}> <div style={{ marginTop: 50, marginBottom: 20 }}>
<h4 style={{color: "#FFFFFF"}}>Pipelines</h4> <Typography variant='h6' color="textPrimary" >Pipelines</Typography>
<span> <Typography variant='body2' color="textSecondary" >
Controls Pipelines on your Orborus Runners, e.g. for Log Ingestion, MQ subscriptions or Sigma Detections.{" "} Controls Pipelines on your Orborus Runners, e.g. for Log Ingestion, MQ subscriptions or Sigma Detections.{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="/docs/triggers#pipelines" href="/docs/triggers#pipelines"
style={{ textDecoration: "none", color: "#f85a3e" }} style={{ color: theme.palette.primary.main }}
> >
Learn more Learn more
</a> </a>
</span> </Typography>
<div style={{marginBottom: 10, marginTop: 10, }}/> <div style={{marginBottom: 10, marginTop: 10, }}/>
<Button <Button
style={{ backgroundColor: '#ff8544', color: "#1a1a1a", borderRadius: 4, textTransform: "capitalize", fontSize: 16, }} style={{ borderRadius: 4, textTransform: "capitalize", fontSize: 16, }}
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => setPipelineModalOpen(true)} onClick={() => setPipelineModalOpen(true)}
@@ -958,7 +960,7 @@ const SchedulesTab = memo((props) => {
style={{ style={{
borderRadius: 4, borderRadius: 4,
marginTop: 24, marginTop: 24,
border: "1px solid #494949", border: theme.palette.defaultBorder,
width: "100%", width: "100%",
overflowX: pipelines?.length === 0 ? "hidden" : "auto", overflowX: pipelines?.length === 0 ? "hidden" : "auto",
paddingBottom: 0, paddingBottom: 0,
@@ -974,7 +976,7 @@ const SchedulesTab = memo((props) => {
overflowX: "auto", overflowX: "auto",
paddingBottom: 0 paddingBottom: 0
}}> }}>
<ListItem style={{width:"100%", borderBottom:"1px solid #494949", display: "table-row"}}> <ListItem style={{width:"100%", borderBottom:theme.palette.defaultBorder, display: "table-row"}}>
{["Command", "Environment", "Total Runs", "Actions"].map((header, index) => ( {["Command", "Environment", "Total Runs", "Actions"].map((header, index) => (
<ListItemText <ListItemText
key={index} key={index}
@@ -984,7 +986,7 @@ const SchedulesTab = memo((props) => {
padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px", padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px",
whiteSpace: "nowrap", whiteSpace: "nowrap",
textOverflow: "ellipsis", textOverflow: "ellipsis",
borderBottom: "1px solid #494949", borderBottom: theme.palette.defaultBorder,
position: "sticky", position: "sticky",
}} }}
/> />
@@ -997,7 +999,7 @@ const SchedulesTab = memo((props) => {
key={rowIndex} key={rowIndex}
style={{ style={{
display: "table-row", display: "table-row",
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
}} }}
> >
{Array(5) {Array(5)
@@ -1015,7 +1017,7 @@ const SchedulesTab = memo((props) => {
variant="text" variant="text"
animation="wave" animation="wave"
sx={{ sx={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.loaderColor,
height: "20px", height: "20px",
borderRadius: "4px", borderRadius: "4px",
}} }}
@@ -1031,14 +1033,14 @@ const SchedulesTab = memo((props) => {
): ( ): (
pipelines?.length === 0 ? ( pipelines?.length === 0 ? (
<div style={{width: "100%", textAlign: "center", }}> <div style={{width: "100%", textAlign: "center", }}>
<Typography style={{color: "#FFFFFF", padding: 20,width: "100%", fontSize: 16, textAlign: 'center'}}>No pipeline trigger found</Typography> <Typography style={{color: theme.palette.text.primary, padding: 20,width: "100%", fontSize: 16, textAlign: 'center'}}>No pipeline trigger found</Typography>
</div> </div>
):( ):(
pipelines.map((pipeline, index) => { pipelines.map((pipeline, index) => {
var bgColor = "#212121" var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF";
if (index % 2 === 0) { if (index % 2 === 0) {
bgColor = "#1a1a1a"; bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA";
} }
return ( return (
@@ -1094,4 +1096,3 @@ const SchedulesTab = memo((props) => {
}); });
export default SchedulesTab; export default SchedulesTab;
+108 -62
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useContext } from 'react'; import React, { useState, useEffect, useRef, useContext } from 'react';
import theme from '../theme.jsx'; import {getTheme} from '../theme.jsx';
import { useNavigate, Link, useParams } from "react-router-dom"; import { useNavigate, Link, useParams } from "react-router-dom";
import { toast } from "react-toastify" import { toast } from "react-toastify"
@@ -47,16 +47,19 @@ const chipStyle = {
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
} }
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const SearchData = props => { const SearchData = props => {
const { serverside, globalUrl, userdata } = props const { serverside, globalUrl, userdata } = props
let navigate = useNavigate(); let navigate = useNavigate();
const { searchBarModalOpen, setSearchBarModalOpen } = useContext(Context); const { searchBarModalOpen, setSearchBarModalOpen, isDocSearchModalOpen } = useContext(Context);
const borderRadius = 3 const borderRadius = 3
const node = useRef() const node = useRef()
const [searchOpen, setSearchOpen] = useState(false) const [searchOpen, setSearchOpen] = useState(false)
const [oldPath, setOldPath] = useState("") const [oldPath, setOldPath] = useState("")
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const isDocSearchModal = isDocSearchModalOpen;
const {themeMode, supportEmail} = useContext(Context);
const theme = getTheme(themeMode)
const handleLinkClick = () => { const handleLinkClick = () => {
if (searchBarModalOpen) { if (searchBarModalOpen) {
@@ -90,6 +93,8 @@ const SearchData = props => {
const [inputValue, setInputValue] = useState(currentRefinement); const [inputValue, setInputValue] = useState(currentRefinement);
const textFieldRef = useRef(null); const textFieldRef = useRef(null);
const debounceTimeoutRef = useRef(null);
const keyPressHandler = (e) => { const keyPressHandler = (e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
e.preventDefault(); e.preventDefault();
@@ -110,10 +115,25 @@ const SearchData = props => {
}, [searchOpen]); }, [searchOpen]);
useEffect(() => { useEffect(() => {
if (currentRefinement !== inputValue) { // Clear any existing timeout to prevent multiple executions
refine(inputValue); if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
} }
}, [currentRefinement]);
// Set a new timeout that will execute the search after a 200ms delay
debounceTimeoutRef.current = setTimeout(() => {
refine(inputValue);
setSearchOpen(inputValue.trim() !== '');
}, 200);
// Cleanup function that runs when component unmounts or when dependencies change
// This ensures we don't have any hanging timeouts
return () => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
};
}, [inputValue, refine]); // Effect runs when inputValue or refine function changes
return ( return (
@@ -122,14 +142,13 @@ const SearchData = props => {
> >
<TextField <TextField
fullWidth fullWidth
style={{ zIndex: 1100, marginTop: -20, marginBottom: 200, position: "fixed", backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, width: 685, }} style={{ zIndex: 1100, marginTop: -20, marginBottom: 200, position: "fixed", backgroundColor: theme.palette.textFieldStyle.backgroundColor, borderRadius: borderRadius, width: 690, }}
InputProps={{ InputProps={{
style: { style: {
color: "white", color: theme.palette.textFieldStyle.color,
fontSize: "1em", fontSize: "1em",
height: 50, height: 50,
margin: 0, margin: 0,
fontSize: "0.9em",
paddingLeft: 10, paddingLeft: 10,
}, },
disableUnderline: true, disableUnderline: true,
@@ -149,7 +168,7 @@ const SearchData = props => {
autoComplete='off' autoComplete='off'
type="search" type="search"
color="primary" color="primary"
placeholder="Find Public Apps, Workflows, Documentation..." placeholder={isDocSearchModal ? "Type to search documentation..." : "Find Public Apps, Workflows, Documentation..."}
value={inputValue} value={inputValue}
id="shuffle_search_field" id="shuffle_search_field"
onClick={(event) => { onClick={(event) => {
@@ -163,12 +182,6 @@ const SearchData = props => {
onChange={(event) => { onChange={(event) => {
const newValue = event.target.value; const newValue = event.target.value;
setInputValue(newValue); setInputValue(newValue);
refine(newValue);
if (newValue.trim() !== '') {
setSearchOpen(true);
} else {
setSearchOpen(false);
}
}} }}
onKeyDown={keyPressHandler} onKeyDown={keyPressHandler}
inputRef={textFieldRef} inputRef={textFieldRef}
@@ -205,12 +218,12 @@ const SearchData = props => {
const baseImage = <CodeIcon /> const baseImage = <CodeIcon />
return ( return (
<Card elevation={0} style={{ marginRight: 10, marginTop: 50, color: "white", zIndex: 1002, backgroundColor: theme.palette.inputColor, width: "100%", left: 75, boxShadows: "none", }}> <Card elevation={0} style={{ marginRight: 10, marginTop: 50, color: theme.palette.text.primary, zIndex: 1002, backgroundColor: theme.palette.textFieldStyle.backgroundColor, width: "100%", left: 75, boxShadows: "none", }}>
<Typography variant="h6" style={{ margin: "10px 10px 0px 20px", color: "#FF8444", borderBottom: "1px solid", width: 105 }}> <Typography variant="h6" style={{ margin: "10px 10px 0px 20px", color: "#FF8444", borderBottom: "1px solid", width: 105 }}>
Workflows Workflows
</Typography> </Typography>
<List style={{ backgroundColor: theme.palette.inputColor, }}> <List style={{ backgroundColor: themeMode === "dark" ? "#2A2A2A" : "#F5F5F5", }}>
{hits.length === 0 ? {hits.length === 0 ?
<ListItem style={outerlistitemStyle}> <ListItem style={outerlistitemStyle}>
<ListItemAvatar onClick={() => console.log(hits)}> <ListItemAvatar onClick={() => console.log(hits)}>
@@ -230,7 +243,7 @@ const SearchData = props => {
overflowX: "hidden", overflowX: "hidden",
overflowY: "hidden", overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)", borderBottom: "1px solid rgba(255,255,255,0.4)",
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", backgroundColor: mouseHoverIndex === index ? theme.palette.hoverColor : "inherit",
cursor: "pointer", cursor: "pointer",
marginLeft: 5, marginLeft: 5,
marginRight: 5, marginRight: 5,
@@ -384,14 +397,14 @@ const SearchData = props => {
}) })
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
toast(`Failed to ${type} the app for your organization. Please try again or contact support@shuffler.io`) toast(`Failed to ${type} the app for your organization. Please try again or contact ${supportEmail}`)
} }
return response.json() return response.json()
}) })
.then((responseJson) => { .then((responseJson) => {
if (responseJson.success === false) { if (responseJson.success === false) {
toast(`Failed to ${type} the app for your organization. Please try again or contact support@shuffler.io for more info`) toast(`Failed to ${type} the app for your organization. Please try again or contact ${supportEmail} for more info`)
} else { } else {
toast(`App successfully ${type}d. It may now be used in your workflows.`) toast(`App successfully ${type}d. It may now be used in your workflows.`)
} }
@@ -426,7 +439,7 @@ const SearchData = props => {
const baseImage = <LibraryBooksIcon /> const baseImage = <LibraryBooksIcon />
return ( return (
<Card elevation={0} style={{ marginRight: 10, color: "white", zIndex: 999, backgroundColor: theme.palette.inputColor, width: 685, boxShadows: "none", }}> <Card elevation={0} style={{ marginRight: 10, color: "white", zIndex: 999, backgroundColor: theme.palette.textFieldStyle.backgroundColor, width: 685, boxShadows: "none", }}>
{/* <IconButton style={{ zIndex: 5000, position: "absolute", right: 14, color: "grey" }} onClick={() => { {/* <IconButton style={{ zIndex: 5000, position: "absolute", right: 14, color: "grey" }} onClick={() => {
setSearchOpen(false) setSearchOpen(false)
}}> }}>
@@ -436,7 +449,7 @@ const SearchData = props => {
Apps Apps
</Typography> </Typography>
<List style={{ backgroundColor: theme.palette.inputColor, }}> <List style={{ backgroundColor: themeMode === "dark" ? "#2A2A2A" : "#F5F5F5", }}>
{hits.length === 0 ? {hits.length === 0 ?
<ListItem style={outerlistitemStyle}> <ListItem style={outerlistitemStyle}>
<ListItemAvatar onClick={() => console.log(hits)}> <ListItemAvatar onClick={() => console.log(hits)}>
@@ -456,7 +469,7 @@ const SearchData = props => {
overflowX: "hidden", overflowX: "hidden",
overflowY: "hidden", overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)", borderBottom: "1px solid rgba(255,255,255,0.4)",
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", backgroundColor: mouseHoverIndex === index ? theme.palette.hoverColor : "inherit",
cursor: "pointer", cursor: "pointer",
marginLeft: 5, marginLeft: 5,
marginRight: 5, marginRight: 5,
@@ -605,7 +618,11 @@ const SearchData = props => {
} }
if (hits.length > 4) { if (hits.length > 4) {
hits = hits.slice(0, 4) if(!isDocSearchModal) {
hits = hits.slice(0, 4)
} else {
hits = hits.slice(0, 15)
}
} }
const type = "documentation" const type = "documentation"
@@ -614,15 +631,19 @@ const SearchData = props => {
//console.log(type, hits.length, hits) //console.log(type, hits.length, hits)
return ( return (
<Card elevation={0} style={{ marginRight: 10, marginTop: 50, color: "white", zIndex: 1002, backgroundColor: theme.palette.inputColor, width: "100%", left: 470, boxShadows: "none", }}> <Card elevation={0} style={{ marginRight: 10, marginTop: isDocSearchModal ? 0 : 50, color: "white", zIndex: 1002, backgroundColor: theme.palette.textFieldStyle.backgroundColor, width: "100%", left: 470, boxShadows: "none", }}>
{/* <IconButton style={{ zIndex: 5000, position: "absolute", right: 14, color: "grey" }} onClick={() => { {/* <IconButton style={{ zIndex: 5000, position: "absolute", right: 14, color: "grey" }} onClick={() => {
setSearchOpen(false) setSearchOpen(false)
}}> }}>
<CloseIcon /> <CloseIcon />
</IconButton> */} </IconButton> */}
<Typography variant="h6" style={{ margin: "10px 10px 0px 20px", color: "#FF8444", borderBottom: "1px solid", width: 152 }}> {
Documentation !isDocSearchModal && (
</Typography> <Typography variant="h6" style={{ margin: "10px 10px 0px 20px", color: "#FF8444", borderBottom: "1px solid", width: 152 }}>
Documentation
</Typography>
)
}
{/* {/*
<IconButton edge="end" aria-label="delete" style={{position: "absolute", top: 5, right: 15,}} onClick={() => { <IconButton edge="end" aria-label="delete" style={{position: "absolute", top: 5, right: 15,}} onClick={() => {
setSearchOpen(false) setSearchOpen(false)
@@ -630,7 +651,7 @@ const SearchData = props => {
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
*/} */}
<List style={{ backgroundColor: theme.palette.inputColor, }}> <List style={{ backgroundColor: themeMode === "dark" ? "#2A2A2A" : "#F5F5F5", marginTop: isDocSearchModal ? 35 : 0, }}>
{hits.length === 0 ? {hits.length === 0 ?
<ListItem style={outerlistitemStyle}> <ListItem style={outerlistitemStyle}>
<ListItemAvatar onClick={() => console.log(hits)}> <ListItemAvatar onClick={() => console.log(hits)}>
@@ -650,12 +671,12 @@ const SearchData = props => {
overflowX: "hidden", overflowX: "hidden",
overflowY: "hidden", overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)", borderBottom: "1px solid rgba(255,255,255,0.4)",
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", backgroundColor: mouseHoverIndex === index ? theme.palette.hoverColor : "inherit",
cursor: "pointer", cursor: "pointer",
marginLeft: 5, marginLeft: 5,
marginRight: 5, marginRight: 5,
maxHeight: 75, maxHeight: isDocSearchModal ? 100 : 75,
minHeight: 75, minHeight: isDocSearchModal ? 100 : 75,
maxWidth: 420, maxWidth: 420,
minWidth: "100%", minWidth: "100%",
} }
@@ -666,9 +687,13 @@ const SearchData = props => {
(hit.name.charAt(0).toUpperCase() + hit.name.slice(1)).replaceAll("_", " ") (hit.name.charAt(0).toUpperCase() + hit.name.slice(1)).replaceAll("_", " ")
if (name.length > 30) { if (name.length > 30) {
name = name.slice(0, 30) + "..." if(isDocSearchModal) {
name = name.slice(0, 70) + "..."
} else {
name = name.slice(0, 30) + "..."
}
} }
const secondaryText = hit.data !== undefined ? hit.data.slice(0, 40) + "..." : "" const secondaryText = hit.data !== undefined ? hit.data.slice(0, 80) + "..." : ""
const avatar = hit.image_url === undefined ? const avatar = hit.image_url === undefined ?
baseImage baseImage
: :
@@ -719,7 +744,7 @@ const SearchData = props => {
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={name} primary={name}
secondary={secondaryText} secondary={<div style={{ padding: '8px 0' }}>{secondaryText}</div>}
/> />
{/* {/*
<ListItemSecondaryAction> <ListItemSecondaryAction>
@@ -763,13 +788,20 @@ const SearchData = props => {
window.open(modifiedUrl, '_blank'); window.open(modifiedUrl, '_blank');
}; };
return ( return (
<Card elevation={0} style={{ marginRight: 10, marginTop: 50, color: "white", zIndex: 1002, backgroundColor: theme.palette.inputColor, width: "100%", left: 470, boxShadows: "none", }}> <Card elevation={0} style={{ marginRight: 10, marginTop: 50, color: "white", zIndex: 1002, backgroundColor: theme.palette.textFieldStyle.backgroundColor, width: "100%", left: 470, boxShadows: "none", }}>
<Typography variant="h6" style={{ margin: "10px 10px 0px 20px", color: "#FF8444", borderBottom: "1px solid", width: 152 }}> <Typography variant="h6" style={{ margin: "10px 10px 0px 20px", color: "#FF8444", borderBottom: "1px solid", width: 152 }}>
Discord Chat Discord Chat
</Typography> </Typography>
<List style={{ backgroundColor: theme.palette.inputColor, }}> <List style={{ backgroundColor: themeMode === "dark" ? "#2A2A2A" : "#F5F5F5", }}>
{hits.length === 0 ? {hits.length === 0 ?
<ListItem style={outerlistitemStyle}> <ListItem
sx={{
"&:hover": {
backgroundColor: theme.palette.hoverColor
}
}}
style={outerlistitemStyle}
>
<ListItemAvatar onClick={() => console.log(hits)}> <ListItemAvatar onClick={() => console.log(hits)}>
<Avatar> <Avatar>
<FolderIcon /> <FolderIcon />
@@ -781,7 +813,11 @@ const SearchData = props => {
/> />
</ListItem>: </ListItem>:
hits.map((chat, index) => ( hits.map((chat, index) => (
<ListItem onClick={() => handleHitClick(chat.url)} key={index} style={{ cursor: "pointer", borderBottom: "1px solid rgba(255,255,255,0.4)" }} onMouseOver={() => setMouseHoverIndex(index)}> <ListItem sx={{
"&:hover": {
backgroundColor: theme.palette.hoverColor
}
}} onClick={() => handleHitClick(chat.url)} key={index} style={{ cursor: "pointer", borderBottom: "1px solid rgba(255,255,255,0.4)" }} onMouseOver={() => setMouseHoverIndex(index)}>
<ListItemAvatar> <ListItemAvatar>
<Avatar src="/discord-logo.png" /> <Avatar src="/discord-logo.png" />
</ListItemAvatar> </ListItemAvatar>
@@ -897,28 +933,38 @@ const SearchData = props => {
const modalView = ( const modalView = (
<div> <div>
<Grid container style={{ display: "contents", }}> {
<Grid item xs="auto" style={{}}> !isDocSearchModal ? (
<Index indexName="appsearch"> <Grid container style={{ display: "contents", }}>
<CustomAppHits /> <Grid item xs="auto" style={{}}>
</Index> <Index indexName="appsearch">
<CustomAppHits />
</Index>
</Grid>
<Grid item xs="auto" style={{}}>
<Index indexName="workflows">
<CustomWorkflowHits />
</Index>
</Grid>
<Grid item xs="auto" style={{}}>
<Index indexName="documentation">
<CustomDocHits />
</Index>
</Grid>
<Grid item xs="auto" style={{}}>
<Index indexName="discord_chat">
<CustomDiscordHits />
</Index>
</Grid>
</Grid> </Grid>
<Grid item xs="auto" style={{}}> ) : (
<Index indexName="workflows"> <Grid item xs="auto" style={{width: "100%"}}>
<CustomWorkflowHits /> <Index indexName="documentation">
</Index> <CustomDocHits />
</Grid> </Index>
<Grid item xs="auto" style={{}}> </Grid>
<Index indexName="documentation"> )
<CustomDocHits /> }
</Index>
</Grid>
<Grid item xs="auto" style={{}}>
<Index indexName="discord_chat">
<CustomDiscordHits />
</Index>
</Grid>
</Grid>
</div> </div>
) )
@@ -932,7 +978,7 @@ const SearchData = props => {
<CustomSearchBox /> <CustomSearchBox />
{modalView} {modalView}
</InstantSearch> </InstantSearch>
{gettingStartData} {!isDocSearchModal && gettingStartData}
</div> </div>
) )
} }
+4 -3
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useContext } from 'react'; import React, { useState, useEffect, useRef, useContext } from 'react';
import theme from '../theme.jsx'; import {getTheme} from '../theme.jsx';
import { useNavigate, Link, useParams } from "react-router-dom"; import { useNavigate, Link, useParams } from "react-router-dom";
import SearchBox from "../components/SearchData.jsx"; import SearchBox from "../components/SearchData.jsx";
@@ -46,7 +46,8 @@ const chipStyle = {
const SearchField = props => { const SearchField = props => {
const { serverside, userdata, isMobile, isLoaded, globalUrl, isHeader, isLoggedIn, small, rounded } = props const { serverside, userdata, isMobile, isLoaded, globalUrl, isHeader, isLoggedIn, small, rounded } = props
const {themeMode} = useContext(Context);
const theme = getTheme(themeMode);
const {searchBarModalOpen, setSearchBarModalOpen} = useContext(Context); const {searchBarModalOpen, setSearchBarModalOpen} = useContext(Context);
let navigate = useNavigate(); let navigate = useNavigate();
@@ -87,7 +88,7 @@ const SearchField = props => {
height: 785, height: 785,
borderRadius: 16, borderRadius: 16,
border: "1px solid var(--Container-Stroke, #494949)", border: "1px solid var(--Container-Stroke, #494949)",
background: "var(--Container, #000000)", background: theme.palette.DialogStyle.backgroundColor,
boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)", boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)",
zIndex: 13000, zIndex: 13000,
}, },
+151 -56
View File
@@ -1,4 +1,4 @@
import React, { useRef, useState, useEffect, useLayoutEffect, } from 'react'; import React, { useRef, useState, useEffect, useLayoutEffect, useContext } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import '../codeeditor-index.css'; import '../codeeditor-index.css';
import { import {
@@ -17,8 +17,8 @@ import {
ButtonGroup, ButtonGroup,
Collapse, Collapse,
} from '@mui/material'; } from '@mui/material';
import { Context } from '../context/ContextApi.jsx';
import theme from '../theme.jsx'; import {getTheme} from '../theme.jsx';
import Checkbox from '@mui/material/Checkbox'; import Checkbox from '@mui/material/Checkbox';
import { isMobile } from "react-device-detect" import { isMobile } from "react-device-detect"
import { NestedMenuItem } from "mui-nested-menu" import { NestedMenuItem } from "mui-nested-menu"
@@ -46,6 +46,7 @@ import {
DragIndicator as DragIndicatorIcon, DragIndicator as DragIndicatorIcon,
RestartAlt as RestartAltIcon, RestartAlt as RestartAltIcon,
ArrowForward as ArrowForwardIcon, ArrowForward as ArrowForwardIcon,
KeyboardReturn as KeyboardReturnIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
@@ -63,6 +64,7 @@ import AceEditor from "react-ace";
import ace from "ace-builds"; import ace from "ace-builds";
import 'ace-builds/src-noconflict/mode-python'; import 'ace-builds/src-noconflict/mode-python';
import 'ace-builds/src-noconflict/mode-json'; import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/mode-yaml';
//import 'ace-builds/src-noconflict/theme-twilight'; //import 'ace-builds/src-noconflict/theme-twilight';
//import 'ace-builds/src-noconflict/theme-solarized_dark'; //import 'ace-builds/src-noconflict/theme-solarized_dark';
import 'ace-builds/src-noconflict/theme-gruvbox'; import 'ace-builds/src-noconflict/theme-gruvbox';
@@ -89,13 +91,17 @@ const liquidFilters = [
const pythonFilters = [ const pythonFilters = [
{ "name": "Hello World", "value": `print("hello world")`, "example": `` }, { "name": "Hello World", "value": `print("hello world")`, "example": `` },
{ "name": "Using Shuffle variables", "value": `import json\nnodevalue = r\"\"\"$exec\"\"\"\nif not nodevalue:\n nodevalue = r\"\"\"{\"sample\": \"string\", \"int\": 1}\"\"\"\n \njsondata = json.loads(nodevalue)\nprint(jsondata)`, "example": `` }, { "name": "Using Shuffle variables", "value": `import json\nnodevalue = r\"\"\"$exec\"\"\"\nif not nodevalue:\n nodevalue = r\"\"\"{\"sample\": \"string\", \"int\": 1}\"\"\"\n \njsondata = json.loads(nodevalue)\nprint(jsondata)`, "example": `` },
{ "name": "Filter a list", "value": `import json\nnodevalue = r\"\"\"$exec\"\"\"\nif not nodevalue:\n nodevalue = r\"\"\"[{\"sample\": \"string\", \"int\": 1, "malicious": "no"}, {\"sample\": \"string2\", \"int\": 1, "malicious": "yes"}]\"\"\"\n \njsondata = json.loads(nodevalue)\nfiltered = []\nfor item in jsondata:\n try:\n if item[\"malicious\"] == \"yes\":\n filtered.append(item)\n except:\n pass\nprint(json.dumps(filtered))`, "example": `` },
{ "name": "Print Execution ID", "value": `print(self.current_execution_id)`, "example": `` }, { "name": "Print Execution ID", "value": `print(self.current_execution_id)`, "example": `` },
{ "name": "Get full execution details", "value": `print(self.full_execution)`, "example": `` }, { "name": "Get full execution details", "value": `print(self.full_execution)`, "example": `` },
{ "name": "Use files", "value": `# Create a sample file\nfiles = [{\n \"filename\": \"test.txt\",\n \"data\": \"Testdata\"\n}]\nret = self.set_files(files)\n\n# Get the content of the file from Shuffle storage\n# Originally a byte string in the \"data\" key\nfile_content = (self.get_file(ret[0])[\"data\"]).decode()\nprint(file_content)`, "example": `` }, { "name": "Use files", "value": `# Create a sample file\nfiles = [{\n \"filename\": \"test.txt\",\n \"data\": \"Testdata\"\n}]\nret = self.set_files(files)\n\n# Get the content of the file from Shuffle storage\n# Originally a byte string in the \"data\" key\nfile_content = (self.get_file(ret[0])[\"data\"]).decode()\nprint(file_content)`, "example": `` },
{ "name": "Use datastore", "value": `key = \"testkey\"\nvalue = \"The value of the testkey\"\n\nself.set_cache(key, value)\n\n# Print the details of the key after it's been updated\n# To get the value, use self.get_cache(key)[\"value\"]\nprint(self.get_cache(key))`, "example": `` }, { "name": "Use datastore", "value": `key = \"testkey\"\nvalue = \"The value of the testkey\"\n\nself.set_key(key, value)\n\n# Print the details of the key after it's been updated\n# To get the value, use self.get_key(key)[\"value\"]\nprint(self.get_key(key))`, "example": `` },
{ "name": "Run an App Action", "value": `response = shuffle.run_app(app_id="app", action="action_name", auth="authentication_id", params={})\nprint(response)`, "example": ``, "disabled": true, },
{ "name": "Run a Singul AI Action", "value": `response = singul.create_ticket(app="jira/iris/ticketingsystem", fields={"title": "Test ticket!"})\nprint(response)`, "example": ``, "disabled": true, }, { "name": "Run a Subflow", "value": `response = shuffle.run_workflow(workflow_id="", start_command="Runtime arg here!", wait=True)\nprint(response)`, "example": ``, "disabled": false, },
{ "name": "Run an App Action", "value": `response = shuffle.run_app(app_id="app", action="action_name", auth="authentication_id", params={})\nprint(response)`, "example": ``, "disabled": false, },
{ "name": "Run a Singul AI Action", "value": `response = singul.cases.create_ticket(app="jira", fields={"title": "Test ticket!"})\nprint(response)`, "example": ``, "disabled": false, },
] ]
@@ -114,6 +120,7 @@ const CodeEditor = (props) => {
handleActionParamChange, handleActionParamChange,
setcodedata, setcodedata,
isFileEditor, isFileEditor,
isWorkflowEditor,
runUpdateText, runUpdateText,
toolsAppId, toolsAppId,
parameterName, parameterName,
@@ -127,7 +134,7 @@ const CodeEditor = (props) => {
fieldname, fieldname,
contentLoading, contentLoading,
editorData, editorData,
handleSubflowParamChange, handleTriggerParamChange,
setAiQueryModalOpen, setAiQueryModalOpen,
fullScreenMode, fullScreenMode,
environment, environment,
@@ -138,12 +145,9 @@ const CodeEditor = (props) => {
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
//const { setContainer } = useCodeMirror({
// container: editorRef.current,
// extensions,
// value: localcodedata,
//})
// const {codelang, setcodelang} = props // const {codelang, setcodelang} = props
const {themeMode, supportEmail} = useContext(Context)
const theme = getTheme(themeMode)
const [validation, setValidation] = React.useState(false); const [validation, setValidation] = React.useState(false);
const [expOutput, setExpOutput] = React.useState(" "); const [expOutput, setExpOutput] = React.useState(" ");
@@ -205,7 +209,7 @@ const CodeEditor = (props) => {
const triggerField = searchParams.get('trigger_field'); const triggerField = searchParams.get('trigger_field');
const triggerName = searchParams.get('trigger_name'); const triggerName = searchParams.get('trigger_name');
const conditionId = searchParams.get('condition_id'); const conditionId = searchParams.get('condition_id');
const conditionField = searchParams.get('field'); const conditionField = searchParams.get('condition_field');
useEffect(() => { useEffect(() => {
if (actionId === undefined || actionId === null) { if (actionId === undefined || actionId === null) {
@@ -244,7 +248,7 @@ const CodeEditor = (props) => {
setSelectedCondition(condition); setSelectedCondition(condition);
// Update available variables when condition changes // Update available variables when condition changes
updateAvailableVariables(actionlist); updateAvailableVariables(actionlist);
}, [conditionId, fieldName]) }, [conditionId, conditionField])
// Extract variable updating logic into a separate function // Extract variable updating logic into a separate function
const updateAvailableVariables = (actionlist) => { const updateAvailableVariables = (actionlist) => {
@@ -298,8 +302,19 @@ const CodeEditor = (props) => {
setMainVariables(tmpVariables) setMainVariables(tmpVariables)
} }
const handleKeyDown = (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
event.preventDefault()
const tryItButton = document.getElementById("try-it-button")
if (tryItButton !== undefined && tryItButton !== null) {
tryItButton.click()
}
}
}
// Remove the original useEffect for actionlist since we'll update on action/trigger changes // Remove the original useEffect for actionlist since we'll update on action/trigger changes
useEffect(() => { useEffect(() => {
document.addEventListener("keydown", handleKeyDown)
updateAvailableVariables(actionlist) updateAvailableVariables(actionlist)
}, []) }, [])
@@ -886,6 +901,8 @@ const CodeEditor = (props) => {
// Whelp this is inefficient af. Single loop pls // Whelp this is inefficient af. Single loop pls
// When the found array is empty. // When the found array is empty.
if (found !== null && found !== undefined) { if (found !== null && found !== undefined) {
//console.log("FOUND: ", found)
try { try {
for (var i = 0; i < found.length; i++) { for (var i = 0; i < found.length; i++) {
try { try {
@@ -921,27 +938,71 @@ const CodeEditor = (props) => {
} }
} }
// Find the location to ensure replacements happen correctly
var foundlocation = -1
for (var j = 0; j < input.length; j++) {
const foundStringSize = fixedVariable.length
const foundslice = input.slice(j, j + foundStringSize)
//console.log("FOUNDSLICE: ", foundslice)
if (fixedVariable !== foundslice) {
continue
}
// Check if it matches EXACTLY or not, as there may be more AFTER the found[i]
const nextchar = input.slice(j + foundStringSize, j + foundStringSize + 1)
if (nextchar === ".") {
continue
}
foundlocation = j
break
}
// FIXME: There is something wrong here with:
// $variable.#
// vs
// $variable.#.subvalue
// if you put both of those lines in the same editor, then it will replace both (somehow). Make sure $variable.#.subvalue exists while testing.
console.log("FOUNDLOC: ", fixedVariable, foundlocation)
for (var j = 0; j < actionlist.length; j++) { for (var j = 0; j < actionlist.length; j++) {
if (fixedVariable.slice(1,).toLowerCase() !== actionlist[j].autocomplete.toLowerCase()) { if (fixedVariable.slice(1,).toLowerCase() !== actionlist[j].autocomplete.toLowerCase()) {
continue continue
} }
// Look for the location of found[i] in the input, as to make sure to skip parts of the input in the replace. Find ALL spots for it
valuefound = true valuefound = true
var newvalue = ""
try { try {
if (typeof actionlist[j].example === "object") {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1); if (typeof actionlist[j].example === "object") {
newvalue = JSON.stringify(actionlist[j].example)
} else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) { } else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
newvalue = JSON.stringify(actionlist[j].example)
} else { } else {
const newExample = fixStringInput(actionlist[j].example) const newExample = fixStringInput(actionlist[j].example)
input = input.replace(found[i], newExample, -1)
newvalue = newExample
} }
} catch (e) { } catch (e) {
input = input.replace(found[i], actionlist[j].example, -1) newvalue = actionlist[j].example
} }
try {
console.log("REPLACE: ", foundlocation, fixedVariable, newvalue)
if (newvalue !== "") {
if (foundlocation === -1) {
input = input.replace(fixedVariable, newvalue, 1)
} else {
// Ensures we don't just randomly replace the first value we find
const replacedSlice = input.slice(foundlocation, input.length).replace(fixedVariable, newvalue, 1)
input = input.slice(0, foundlocation) + replacedSlice
}
}
} catch (e) {
console.log("Replace error: ", e)
}
} }
if (!valuefound) { if (!valuefound) {
@@ -1334,7 +1395,7 @@ const CodeEditor = (props) => {
const usedposition = e.offsetY const usedposition = e.offsetY
if (usedposition === undefined || usedposition === null) { if (usedposition === undefined || usedposition === null) {
toast.info("Error: LayerY is undefined or null. Please contact support@shuffler.io") toast.info(`Error: LayerY is undefined or null. Please contact ${supportEmail}`)
return return
} }
@@ -1461,17 +1522,19 @@ const CodeEditor = (props) => {
setActiveDialog("codeeditor") setActiveDialog("codeeditor")
} }
}, },
style: { sx: {
// zIndex: 12501, // zIndex: 12501,
pointerEvents: "auto", pointerEvents: "auto",
color: "white", color: theme.palette.DialogStyle.color,
minWidth: isMobile ? "100%" : isFileEditor ? 650 : "80%", minWidth: isMobile || isWorkflowEditor ? "100%" : isFileEditor ? "650px" : "80%",
maxWidth: isMobile ? "100%" : isFileEditor ? 650 : 1100, maxWidth: isMobile || isWorkflowEditor ? "100%" : isFileEditor ? "650px" : "1100px",
minHeight: isMobile ? "100%" : "auto", minHeight: isMobile || isWorkflowEditor ? "100%" : "auto",
maxHeight: isMobile ? "100%" : 700, maxHeight: isMobile || isWorkflowEditor ? "100%" : "700px",
border: "3px solid rgba(255,255,255,0.3)", border: "3px solid rgba(255,255,255,0.3)",
padding: isMobile ? "25px 10px 25px 10px" : 25, padding: isMobile ? "25px 10px 25px 10px" : isWorkflowEditor ? "25px 10px 25px 200px" : "25px",
backgroundColor: "black", backgroundColor: themeMode === "dark" ? "black" : theme.palette.DialogStyle.backgroundColor,
opacity: isWorkflowEditor ? 0.93 : 1,
}, },
}} }}
> >
@@ -1522,7 +1585,10 @@ const CodeEditor = (props) => {
color: "grey", color: "grey",
}} }}
onClick={() => { onClick={() => {
navigate("") if (isFileEditor !== true) {
navigate("")
}
setExpansionModalOpen(false) setExpansionModalOpen(false)
}} }}
> >
@@ -1652,7 +1718,7 @@ const CodeEditor = (props) => {
Code Editor Code Editor
</DialogTitle> </DialogTitle>
*/} */}
{isFileEditor ? null : {isFileEditor || isWorkflowEditor ? null :
<div style={{ display: "flex", maxHeight: 40, }}> <div style={{ display: "flex", maxHeight: 40, }}>
<ButtonGroup style={{ borderRadius: theme.palette.borderRadius, }}> <ButtonGroup style={{ borderRadius: theme.palette.borderRadius, }}>
{userdata !== undefined && userdata !== null && userdata.support === true ? {userdata !== undefined && userdata !== null && userdata.support === true ?
@@ -1666,6 +1732,7 @@ const CodeEditor = (props) => {
style={{ style={{
textTransform: "none", textTransform: "none",
width: 175, width: 175,
textWrap: 'nowrap'
}} }}
onClick={(event) => { onClick={(event) => {
setSourceDataOpen(!sourceDataOpen) setSourceDataOpen(!sourceDataOpen)
@@ -1685,6 +1752,7 @@ const CodeEditor = (props) => {
style={{ style={{
textTransform: "none", textTransform: "none",
width: 120, width: 120,
textWrap: "nowrap"
}} }}
onClick={(event) => { onClick={(event) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
@@ -1720,13 +1788,14 @@ const CodeEditor = (props) => {
color="secondary" color="secondary"
style={{ style={{
textTransform: "none", textTransform: "none",
width: 120, width: 145,
textWrap: "nowrap"
}} }}
onClick={(event) => { onClick={(event) => {
setAnchorEl3(event.currentTarget); setAnchorEl3(event.currentTarget);
}} }}
> >
Python Code Python Examples
</Button> </Button>
<Button <Button
id="basic-button" id="basic-button"
@@ -1738,6 +1807,7 @@ const CodeEditor = (props) => {
style={{ style={{
textTransform: "none", textTransform: "none",
width: 130, width: 130,
textWrap: "nowrap",
}} }}
onClick={(event) => { onClick={(event) => {
setMenuPosition({ setMenuPosition({
@@ -1761,7 +1831,12 @@ const CodeEditor = (props) => {
> >
{pythonFilters.map((item, index) => { {pythonFilters.map((item, index) => {
return ( return (
<MenuItem key={index} onClick={() => { <MenuItem
style={{
borderTop: item.name === "Use files" || (item.name.toLowerCase().includes("run") && item.name.toLowerCase().includes("subflow")) ? "2px solid rgba(255,255,255,0.3)" : "none",
}}
key={index} onClick={() => {
if (item.disabled) { if (item.disabled) {
toast.error("This feature may not work in your environment until you update your Shuffle Tools app.", { autoClose: 10000 }) toast.error("This feature may not work in your environment until you update your Shuffle Tools app.", { autoClose: 10000 })
} }
@@ -2114,15 +2189,15 @@ const CodeEditor = (props) => {
console.log("DROP: ", e) console.log("DROP: ", e)
}} }}
> >
{(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor ? ( {(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor || isWorkflowEditor ? (
<AceEditor <AceEditor
id="shuffle-codeeditor" id="shuffle-codeeditor"
name="shuffle-codeeditor" name="shuffle-codeeditor"
value={localcodedata} value={localcodedata}
mode={selectedAction === undefined ? "json" : selectedAction.name === "execute_python" ? "python" : selectedAction.name === "execute_bash" ? "bash" : "json"} mode={isWorkflowEditor ? "yaml" : selectedAction === undefined ? "json" : selectedAction.name === "execute_python" ? "python" : selectedAction.name === "execute_bash" ? "bash" : "json"}
theme="gruvbox" theme="gruvbox"
height={isFileEditor ? 450 : 550} height={isFileEditor ? 450 : isWorkflowEditor ? "90vh" : 550}
width={isFileEditor ? 650 : "100%"} width={isFileEditor ? 650 : isWorkflowEditor ? "90vw" : "100%"}
markers={markers} markers={markers}
highlightActiveLine={false} highlightActiveLine={false}
@@ -2183,7 +2258,7 @@ const CodeEditor = (props) => {
</div> </div>
</div> </div>
{isFileEditor ? null : {isFileEditor || isWorkflowEditor ? null :
<div style={{ <div style={{
flex: sourceDataOpen ? 1.5 : 3, flex: sourceDataOpen ? 1.5 : 3,
marginLeft: 5, marginLeft: 5,
@@ -2199,7 +2274,9 @@ const CodeEditor = (props) => {
paddingLeft: 10, paddingLeft: 10,
paddingTop: 0, paddingTop: 0,
display: "flex", display: "flex",
cursor: "move" cursor: "move",
color: theme.palette.DialogStyle.color,
backgroundColor: "transparent",
}} }}
> >
<div> <div>
@@ -2227,7 +2304,7 @@ const CodeEditor = (props) => {
{ {
selectedEdge && Object.keys(selectedEdge).length > 0 ? selectedEdge && Object.keys(selectedEdge).length > 0 ?
<ArrowForwardIcon style={{ <ArrowForwardIcon style={{
color: "rgba(255,255,255,0.7)", color: theme.palette.textPrimary,
fontSize: 18, fontSize: 18,
marginLeft: -5, marginLeft: -5,
marginRight: -5, marginRight: -5,
@@ -2252,7 +2329,7 @@ const CodeEditor = (props) => {
} }
</div> </div>
: :
<span style={{ color: "white" }}> <span style={{ color: theme.palette.text.primary }}>
{selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ? {selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ?
"Code to run" : "Code to run" :
triggerId ? triggerId ?
@@ -2269,9 +2346,8 @@ const CodeEditor = (props) => {
<div style={{}}> <div style={{}}>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top"> <Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
<Button <Button
variant="outlined" id="try-it-button"
disabled={executing} disabled={executing}
color="primary"
style={{ style={{
border: `1px solid rgba(255, 255, 255, 0.15)`, border: `1px solid rgba(255, 255, 255, 0.15)`,
position: "absolute", position: "absolute",
@@ -2282,11 +2358,14 @@ const CodeEditor = (props) => {
zIndex: 1200, zIndex: 1200,
fontWeight: 500, fontWeight: 500,
fontSize: 14, fontSize: 14,
backgroundColor: "rgba(33, 33, 33, 0.95)", textTransform: "none",
backgroundColor: theme.palette.platformColor,
backdropFilter: "blur(8px)", backdropFilter: "blur(8px)",
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)", boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)",
transition: "all 0.2s ease", transition: "all 0.2s ease",
borderRadius: "4px", paddingRight: 20,
color: "#FF8544",
borderRadius: theme.palette?.borderRadius,
"&:hover": { "&:hover": {
backgroundColor: "rgba(45, 45, 45, 0.95)", backgroundColor: "rgba(45, 45, 45, 0.95)",
transform: "translateY(-1px)", transform: "translateY(-1px)",
@@ -2303,7 +2382,23 @@ const CodeEditor = (props) => {
{executing ? {executing ?
<CircularProgress style={{ height: 18, width: 18, }} /> <CircularProgress style={{ height: 18, width: 18, }} />
: :
<span>{selectedAction === undefined ? "Try it" : selectedAction.name === "execute_python" ? "Run Python Code" : selectedAction.name === "execute_bash" ? "Run Bash" : "Try it"}<PlayArrowIcon style={{ height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} /> </span> <span>
<PlayArrowIcon style={{ height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} />
{selectedAction === undefined ? <Typography style={{color: "inherit"}}>Try it</Typography> : selectedAction.name === "execute_python" ? "Run Python Code" : selectedAction.name === "execute_bash" ? "Run Bash" : "Try it"}
<span
style={{
color: "#C8C8C8",
fontSize: "12px",
whiteSpace: "nowrap",
marginLeft: 5,
marginRight: 10,
}}
>
<kbd>Ctrl</kbd> + <kbd><KeyboardReturnIcon style={{width: 13, position: "absolute", marginLeft: 3, top: 5, }}/></kbd>
</span>
</span>
} }
</Button> </Button>
</Tooltip> </Tooltip>
@@ -2428,7 +2523,7 @@ const CodeEditor = (props) => {
</div> </div>
<div style={{ display: 'flex' }}> <div style={{ display: 'flex', width: isWorkflowEditor ? "90%" : "100%", }}>
<Button <Button
style={{ style={{
height: 35, height: 35,
@@ -2451,6 +2546,7 @@ const CodeEditor = (props) => {
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
disabled={isWorkflowEditor}
style={{ style={{
height: 35, height: 35,
flex: 1, flex: 1,
@@ -2458,13 +2554,11 @@ const CodeEditor = (props) => {
marginTop: 5, marginTop: 5,
}} }}
onClick={(event) => { onClick={(event) => {
/* if (isWorkflowEditor === true) {
const clickedFieldId = "rightside_field_" + fieldCount setExpansionModalOpen(false)
const clickedField = document.getElementById(clickedFieldId) navigate("")
if (clickedField !== undefined && clickedField !== null) { return
clickedField.focus()
} }
*/
if (isFileEditor !== true) { if (isFileEditor !== true) {
navigate("") navigate("")
@@ -2475,7 +2569,8 @@ const CodeEditor = (props) => {
var fixedcodedata = localcodedata var fixedcodedata = localcodedata
const valid = validateJson(localcodedata, true) const valid = validateJson(localcodedata, true)
if (valid.valid) { if (valid.valid) {
fixedcodedata = JSON.stringify(valid.result, null, 2) //fixedcodedata = JSON.stringify(valid.result, null, 2)
fixedcodedata = JSON.stringify(valid.result)
} }
// console.log(codedata) // console.log(codedata)
@@ -2491,7 +2586,7 @@ const CodeEditor = (props) => {
// Handle condition fields // Handle condition fields
if (conditionField !== null && handleConditionFieldChange !== undefined) { if (conditionField !== null && handleConditionFieldChange !== undefined) {
handleConditionFieldChange(conditionField, fieldName, fixedcodedata); handleConditionFieldChange(conditionField, fixedcodedata);
} }
// Handle action fields // Handle action fields
else if (actionId !== undefined && actionId !== null && actionId.length > 0) { else if (actionId !== undefined && actionId !== null && actionId.length > 0) {
@@ -2499,7 +2594,7 @@ const CodeEditor = (props) => {
} }
// Handle trigger fields // Handle trigger fields
else if (triggerId !== undefined && triggerId !== null && triggerId.length > 0) { else if (triggerId !== undefined && triggerId !== null && triggerId.length > 0) {
handleSubflowParamChange(triggerId, triggerField, fixedcodedata) handleTriggerParamChange(triggerId, triggerField, fixedcodedata)
} }
setExpansionModalOpen(false) setExpansionModalOpen(false)
File diff suppressed because it is too large Load Diff
+110 -59
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useContext, memo } from "react"; import React, { useState, useEffect, useContext, memo } from "react";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { Context } from "../context/ContextApi.jsx";
import { import {
FormControl, FormControl,
InputLabel, InputLabel,
@@ -35,7 +35,7 @@ import {
import ModeEditOutlineOutlinedIcon from '@mui/icons-material/ModeEditOutlineOutlined'; import ModeEditOutlineOutlinedIcon from '@mui/icons-material/ModeEditOutlineOutlined';
import ContentCopyOutlinedIcon from '@mui/icons-material/ContentCopyOutlined'; import ContentCopyOutlinedIcon from '@mui/icons-material/ContentCopyOutlined';
import theme from "../theme.jsx"; import {getTheme} from "../theme.jsx";
const ITEM_HEIGHT = 48; const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8; const ITEM_PADDING_TOP = 8;
const MenuProps = { const MenuProps = {
@@ -75,12 +75,16 @@ const UserManagmentTab = memo((props) => {
const [logsViewModal, setLogsViewModal] = React.useState(false); const [logsViewModal, setLogsViewModal] = React.useState(false);
const [ipSelected, setIpSelected] = React.useState(""); const [ipSelected, setIpSelected] = React.useState("");
const [userLogViewing, setUserLogViewing] = React.useState({}); const [userLogViewing, setUserLogViewing] = React.useState({});
const { themeMode, supportEmail, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
useEffect(() => { useEffect(() => {
if (selectedOrganization?.mfa_required !== MFARequired) { if (selectedOrganization?.mfa_required !== MFARequired) {
setMFARequired(selectedOrganization?.mfa_required); setMFARequired(selectedOrganization?.mfa_required);
} }
}, [selectedOrganization]); }, [selectedOrganization]);
useEffect(() => { if(users?.length === 0){ useEffect(() => { if(users?.length === 0){
getUsers(); getUsers();
} }, []); } }, []);
@@ -248,6 +252,14 @@ const UserManagmentTab = memo((props) => {
return; return;
} }
if (event.target.value.includes("ALL")) {
toast.info("Adding to available all sub-organizations. This may take a minute.")
event.target.value = selectedOrganization.child_orgs.map((org) => org.id)
} else if (event.target.value.includes("None")) {
toast.info("Removing from all sub-organizations. This may take a minute")
event.target.value = []
}
console.log("event: ", event.target.value); console.log("event: ", event.target.value);
setMatchingOrganizations(event.target.value); setMatchingOrganizations(event.target.value);
// Workaround for empty orgs // Workaround for empty orgs
@@ -286,6 +298,14 @@ const UserManagmentTab = memo((props) => {
}} }}
MenuProps={MenuProps} MenuProps={MenuProps}
> >
<MenuItem key={-2} value={"None"}>
<Checkbox checked={false} />
<ListItemText primary={"None"} />
</MenuItem>
<MenuItem key={-1} value={"ALL"}>
<Checkbox checked={false} />
<ListItemText primary={"ALL"} />
</MenuItem>
{selectedOrganization.child_orgs.map((org, index) => ( {selectedOrganization.child_orgs.map((org, index) => (
<MenuItem key={index} value={org.id}> <MenuItem key={index} value={org.id}>
<Checkbox checked={matchingOrganizations.indexOf(org.id) > -1} /> <Checkbox checked={matchingOrganizations.indexOf(org.id) > -1} />
@@ -347,7 +367,7 @@ const UserManagmentTab = memo((props) => {
toast("Failed to deactivate user: " + responseJson.reason); toast("Failed to deactivate user: " + responseJson.reason);
} else if (responseJson.success === false) { } else if (responseJson.success === false) {
toast( toast(
"Failed to deactivate user. Please contact support@shuffler.io if this persists.", `Failed to deactivate user. Please contact ${supportEmail} if this persists.`,
); );
} else { } else {
toast("Changed activation for user " + data.id); toast("Changed activation for user " + data.id);
@@ -586,7 +606,7 @@ const UserManagmentTab = memo((props) => {
}} }}
> >
<DialogTitle> <DialogTitle>
<Typography style={{ color: "white", textTransform: 'none', fontSize: 24 }}> <Typography variant="h5" style={{ textTransform: 'none', fontSize: 24 }}>
Add user Add user
</Typography> </Typography>
</DialogTitle> </DialogTitle>
@@ -603,7 +623,7 @@ const UserManagmentTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: "50px", height: "50px",
color: "white", color: theme.palette.textFieldStyle.color,
fontSize: "1em", fontSize: "1em",
}, },
}} }}
@@ -636,7 +656,7 @@ const UserManagmentTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: "50px", height: "50px",
color: "white", color: theme.palette.textFieldStyle.color,
fontSize: "1em", fontSize: "1em",
}, },
}} }}
@@ -666,17 +686,17 @@ const UserManagmentTab = memo((props) => {
</div> </div>
{loginInfo} {loginInfo}
</DialogContent> </DialogContent>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', p: 2, backgroundColor: "#212121" }}> <Box sx={{ display: 'flex', justifyContent: 'flex-end', p: 2, backgroundColor: theme.palette.platformColor }}>
<Button <Button
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#ff8544", marginRight: 5 }} style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, marginRight: 5, color: theme.palette.primary.main }}
onClick={() => setModalOpen(false)} onClick={() => setModalOpen(false)}
color="primary"
> >
Cancel Cancel
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, color: "#1a1a1a", backgroundColor: "#ff8544" }} color="primary"
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16,}}
onClick={() => { onClick={() => {
if (isCloud) { if (isCloud) {
inviteUser(modalUser); inviteUser(modalUser);
@@ -684,7 +704,6 @@ const UserManagmentTab = memo((props) => {
submitUser(modalUser); submitUser(modalUser);
} }
}} }}
color="primary"
> >
Submit Submit
</Button> </Button>
@@ -735,7 +754,7 @@ const UserManagmentTab = memo((props) => {
}} }}
> >
<DialogTitle style={{ maxWidth: "800px", width: "100%", textAlign: "center", margin: "auto", backgroundColor: theme?.palette?.DialogStyle?.backgroundColor}}> <DialogTitle style={{ maxWidth: "800px", width: "100%", textAlign: "center", margin: "auto", backgroundColor: theme?.palette?.DialogStyle?.backgroundColor}}>
<span style={{ color: "white", backgroundColor: theme?.palette?.DialogStyle?.backgroundColor }}> <span style={{ color: theme.palette.text.primary, backgroundColor: theme?.palette?.DialogStyle?.backgroundColor }}>
<EditIcon style={{ marginTop: 5 }} /> Editing {selectedUser.username} <EditIcon style={{ marginTop: 5 }} /> Editing {selectedUser.username}
</span> </span>
</DialogTitle> </DialogTitle>
@@ -752,7 +771,7 @@ const UserManagmentTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: 50, height: 50,
color: "white", color: theme.palette.textFieldStyle.color,
}, },
}} }}
color="primary" color="primary"
@@ -795,7 +814,7 @@ const UserManagmentTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: 50, height: 50,
color: "white", color: theme.palette.textFieldStyle.color,
}, },
}} }}
color="primary" color="primary"
@@ -829,9 +848,10 @@ const UserManagmentTab = memo((props) => {
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
}} }}
/> />
<div style={{ margin: "auto", maxWidth: 450 }}> <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', margin: "auto", maxWidth: 450 }}>
<Button <div style={{ display: "flex", justifyContent: "space-between", width: "100%" }}>
style={{textTransform: 'none', fontSize: 16}} <Button
style={{textTransform: 'none', fontSize: 16, whiteSpace: 'nowrap', textWrap: 'nowarp'}}
variant="outlined" variant="outlined"
color="primary" color="primary"
disabled={selectedUser.username === userdata.username} disabled={selectedUser.username === userdata.username}
@@ -843,7 +863,7 @@ const UserManagmentTab = memo((props) => {
{selectedUser.active ? "Delete from org" : "Delete from org"} {selectedUser.active ? "Delete from org" : "Delete from org"}
</Button> </Button>
<Button <Button
style={{ textTransform: 'none', fontSize: 16 }} style={{ textTransform: 'none', fontSize: 16, whiteSpace: 'nowrap', textWrap: 'nowarp', }}
variant="outlined" variant="outlined"
color="primary" color="primary"
disabled={ disabled={
@@ -864,7 +884,7 @@ const UserManagmentTab = memo((props) => {
} }
variant="outlined" variant="outlined"
color="primary" color="primary"
style={{textTransform: 'none', fontSize: 16}} style={{textTransform: 'none', fontSize: 16, whiteSpace: 'nowrap', textWrap: 'nowarp', }}
> >
{selectedUser.mfa_info !== undefined && {selectedUser.mfa_info !== undefined &&
selectedUser.mfa_info !== null && selectedUser.mfa_info !== null &&
@@ -872,6 +892,7 @@ const UserManagmentTab = memo((props) => {
? "Disable 2FA" ? "Disable 2FA"
: "Enable 2FA"} : "Enable 2FA"}
</Button> </Button>
</div>
{isCloud && userdata.support && selectedUser.id !== userdata.id ? ( {isCloud && userdata.support && selectedUser.id !== userdata.id ? (
<Button <Button
@@ -881,6 +902,8 @@ const UserManagmentTab = memo((props) => {
marginTop: 50, marginTop: 50,
border: "1px solid #d52b2b", border: "1px solid #d52b2b",
textTransform: "none", textTransform: "none",
whiteSpace: 'nowrap',
textWrap: 'nowarp',
color: color:
showDeleteAccountTextbox === true && showDeleteAccountTextbox === true &&
deleteAccountText?.length > 0 && deleteAccountText?.length > 0 &&
@@ -932,7 +955,7 @@ const UserManagmentTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: 50, height: 50,
color: "white", color: theme.palette.textFieldStyle.color,
}, },
}} }}
color="primary" color="primary"
@@ -1003,7 +1026,7 @@ const UserManagmentTab = memo((props) => {
InputProps={{ InputProps={{
style: { style: {
height: 50, height: 50,
color: "white", color: theme.palette.textFieldStyle.color,
fontSize: "1em", fontSize: "1em",
}, },
maxLength: 6, maxLength: 6,
@@ -1111,7 +1134,7 @@ const UserManagmentTab = memo((props) => {
}} }}
> >
<DialogTitle> <DialogTitle>
<span style={{ color: "white" }}>User Logs</span> <span style={{ color: theme.palette.text.primary }}>User Logs</span>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
{/* ask user for which IP they want to see logs for by iterating of user.login_info */} {/* ask user for which IP they want to see logs for by iterating of user.login_info */}
@@ -1253,23 +1276,23 @@ const UserManagmentTab = memo((props) => {
) : null ) : null
return ( return (
<div style={{ width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121',borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", }}> <div style={{ width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor, borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", }}>
{modalView} {modalView}
{editUserModal} {editUserModal}
{logview} {logview}
<div style={{ height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}> <div style={{ height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}> <div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin' }}>
<div style={{display: 'flex', justifyContent: 'space-between'}}> <div style={{display: 'flex', justifyContent: 'space-between'}}>
<div> <div>
<div style={{ marginBottom: 20 }}> <div style={{ marginBottom: 20 }}>
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#FFFFFF" }}>User Management</h2> <Typography variant="h5" style={{ marginBottom: 8, marginTop: 0, }}>User Management</Typography>
<span style={{ color: "#9E9E9E" }}> <Typography variant="body2" color="textSecondary">
Add, edit, distribute or remove users from your organization.{" "} Add, edit, distribute or remove users from your organization.{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="/admin?admin_tab=sso" href="/admin?admin_tab=sso"
style={{ color: "#FF8444" }} style={{ color: theme.palette.linkColor }}
> >
Configure SSO Configure SSO
</a> </a>
@@ -1280,15 +1303,15 @@ const UserManagmentTab = memo((props) => {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="/docs/organizations#user_management" href="/docs/organizations#user_management"
style={{ color: "#FF8444" }} style={{ color: theme.palette.linkColor }}
> >
learn more about users learn more about users
</a> </a>
</span> </Typography>
</div> </div>
<div /> <div />
<Button <Button
style={{ color: "#1a1a1a", backgroundColor: "#ff8544",fontSize: 16, textTransform: 'none', borderRadius: 4, width: 162, height: 40, boxShadow: 'none' }} style={{ fontSize: 16, textTransform: 'none', borderRadius: 4, width: 162, height: 40, boxShadow: 'none' }}
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
@@ -1296,9 +1319,9 @@ const UserManagmentTab = memo((props) => {
Add user Add user
</Button> </Button>
<Button <Button
style={{ backgroundColor: "#2F2F2F", boxShadow: 'none', borderRadius: 4, width: 81, height: 40, marginLeft: 16, marginRight: 15 }} style={{ boxShadow: 'none', borderRadius: 4, width: 81, height: 40, marginLeft: 16, marginRight: 15 }}
variant="contained" variant="contained"
color="primary" color="secondary"
onClick={() => getUsers()} onClick={() => getUsers()}
> >
<CachedIcon /> <CachedIcon />
@@ -1318,7 +1341,7 @@ const UserManagmentTab = memo((props) => {
style={{ style={{
borderRadius: 4, borderRadius: 4,
marginTop: 24, marginTop: 24,
border: "1px solid #494949", border: theme.palette.defaultBorder,
width: "100%", width: "100%",
overflowX: "auto", overflowX: "auto",
paddingBottom: 0, paddingBottom: 0,
@@ -1334,8 +1357,8 @@ const UserManagmentTab = memo((props) => {
paddingBottom: 0, paddingBottom: 0,
}} }}
> >
<ListItem style={{ width: "100%", padding: "10px 10px 10px 0px", verticalAlign: 'middle', borderBottom: "1px solid #494949", display: "table-row" }}> <ListItem style={{ width: "100%", padding: "10px 10px 10px 0px", verticalAlign: 'middle', borderBottom: theme.palette.defaultBorder, display: "table-row" }}>
{["Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => ( {[...(isCloud ? ["Region"] : []), "Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => (
<ListItemText <ListItemText
key={index} key={index}
primary={header} primary={header}
@@ -1344,7 +1367,7 @@ const UserManagmentTab = memo((props) => {
padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px", padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px",
whiteSpace: "nowrap", whiteSpace: "nowrap",
textOverflow: "ellipsis", textOverflow: "ellipsis",
borderBottom: "1px solid #494949", borderBottom: theme.palette.defaultBorder,
position: "sticky", position: "sticky",
verticalAlign: "middle", verticalAlign: "middle",
}} }}
@@ -1357,10 +1380,10 @@ const UserManagmentTab = memo((props) => {
key={rowIndex} key={rowIndex}
style={{ style={{
display: "table-row", display: "table-row",
backgroundColor: "#212121", backgroundColor: theme.palette.platformColor,
}} }}
> >
{Array(9) {Array(isCloud ? 7 : 6)
.fill() .fill()
.map((_, colIndex) => ( .map((_, colIndex) => (
<ListItemText <ListItemText
@@ -1374,7 +1397,7 @@ const UserManagmentTab = memo((props) => {
variant="text" variant="text"
animation="wave" animation="wave"
sx={{ sx={{
backgroundColor: "#1a1a1a", backgroundColor: theme.palette.loaderColor,
height: "20px", height: "20px",
borderRadius: "4px", borderRadius: "4px",
}} }}
@@ -1385,9 +1408,9 @@ const UserManagmentTab = memo((props) => {
)) ))
): users === 0 ? null ): users === 0 ? null
: users?.map((data, index) => { : users?.map((data, index) => {
var bgColor = "#212121"; var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF";
if (index % 2 === 0) { if (index % 2 === 0) {
bgColor = "#1A1A1A"; bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA";
} }
const timeNow = new Date().getTime(); const timeNow = new Date().getTime();
@@ -1418,8 +1441,7 @@ const UserManagmentTab = memo((props) => {
style={{ style={{
cursor: "pointer", cursor: "pointer",
textDecoration: "underline", textDecoration: "underline",
textDecorationColor: "#F76742", color: theme.palette.linkColor,
color: "#F76742",
}} }}
onClick={() => { onClick={() => {
setLogsViewModal(true); setLogsViewModal(true);
@@ -1436,8 +1458,16 @@ const UserManagmentTab = memo((props) => {
); );
} }
const userRegion = data?.user_geo_info?.country?.iso_code
return ( return (
<ListItem key={index} style={{ backgroundColor: bgColor, display: 'table-row', borderBottomLeftRadius: users?.length - 1 === index ? 8 : 0, borderBottomRightRadius: users?.length - 1 === index ? 8 : 0 }}> <ListItem key={index} style={{ backgroundColor: bgColor, display: 'table-row', borderBottomLeftRadius: users?.length - 1 === index ? 8 : 0, borderBottomRightRadius: users?.length - 1 === index ? 8 : 0 }}>
{isCloud ? (
<ListItemText
primary={(<img src={`https://flagcdn.com/48x36/${userRegion.toLowerCase()}.png`} alt={data?.user_geo_info?.country?.iso_code} style={{ marginRight: 30, width: 25, height: 23, }} />)}
style={{ display: 'table-cell', verticalAlign: 'middle', textAlign: 'center' }}
/>) : null}
<ListItemText <ListItemText
primary={( primary={(
<Tooltip title={data.username || 'No username available'}> <Tooltip title={data.username || 'No username available'}>
@@ -1449,7 +1479,7 @@ const UserManagmentTab = memo((props) => {
maxWidth: 150, maxWidth: 150,
minWidth: 100, minWidth: 100,
width: 'auto', width: 'auto',
color: "#FF8444", color: theme.palette.primary.main,
textOverflow: "ellipsis", textOverflow: "ellipsis",
whiteSpace: "nowrap", whiteSpace: "nowrap",
overflow: "hidden", overflow: "hidden",
@@ -1506,8 +1536,8 @@ const UserManagmentTab = memo((props) => {
setUser(data.id, "role", e.target.value); setUser(data.id, "role", e.target.value);
}} }}
sx={{ sx={{
backgroundColor: "#1A1A1A", backgroundColor: theme.palette.backgroundColor,
color: "white", color: theme.palette.textColor,
height: "50px", height: "50px",
borderRadius: "4px", borderRadius: "4px",
marginTop: "8px", marginTop: "8px",
@@ -1526,27 +1556,35 @@ const UserManagmentTab = memo((props) => {
> >
<MenuItem <MenuItem
sx={{ sx={{
backgroundColor: theme.palette.textFieldStyle.backgroundColor, backgroundColor: theme.palette.backgroundColor,
color: "white", color: theme.palette.textColor,
"&:hover": {
backgroundColor: theme.palette.hoverColor,
},
}} }}
value={"admin"} value={"admin"}
> >
Org Admin Org Admin
</MenuItem> </MenuItem>
<MenuItem <MenuItem
style={{ sx={{
backgroundColor: theme.palette.textFieldStyle.backgroundColor, backgroundColor: theme.palette.backgroundColor,
color: "white", color: theme.palette.textColor,
"&:hover": {
backgroundColor: theme.palette.hoverColor,
},
}} }}
value={"user"} value={"user"}
> >
Org User Org User
</MenuItem> </MenuItem>
<MenuItem <MenuItem
style={{ sx={{
backgroundColor: theme.palette.textFieldStyle.backgroundColor, backgroundColor: theme.palette.backgroundColor,
color: "white", color: theme.palette.textColor,
"&:hover": {
backgroundColor: theme.palette.hoverColor,
},
}} }}
value={"org-reader"} value={"org-reader"}
> >
@@ -1557,10 +1595,12 @@ const UserManagmentTab = memo((props) => {
style={{ display:'table-cell', verticalAlign: 'middle' }} style={{ display:'table-cell', verticalAlign: 'middle' }}
/> />
{/*
<ListItemText <ListItemText
primary={data.active ? "True" : "False"} primary={data.active ? "True" : "False"}
style={{display:'table-cell',verticalAlign: 'middle' , padding: "8px", textAlign: "center", color: data.active ? "#02CB70" : "#F53434" }} style={{display:'table-cell',verticalAlign: 'middle' , padding: "8px", textAlign: "center", color: data.active ? "#02CB70" : "#F53434" }}
/> />
*/}
<ListItemText <ListItemText
primary={ primary={
@@ -1573,7 +1613,6 @@ const UserManagmentTab = memo((props) => {
style={{ display:'table-cell',verticalAlign: 'middle', padding: "8px", }} style={{ display:'table-cell',verticalAlign: 'middle', padding: "8px", }}
/> />
{/*
<ListItemText <ListItemText
primary={ primary={
data?.mfa_info !== undefined && data?.mfa_info !== undefined &&
@@ -1584,7 +1623,6 @@ const UserManagmentTab = memo((props) => {
} }
style={{ display:'table-cell', verticalAlign: 'middle',padding: "8px", color: data.mfa_info.active ? "#02CB70" : "#F53434" }} style={{ display:'table-cell', verticalAlign: 'middle',padding: "8px", color: data.mfa_info.active ? "#02CB70" : "#F53434" }}
/> />
*/}
{selectedOrganization?.child_orgs !== undefined && {selectedOrganization?.child_orgs !== undefined &&
selectedOrganization?.child_orgs !== null && selectedOrganization?.child_orgs !== null &&
@@ -1648,7 +1686,20 @@ const UserManagmentTab = memo((props) => {
} }
}} }}
> >
<img src="/icons/editIcon.svg" alt="edit icon" style={{width: 24, height: 24}} /> <svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.1038 4.66848C16.3158 4.45654 16.5674 4.28843 16.8443 4.17373C17.1212 4.05903 17.418 4 17.7177 4C18.0174 4 18.3142 4.05903 18.5911 4.17373C18.868 4.28843 19.1196 4.45654 19.3315 4.66848C19.5435 4.88041 19.7116 5.13201 19.8263 5.40891C19.941 5.68582 20 5.9826 20 6.28232C20 6.58204 19.941 6.87882 19.8263 7.15573C19.7116 7.43263 19.5435 7.68423 19.3315 7.89617L8.43807 18.7896L4 20L5.21038 15.5619L16.1038 4.66848Z"
stroke={themeMode=== "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</IconButton> </IconButton>
{/* <Button {/* <Button
onClick={() => { onClick={() => {

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