@@ -0,0 +1,82 @@
|
||||
name: nightly-dockerbuild
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- nightly
|
||||
paths:
|
||||
- "**"
|
||||
- "!.github/**"
|
||||
- "!**.md"
|
||||
- "!docker-compose.yml"
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- app: frontend
|
||||
path: frontend
|
||||
version: nightly
|
||||
experimental: true
|
||||
- app: backend
|
||||
path: backend
|
||||
version: nightly
|
||||
experimental: true
|
||||
- app: orborus
|
||||
path: functions/onprem/orborus
|
||||
version: nightly
|
||||
experimental: true
|
||||
- app: worker
|
||||
path: functions/onprem/worker
|
||||
version: nightly
|
||||
experimental: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: "amd64,arm64,arm"
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to Ghcr
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Ghcr Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v4
|
||||
env:
|
||||
BUILDX_NO_DEFAULT_LOAD: true
|
||||
with:
|
||||
logout: false
|
||||
context: ${{ matrix.path }}/
|
||||
file: ${{ matrix.path }}/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
tags: |
|
||||
ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ matrix.version }}
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ matrix.version }}
|
||||
frikky/shuffle-${{ matrix.app }}:${{ matrix.version }}
|
||||
frikky/shuffle:${{ matrix.app }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
@@ -33,16 +33,21 @@ jobs:
|
||||
sudo apt-get install helm -y --no-install-recommends
|
||||
|
||||
- name: Set versions
|
||||
id: set_versions
|
||||
run: |
|
||||
if [[ ${{ github.event_name }} == 'release' ]]; then
|
||||
CHART_VERSION="${{ github.event.release.tag_name }}"
|
||||
APP_VERSION="${{ github.event.release.tag_name }}"
|
||||
TAG_NAME="${{ github.event.release.tag_name }}"
|
||||
|
||||
# Remove the v prefix
|
||||
VERSION=${TAG_NAME#v}
|
||||
|
||||
APP_VERSION="${VERSION}"
|
||||
CHART_VERSION="${VERSION}"
|
||||
else
|
||||
CHART_VERSION="0.0.0-nightly-untagged-latest"
|
||||
APP_VERSION="nightly"
|
||||
CHART_VERSION="0.0.0-nightly-untagged-latest"
|
||||
fi
|
||||
|
||||
echo "APP_VERSION set to ${APP_VERSION}"
|
||||
echo "CHART_VERSION set to ${CHART_VERSION}. Validating..."
|
||||
|
||||
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
||||
@@ -55,8 +60,8 @@ jobs:
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
echo "CHART_VERSION=${CHART_VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "CHART_VERSION=${CHART_VERSION}" >> "$GITHUB_ENV"
|
||||
echo "APP_VERSION=${APP_VERSION}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Update helm dependencies
|
||||
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 }}
|
||||
|
||||
- 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
|
||||
|
||||
+56
-20
@@ -604,11 +604,27 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
||||
// return
|
||||
//}
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`))
|
||||
return
|
||||
var err error
|
||||
body := []byte{}
|
||||
//log.Printf("IMAGE REQUEST BODY: %#v", request.Body)
|
||||
if request.Body == nil || request.Body == http.NoBody {
|
||||
// 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
|
||||
@@ -630,28 +646,48 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
||||
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()
|
||||
images, err := dockercli.ImageList(ctx, image.ListOptions{
|
||||
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 _, tag := range image.RepoTags {
|
||||
//log.Printf("[DEBUG] Tag: %s", tag)
|
||||
if strings.Contains(tag, "<none>") {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.ToLower(tag) == strings.ToLower(version.Name) {
|
||||
img = image
|
||||
tagFound = tag
|
||||
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shuffle/shuffle-shared v0.8.18
|
||||
github.com/shuffle/shuffle-shared v0.8.35
|
||||
golang.org/x/crypto v0.36.0
|
||||
google.golang.org/api v0.176.1
|
||||
google.golang.org/grpc v1.68.1
|
||||
|
||||
@@ -333,8 +333,8 @@ github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fc
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shuffle/shuffle-shared v0.8.18 h1:7f7cV+P2pr/g44i+AI8P0UheEe8oG8O2V8loBfe9YSw=
|
||||
github.com/shuffle/shuffle-shared v0.8.18/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI=
|
||||
github.com/shuffle/shuffle-shared v0.8.35 h1:3awc0TrsLLZiQeWD2XGIkTnFbczAG0cMfy1+cB/P7zg=
|
||||
github.com/shuffle/shuffle-shared v0.8.35/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI=
|
||||
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=
|
||||
|
||||
+18
-5
@@ -3543,7 +3543,12 @@ func handleCloudJob(job shuffle.CloudSyncJob) error {
|
||||
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)
|
||||
outlookClient, _, err := shuffle.GetOutlookClient(ctx, "", hook.OauthToken, redirectUrl)
|
||||
if err != nil {
|
||||
@@ -4197,20 +4202,26 @@ func runInitEs(ctx context.Context) {
|
||||
|
||||
cleanupJob := func() 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 {
|
||||
// Allowed without PROXY management as it's localhost
|
||||
// client := shuffle.GetExternalClient(syncUrl)
|
||||
|
||||
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(
|
||||
"GET",
|
||||
url,
|
||||
nil,
|
||||
)
|
||||
|
||||
// FIXME: This will stop working of the user rotates their key lol
|
||||
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, parsedApikey))
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed CREATING environment request for %s: %s", environment, err)
|
||||
@@ -4231,7 +4242,7 @@ func runInitEs(ctx context.Context) {
|
||||
}
|
||||
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)
|
||||
url = fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/rerun", backendPort, environment)
|
||||
req, err = http.NewRequest(
|
||||
"GET",
|
||||
url,
|
||||
@@ -4256,7 +4267,8 @@ func runInitEs(ctx context.Context) {
|
||||
log.Printf("[ERROR] Failed setting respbody %s", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[DEBUG] Successfully ran workflow RERUN request for %s. Body: %s", environment, string(respBody))
|
||||
|
||||
//log.Printf("[DEBUG] Ran workflow RERUN request for %s with the response. Body: %s", environment, string(respBody))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5373,6 +5385,7 @@ func main() {
|
||||
if innerPort == "" {
|
||||
log.Printf("[DEBUG] Running on %s:5001", hostname)
|
||||
log.Fatal(http.ListenAndServe(":5001", nil))
|
||||
os.Setenv("BACKEND_PORT", "5001")
|
||||
} else {
|
||||
log.Printf("[DEBUG] Running on %s:%s", hostname, innerPort)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", innerPort), nil))
|
||||
|
||||
+16
-517
@@ -22,9 +22,6 @@ import (
|
||||
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
|
||||
//gyaml "github.com/ghodss/yaml"
|
||||
|
||||
"github.com/h2non/filetype"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
@@ -36,14 +33,6 @@ import (
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
http2 "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
//http2 "gopkg.in/src-d/go-git.v5/plumbing/transport/http"
|
||||
//http2 "github.com/go-git/go-git/plumbing/transport/http"
|
||||
|
||||
//"github.com/gorilla/websocket"
|
||||
//"google.golang.org/appengine"
|
||||
//"google.golang.org/appengine/memcache"
|
||||
//"cloud.google.com/go/firestore"
|
||||
// "google.golang.org/api/option"
|
||||
gyaml "github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
@@ -208,7 +197,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
|
||||
}
|
||||
|
||||
// remove items from DB
|
||||
parsedId := fmt.Sprintf("workflowqueue-%s", id)
|
||||
parsedId := strings.ReplaceAll(fmt.Sprintf("workflowqueue-%s", id), " ", "-")
|
||||
ids := []string{}
|
||||
for _, execution := range removeExecutionRequests.Data {
|
||||
ids = append(ids, execution.ExecutionId)
|
||||
@@ -985,7 +974,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to list child workflows: %s", err)
|
||||
} else {
|
||||
log.Printf("\n\n[DEBUG] Found %d child workflows for workflow %s\n\n", len(childWorkflows), workflow.ID)
|
||||
//log.Printf("\n\n[DEBUG] Found %d child workflows for workflow %s\n\n", len(childWorkflows), workflow.ID)
|
||||
|
||||
// Find cookies and append them to request.Header to replicate current request as closely as possible
|
||||
for _, childWorkflow := range childWorkflows {
|
||||
@@ -1141,507 +1130,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
||||
return shuffle.WorkflowExecution{}, "Failed unmarshal during execution", err
|
||||
}
|
||||
|
||||
/*
|
||||
makeNew := true
|
||||
start, startok := request.URL.Query()["start"]
|
||||
if request.Method == "POST" {
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed request POST read: %s", err)
|
||||
return shuffle.WorkflowExecution{}, "Failed getting body", err
|
||||
}
|
||||
|
||||
// This one doesn't really matter.
|
||||
log.Printf("[INFO] Running POST execution with body of length %d for workflow %s", len(string(body)), workflowExecution.Workflow.ID)
|
||||
|
||||
if len(body) >= 4 {
|
||||
if body[0] == 34 && body[len(body)-1] == 34 {
|
||||
body = body[1 : len(body)-1]
|
||||
}
|
||||
if body[0] == 34 && body[len(body)-1] == 34 {
|
||||
body = body[1 : len(body)-1]
|
||||
}
|
||||
}
|
||||
|
||||
sourceAuth, sourceAuthOk := request.URL.Query()["source_auth"]
|
||||
if sourceAuthOk {
|
||||
//log.Printf("\n\n\nSETTING SOURCE WORKFLOW AUTH TO %s!!!\n\n\n", sourceAuth[0])
|
||||
workflowExecution.ExecutionSourceAuth = sourceAuth[0]
|
||||
} else {
|
||||
//log.Printf("Did NOT get source workflow")
|
||||
}
|
||||
|
||||
sourceNode, sourceNodeOk := request.URL.Query()["source_node"]
|
||||
if sourceNodeOk {
|
||||
//log.Printf("\n\n\nSETTING SOURCE WORKFLOW NODE TO %s!!!\n\n\n", sourceNode[0])
|
||||
workflowExecution.ExecutionSourceNode = sourceNode[0]
|
||||
} else {
|
||||
//log.Printf("Did NOT get source workflow")
|
||||
}
|
||||
|
||||
//workflowExecution.ExecutionSource = "default"
|
||||
sourceWorkflow, sourceWorkflowOk := request.URL.Query()["source_workflow"]
|
||||
if sourceWorkflowOk {
|
||||
//log.Printf("Got source workflow %s", sourceWorkflow)
|
||||
workflowExecution.ExecutionSource = sourceWorkflow[0]
|
||||
} else {
|
||||
//log.Printf("Did NOT get source workflow")
|
||||
}
|
||||
|
||||
sourceExecution, sourceExecutionOk := request.URL.Query()["source_execution"]
|
||||
if sourceExecutionOk {
|
||||
//log.Printf("[INFO] Got source execution%s", sourceExecution)
|
||||
workflowExecution.ExecutionParent = sourceExecution[0]
|
||||
} else {
|
||||
//log.Printf("Did NOT get source execution")
|
||||
}
|
||||
|
||||
if len(string(body)) < 50 {
|
||||
//log.Println(body)
|
||||
// String in string
|
||||
//log.Println(body)
|
||||
|
||||
//if string(body)[0] == "\"" && string(body)[string(body)
|
||||
log.Printf("[DEBUG] Body: %s", string(body))
|
||||
}
|
||||
|
||||
var execution shuffle.ExecutionRequest
|
||||
err = json.Unmarshal(body, &execution)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed execution POST unmarshalling for execution %s - continuing anyway: %s", execution.ExecutionId, err)
|
||||
//return shuffle.WorkflowExecution{}, "", err
|
||||
}
|
||||
|
||||
if execution.Start == "" && len(body) > 0 {
|
||||
execution.ExecutionArgument = string(body)
|
||||
}
|
||||
|
||||
// FIXME - this should have "execution_argument" from executeWorkflow frontend
|
||||
//log.Printf("EXEC: %#v", execution)
|
||||
if len(execution.ExecutionArgument) > 0 {
|
||||
workflowExecution.ExecutionArgument = execution.ExecutionArgument
|
||||
}
|
||||
|
||||
if len(execution.ExecutionSource) > 0 {
|
||||
workflowExecution.ExecutionSource = execution.ExecutionSource
|
||||
}
|
||||
|
||||
//log.Printf("Execution data: %#v", execution)
|
||||
if len(execution.Start) == 36 && len(workflow.Actions) > 0 {
|
||||
log.Printf("[INFO] Should start execution on node %s", execution.Start)
|
||||
workflowExecution.Start = execution.Start
|
||||
|
||||
found := false
|
||||
for _, action := range workflow.Actions {
|
||||
if action.ID == execution.Start {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.Printf("[ERROR] Action %s was NOT found! Exiting execution.", execution.Start)
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", workflow.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", workflow.Start))
|
||||
}
|
||||
} else if len(execution.Start) > 0 {
|
||||
//log.Printf("[INFO] !")
|
||||
//log.Printf("[ERROR] START ACTION %s IS WRONG ID LENGTH %d!", execution.Start, len(execution.Start))
|
||||
//return shuffle.WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", execution.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", execution.Start))
|
||||
}
|
||||
|
||||
if len(execution.ExecutionId) == 36 {
|
||||
workflowExecution.ExecutionId = execution.ExecutionId
|
||||
} else {
|
||||
sessionToken := uuid.NewV4()
|
||||
workflowExecution.ExecutionId = sessionToken.String()
|
||||
}
|
||||
} else {
|
||||
// Check for parameters of start and ExecutionId
|
||||
// This is mostly used for user input trigger
|
||||
|
||||
answer, answerok := request.URL.Query()["answer"]
|
||||
referenceId, referenceok := request.URL.Query()["reference_execution"]
|
||||
if answerok && referenceok && len(answer) > 0 && len(referenceId) > 0 {
|
||||
// If answer is false, reference execution with result
|
||||
log.Printf("[INFO] Answer is OK AND reference is OK!")
|
||||
if answer[0] == "false" {
|
||||
log.Printf("Should update reference and return, no need for further execution!")
|
||||
|
||||
// Get the reference execution
|
||||
oldExecution, err := shuffle.GetWorkflowExecution(ctx, referenceId[0])
|
||||
if err != nil {
|
||||
log.Printf("Failed getting execution (execution) %s: %s", referenceId[0], err)
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed getting execution ID %s because it doesn't exist.", referenceId[0]), err
|
||||
}
|
||||
|
||||
if oldExecution.Workflow.ID != id {
|
||||
log.Println("Wrong workflowid!")
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Bad ID %s", referenceId), errors.New("Bad ID")
|
||||
}
|
||||
|
||||
newResults := []shuffle.ActionResult{}
|
||||
//log.Printf("%#v", oldExecution.Results)
|
||||
for _, result := range oldExecution.Results {
|
||||
log.Printf("%s - %s", result.Action.ID, start[0])
|
||||
if result.Action.ID == start[0] {
|
||||
note, noteok := request.URL.Query()["note"]
|
||||
if noteok && len(note) > 0 {
|
||||
result.Result = fmt.Sprintf("User note: %s", note[0])
|
||||
} else {
|
||||
result.Result = fmt.Sprintf("User clicked %s", answer[0])
|
||||
}
|
||||
|
||||
// Stopping the whole thing
|
||||
result.CompletedAt = int64(time.Now().Unix())
|
||||
result.Status = "ABORTED"
|
||||
oldExecution.Status = result.Status
|
||||
oldExecution.Result = result.Result
|
||||
oldExecution.LastNode = result.Action.ID
|
||||
}
|
||||
|
||||
newResults = append(newResults, result)
|
||||
}
|
||||
|
||||
oldExecution.Results = newResults
|
||||
err = shuffle.SetWorkflowExecution(ctx, *oldExecution, true)
|
||||
if err != nil {
|
||||
log.Printf("Error saving workflow execution actionresult setting: %s", err)
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed setting workflowexecution actionresult in execution: %s", err), err
|
||||
}
|
||||
|
||||
return shuffle.WorkflowExecution{}, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
if referenceok {
|
||||
log.Printf("Handling an old execution continuation!")
|
||||
// Will use the old name, but still continue with NEW ID
|
||||
oldExecution, err := shuffle.GetWorkflowExecution(ctx, referenceId[0])
|
||||
if err != nil {
|
||||
log.Printf("Failed getting execution (execution) %s: %s", referenceId[0], err)
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed getting execution ID %s because it doesn't exist.", referenceId[0]), err
|
||||
}
|
||||
|
||||
workflowExecution = *oldExecution
|
||||
}
|
||||
|
||||
if len(workflowExecution.ExecutionId) == 0 {
|
||||
sessionToken := uuid.NewV4()
|
||||
workflowExecution.ExecutionId = sessionToken.String()
|
||||
} else {
|
||||
log.Printf("Using the same executionId as before: %s", workflowExecution.ExecutionId)
|
||||
makeNew = false
|
||||
}
|
||||
|
||||
// Don't override workflow defaults
|
||||
}
|
||||
|
||||
if startok {
|
||||
//log.Printf("\n\n[INFO] Setting start to %s based on query!\n\n", start[0])
|
||||
//workflowExecution.Workflow.Start = start[0]
|
||||
workflowExecution.Start = start[0]
|
||||
}
|
||||
|
||||
// FIXME - regex uuid, and check if already exists?
|
||||
if len(workflowExecution.ExecutionId) != 36 {
|
||||
log.Printf("Invalid uuid: %s", workflowExecution.ExecutionId)
|
||||
return shuffle.WorkflowExecution{}, "Invalid uuid", err
|
||||
}
|
||||
|
||||
// FIXME - find owner of workflow
|
||||
// FIXME - get the actual workflow itself and build the request
|
||||
// MAYBE: Don't send the workflow within the pubsub, as this requires more data to be sent
|
||||
// Check if a worker already exists for company, else run one with:
|
||||
// locations, project IDs and subscription names
|
||||
|
||||
// When app is executed:
|
||||
// Should update with status execution (somewhere), which will trigger the next node
|
||||
// IF action.type == internal, we need the internal watcher to be running and executing
|
||||
// This essentially means the WORKER has to be the responsible party for new actions in the INTERNAL landscape
|
||||
// Results are ALWAYS posted back to cloud@execution_id?
|
||||
if makeNew {
|
||||
workflowExecution.Type = "workflow"
|
||||
//workflowExecution.Stream = "tmp"
|
||||
//workflowExecution.WorkflowQueue = "tmp"
|
||||
//workflowExecution.SubscriptionNameNodestream = "testcompany-nodestream"
|
||||
//workflowExecution.Locations = []string{"europe-west2"}
|
||||
workflowExecution.ProjectId = gceProject
|
||||
workflowExecution.WorkflowId = workflow.ID
|
||||
workflowExecution.StartedAt = int64(time.Now().Unix())
|
||||
workflowExecution.CompletedAt = 0
|
||||
workflowExecution.Authorization = uuid.NewV4().String()
|
||||
|
||||
// Status for the entire workflow.
|
||||
workflowExecution.Status = "EXECUTING"
|
||||
}
|
||||
|
||||
if len(workflowExecution.ExecutionSource) == 0 {
|
||||
log.Printf("[INFO] No execution source (trigger) specified. Setting to default")
|
||||
workflowExecution.ExecutionSource = "default"
|
||||
} else {
|
||||
log.Printf("[INFO] Execution source is %s for execution ID %s in workflow %s", workflowExecution.ExecutionSource, workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
||||
}
|
||||
|
||||
workflowExecution.ExecutionVariables = workflow.ExecutionVariables
|
||||
if len(workflowExecution.Start) == 0 && len(workflowExecution.Workflow.Start) > 0 {
|
||||
workflowExecution.Start = workflowExecution.Workflow.Start
|
||||
}
|
||||
|
||||
startnodeFound := false
|
||||
newStartnode := ""
|
||||
for _, item := range workflowExecution.Workflow.Actions {
|
||||
if item.ID == workflowExecution.Start {
|
||||
startnodeFound = true
|
||||
}
|
||||
|
||||
if item.IsStartNode {
|
||||
newStartnode = item.ID
|
||||
}
|
||||
}
|
||||
|
||||
if !startnodeFound {
|
||||
log.Printf("[INFO] Couldn't find startnode %s. Remapping to %#v", workflowExecution.Start, newStartnode)
|
||||
|
||||
if len(newStartnode) > 0 {
|
||||
workflowExecution.Start = newStartnode
|
||||
} else {
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Startnode couldn't be found"), errors.New("Startnode isn't defined in this workflow..")
|
||||
}
|
||||
}
|
||||
|
||||
childNodes := shuffle.FindChildNodes(workflowExecution.Workflow, workflowExecution.Start, []string{}, []string{})
|
||||
|
||||
startFound := false
|
||||
newActions := []shuffle.Action{}
|
||||
defaultResults := []shuffle.ActionResult{}
|
||||
|
||||
for _, action := range workflowExecution.Workflow.Actions {
|
||||
//action.LargeImage = ""
|
||||
if action.ID == workflowExecution.Start {
|
||||
startFound = true
|
||||
}
|
||||
//log.Println(action.Environment)
|
||||
|
||||
if action.Environment == "" {
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Environment is not defined for %s", action.Name), errors.New("Environment not defined!")
|
||||
}
|
||||
|
||||
action.LargeImage = ""
|
||||
if len(action.Label) == 0 {
|
||||
action.Label = action.ID
|
||||
}
|
||||
//log.Printf("LABEL: %s", action.Label)
|
||||
newActions = append(newActions, action)
|
||||
|
||||
// If the node is NOT found, it's supposed to be set to SKIPPED,
|
||||
// as it's not a childnode of the startnode
|
||||
// This is a configuration item for the workflow itself.
|
||||
if len(workflowExecution.Results) > 0 {
|
||||
defaultResults = []shuffle.ActionResult{}
|
||||
for _, result := range workflowExecution.Results {
|
||||
if result.Status == "WAITING" {
|
||||
result.Status = "FINISHED"
|
||||
result.Result = "Continuing"
|
||||
}
|
||||
|
||||
defaultResults = append(defaultResults, result)
|
||||
}
|
||||
} else if len(workflowExecution.Results) == 0 && !workflowExecution.Workflow.Configuration.StartFromTop {
|
||||
found := false
|
||||
for _, nodeId := range childNodes {
|
||||
if nodeId == action.ID {
|
||||
//log.Printf("Found %s", action.ID)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
if action.ID == workflowExecution.Start {
|
||||
continue
|
||||
}
|
||||
|
||||
//log.Printf("[WARNING] Set %s to SKIPPED as it's NOT a childnode of the startnode.", action.ID)
|
||||
curaction := shuffle.Action{
|
||||
AppName: action.AppName,
|
||||
AppVersion: action.AppVersion,
|
||||
Label: action.Label,
|
||||
Name: action.Name,
|
||||
ID: action.ID,
|
||||
}
|
||||
//action
|
||||
//curaction.Parameters = []
|
||||
defaultResults = append(defaultResults, shuffle.ActionResult{
|
||||
Action: curaction,
|
||||
ExecutionId: workflowExecution.ExecutionId,
|
||||
Authorization: workflowExecution.Authorization,
|
||||
Result: "Skipped because it's not under the startnode",
|
||||
StartedAt: 0,
|
||||
CompletedAt: 0,
|
||||
Status: "SKIPPED",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeTriggers := []string{}
|
||||
for triggerIndex, trigger := range workflowExecution.Workflow.Triggers {
|
||||
//log.Printf("[INFO] ID: %s vs %s", trigger.ID, workflowExecution.Start)
|
||||
if trigger.ID == workflowExecution.Start {
|
||||
if trigger.AppName == "User Input" {
|
||||
startFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if trigger.AppName == "User Input" || trigger.AppName == "Shuffle Workflow" {
|
||||
found := false
|
||||
for _, node := range childNodes {
|
||||
if node == trigger.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
//log.Printf("SHOULD SET TRIGGER %s TO BE SKIPPED", trigger.ID)
|
||||
|
||||
curaction := shuffle.Action{
|
||||
AppName: "shuffle-subflow",
|
||||
AppVersion: trigger.AppVersion,
|
||||
Label: trigger.Label,
|
||||
Name: trigger.Name,
|
||||
ID: trigger.ID,
|
||||
}
|
||||
|
||||
defaultResults = append(defaultResults, shuffle.ActionResult{
|
||||
Action: curaction,
|
||||
ExecutionId: workflowExecution.ExecutionId,
|
||||
Authorization: workflowExecution.Authorization,
|
||||
Result: "Skipped because it's not under the startnode",
|
||||
StartedAt: 0,
|
||||
CompletedAt: 0,
|
||||
Status: "SKIPPED",
|
||||
})
|
||||
} else {
|
||||
// Replaces trigger with the subflow
|
||||
//if trigger.AppName == "Shuffle Workflow" {
|
||||
// replaceActions := false
|
||||
// workflowAction := ""
|
||||
// for _, param := range trigger.Parameters {
|
||||
// if param.Name == "argument" && !strings.Contains(param.Value, ".#") {
|
||||
// replaceActions = true
|
||||
// }
|
||||
|
||||
// if param.Name == "startnode" {
|
||||
// workflowAction = param.Value
|
||||
// }
|
||||
// }
|
||||
|
||||
// if replaceActions {
|
||||
// replacementNodes, newBranches, lastnode := shuffle.GetReplacementNodes(ctx, workflowExecution, trigger, trigger.Label)
|
||||
// log.Printf("REPLACEMENTS: %d, %d", len(replacementNodes), len(newBranches))
|
||||
// if len(replacementNodes) > 0 {
|
||||
// for _, action := range replacementNodes {
|
||||
// found := false
|
||||
|
||||
// for subActionIndex, subaction := range newActions {
|
||||
// if subaction.ID == action.ID {
|
||||
// found = true
|
||||
// //newActions[subActionIndex].Name = action.Name
|
||||
// newActions[subActionIndex].Label = action.Label
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// if !found {
|
||||
// action.SubAction = true
|
||||
// newActions = append(newActions, action)
|
||||
// }
|
||||
|
||||
// // Check if it's already set to have a value
|
||||
// for resultIndex, result := range defaultResults {
|
||||
// if result.Action.ID == action.ID {
|
||||
// defaultResults = append(defaultResults[:resultIndex], defaultResults[resultIndex+1:]...)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// for _, branch := range newBranches {
|
||||
// workflowExecution.Workflow.Branches = append(workflowExecution.Workflow.Branches, branch)
|
||||
// }
|
||||
|
||||
// // Append branches:
|
||||
// // parent -> new inner node (FIRST one)
|
||||
// for branchIndex, branch := range workflowExecution.Workflow.Branches {
|
||||
// if branch.DestinationID == trigger.ID {
|
||||
// log.Printf("REPLACE DESTINATION WITH %s!!", workflowAction)
|
||||
// workflowExecution.Workflow.Branches[branchIndex].DestinationID = workflowAction
|
||||
// }
|
||||
|
||||
// if branch.SourceID == trigger.ID {
|
||||
// log.Printf("REPLACE SOURCE WITH LASTNODE %s!!", lastnode)
|
||||
// workflowExecution.Workflow.Branches[branchIndex].SourceID = lastnode
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Remove the trigger
|
||||
// removeTriggers = append(removeTriggers, workflowExecution.Workflow.Triggers[triggerIndex].ID)
|
||||
// }
|
||||
|
||||
// log.Printf("NEW ACTION LENGTH %d, RESULT: %d, Triggers: %d, BRANCHES: %d", len(newActions), len(defaultResults), len(workflowExecution.Workflow.Triggers), len(workflowExecution.Workflow.Branches))
|
||||
// }
|
||||
//}
|
||||
_ = triggerIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//newTriggers := []shuffle.Trigger{}
|
||||
//for _, trigger := range workflowExecution.Workflow.Triggers {
|
||||
// found := false
|
||||
// for _, triggerId := range removeTriggers {
|
||||
// if trigger.ID == triggerId {
|
||||
// found = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// if found {
|
||||
// log.Printf("[WARNING] Removed trigger %s during execution", trigger.ID)
|
||||
// continue
|
||||
// }
|
||||
|
||||
// newTriggers = append(newTriggers, trigger)
|
||||
//}
|
||||
//workflowExecution.Workflow.Triggers = newTriggers
|
||||
|
||||
if !startFound {
|
||||
if len(workflowExecution.Start) == 0 && len(workflowExecution.Workflow.Start) > 0 {
|
||||
workflowExecution.Start = workflow.Start
|
||||
} else if len(workflowExecution.Workflow.Actions) > 0 {
|
||||
workflowExecution.Start = workflowExecution.Workflow.Actions[0].ID
|
||||
} else {
|
||||
log.Printf("[ERROR] Startnode %s doesn't exist!!", workflowExecution.Start)
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow action %s doesn't exist in workflow", workflowExecution.Start), errors.New(fmt.Sprintf(`Workflow start node "%s" doesn't exist. Exiting!`, workflowExecution.Start))
|
||||
}
|
||||
}
|
||||
|
||||
//log.Printf("EXECUTION START: %s", workflowExecution.Start)
|
||||
|
||||
// Verification for execution environments
|
||||
workflowExecution.Results = defaultResults
|
||||
workflowExecution.Workflow.Actions = newActions
|
||||
onpremExecution := true
|
||||
_ = onpremExecution
|
||||
environments := []string{}
|
||||
|
||||
if len(workflowExecution.ExecutionOrg) == 0 && len(workflow.ExecutingOrg.Id) > 0 {
|
||||
workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id
|
||||
}
|
||||
*/
|
||||
|
||||
//workflowExecution, execInfo, _, workflowExecErr := shuffle.PrepareWorkflowExecution(ctx, workflow, request, int64(maxExecutionDepth))
|
||||
err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err)
|
||||
@@ -1652,7 +1140,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
||||
environments := execInfo.Environments
|
||||
var allEnvs []shuffle.Environment
|
||||
if len(workflowExecution.ExecutionOrg) > 0 {
|
||||
//log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg)
|
||||
|
||||
allEnvironments, err := shuffle.GetEnvironments(ctx, workflowExecution.ExecutionOrg)
|
||||
if err != nil {
|
||||
@@ -3557,9 +3044,21 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
log.Printf("[INFO] Starting validation of execution %s", workflowExecution.ExecutionId)
|
||||
shouldRerun := false
|
||||
rerun, rerunOk := query["rerun"]
|
||||
if rerunOk && len(rerun) > 0 && rerun[0] == "true" {
|
||||
shouldRerun = true
|
||||
}
|
||||
|
||||
if shouldRerun {
|
||||
log.Printf("[DEBUG] Returning single action execution ID for rerun: %s", workflowExecution.ExecutionId)
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization)))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Starting validation of execution %s", workflowExecution.ExecutionId)
|
||||
time.Sleep(2 * time.Second)
|
||||
returnBody := shuffle.HandleRetValidation(ctx, workflowExecution, 1)
|
||||
returnBytes, err := json.Marshal(returnBody)
|
||||
if err != nil {
|
||||
|
||||
@@ -33,6 +33,9 @@ const AdminNavBar = (props) => {
|
||||
|
||||
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(() => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const tabName = queryParams.get('tab');
|
||||
|
||||
@@ -891,7 +891,7 @@ const Billing = memo((props) => {
|
||||
</span>
|
||||
: null}
|
||||
|
||||
{showSupport ?
|
||||
{/* {showSupport ?
|
||||
<Button variant="outlined" color="primary" style={{ marginTop: 20, marginBottom: 10, }} onClick={() => {
|
||||
if (window.drift !== undefined) {
|
||||
//window.drift.api.startInteraction({ interactionId: 340045 })
|
||||
@@ -902,7 +902,7 @@ const Billing = memo((props) => {
|
||||
}}>
|
||||
Get Support
|
||||
</Button>
|
||||
: null}
|
||||
: null} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2021,7 +2021,7 @@ const Billing = memo((props) => {
|
||||
|
||||
<div style={{ display: "flex", width: clickedFromOrgTab ? "100%" : "auto", overflowX: 'auto', overflowY: 'hidden', scrollbarWidth: 'thin', scrollbarColor: '#494949 #2f2f2f', height: isChildOrg ? 0 : "100%", marginTop: 20}} >
|
||||
<div style={{ display: "flex", flexDirection: "column", width: "100%", }}>
|
||||
{isCloud &&
|
||||
{/* {isCloud &&
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
selectedOrganization.subscriptions !== null &&
|
||||
selectedOrganization.subscriptions.length > 0 &&
|
||||
@@ -2043,7 +2043,7 @@ const Billing = memo((props) => {
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
: null} */}
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "row", width: "100%", marginTop: 20, marginBottom: 20, maxWidth: 860, }}>
|
||||
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null :
|
||||
|
||||
@@ -151,13 +151,16 @@ const CacheView = memo((props) => {
|
||||
if (fileCategories.length === 1 && fileCategories[0] === "default") {
|
||||
var newcategories = ["default"]
|
||||
for (var key in responseJson.keys) {
|
||||
if (responseJson.keys[key].category !== undefined && responseJson.keys[key].category !== null && responseJson.keys[key].category !== "" && !fileCategories.includes(responseJson.keys[key].category)) {
|
||||
newcategories.push(responseJson.keys[key].category);
|
||||
var category = responseJson.keys[key].category
|
||||
if (category !== undefined && category !== null && category !== ""){
|
||||
category = category.replaceAll(" ", "_")
|
||||
|
||||
if (!newcategories.includes(category)) {
|
||||
newcategories.push(category)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("CATEGORIES: ", newcategories)
|
||||
|
||||
setFileCategories(newcategories)
|
||||
}
|
||||
}
|
||||
@@ -357,6 +360,7 @@ const CacheView = memo((props) => {
|
||||
<span style={{ color: "white" }}>
|
||||
{ editCache ? "Edit Key" : "Add Key"}{selectedCategory === "" || selectedCategory === "default" ? "" : ` in category '${selectedCategory}'`}
|
||||
</span>
|
||||
|
||||
</DialogTitle>
|
||||
<div style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: "#212121", }}>
|
||||
Key
|
||||
@@ -1041,13 +1045,13 @@ const CacheView = memo((props) => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={data?.org_id !== selectedOrganization.id ? "You can not delete this key as it is controlled by parent organization." : "Delete this key" }
|
||||
title={selectedOrganization?.id !== undefined && data?.org_id !== selectedOrganization.id ? "You can not delete this key as it is controlled by parent organization." : "Delete this key" }
|
||||
aria-label={"Delete"}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
style={{ padding: "6px" }}
|
||||
disabled={data.org_id !== selectedOrganization.id ? true : false}
|
||||
disabled={selectedOrganization?.id === undefined ? false : data.org_id !== selectedOrganization.id ? true : false}
|
||||
onClick={() => {
|
||||
deleteCache(orgId, data.key);
|
||||
//deleteFile(orgId);
|
||||
|
||||
@@ -545,6 +545,8 @@ const EditWorkflow = (props) => {
|
||||
}}
|
||||
>
|
||||
<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" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
@@ -774,8 +774,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
}, [window?.location?.pathname]);
|
||||
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
|
||||
const showPartnerLogo = userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.image !== undefined && userdata?.active_org?.image !== null && userdata?.active_org?.image.length > 0
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -836,11 +836,13 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Link to="/">
|
||||
<Link to={isCloud && !showPartnerLogo ? "/" : "/workflows"}>
|
||||
<img
|
||||
src={ShuffleLogo}
|
||||
src={
|
||||
showPartnerLogo ? userdata?.active_org?.image : ShuffleLogo
|
||||
}
|
||||
alt="Shuffle Logo"
|
||||
style={{ width: 24, height: 24 }}
|
||||
style={{ width: showPartnerLogo ? 30 : 24, height: showPartnerLogo ? 30 : 24 }}
|
||||
/>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
@@ -1611,7 +1613,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
}}
|
||||
>
|
||||
|
||||
{expandLeftNav &&
|
||||
{userdata?.licensed !== true && !userdata?.org_status?.includes("integration_partner") && expandLeftNav &&
|
||||
<Button
|
||||
variant="outlined"
|
||||
style={{marginBottom: 15, borderWidth: 2, }}
|
||||
|
||||
@@ -25,6 +25,8 @@ import {
|
||||
CardContent,
|
||||
ButtonGroup,
|
||||
DialogContentText,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
@@ -87,29 +89,80 @@ const LicencePopup = (props) => {
|
||||
const [calculatedCores, setCalculatedCores] = useState('600')
|
||||
const [onpremSelectedValue, setOnpremSelectedValue] = useState(8)
|
||||
|
||||
const payasyougo = "Pay as you go"
|
||||
const [billingCycle, setBillingCycle] = useState("annual")
|
||||
const [scaleValue, setScaleValue] = useState(
|
||||
new URLSearchParams(window.location.search).get("app_runs") ||
|
||||
(userdata?.app_execution_limit / 1000) + 50 || 10
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setScaleValue((userdata?.app_execution_limit / 1000) + 50 || 10)
|
||||
}, [userdata])
|
||||
|
||||
const getPrice = (basePrice) => {
|
||||
return Math.round(billingCycle === "annual" ? basePrice * 0.9 : basePrice); // 10% discount for annual
|
||||
};
|
||||
|
||||
const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : ""
|
||||
|
||||
// Handle slider change for Scale plan
|
||||
const handleScaleChange = (event, newValue) => {
|
||||
setScaleValue(newValue);
|
||||
|
||||
// Add app runs to URL query params
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
urlSearchParams.set("app_runs", newValue); // Convert to actual app runs (k to actual number)
|
||||
const newUrl = `${window.location.pathname}?${urlSearchParams.toString()}`;
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
};
|
||||
|
||||
// Handle billing cycle change
|
||||
const handleBillingCycleChange = (event, newValue) => {
|
||||
if (newValue !== null) {
|
||||
setBillingCycle(newValue);
|
||||
|
||||
if(isCloud){
|
||||
ReactGA.event({
|
||||
category: 'Billingpage',
|
||||
action: 'Billing Cycle Changed',
|
||||
label: `${billingCycle} -> ${newValue}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Add billing cycle to URL query params
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
urlSearchParams.set("billing_cycle", newValue);
|
||||
const newUrl = `${
|
||||
window.location.pathname
|
||||
}?${urlSearchParams.toString()}`;
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const payasyougo = "Pay as you go"
|
||||
|
||||
const paperStyle = {
|
||||
padding: 20,
|
||||
paddingBottom: 30,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
height: "100%"
|
||||
}
|
||||
|
||||
billingInfo.subscription = {
|
||||
"active": true,
|
||||
"name": "Pay as you go",
|
||||
"price": typecost_single,
|
||||
"currency": "USD",
|
||||
"currency_text": "$",
|
||||
"interval": "app run / month",
|
||||
"name": userdata?.app_execution_limit === 10000 ? "10,000 App Runs" : "2,000 App Runs",
|
||||
"price": "Free",
|
||||
"currency": "Free",
|
||||
"currency_text": "",
|
||||
"interval": "",
|
||||
"description": "Pay as you go",
|
||||
"features": [
|
||||
"Basic Support",
|
||||
"Includes 10.000 app run/month for free. ",
|
||||
"Pay for what you use with no minimum commitment and cancel anytime."
|
||||
"Community Support",
|
||||
`Includes ${userdata?.app_execution_limit === 10000 ? "10,000" : "2,000"} app run/month for free. `,
|
||||
"Get all 2500+ Apps and 10 Workflows",
|
||||
"Invite up to 5 users"
|
||||
],
|
||||
"limit": 10000,
|
||||
"limit": userdata?.app_execution_limit === 10000 ? 10000 : 2000,
|
||||
}
|
||||
|
||||
const sendSignatureRequest = (subscription) => {
|
||||
@@ -151,32 +204,32 @@ const LicencePopup = (props) => {
|
||||
const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed)
|
||||
const [hovered, setHovered] = React.useState(false)
|
||||
const [newBillingEmail, setNewBillingEmail] = useState('');
|
||||
var top_text = "Base Cloud Access"
|
||||
if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) {
|
||||
subscription.name = "Enterprise"
|
||||
subscription.currency_text = "$"
|
||||
subscription.price = subscription.level * 180
|
||||
subscription.limit = subscription.level * 100000
|
||||
subscription.interval = subscription.recurrence
|
||||
subscription.features = [
|
||||
"Includes " + subscription.limit + " app runs/month. ",
|
||||
"Multi-Tenancy and Region-Selection",
|
||||
"And all other features from /pricing",
|
||||
]
|
||||
}
|
||||
var top_text = "Starter Plan"
|
||||
// if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) {
|
||||
// subscription.name = "Enterprise"
|
||||
// subscription.currency_text = "$"
|
||||
// subscription.price = subscription.level * 180
|
||||
// subscription.limit = subscription.level * 100000
|
||||
// subscription.interval = subscription.recurrence
|
||||
// subscription.features = [
|
||||
// "Includes " + subscription.limit + " app runs/month. ",
|
||||
// "Multi-Tenancy and Region-Selection",
|
||||
// "And all other features from /pricing",
|
||||
// ]
|
||||
// }
|
||||
|
||||
if (userdata?.app_execution_limit >= 300000) {
|
||||
subscription.name = "Enterprise"
|
||||
subscription.currency_text = "$"
|
||||
subscription.price = typecost_single
|
||||
subscription.limit = userdata?.app_execution_limit
|
||||
subscription.interval = "app run / month"
|
||||
subscription.features = [
|
||||
"Includes " + (userdata?.app_execution_limit/1000) + "K app runs/month. ",
|
||||
"Multi-Tenancy and Region-Selection",
|
||||
"And all other features from /pricing",
|
||||
]
|
||||
}
|
||||
// if (userdata?.app_execution_limit >= 300000) {
|
||||
// subscription.name = "Enterprise"
|
||||
// subscription.currency_text = "$"
|
||||
// subscription.price = typecost_single
|
||||
// subscription.limit = userdata?.app_execution_limit
|
||||
// subscription.interval = "app run / month"
|
||||
// subscription.features = [
|
||||
// "Includes " + (userdata?.app_execution_limit/1000) + "K app runs/month. ",
|
||||
// "Multi-Tenancy and Region-Selection",
|
||||
// "And all other features from /pricing",
|
||||
// ]
|
||||
// }
|
||||
|
||||
|
||||
var newPaperstyle = JSON.parse(JSON.stringify(paperStyle))
|
||||
@@ -189,12 +242,12 @@ const LicencePopup = (props) => {
|
||||
var showSupport = false
|
||||
if (subscription.name.includes("default")) {
|
||||
top_text = "Custom Contract"
|
||||
newPaperstyle.border = "1px solid #f85a3e"
|
||||
// newPaperstyle.border = "1px solid #f85a3e"
|
||||
showSupport = true
|
||||
}
|
||||
|
||||
if (subscription.name.includes("App Run Units")) {
|
||||
top_text = "Cloud Access"
|
||||
top_text = "Scale Plan"
|
||||
showSupport = true
|
||||
}
|
||||
|
||||
@@ -357,7 +410,11 @@ const LicencePopup = (props) => {
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{subscription.active === true && !isScale && <Button style={{ backgroundColor: '#2F2F2F', color: "white", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', width: 144, height: 40 }}>Current Plan</Button> }
|
||||
{subscription.active === true && !isScale && <Button style={{ backgroundColor: '#2f2f2f', color: "#ffffff", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', fontSize: 13 }}
|
||||
variant="contained"
|
||||
color="primary">
|
||||
Current Plan
|
||||
</Button>}
|
||||
<div style={{ display: "flex" }}>
|
||||
{top_text === "Base Cloud Access" && userdata.has_card_available === true && !isScale ?
|
||||
<Chip
|
||||
@@ -381,7 +438,7 @@ const LicencePopup = (props) => {
|
||||
color="primary"
|
||||
/>
|
||||
: null}
|
||||
<Typography variant="h6" style={{ marginTop: 10, marginBottom: 10, flex: 5, whiteSpace: 'nowrap' }}>
|
||||
<Typography variant="h6" style={{ marginTop: 25, marginBottom: 10, flex: 5, whiteSpace: 'nowrap' }}>
|
||||
{top_text}
|
||||
</Typography>
|
||||
|
||||
@@ -396,7 +453,7 @@ const LicencePopup = (props) => {
|
||||
}}
|
||||
/>
|
||||
: null}
|
||||
{isCloud && highlight === true && top_text !== "Base Cloud Access" ?
|
||||
{isCloud && highlight === true && top_text !== "Starter Plan" ?
|
||||
<Tooltip
|
||||
title="Sign EULA"
|
||||
placement="top"
|
||||
@@ -426,7 +483,7 @@ const LicencePopup = (props) => {
|
||||
{subscription.currency_text}{subscription.price}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{ marginLeft: 10, marginTop: 15, marginBottom: 10 }}>
|
||||
/ {subscription.interval}
|
||||
{subscription.interval.length > 0 ? `/ ${subscription.interval}` : ""}
|
||||
</Typography>
|
||||
</div>
|
||||
: null}
|
||||
@@ -517,23 +574,17 @@ const LicencePopup = (props) => {
|
||||
: null}
|
||||
</ul>
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
|
||||
{subscription.name.includes("Scale") ?
|
||||
""
|
||||
:
|
||||
|
||||
userdata.has_card_available === true ?
|
||||
"While you have a card attached to your account, Shuffle will no longer prevent workflows from running. Billing will occur at the start of each month."
|
||||
:
|
||||
{
|
||||
isCloud ?
|
||||
userdata?.app_execution_limit && userdata?.app_execution_limit >= 300000 ?
|
||||
"You have subscribed to the Enterprise plan, which includes " + (userdata?.app_execution_limit/1000) + "K app runs/month. You can increase the limit by upgrading current plan. Contact support@shuffler.io for more information." :
|
||||
`You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.`
|
||||
userdata?.app_execution_limit && userdata?.app_execution_limit !== 10000 ?
|
||||
"You have already subscribed to the Scale plan, which includes " + (userdata?.app_execution_limit/1000) + "K app runs/month. You can increase the limit by upgrading current plan. Contact support@shuffler.io for more information." :
|
||||
`You are using free Starter plan with max ${userdata?.app_execution_limit === 10000 ? "10,000" : "2,000"} runs per month. Upgrade to increase this limit.`
|
||||
|
||||
:
|
||||
`You are not subscribed to any plan and are using the free, open source plan. This plan has no enforced limits, but scale issues may occur due to CPU congestion.`
|
||||
}
|
||||
</Typography>
|
||||
{isCloud && (userdata.has_card_available === true || selectedOrganization?.Billing?.Email?.length > 0 )?
|
||||
{/* {isCloud && (userdata.has_card_available === true || selectedOrganization?.Billing?.Email?.length > 0 )?
|
||||
<div>
|
||||
<span>Billing email: {BillingEmail}</span>
|
||||
<Button
|
||||
@@ -611,15 +662,14 @@ const LicencePopup = (props) => {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
: null}
|
||||
: null} */}
|
||||
</div>
|
||||
{isCloud ? (
|
||||
<Button
|
||||
fullWidth
|
||||
disabled={false}
|
||||
color="primary"
|
||||
style={{
|
||||
marginTop: !userdata.has_card_available ? 20 : 10,
|
||||
marginTop: !userdata.has_card_available ? 25 : 10,
|
||||
borderRadius: 4,
|
||||
height: 40,
|
||||
fontSize: 16,
|
||||
@@ -630,20 +680,15 @@ const LicencePopup = (props) => {
|
||||
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isCloud) {
|
||||
handlePayasyougo(userdata, selectedOrganization, BillingEmail)
|
||||
//navigate("/pricing?tab=cloud&highlight=true")
|
||||
} else {
|
||||
//window.open("https://shuffler.io/pricing?tab=onprem&highlight=true", "_blank")
|
||||
handlePayasyougo()
|
||||
}
|
||||
console.log("Subscription: ", subscription.name)
|
||||
if(!subscription.name.includes("App Run Units")) {
|
||||
window.open("https://discord.gg/B2CBzUm", "_blank")
|
||||
} else {
|
||||
window.open("mailto:support@shuffler.io", "_blank")
|
||||
}
|
||||
}}
|
||||
>
|
||||
{userdata.has_card_available === true ?
|
||||
"Manage Card Details"
|
||||
:
|
||||
"Add Card Details"
|
||||
}
|
||||
Get Support
|
||||
</Button> ) : null}
|
||||
<Button
|
||||
variant="outlined"
|
||||
@@ -709,42 +754,42 @@ const LicencePopup = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log("New variant: ", shuffleVariant)
|
||||
// useEffect(() => {
|
||||
// console.log("New variant: ", shuffleVariant)
|
||||
|
||||
if (shuffleVariant === 1) {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(8)
|
||||
} else {
|
||||
if (userdata && userdata?.app_execution_limit) {
|
||||
if (userdata.app_execution_limit >= 300000 && userdata.app_execution_limit < 400000) {
|
||||
setSelectedValue(400)
|
||||
setCalculatedCost("$1280")
|
||||
}else if (userdata?.app_execution_limit >= 400000 && userdata?.app_execution_limit < 500000) {
|
||||
setSelectedValue(500)
|
||||
setCalculatedCost("$1600")
|
||||
} else if (userdata?.app_execution_limit >= 500000 && userdata?.app_execution_limit < 600000) {
|
||||
setSelectedValue(600)
|
||||
setCalculatedCost("$1920")
|
||||
} else if (userdata?.app_execution_limit >= 600000 && userdata?.app_execution_limit < 700000) {
|
||||
setSelectedValue(700)
|
||||
setCalculatedCost("$2240")
|
||||
} else if (userdata?.app_execution_limit >= 700000 && userdata?.app_execution_limit < 800000) {
|
||||
setSelectedValue(800)
|
||||
setCalculatedCost("$2560")
|
||||
} else if (userdata?.app_execution_limit >= 800000 && userdata?.app_execution_limit < 900000) {
|
||||
setSelectedValue(900)
|
||||
setCalculatedCost("$2880")
|
||||
}else {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(300)
|
||||
}
|
||||
}else {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(300)
|
||||
}
|
||||
}
|
||||
}, [shuffleVariant])
|
||||
// if (shuffleVariant === 1) {
|
||||
// setCalculatedCost("$960")
|
||||
// setSelectedValue(8)
|
||||
// } else {
|
||||
// if (userdata && userdata?.app_execution_limit) {
|
||||
// if (userdata.app_execution_limit >= 30000 && userdata.app_execution_limit < 40000) {
|
||||
// setSelectedValue(400)
|
||||
// setCalculatedCost("$1280")
|
||||
// }else if (userdata?.app_execution_limit >= 40000 && userdata?.app_execution_limit < 50000) {
|
||||
// setSelectedValue(500)
|
||||
// setCalculatedCost("$1600")
|
||||
// } else if (userdata?.app_execution_limit >= 500000 && userdata?.app_execution_limit < 600000) {
|
||||
// setSelectedValue(600)
|
||||
// setCalculatedCost("$1920")
|
||||
// } else if (userdata?.app_execution_limit >= 60000 && userdata?.app_execution_limit < 70000) {
|
||||
// setSelectedValue(700)
|
||||
// setCalculatedCost("$2240")
|
||||
// } else if (userdata?.app_execution_limit >= 70000 && userdata?.app_execution_limit < 80000) {
|
||||
// setSelectedValue(800)
|
||||
// setCalculatedCost("$2560")
|
||||
// } else if (userdata?.app_execution_limit >= 80000 && userdata?.app_execution_limit < 90000) {
|
||||
// setSelectedValue(900)
|
||||
// setCalculatedCost("$2880")
|
||||
// }else {
|
||||
// setCalculatedCost("$960")
|
||||
// setSelectedValue(300)
|
||||
// }
|
||||
// }else {
|
||||
// setCalculatedCost("$960")
|
||||
// setSelectedValue(300)
|
||||
// }
|
||||
// }
|
||||
// }, [userdata])
|
||||
|
||||
if (typeof window === 'undefined' || window.location === undefined) {
|
||||
return null
|
||||
@@ -900,71 +945,121 @@ const LicencePopup = (props) => {
|
||||
}
|
||||
|
||||
console.log("Priceitem: ", shuffleVariant)
|
||||
// const isLoggedInHandler = () => {
|
||||
// if (calculatedCost === payasyougo) {
|
||||
// handlePayasyougo(props.userdata)
|
||||
// return
|
||||
// }
|
||||
|
||||
// const priceItem =
|
||||
// window.location.origin === "https://shuffler.io/" || "https://sandbox.shuffler.io/"
|
||||
// ? shuffleVariant === 0
|
||||
// ? "price_1PWI3uDzMUgUjxHSffUBwWCy"
|
||||
// : "price_1PWI8EDzMUgUjxHSfEhUB7oL"
|
||||
|
||||
// : shuffleVariant === 0
|
||||
// ? "price_1PZPSSEJjT17t98NLJoTMYja"
|
||||
// : "price_1PZPQuEJjT17t98N3yORUtd9";
|
||||
|
||||
// const successUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=success`
|
||||
// const failUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=failure`
|
||||
// const quantity = shuffleVariant === 0 ? selectedValue / 100 : selectedValue
|
||||
|
||||
// console.log("Priceitem: ", priceItem, quantity, shuffleVariant)
|
||||
// var checkoutObject = {
|
||||
// lineItems: [
|
||||
// {
|
||||
// price: priceItem,
|
||||
// quantity: quantity,
|
||||
// },
|
||||
// ],
|
||||
// mode: "subscription",
|
||||
// billingAddressCollection: "auto",
|
||||
// successUrl: successUrl,
|
||||
// cancelUrl: failUrl,
|
||||
// clientReferenceId: props.userdata.active_org.id,
|
||||
// }
|
||||
|
||||
// if (stripe === undefined || stripe === null || stripe.redirectToCheckout === undefined) {
|
||||
// window.open("https://shuffler.io/admin?admin_tab=billingstats&payment=stripe_error", "_self")
|
||||
// }
|
||||
|
||||
// stripe.redirectToCheckout(checkoutObject)
|
||||
// .then(function (result) {
|
||||
// console.log("SUCCESS STRIPE?: ", result)
|
||||
|
||||
// ReactGA.event({
|
||||
// category: "pricing",
|
||||
// action: "add_card_success",
|
||||
// label: "",
|
||||
// })
|
||||
// })
|
||||
// .catch(function (error) {
|
||||
// console.error("STRIPE ERROR: ", error)
|
||||
|
||||
// ReactGA.event({
|
||||
// category: "pricing",
|
||||
// action: "add_card_error",
|
||||
// label: "",
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
const isLoggedInHandler = () => {
|
||||
if (calculatedCost === payasyougo) {
|
||||
handlePayasyougo(props.userdata)
|
||||
return
|
||||
var priceItem;
|
||||
if (window.location.origin === "https://shuffler.io" || window.location.origin === "https://sandbox.shuffler.io") {
|
||||
priceItem = billingCycle === "monthly" ? "price_1R66rbEJjT17t98NHIQ78nrz" : "price_1R671UEJjT17t98NzfqWvSG7"
|
||||
} else if (window.location.origin === "http://localhost:3002") {
|
||||
priceItem = billingCycle === "monthly" ? "price_1R678hEJjT17t98Nai5J50gs" : "price_1R6c84EJjT17t98NR68gUfT7"
|
||||
}
|
||||
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=success`;
|
||||
const failUrl = `${window.location.origin}/pricing?admin_tab=billingstats&payment=failure`;
|
||||
|
||||
let quantity;
|
||||
|
||||
if (billingCycle === "monthly") {
|
||||
quantity = scaleValue / 10
|
||||
} else {
|
||||
quantity = (scaleValue / 10) * 12
|
||||
}
|
||||
|
||||
redirectToCheckout(priceItem, quantity, successUrl, failUrl);
|
||||
};
|
||||
|
||||
const redirectToCheckout = (priceItem, quantity, successUrl, failUrl) => {
|
||||
const checkoutObject = {
|
||||
lineItems: [
|
||||
{
|
||||
price: priceItem,
|
||||
quantity: quantity,
|
||||
},
|
||||
],
|
||||
mode: "subscription",
|
||||
billingAddressCollection: "auto",
|
||||
successUrl: successUrl,
|
||||
cancelUrl: failUrl,
|
||||
clientReferenceId: userdata.active_org.id,
|
||||
};
|
||||
|
||||
console.log("OBJECT: ", priceItem, checkoutObject);
|
||||
|
||||
stripe
|
||||
.redirectToCheckout(checkoutObject)
|
||||
.then(function (result) {
|
||||
console.log("SUCCESS STRIPE?: ", result);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error("STRIPE ERROR: ", error);
|
||||
});
|
||||
};
|
||||
|
||||
const priceItem =
|
||||
window.location.origin === "https://shuffler.io/" || "https://sandbox.shuffler.io/"
|
||||
? shuffleVariant === 0
|
||||
? "price_1PWI3uDzMUgUjxHSffUBwWCy"
|
||||
: "price_1PWI8EDzMUgUjxHSfEhUB7oL"
|
||||
|
||||
: shuffleVariant === 0
|
||||
? "price_1PZPSSEJjT17t98NLJoTMYja"
|
||||
: "price_1PZPQuEJjT17t98N3yORUtd9";
|
||||
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=success`
|
||||
const failUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=failure`
|
||||
const quantity = shuffleVariant === 0 ? selectedValue / 100 : selectedValue
|
||||
|
||||
console.log("Priceitem: ", priceItem, quantity, shuffleVariant)
|
||||
var checkoutObject = {
|
||||
lineItems: [
|
||||
{
|
||||
price: priceItem,
|
||||
quantity: quantity,
|
||||
},
|
||||
],
|
||||
mode: "subscription",
|
||||
billingAddressCollection: "auto",
|
||||
successUrl: successUrl,
|
||||
cancelUrl: failUrl,
|
||||
clientReferenceId: props.userdata.active_org.id,
|
||||
}
|
||||
|
||||
if (stripe === undefined || stripe === null || stripe.redirectToCheckout === undefined) {
|
||||
window.open("https://shuffler.io/admin?admin_tab=billingstats&payment=stripe_error", "_self")
|
||||
}
|
||||
|
||||
stripe.redirectToCheckout(checkoutObject)
|
||||
.then(function (result) {
|
||||
console.log("SUCCESS STRIPE?: ", result)
|
||||
|
||||
ReactGA.event({
|
||||
category: "pricing",
|
||||
action: "add_card_success",
|
||||
label: "",
|
||||
})
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error("STRIPE ERROR: ", error)
|
||||
|
||||
ReactGA.event({
|
||||
category: "pricing",
|
||||
action: "add_card_error",
|
||||
label: "",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
console.log("Selected Organization: ", selectedOrganization.subscriptions)
|
||||
return (
|
||||
<div>
|
||||
<Grid container spacing={2} columns={16} style={{ flexDirection: "row", flexWrap: "nowrap", borderRadius: '16px', display: "flex", }}>
|
||||
<Grid container spacing={2} columns={16} style={{ flexDirection: "row", flexWrap: "nowrap", borderRadius: '16px', display: "flex"}}>
|
||||
<Grid item xs={8}>
|
||||
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ?
|
||||
{selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0 ?
|
||||
<SubscriptionObject
|
||||
index={0}
|
||||
globalUrl={globalUrl}
|
||||
@@ -976,7 +1071,29 @@ const LicencePopup = (props) => {
|
||||
subscription={billingInfo.subscription}
|
||||
highlight={selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0}
|
||||
/>
|
||||
: !isCloud ?
|
||||
:
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
selectedOrganization.subscriptions !== null &&
|
||||
selectedOrganization.subscriptions.length > 0 ?
|
||||
selectedOrganization.subscriptions
|
||||
.reverse()
|
||||
.map((sub, index) => {
|
||||
return (
|
||||
<SubscriptionObject
|
||||
index={index + 1}
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={serverside}
|
||||
billingInfo={billingInfo}
|
||||
stripeKey={stripeKey}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={sub}
|
||||
highlight={true}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
{!isCloud ?
|
||||
<span style={{ display: "flex", }}>
|
||||
<SubscriptionObject
|
||||
index={0}
|
||||
@@ -1044,61 +1161,184 @@ const LicencePopup = (props) => {
|
||||
})
|
||||
: null} */}
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
<Grid item xs={8} sx={{ height: "100%" }}>
|
||||
<Grid style={{}}>
|
||||
{errorMessage.length > 0 ? <Typography variant="h4">Error: {errorMessage}</Typography> : null}
|
||||
<Card style={{
|
||||
padding: 20,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
border: !isScale ? "1px solid #f85a3e" : 'none',
|
||||
background:
|
||||
"linear-gradient(to right, #212121, #212121) padding-box, linear-gradient(90deg, #F86744 0%, #F34475 100%) border-box",
|
||||
borderWidth: "2px",
|
||||
borderStyle: "solid",
|
||||
borderColor: "transparent",
|
||||
}}>
|
||||
<div>
|
||||
{ !isScale && <Button style={{ backgroundColor: 'rgba(255, 132, 68, 0.2)', color: "#FF8444", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', }}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 25,
|
||||
marginLeft: -2,
|
||||
}}
|
||||
>
|
||||
<Button style={{ backgroundColor: 'rgba(255, 132, 68, 0.2)', color: "#FF8444", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', fontSize: 13 }}
|
||||
variant="contained"
|
||||
color="primary">Recommended </Button> }
|
||||
|
||||
color="primary">Recommended
|
||||
</Button>
|
||||
{
|
||||
billingCycle === "annual" &&
|
||||
(
|
||||
<Box
|
||||
sx={{
|
||||
background: "rgba(248, 103, 68, 0.1)",
|
||||
py: 0.5,
|
||||
px: 1.5,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
background:
|
||||
"linear-gradient(90deg, #FF8544 0%, #FB47A0 100%)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
backgroundClip: "text",
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
md: "14px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
10% OFF
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 10,
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" style={{ }}>
|
||||
{scaleValue > 300 ? "Enterprise Plan" : "Scale Plan"}
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
<ToggleButtonGroup
|
||||
value={billingCycle}
|
||||
exclusive
|
||||
onChange={handleBillingCycleChange}
|
||||
aria-label="billing cycle"
|
||||
sx={{
|
||||
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
borderRadius: "30px",
|
||||
marginTop: -1,
|
||||
padding: "3px",
|
||||
"& .MuiToggleButton-root": {
|
||||
border: "none",
|
||||
borderRadius: "30px",
|
||||
color: "#fff",
|
||||
padding: "6px 22px",
|
||||
textTransform: "none",
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
},
|
||||
"&.Mui-selected": {
|
||||
backgroundColor: "#fff",
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
color: "#1A1A1A",
|
||||
fontWeight: "bold",
|
||||
"&:hover": {
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="monthly" aria-label="monthly">
|
||||
Monthly
|
||||
</ToggleButton>
|
||||
<ToggleButton value="annual" aria-label="annual">
|
||||
Annual
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
</div>
|
||||
<Typography variant="h6" style={{ marginTop: 10, marginBottom: 10, flex: 5, }}>{shuffleVariant === 1 ? "Scale" : "Enterprise"}</Typography>
|
||||
<Divider />
|
||||
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20 }}>
|
||||
{shuffleVariant === 0 ?
|
||||
"SaaS / Cloud - Per Month"
|
||||
:
|
||||
"Open Source + Scale License"
|
||||
}
|
||||
App Runs Units
|
||||
</Typography>
|
||||
|
||||
<Typography style={{ minHeight: 46, cursor: calculatedCores === "Get A Quote" ? "pointer" : "inherit", }} onClick={() => {
|
||||
|
||||
if (calculatedCores === "Get A Quote") {
|
||||
console.log("Clicked on get a quote")
|
||||
if (window.drift !== undefined) {
|
||||
window.drift.api.startInteraction({ interactionId: 340785 })
|
||||
}
|
||||
}
|
||||
}}>{calculatedCost}</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{}}>For {shuffleVariant === 1 ? `${selectedValue} CPU cores` : `${selectedValue}k App Runs`}: </Typography>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
|
||||
<Slider
|
||||
aria-label="Small steps"
|
||||
style={{ width: "80%", margin: "auto" }}
|
||||
onChange={(event, newValue) => {
|
||||
handleChange(event, newValue)
|
||||
<div style={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 10 , marginTop: 10 }}>
|
||||
<Typography style={{
|
||||
fontSize: 24,
|
||||
marginTop: 7,
|
||||
marginBottom: 10,
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
{scaleValue > 300 ? "Let's Talk" : `$${getPrice(32) * (scaleValue / 10)}`}
|
||||
</Typography>
|
||||
<Typography
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
marginBottom: "-2px",
|
||||
marginLeft: scaleValue > 300 ? 1 : 0,
|
||||
}}
|
||||
marks
|
||||
value={selectedValue}
|
||||
step={shuffleVariant === 0 ? 100 : 4}
|
||||
min={shuffleVariant === 0 ? 100 : 8}
|
||||
max={shuffleVariant === 0 ? 1000 : 32}
|
||||
valueLabelDisplay="auto"
|
||||
/>
|
||||
>
|
||||
{scaleValue > 300 ? `for ${scaleValue > 500 ? "500k+" : `${scaleValue}k`} App Runs` : `/month for ${scaleValue}k App Runs`}
|
||||
</Typography>
|
||||
</div>
|
||||
<Box sx={{ px: 1 }}>
|
||||
<Slider
|
||||
value={scaleValue}
|
||||
onChange={handleScaleChange}
|
||||
aria-labelledby="scale-slider"
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => {
|
||||
if(value === 510){
|
||||
return "500k+"
|
||||
}
|
||||
return `${value}k`
|
||||
}}
|
||||
step={10}
|
||||
min={10}
|
||||
max={510}
|
||||
marks
|
||||
sx={{
|
||||
color: "#ff8544",
|
||||
"& .MuiSlider-thumb": {
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
"& .MuiSlider-valueLabel": {
|
||||
backgroundColor: "rgba(33, 33, 33, 1)",
|
||||
color: "rgba(241, 241, 241, 1)",
|
||||
fontSize: 14,
|
||||
borderRadius: "4px",
|
||||
border: "1px solid rgba(73, 73, 73, 1)",
|
||||
fontFamily: theme?.typography?.fontFamily,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{defaultTaskIcon}</span>
|
||||
<Typography style={{ fontSize: 14, marginLeft: 8 }}>Priority Support</Typography>
|
||||
<Typography style={{ fontSize: 14, marginLeft: 8 }}>Standard Email Support</Typography>
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1117,7 +1357,7 @@ const LicencePopup = (props) => {
|
||||
<Divider />
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{defaultTaskIcon}</span>
|
||||
<Typography style={{ fontSize: 14, marginLeft: 8 }}>Help with Workflow and App development</Typography>
|
||||
<Typography style={{ fontSize: 14, marginLeft: 8 }}>30 Days workflow run history</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 20, }} />
|
||||
@@ -1136,7 +1376,7 @@ const LicencePopup = (props) => {
|
||||
|
||||
navigate("/pricing")
|
||||
} else {
|
||||
window.open("https://shuffler.io/pricing?tab=onprem", "_blank")
|
||||
window.open("https://shuffler.io/pricing?tab=Self-Hosted", "_blank")
|
||||
}
|
||||
}}
|
||||
color="primary"
|
||||
@@ -1149,6 +1389,10 @@ const LicencePopup = (props) => {
|
||||
style={{ borderRadius: 4, textTransform: "capitalize", color: "#1a1a1a", backgroundColor: "#ff8544", width: "100%", fontSize: 16}}
|
||||
onClick={() => {
|
||||
if (isCloud) {
|
||||
if(scaleValue > 300){
|
||||
navigate("/contact?category=cloud_enterprise_plan")
|
||||
return;
|
||||
}
|
||||
ReactGA.event({
|
||||
category: "header",
|
||||
action: "upgread_clicks_popup",
|
||||
@@ -1170,7 +1414,7 @@ const LicencePopup = (props) => {
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
Upgrade
|
||||
{scaleValue > 300 ? "Let's Talk" : "Upgrade"}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogActions>
|
||||
|
||||
@@ -61,7 +61,7 @@ const menuData = {
|
||||
description:
|
||||
"Connect and run actions seamlessly between different platforms.",
|
||||
icon: "/images/logos/singul.svg",
|
||||
path: "https://singul.io/",
|
||||
path: "https://singul-docs.gitbook.io/singul/getting-started",
|
||||
gaData: {
|
||||
category: "navbar",
|
||||
action: "products_click",
|
||||
@@ -1247,7 +1247,7 @@ const Navbar = (props) => {
|
||||
letterSpacing: '0.5px',
|
||||
}}
|
||||
>
|
||||
Coming Soon
|
||||
Beta: Coming Soon
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -1370,7 +1370,7 @@ const Navbar = (props) => {
|
||||
label: "go_to_pricing",
|
||||
})
|
||||
} else {
|
||||
window.open("https://shuffler.io/pricing", '_blank');
|
||||
window.open("https://shuffler.io/pricing?env=Self-Hosted", '_blank');
|
||||
return;
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -538,7 +538,7 @@ const OrgHeaderexpandedNew = (props) => {
|
||||
renderValue={(selected) => selected.join(', ')}
|
||||
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}>
|
||||
<Checkbox checked={selectedStatus.indexOf(name) > -1} />
|
||||
<ListItemText primary={name} />
|
||||
|
||||
@@ -120,7 +120,7 @@ const OrganizationTab = (props) => {
|
||||
isLoaded={isLoaded}
|
||||
/>
|
||||
);
|
||||
case 'branding(beta)':
|
||||
case 'branding':
|
||||
return <Branding
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
@@ -140,7 +140,7 @@ const OrganizationTab = (props) => {
|
||||
return (
|
||||
<div style={{ height: "100%", width: "100%", color: '#FFFFFF', backgroundColor: '#212121', borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", boxSizing: 'border-box' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-around', width: "100%", borderBottom: '1px solid #494949' ,boxSizing: 'border-box' }}>
|
||||
{['Org Configuration', "sso", "Notifications", 'Billing & Stats', 'Branding (Beta)'].map((tabName, index) => (
|
||||
{['Org Configuration', "sso", "Notifications", 'Billing & Stats', 'Branding'].map((tabName, index) => (
|
||||
<Tooltip
|
||||
key={index}
|
||||
title={
|
||||
|
||||
@@ -179,6 +179,7 @@ const ParsedAction = (props) => {
|
||||
|
||||
suborgWorkflows,
|
||||
originalWorkflow,
|
||||
runFromHere,
|
||||
} = props;
|
||||
|
||||
let navigate = useNavigate()
|
||||
@@ -233,11 +234,16 @@ const ParsedAction = (props) => {
|
||||
// auth, required, optional
|
||||
var changed = false
|
||||
if (selectedActionParameters === undefined || selectedActionParameters === null || selectedActionParameters.length === 0) {
|
||||
console.log("Returning because no params")
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedApp !== undefined && selectedApp !== null && selectedApp.generated !== true) {
|
||||
return
|
||||
|
||||
if (isAgent || isIntegration) {
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Fixing required fields with a shitty structure :)
|
||||
@@ -346,13 +352,23 @@ const ParsedAction = (props) => {
|
||||
// 3. generated fields & all else
|
||||
|
||||
|
||||
const newparams = auth
|
||||
var newparams = auth
|
||||
.concat(bodyfield)
|
||||
.concat(required)
|
||||
.concat(generated_optional)
|
||||
.concat(special_optional)
|
||||
.concat(optional)
|
||||
|
||||
const dedupedParams = []
|
||||
for (var paramKey in newparams) {
|
||||
const param = newparams[paramKey]
|
||||
if (dedupedParams.find(item => item.name === param.name) === undefined) {
|
||||
dedupedParams.push(param)
|
||||
}
|
||||
}
|
||||
|
||||
newparams = dedupedParams
|
||||
|
||||
var newkeyorder = []
|
||||
for (let paramkey in newparams) {
|
||||
//console.log("Param: ", newparams[paramkey])
|
||||
@@ -666,11 +682,14 @@ const ParsedAction = (props) => {
|
||||
|
||||
// Process workflowExecutions
|
||||
if (workflowExecutions.length > 0) {
|
||||
var appended = false
|
||||
var foundvalue = ""
|
||||
for (let execution of workflowExecutions) {
|
||||
const execArg = execution.execution_argument;
|
||||
if (execArg && execArg.length > 0) {
|
||||
const valid = validateJson(execArg);
|
||||
if (valid.valid) {
|
||||
appended = true
|
||||
newActionList.push({
|
||||
type: "Runtime Argument",
|
||||
name: "Runtime Argument",
|
||||
@@ -681,9 +700,23 @@ const ParsedAction = (props) => {
|
||||
})
|
||||
|
||||
break
|
||||
} else {
|
||||
foundvalue = execArg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!appended && foundvalue !== undefined && foundvalue !== "") {
|
||||
newActionList.push({
|
||||
type: "Runtime Argument",
|
||||
name: "Runtime Argument",
|
||||
highlight: "exec",
|
||||
autocomplete: "exec",
|
||||
|
||||
value: foundvalue,
|
||||
example: foundvalue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add default Runtime Argument if none were added
|
||||
@@ -772,6 +805,8 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
|
||||
labels.push(parentNode.label);
|
||||
|
||||
var secondaryExample = ""
|
||||
let exampleData = parentNode.example ?? "";
|
||||
if (parentNode?.app_name === "http") {
|
||||
exampleData = ""
|
||||
@@ -782,9 +817,22 @@ const ParsedAction = (props) => {
|
||||
const foundResult = exec.results?.find(result => result?.action?.id === parentNode?.id);
|
||||
if (foundResult) {
|
||||
const valid = validateJson(foundResult.result);
|
||||
if (valid.valid && valid.result.success !== false) {
|
||||
exampleData = valid.result
|
||||
break
|
||||
if (valid.valid) {
|
||||
|
||||
// Check if array, and if first item is object + success
|
||||
if (Array.isArray(valid.result) && valid.result.length > 0 && typeof valid.result[0] === "object") {
|
||||
if (valid.result[0].success !== false) {
|
||||
exampleData = valid.result[0]
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if (valid.result.success !== false) {
|
||||
exampleData = valid.result
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
secondaryExample = foundResult.result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -818,6 +866,10 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exampleData === "" && secondaryExample !== "") {
|
||||
exampleData = secondaryExample
|
||||
}
|
||||
|
||||
if (parentNode.label === undefined) {
|
||||
@@ -1162,7 +1214,9 @@ const ParsedAction = (props) => {
|
||||
selectedAction.parameters[1].value = splitparsed[1]
|
||||
|
||||
if (splitparsed.length > 2) {
|
||||
toast.warn("Filter list only supports filtering at the first level. If you want multi-level filtering, please use the 'execute python' action with the 'filter a list' function in the code editor.")
|
||||
toast.warn("Filter list only supports filtering on the first list. If you want multi-level filtering, please use the 'execute python' action with the 'filter a list' function in the code editor.", {
|
||||
autoClose: 10000,
|
||||
})
|
||||
} else if (selectedAction.parameters[1].value.includes(".#")) {
|
||||
toast.warn("This filter may not work due to using .# indexing. Please use the 'execute python' action and try the 'filter a list' function in the code editor.")
|
||||
}
|
||||
@@ -1814,6 +1868,38 @@ const ParsedAction = (props) => {
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography variant="body2" style={{margin: 3, }}>
|
||||
Rerun this action with results from previous executions. Built for testing individual actions in the middle of workflows.
|
||||
</Typography>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
marginLeft: 115,
|
||||
textTransform: "none",
|
||||
}}
|
||||
disabled={autoCompleting}
|
||||
onClick={() => {
|
||||
if (runFromHere !== undefined) {
|
||||
runFromHere(selectedAction)
|
||||
} else {
|
||||
toast.error("Function not available. Please contact support@shuffler.io")
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PlayArrowIcon style={{marginRight: 5, }}/>
|
||||
Rerun
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{(selectedAction?.generated === true && selectedAction?.app_version === "1.0.0") || (selectedAction?.app_name === "Shuffle Tools" && selectedAction?.app_version !== "1.2.0") ?
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -1875,12 +1961,12 @@ const ParsedAction = (props) => {
|
||||
toast.success("Changed version of all nodes to " + event.target.value)
|
||||
}}
|
||||
style={{
|
||||
marginTop: 10,
|
||||
position: "absolute",
|
||||
top: 10, right: 10,
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
height: 35,
|
||||
marginleft: 10,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
}}
|
||||
SelectDisplayProps={{
|
||||
@@ -4120,15 +4206,12 @@ const ParsedAction = (props) => {
|
||||
fullWidth
|
||||
id={"rightside_field_" + count}
|
||||
onChange={(e) => {
|
||||
console.log("MULTI SELECT: ", multi, e.target.value)
|
||||
|
||||
changeActionParameter(e, count, data);
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
color: "white",
|
||||
height: "50px",
|
||||
backgroundColor: theme.palette.platformColor,
|
||||
height: 40,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
}}
|
||||
>
|
||||
@@ -4525,6 +4608,7 @@ const ParsedAction = (props) => {
|
||||
minWidth: 250,
|
||||
maxWidth: 250,
|
||||
marginRight: 0,
|
||||
paddingLeft: 12,
|
||||
}}
|
||||
value={innerdata}
|
||||
onMouseOver={() => handleMouseover()}
|
||||
@@ -4685,8 +4769,9 @@ const ParsedAction = (props) => {
|
||||
</Tooltip>
|
||||
: null}
|
||||
|
||||
<Tooltip title="Expand editor window" placement="top">
|
||||
|
||||
{((data.options !== undefined && data.options !== null && data.options.length > 0) || (selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0)) ? null :
|
||||
<Tooltip title="Expand editor window" placement="top">
|
||||
<OpenInFullIcon
|
||||
style={{ color: "rgba(255,255,255,0.7)", cursor: "pointer", margin: multiline ? 5 : 0, height: 20, width: 20, }}
|
||||
onMouseOver={(event) => {
|
||||
@@ -4728,6 +4813,7 @@ const ParsedAction = (props) => {
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
@@ -4737,6 +4823,7 @@ const ParsedAction = (props) => {
|
||||
showDropdownNumber === count &&
|
||||
data.variant === "STATIC_VALUE" &&
|
||||
jsonList.length > 0 ? (
|
||||
|
||||
<FormControl fullWidth style={{ marginTop: 0 }}>
|
||||
<InputLabel
|
||||
id="action-autocompleter"
|
||||
|
||||
@@ -837,12 +837,13 @@ const NotificationItem = memo((props) => {
|
||||
color="secondary"
|
||||
style={{
|
||||
height: 50,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={() => {
|
||||
dismissNotification(data.id);
|
||||
}}
|
||||
>
|
||||
Mark Read
|
||||
Mark as Read
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
DragIndicator as DragIndicatorIcon,
|
||||
RestartAlt as RestartAltIcon,
|
||||
ArrowForward as ArrowForwardIcon,
|
||||
KeyboardReturn as KeyboardReturnIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
|
||||
@@ -94,9 +95,12 @@ const pythonFilters = [
|
||||
{ "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 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": "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": "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 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, },
|
||||
|
||||
|
||||
]
|
||||
|
||||
@@ -299,8 +303,19 @@ const CodeEditor = (props) => {
|
||||
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
|
||||
useEffect(() => {
|
||||
document.addEventListener("keydown", handleKeyDown)
|
||||
updateAvailableVariables(actionlist)
|
||||
}, [])
|
||||
|
||||
@@ -887,6 +902,8 @@ const CodeEditor = (props) => {
|
||||
// Whelp this is inefficient af. Single loop pls
|
||||
// When the found array is empty.
|
||||
if (found !== null && found !== undefined) {
|
||||
|
||||
//console.log("FOUND: ", found)
|
||||
try {
|
||||
for (var i = 0; i < found.length; i++) {
|
||||
try {
|
||||
@@ -921,28 +938,72 @@ 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++) {
|
||||
if (fixedVariable.slice(1,).toLowerCase() !== actionlist[j].autocomplete.toLowerCase()) {
|
||||
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
|
||||
var newvalue = ""
|
||||
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("[")) {
|
||||
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
|
||||
|
||||
newvalue = JSON.stringify(actionlist[j].example)
|
||||
} else {
|
||||
const newExample = fixStringInput(actionlist[j].example)
|
||||
input = input.replace(found[i], newExample, -1)
|
||||
|
||||
newvalue = newExample
|
||||
}
|
||||
} 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) {
|
||||
@@ -1721,13 +1782,13 @@ const CodeEditor = (props) => {
|
||||
color="secondary"
|
||||
style={{
|
||||
textTransform: "none",
|
||||
width: 120,
|
||||
width: 145,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
setAnchorEl3(event.currentTarget);
|
||||
}}
|
||||
>
|
||||
Python Code
|
||||
Python Examples
|
||||
</Button>
|
||||
<Button
|
||||
id="basic-button"
|
||||
@@ -1762,7 +1823,12 @@ const CodeEditor = (props) => {
|
||||
>
|
||||
{pythonFilters.map((item, index) => {
|
||||
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) {
|
||||
toast.error("This feature may not work in your environment until you update your Shuffle Tools app.", { autoClose: 10000 })
|
||||
}
|
||||
@@ -2270,6 +2336,7 @@ const CodeEditor = (props) => {
|
||||
<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">
|
||||
<Button
|
||||
id="try-it-button"
|
||||
variant="outlined"
|
||||
disabled={executing}
|
||||
color="primary"
|
||||
@@ -2283,11 +2350,13 @@ const CodeEditor = (props) => {
|
||||
zIndex: 1200,
|
||||
fontWeight: 500,
|
||||
fontSize: 14,
|
||||
textTransform: "none",
|
||||
backgroundColor: "rgba(33, 33, 33, 0.95)",
|
||||
backdropFilter: "blur(8px)",
|
||||
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)",
|
||||
transition: "all 0.2s ease",
|
||||
borderRadius: "4px",
|
||||
paddingRight: 20,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(45, 45, 45, 0.95)",
|
||||
transform: "translateY(-1px)",
|
||||
@@ -2304,7 +2373,23 @@ const CodeEditor = (props) => {
|
||||
{executing ?
|
||||
<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 ? "Try it" : 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>
|
||||
</Tooltip>
|
||||
|
||||
@@ -30,6 +30,8 @@ import {
|
||||
Apps as AppsIcon,
|
||||
Business as BusinessIcon,
|
||||
Flag,
|
||||
ArrowDropDown as ArrowDropDownIcon,
|
||||
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -54,9 +56,12 @@ const TenantsTab = memo((props) => {
|
||||
const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false);
|
||||
const [modalOpen, setModalOpen] = React.useState(false);
|
||||
const [parentOrg, setParentOrg] = React.useState(null);
|
||||
const [parentOrgFlag, setParentOrgFlag] = React.useState(null);
|
||||
const [parentOrgFlag, setParentOrgFlag] = React.useState("gb");
|
||||
const [parentOrgRegionName, setParentOrgRegionName] = React.useState("UK");
|
||||
const [loadOrgs, setLoadOrgs] = React.useState(true);
|
||||
const [, forceUpdate] = React.useState();
|
||||
const [suborglistOpen, setSuborglistOpen] = React.useState(false);
|
||||
const [allTenantsOpen, setAllTenantsOpen] = React.useState(false);
|
||||
const itemColor = "black";
|
||||
|
||||
useEffect(() => {
|
||||
@@ -82,9 +87,10 @@ const TenantsTab = memo((props) => {
|
||||
}
|
||||
}
|
||||
setParentOrgFlag(regionCode);
|
||||
setParentOrgRegionName(regiontag);
|
||||
}
|
||||
}
|
||||
}, [parentOrg]);
|
||||
}, [parentOrg, parentOrgFlag]);
|
||||
|
||||
var syncList = [
|
||||
{
|
||||
@@ -164,7 +170,8 @@ const TenantsTab = memo((props) => {
|
||||
regionCode = "ca";
|
||||
}
|
||||
}
|
||||
setParentOrgFlag(regionCode);
|
||||
setParentOrgFlag(regionCode);
|
||||
setParentOrgRegionName(regiontag);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -479,8 +486,8 @@ const TenantsTab = memo((props) => {
|
||||
|
||||
const createSubOrg = (currentOrgId, name) => {
|
||||
const data = { name: name, org_id: currentOrgId };
|
||||
console.log(data);
|
||||
const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org`;
|
||||
setSuborglistOpen(true)
|
||||
|
||||
fetch(url, {
|
||||
mode: "cors",
|
||||
@@ -788,7 +795,7 @@ const TenantsTab = memo((props) => {
|
||||
Create, manage and change to sub-organizations (tenants)! {" "}
|
||||
{isCloud
|
||||
? "You can only make a sub organization if you are a customer of shuffle or running a POC of the platform. Please contact support@shuffler.io to try it out."
|
||||
: ''}
|
||||
: ''}
|
||||
<a
|
||||
href="/docs/organizations"
|
||||
target="_blank"
|
||||
@@ -1026,7 +1033,7 @@ const TenantsTab = memo((props) => {
|
||||
src={`https://flagcdn.com/w20/${parentOrgFlag}.png`}
|
||||
style={{ width: "30px", height: "20px", marginRight: "5px" }}
|
||||
/>
|
||||
<ListItemText primary={parentOrgFlag?.toUpperCase()} />
|
||||
<ListItemText primary={parentOrgRegionName?.toUpperCase()} />
|
||||
</div>
|
||||
}
|
||||
style={{ display: "table-cell", padding: 8, verticalAlign: "middle" }}
|
||||
@@ -1147,168 +1154,212 @@ const TenantsTab = memo((props) => {
|
||||
overflowX: "auto",
|
||||
paddingBottom: 0,
|
||||
}}>
|
||||
<ListItem
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "table-row",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
verticalAlign: "middle",
|
||||
}}>
|
||||
<ListItemText
|
||||
primary="Logo"
|
||||
style={{
|
||||
width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
paddingLeft: 20,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
textAlign: "center",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}} />
|
||||
<ListItemText
|
||||
primary="Name"
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center",
|
||||
}} />
|
||||
{isCloud && (
|
||||
<ListItemText
|
||||
primary="Region"
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
display: "table-cell",
|
||||
borderBottom: "1px solid #494949",
|
||||
padding: "0px 8px 8px 8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ListItemText
|
||||
primary="id"
|
||||
style={{
|
||||
minWidth: 400,
|
||||
maxWidth: 400,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Action"
|
||||
style={{
|
||||
minWidth: 400,
|
||||
maxWidth: 400,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
{subOrgs.map((data, index) => {
|
||||
let regiontag = "UK";
|
||||
let regionCode = "gb";
|
||||
{!suborglistOpen ?
|
||||
<ListItem
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "table-row",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
verticalAlign: "middle",
|
||||
itemAlign: "center",
|
||||
|
||||
if (data.region_url?.length > 0) {
|
||||
const regionsplit = data.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";
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Button
|
||||
fullWidth
|
||||
variant="secondary"
|
||||
style={{
|
||||
textTransform: 'none',
|
||||
}}
|
||||
onClick={() => setSuborglistOpen(true)}
|
||||
>
|
||||
Show Sub-Organizations <ArrowDropDownIcon />
|
||||
</Button>
|
||||
}
|
||||
style={{
|
||||
width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
paddingLeft: 20,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
textAlign: "center",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
:
|
||||
<span>
|
||||
<ListItem
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "table-row",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
verticalAlign: "middle",
|
||||
}}>
|
||||
<ListItemText
|
||||
primary="Logo"
|
||||
style={{
|
||||
width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
paddingLeft: 20,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
textAlign: "center",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}} />
|
||||
<ListItemText
|
||||
primary="Name"
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center",
|
||||
}} />
|
||||
{isCloud && (
|
||||
<ListItemText
|
||||
primary="Region"
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
display: "table-cell",
|
||||
borderBottom: "1px solid #494949",
|
||||
padding: "0px 8px 8px 8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ListItemText
|
||||
primary="id"
|
||||
style={{
|
||||
minWidth: 400,
|
||||
maxWidth: 400,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Action"
|
||||
style={{
|
||||
minWidth: 400,
|
||||
maxWidth: 400,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
{subOrgs.map((data, index) => {
|
||||
let regiontag = "UK";
|
||||
let regionCode = "gb";
|
||||
|
||||
if (data.region_url?.length > 0) {
|
||||
const regionsplit = data.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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={index} style={{ backgroundColor: index % 2 === 0 ? '#1A1A1A' : '#212121', width: "100%", borderBottomLeftRadius: 8, display:'table-row', borderBottomRightRadius: 8 }}>
|
||||
<ListItemText primary={<img alt={data?.name} src={data.image || theme.palette.defaultImage} style={imageStyle} />} style={{ width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
display: "table-cell",
|
||||
padding: "8px 8px 8px 20px",
|
||||
textAlign: "center", }} />
|
||||
<ListItemText primary={data.name} style={{ minWidth: 100,
|
||||
maxWidth: 300,
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center", }} />
|
||||
return (
|
||||
<ListItem key={index} style={{ backgroundColor: index % 2 === 0 ? '#1A1A1A' : '#212121', width: "100%", borderBottomLeftRadius: 8, display:'table-row', borderBottomRightRadius: 8 }}>
|
||||
<ListItemText primary={<img alt={data?.name} src={data.image || theme.palette.defaultImage} style={imageStyle} />} style={{ width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
display: "table-cell",
|
||||
padding: "8px 8px 8px 20px",
|
||||
textAlign: "center", }} />
|
||||
<ListItemText primary={data.name} style={{ minWidth: 100,
|
||||
maxWidth: 300,
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center", }} />
|
||||
|
||||
{isCloud && (
|
||||
<ListItemText
|
||||
primary={
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
alt={regiontag}
|
||||
src={`https://flagcdn.com/w20/${regionCode}.png`}
|
||||
style={{ width: "30px", height: "20px", marginRight: "5px" }}
|
||||
/>
|
||||
<ListItemText primary={regiontag?.toUpperCase()} />
|
||||
</div>
|
||||
}
|
||||
style={{ display: "table-cell", padding: 8, verticalAlign: "middle" }}
|
||||
/>
|
||||
)}
|
||||
<ListItemText primary={data.id} style={{ minWidth: 300,
|
||||
maxWidth: 300,
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle", }} />
|
||||
{isCloud && (
|
||||
<ListItemText
|
||||
primary={
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
alt={regiontag}
|
||||
src={`https://flagcdn.com/w20/${regionCode}.png`}
|
||||
style={{ width: "30px", height: "20px", marginRight: "5px" }}
|
||||
/>
|
||||
<ListItemText primary={regiontag?.toUpperCase()} />
|
||||
</div>
|
||||
}
|
||||
style={{ display: "table-cell", padding: 8, verticalAlign: "middle" }}
|
||||
/>
|
||||
)}
|
||||
<ListItemText primary={data.id} style={{ minWidth: 300,
|
||||
maxWidth: 300,
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle", }} />
|
||||
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip title={data.id === userdata?.active_org?.id ? "You are already in this organization." : ""} disableInteractive>
|
||||
<Button
|
||||
color="primary"
|
||||
variant='outlined'
|
||||
disabled={data.id === userdata?.active_org?.id}
|
||||
onClick={() => {
|
||||
handleClickChangeOrg(data.id);
|
||||
}}
|
||||
sx={{
|
||||
boxShadow:"none", textTransform:"capitalize",
|
||||
fontSize:16,
|
||||
'&.Mui-disabled': {
|
||||
backgroundColor: 'rgba(200, 200, 200, 0.5)',
|
||||
color: '#A9A9A9',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Change Active Org
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
style={{ display: "table-cell", verticalAlign: "middle" }}
|
||||
/>
|
||||
</ListItem>
|
||||
)})}
|
||||
|
||||
</span>}
|
||||
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip title={data.id === userdata?.active_org?.id ? "You are already in this organization." : ""} disableInteractive>
|
||||
<Button
|
||||
color="primary"
|
||||
variant='outlined'
|
||||
disabled={data.id === userdata?.active_org?.id}
|
||||
onClick={() => {
|
||||
handleClickChangeOrg(data.id);
|
||||
}}
|
||||
sx={{
|
||||
boxShadow:"none", textTransform:"capitalize",
|
||||
fontSize:16,
|
||||
'&.Mui-disabled': {
|
||||
backgroundColor: 'rgba(200, 200, 200, 0.5)',
|
||||
color: '#A9A9A9',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Change Active Org
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
style={{ display: "table-cell", verticalAlign: "middle" }}
|
||||
/>
|
||||
</ListItem>
|
||||
)})}
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1363,247 +1414,289 @@ const TenantsTab = memo((props) => {
|
||||
paddingBottom: 0,
|
||||
}}
|
||||
>
|
||||
<ListItem
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "table-row",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary="Logo"
|
||||
style={{
|
||||
width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
paddingLeft: 20,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
textAlign: "center",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Name"
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center",
|
||||
}}
|
||||
/>
|
||||
{isCloud && (
|
||||
<ListItemText
|
||||
primary="Region"
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
display: "table-cell",
|
||||
borderBottom: "1px solid #494949",
|
||||
padding: "0px 8px 8px 8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ListItemText
|
||||
primary="id"
|
||||
style={{
|
||||
minWidth: 400,
|
||||
maxWidth: 400,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Action"
|
||||
style={{
|
||||
minWidth: 400,
|
||||
maxWidth: 400,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
{!allTenantsOpen ?
|
||||
<ListItem
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "table-row",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
verticalAlign: "middle",
|
||||
itemAlign: "center",
|
||||
|
||||
{userdata?.orgs?.length <= 0 ? (
|
||||
[...Array(6)].map((_, rowIndex) => (
|
||||
<ListItem
|
||||
key={rowIndex}
|
||||
style={{
|
||||
display: "table-row",
|
||||
backgroundColor: "#212121",
|
||||
}}
|
||||
>
|
||||
{Array(7)
|
||||
.fill()
|
||||
.map((_, colIndex) => (
|
||||
<ListItemText
|
||||
key={colIndex}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: "8px",
|
||||
}}
|
||||
>
|
||||
<Skeleton
|
||||
variant="text"
|
||||
animation="wave"
|
||||
sx={{
|
||||
backgroundColor: "#1a1a1a",
|
||||
height: "20px",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
/>
|
||||
</ListItemText>
|
||||
))}
|
||||
</ListItem>
|
||||
))
|
||||
) : (
|
||||
userdata?.orgs?.length > 0 &&
|
||||
userdata.orgs.map((data, index) => {
|
||||
let regiontag = "UK";
|
||||
let regionCode = "gb";
|
||||
|
||||
if (data.region_url?.length > 0) {
|
||||
const regionsplit = data.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";
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Button
|
||||
fullWidth
|
||||
variant="secondary"
|
||||
style={{
|
||||
textTransform: 'none',
|
||||
}}
|
||||
onClick={() => setAllTenantsOpen(true)}
|
||||
>
|
||||
Show ALL your tenants <ArrowDropDownIcon />
|
||||
</Button>
|
||||
}
|
||||
}
|
||||
}
|
||||
style={{
|
||||
width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
paddingLeft: 20,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
textAlign: "center",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
:
|
||||
<span>
|
||||
<ListItem
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "table-row",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary="Logo"
|
||||
style={{
|
||||
width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
paddingLeft: 20,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
textAlign: "center",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Name"
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center",
|
||||
}}
|
||||
/>
|
||||
{isCloud && (
|
||||
<ListItemText
|
||||
primary="Region"
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
display: "table-cell",
|
||||
borderBottom: "1px solid #494949",
|
||||
padding: "0px 8px 8px 8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ListItemText
|
||||
primary="id"
|
||||
style={{
|
||||
minWidth: 400,
|
||||
maxWidth: 400,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Action"
|
||||
style={{
|
||||
minWidth: 400,
|
||||
maxWidth: 400,
|
||||
display: "table-cell",
|
||||
padding: "0px 8px 8px 8px",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
borderBottom: "1px solid #494949",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={index}
|
||||
style={{
|
||||
display: "table-row",
|
||||
verticalAlign: "middle",
|
||||
padding: 8,
|
||||
backgroundColor: index % 2 === 0 ? "#1A1A1A" : "#212121",
|
||||
borderBottomLeftRadius:
|
||||
userdata?.orgs?.length - 1 === index ? 8 : 0,
|
||||
borderBottomRightRadius:
|
||||
userdata?.orgs?.length - 1 === index ? 8 : 0,
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<img
|
||||
alt={data.name}
|
||||
src={data.image || theme.palette.defaultImage}
|
||||
style={imageStyle}
|
||||
/>
|
||||
}
|
||||
style={{
|
||||
width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
display: "table-cell",
|
||||
padding: "8px 8px 8px 20px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data?.name}
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center",
|
||||
}}
|
||||
></ListItemText>
|
||||
{isCloud ? (
|
||||
<ListItemText
|
||||
primary={
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
alt={regiontag}
|
||||
src={`https://flagcdn.com/w20/${regionCode}.png`}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
{userdata?.orgs?.length <= 0 ? (
|
||||
[...Array(6)].map((_, rowIndex) => (
|
||||
<ListItem
|
||||
key={rowIndex}
|
||||
style={{
|
||||
display: "table-row",
|
||||
backgroundColor: "#212121",
|
||||
}}
|
||||
>
|
||||
{Array(7)
|
||||
.fill()
|
||||
.map((_, colIndex) => (
|
||||
<ListItemText
|
||||
key={colIndex}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: "8px",
|
||||
}}
|
||||
>
|
||||
<Skeleton
|
||||
variant="text"
|
||||
animation="wave"
|
||||
sx={{
|
||||
backgroundColor: "#1a1a1a",
|
||||
height: "20px",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
/>
|
||||
</ListItemText>
|
||||
))}
|
||||
</ListItem>
|
||||
))
|
||||
) : (
|
||||
userdata?.orgs?.length > 0 &&
|
||||
userdata.orgs.map((data, index) => {
|
||||
let regiontag = "UK";
|
||||
let regionCode = "gb";
|
||||
|
||||
<ListItemText primary={regiontag} />
|
||||
</div>
|
||||
}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
></ListItemText>
|
||||
) : null}
|
||||
<ListItemText
|
||||
primary={data.id}
|
||||
style={{
|
||||
minWidth: 300,
|
||||
maxWidth: 300,
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Button
|
||||
variant="outlined"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
textTransform: "none",
|
||||
fontSize: 16,
|
||||
}}
|
||||
disabled={data?.id === userdata?.active_org?.id}
|
||||
onClick={() => {
|
||||
handleClickChangeOrg(data?.id);
|
||||
}}
|
||||
>
|
||||
Change Active Org
|
||||
</Button>
|
||||
}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
></ListItemText>
|
||||
</ListItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
if (data.region_url?.length > 0) {
|
||||
const regionsplit = data.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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={index}
|
||||
style={{
|
||||
display: "table-row",
|
||||
verticalAlign: "middle",
|
||||
padding: 8,
|
||||
backgroundColor: index % 2 === 0 ? "#1A1A1A" : "#212121",
|
||||
borderBottomLeftRadius:
|
||||
userdata?.orgs?.length - 1 === index ? 8 : 0,
|
||||
borderBottomRightRadius:
|
||||
userdata?.orgs?.length - 1 === index ? 8 : 0,
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<img
|
||||
alt={data.name}
|
||||
src={data.image || theme.palette.defaultImage}
|
||||
style={imageStyle}
|
||||
/>
|
||||
}
|
||||
style={{
|
||||
width: 100,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
display: "table-cell",
|
||||
padding: "8px 8px 8px 20px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data?.name}
|
||||
style={{
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center",
|
||||
}}
|
||||
></ListItemText>
|
||||
{isCloud ? (
|
||||
<ListItemText
|
||||
primary={
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
alt={regiontag}
|
||||
src={`https://flagcdn.com/w20/${regionCode}.png`}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
|
||||
<ListItemText primary={regiontag} />
|
||||
</div>
|
||||
}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
></ListItemText>
|
||||
) : null}
|
||||
<ListItemText
|
||||
primary={data.id}
|
||||
style={{
|
||||
minWidth: 300,
|
||||
maxWidth: 300,
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Button
|
||||
variant="outlined"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
textTransform: "none",
|
||||
fontSize: 16,
|
||||
}}
|
||||
disabled={data?.id === userdata?.active_org?.id}
|
||||
onClick={() => {
|
||||
handleClickChangeOrg(data?.id);
|
||||
}}
|
||||
>
|
||||
Change Active Org
|
||||
</Button>
|
||||
}
|
||||
style={{
|
||||
display: "table-cell",
|
||||
padding: 8,
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
></ListItemText>
|
||||
</ListItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</span>}
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -248,6 +248,14 @@ const UserManagmentTab = memo((props) => {
|
||||
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);
|
||||
setMatchingOrganizations(event.target.value);
|
||||
// Workaround for empty orgs
|
||||
@@ -286,6 +294,14 @@ const UserManagmentTab = memo((props) => {
|
||||
}}
|
||||
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) => (
|
||||
<MenuItem key={index} value={org.id}>
|
||||
<Checkbox checked={matchingOrganizations.indexOf(org.id) > -1} />
|
||||
|
||||
@@ -608,8 +608,6 @@ const WorkflowValidationTimeline = (props) => {
|
||||
const ballsize = 8
|
||||
const topMargin = 20
|
||||
|
||||
console.log("CHIP: ", index, chipColor, chipBackground)
|
||||
|
||||
const chipStyle = {
|
||||
height: 40,
|
||||
minWidth: 125,
|
||||
|
||||
@@ -95,23 +95,56 @@ const data = [
|
||||
{
|
||||
selector: `node[type="COMMENT"]`,
|
||||
css: {
|
||||
label: function(element) {
|
||||
return element.data("label")
|
||||
},
|
||||
label: function (element) {
|
||||
return element.data("label")
|
||||
},
|
||||
shape: "roundrectangle",
|
||||
color: "data(color)",
|
||||
width: "data(width)",
|
||||
height: "data(height)",
|
||||
padding: "0px",
|
||||
padding: "5px",
|
||||
margin: "0px",
|
||||
"background-color": "data(backgroundcolor)",
|
||||
"background-image": "data(backgroundimage)",
|
||||
"border-color": "#ffffff",
|
||||
"text-margin-x": "0px",
|
||||
"text-margin-x": "data(textMarginX)",
|
||||
"text-margin-y": "data(textMarginY)",
|
||||
"z-index": 4999,
|
||||
"border-radius": "5px",
|
||||
"background-opacity": "0.5",
|
||||
"text-wrap": "wrap",
|
||||
"text-wrap": "wrap",
|
||||
"text-max-width": "data(width)",
|
||||
"text-halign": function(element) {
|
||||
const align = element?.data("textHalign")
|
||||
if (align === null || align === undefined || align === "") {
|
||||
return "center"
|
||||
}
|
||||
|
||||
return align
|
||||
},
|
||||
"text-valign": function(element) {
|
||||
const align = element?.data("textValign")
|
||||
if (align === null || align === undefined || align === "") {
|
||||
return "center"
|
||||
}
|
||||
|
||||
return align
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[type="RESIZE-HANDLE"]`,
|
||||
css: {
|
||||
shape: "ellipse",
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
"border-width": 1,
|
||||
"border-color": "white",
|
||||
"z-index": 5002,
|
||||
"overlay-opacity": 0,
|
||||
"cursor": "nwse-resize",
|
||||
"opacity": 0,
|
||||
"pointer-events": "auto",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+11
-11
@@ -27,7 +27,7 @@ const theme = createTheme(adaptV4Theme({
|
||||
distributionColor: "#40E0D0",
|
||||
|
||||
green: "#5cc879",
|
||||
borderRadius: 10,
|
||||
borderRadius: 8,
|
||||
defaultBorder: "1px solid rgba(255,255,255,0.3)",
|
||||
|
||||
//jsonTheme: "brewer",
|
||||
@@ -112,39 +112,39 @@ const theme = createTheme(adaptV4Theme({
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: `
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light');
|
||||
src: local('Inter Light'), local('Inter-Light');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular');
|
||||
src: local('Inter Regular'), local('Inter-Regular');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium');
|
||||
src: local('Inter Medium'), local('Inter-Medium');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: local('Roboto SemiBold'), local('Roboto-SemiBold');
|
||||
src: local('Inter SemiBold'), local('Inter-SemiBold');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold');
|
||||
src: local('Inter Bold'), local('Inter-Bold');
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -76,6 +76,18 @@ const Admin2 = (props) => {
|
||||
leads.push("tech partner");
|
||||
}
|
||||
|
||||
if (responseJson.lead_info.integration_partner) {
|
||||
leads.push("integration partner");
|
||||
}
|
||||
|
||||
if (responseJson.lead_info.distribution_partner) {
|
||||
leads.push("distribution partner");
|
||||
}
|
||||
|
||||
if (responseJson.lead_info.service_partner) {
|
||||
leads.push("service partner");
|
||||
}
|
||||
|
||||
if (responseJson.lead_info.creator) {
|
||||
leads.push("creator");
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -943,8 +943,8 @@ const AppCreator = (defaultprops) => {
|
||||
if (newaction.url !== undefined && newaction.url !== null && newaction.url.includes("_shuffle_replace_")) {
|
||||
//const regex = /_shuffle_replace_\d/i;
|
||||
const regex = /_shuffle_replace_\d+/i
|
||||
|
||||
newaction.url = newaction.url.replaceAll(new RegExp(regex, 'g'), "")
|
||||
const newurl = newaction.url.replaceAll(new RegExp(regex, 'g'), "")
|
||||
newaction.url = newurl
|
||||
}
|
||||
|
||||
// Finding category
|
||||
@@ -956,7 +956,6 @@ const AppCreator = (defaultprops) => {
|
||||
if (pathsplit[splitkey].includes("_shuffle_replace_")) {
|
||||
//const regex = /_shuffle_replace_\d/i;
|
||||
const regex = /_shuffle_replace_\d+/i
|
||||
//console.log("NEW: ",
|
||||
pathsplit[splitkey] = pathsplit[splitkey].replaceAll(new RegExp(regex, 'g'), "")
|
||||
}
|
||||
|
||||
@@ -2037,7 +2036,7 @@ const AppCreator = (defaultprops) => {
|
||||
for (let actionkey in actions) {
|
||||
var item = JSON.parse(JSON.stringify(actions[actionkey]))
|
||||
if (item.errors.length > 0) {
|
||||
toast("Saving with error in action " + item.name);
|
||||
//toast("Saving with error in action " + item.name);
|
||||
}
|
||||
|
||||
if (item.name === undefined && item.description !== undefined) {
|
||||
@@ -3858,7 +3857,7 @@ const AppCreator = (defaultprops) => {
|
||||
if (currentAction.url === "" && actions !== undefined && actions !== null && actions.length > 0) {
|
||||
for (var i = 0; i < actions.length; i++) {
|
||||
if (actions[i].name.toLowerCase() === e.target.value.toLowerCase()) {
|
||||
toast("Action with name " + e.target.value + " already exists. If you keep this, it will be overwritten.")
|
||||
//toast("Action with name " + e.target.value + " already exists. If you keep this, it will be overwritten.")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1088,14 +1088,13 @@ const AppExplorer = (props) => {
|
||||
setNewWorkflowTags(newWorkflowTags);
|
||||
}
|
||||
|
||||
// This is annoying (:
|
||||
var securitySchemes = data.components.securityDefinitions;
|
||||
var securitySchemes = data?.components?.securityDefinitions;
|
||||
if (securitySchemes === undefined) {
|
||||
securitySchemes = data.securitySchemes;
|
||||
securitySchemes = data?.securitySchemes;
|
||||
}
|
||||
|
||||
if (securitySchemes === undefined) {
|
||||
securitySchemes = data.components.securitySchemes;
|
||||
securitySchemes = data?.components?.securitySchemes;
|
||||
}
|
||||
|
||||
const allowedfunctions = [
|
||||
@@ -1681,7 +1680,7 @@ const AppExplorer = (props) => {
|
||||
setExecutionResult({
|
||||
valid: false,
|
||||
result:
|
||||
"Couldn't finish execution (2). Please fill all the required fields, and validate the execution.",
|
||||
"Couldn't finish execution OR no result was returned (2). Please fill all the required fields, and validate the execution.",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -343,10 +343,15 @@ const Docs = (defaultprops) => {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("PROPKEY: ", propkey)
|
||||
if (location.pathname.includes("/docs/")) {
|
||||
if (propkey === "cookie_policy" || propkey === "compliance" || propkey === "privacy_policy" || propkey === "terms_of_service") {
|
||||
navigate(`/legal/${propkey}`)
|
||||
}
|
||||
|
||||
if (propkey === "app_creation") {
|
||||
navigate('/docs/apps#app-creation-introduction')
|
||||
}
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
@@ -932,7 +937,7 @@ const Docs = (defaultprops) => {
|
||||
}
|
||||
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.includes("404: Not Found") && !isArticlePage) {
|
||||
navigate("/docs")
|
||||
//navigate("/docs")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1109,6 +1114,7 @@ const Docs = (defaultprops) => {
|
||||
marginTop: 25,
|
||||
}
|
||||
|
||||
const showPartnerLogo = userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.image !== undefined && userdata?.active_org?.image !== null && userdata?.active_org?.image.length > 0
|
||||
const mainpageInfo =
|
||||
<div style={{
|
||||
color: "rgba(255, 255, 255, 0.65)",
|
||||
@@ -1124,10 +1130,12 @@ const Docs = (defaultprops) => {
|
||||
<Typography variant="h4" style={{ textAlign: "center", marginTop: 20 }}>
|
||||
Documentation
|
||||
</Typography>
|
||||
<div style={{ display: "flex", marginTop: 25, }}>
|
||||
<CustomButton title="Talk to Support" icon=<img src="/images/Shuffle_logo_new.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette?.borderRadius, }} /> />
|
||||
<CustomButton title="Ask the community" icon=<img src="/images/social/discord.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette?.borderRadius, }} /> link="https://discord.gg/B2CBzUm" />
|
||||
</div>
|
||||
{showPartnerLogo === true ? null :
|
||||
<div style={{ display: "flex", marginTop: 25, }}>
|
||||
<CustomButton title="Talk to Support" icon=<img src="/images/Shuffle_logo_new.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette?.borderRadius, }} /> />
|
||||
<CustomButton title="Ask the community" icon=<img src="/images/social/discord.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette?.borderRadius, }} /> link="https://discord.gg/B2CBzUm" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div style={{ textAlign: "left" }}>
|
||||
<Typography variant="h6" style={headerStyle} >Tutorial</Typography>
|
||||
|
||||
@@ -323,9 +323,17 @@ const LoginPage = props => {
|
||||
|
||||
if (document !== undefined) {
|
||||
if (register) {
|
||||
document.title = "Login to Shuffle SaaS"
|
||||
if (isCloud) {
|
||||
document.title = "Login to Shuffle SaaS"
|
||||
} else {
|
||||
document.title = "Login to Shuffle"
|
||||
}
|
||||
} else {
|
||||
document.title = "Register to Shuffle SaaS"
|
||||
if (isCloud) {
|
||||
document.title = "Register to Shuffle SaaS"
|
||||
} else {
|
||||
document.title = "Register to Shuffle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +383,7 @@ const LoginPage = props => {
|
||||
padding: "40px",
|
||||
flex: 1,
|
||||
maxWidth: isMobile ? "100%" : 410,
|
||||
minWidth: 410,
|
||||
minWidth: isCloud ? 410 : 475,
|
||||
background: "#212121",
|
||||
borderRadius: "12px",
|
||||
display: "flex",
|
||||
|
||||
@@ -19,6 +19,7 @@ import RecentWorkflow from "../components/RecentWorkflow.jsx";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
Fade,
|
||||
Select,
|
||||
IconButton,
|
||||
CircularProgress,
|
||||
@@ -33,6 +34,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
MenuItem,
|
||||
Autocomplete,
|
||||
} from '@mui/material';
|
||||
|
||||
import {
|
||||
@@ -44,6 +46,7 @@ import {
|
||||
LockOpen as LockOpenIcon,
|
||||
OpenInNew as OpenInNewIcon,
|
||||
Edit as EditIcon,
|
||||
Polyline as PolylineIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
const hrefStyle = {
|
||||
@@ -74,6 +77,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
const [sharingOpen, setSharingOpen] = React.useState(false)
|
||||
const [realtimeMarkdown, setRealtimeMarkdown] = React.useState("")
|
||||
const [forms, setForms] = React.useState([])
|
||||
const [workflows, setWorkflows] = React.useState([])
|
||||
const [boxWidth, setBoxWidth] = React.useState(500)
|
||||
const [inputQuestions, setInputQuestions] = React.useState([])
|
||||
|
||||
@@ -180,6 +184,37 @@ const RunWorkflow = (defaultprops) => {
|
||||
return true
|
||||
}
|
||||
|
||||
const getWorkflows = () => {
|
||||
const url = `${globalUrl}/api/v1/workflows`
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for org forms");
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
toast.error("Failed saving workflow. Please try again.")
|
||||
} else {
|
||||
if (responseJson?.length > 0) {
|
||||
setWorkflows(responseJson)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
//toast.error("Load form error: " + error)
|
||||
})
|
||||
}
|
||||
|
||||
const loadForms = (orgId) => {
|
||||
const url = `${globalUrl}/api/v1/orgs/${orgId}/forms`
|
||||
fetch(url, {
|
||||
@@ -462,6 +497,8 @@ const RunWorkflow = (defaultprops) => {
|
||||
} else {
|
||||
console.log("Started execution")
|
||||
|
||||
start()
|
||||
setExecutionRunning(true);
|
||||
if (answer !== undefined && answer !== null) {
|
||||
console.log("Skipping start")
|
||||
} else {
|
||||
@@ -875,6 +912,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
// Just use this one?
|
||||
var url = execution_id !== undefined && authorization !== undefined ? `${globalUrl}/api/v1/orgs/${orgId}?reference_execution=${execution_id}&authorization=${authorization}` : `${globalUrl}/api/v1/orgs/${orgId}`;
|
||||
|
||||
getWorkflows()
|
||||
loadForms(orgId)
|
||||
|
||||
fetch(url, {
|
||||
@@ -1055,7 +1093,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
|
||||
if (parsedresult.click_info !== undefined && parsedresult.click_info !== null) {
|
||||
if (parsedresult.click_info.user !== undefined && parsedresult.click_info.user !== null && parsedresult.click_info.user.length > 0) {
|
||||
setMessage("Already answered by " + parsedresult.click_info.user)
|
||||
setMessage("Answered by " + parsedresult.click_info.user)
|
||||
|
||||
}
|
||||
} else {
|
||||
@@ -1149,10 +1187,100 @@ const RunWorkflow = (defaultprops) => {
|
||||
No Forms Found
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
<b>Every</b> Workflow is a form, and can be accessed by going to /forms/{`{workflow_id}`}. You can control the form by editing the workflow details in the "Forms" section.
|
||||
</Typography>
|
||||
<b>ALL</b> Workflows are forms, and can be accessed by going to /forms/{`{workflow_id}`}. You can control the form by editing the workflow details in the "Forms" section.
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
|
||||
{workflows === undefined || workflows === null || workflows.length === 0 ? null :
|
||||
<Autocomplete
|
||||
disabled={workflows === undefined || workflows === null || workflows.length === 0}
|
||||
id="form-workflow-search"
|
||||
autoHighlight
|
||||
value={""}
|
||||
ListboxProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
height: 40, // Adjust the input height
|
||||
},
|
||||
'& .MuiAutocomplete-input': {
|
||||
padding: '8px', // Adjust the text padding
|
||||
},
|
||||
}}
|
||||
getOptionSelected={(option, value) => option.id === value.id}
|
||||
getOptionLabel={(option) => {
|
||||
if (
|
||||
option === undefined ||
|
||||
option === null ||
|
||||
option.name === undefined ||
|
||||
option.name === null
|
||||
) {
|
||||
return "No Workflow Selected";
|
||||
}
|
||||
|
||||
const newname = (
|
||||
option.name.charAt(0).toUpperCase() + option.name.substring(1)
|
||||
).replaceAll("_", " ");
|
||||
return newname;
|
||||
}}
|
||||
options={workflows}
|
||||
fullWidth
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
marginTop: 75,
|
||||
}}
|
||||
renderOption={(props, data, state) => {
|
||||
if (data.id === workflow.id) {
|
||||
data = workflow;
|
||||
}
|
||||
|
||||
//key={index}
|
||||
return (
|
||||
<Tooltip arrow placement="left" title={
|
||||
<span style={{}}>
|
||||
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
||||
<img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, maxWidth: 285, borderRadius: theme.palette?.borderRadius, }} />
|
||||
: null}
|
||||
<Typography>
|
||||
Choose Subflow '{data.name}'
|
||||
</Typography>
|
||||
</span>
|
||||
}>
|
||||
<MenuItem
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
onClick={() => {
|
||||
window.location.href = `/forms/${data.id}`
|
||||
}}
|
||||
value={data}
|
||||
>
|
||||
<PolylineIcon style={{ marginRight: 8 }} />
|
||||
{data.name}
|
||||
</MenuItem>
|
||||
</Tooltip>
|
||||
)
|
||||
}}
|
||||
renderInput={(params) => {
|
||||
return (
|
||||
<div style={{ display: "flex", }}>
|
||||
<TextField
|
||||
style={theme.palette.textFieldStyle}
|
||||
{...params}
|
||||
label="Find your form"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1325,7 +1453,8 @@ const RunWorkflow = (defaultprops) => {
|
||||
})}
|
||||
</div>
|
||||
:
|
||||
answer !== undefined && answer !== null ? null :
|
||||
(answer !== undefined && answer !== null) || message !== "" ? null :
|
||||
|
||||
<span>
|
||||
Runtime Argument
|
||||
<div style={{marginBottom: 5}}>
|
||||
@@ -1334,7 +1463,13 @@ const RunWorkflow = (defaultprops) => {
|
||||
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
|
||||
multiLine
|
||||
maxRows={2}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
InputProps={{
|
||||
autocomplete: "off",
|
||||
form: {
|
||||
autocomplete: "off",
|
||||
},
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
@@ -1375,9 +1510,11 @@ const RunWorkflow = (defaultprops) => {
|
||||
{message}. You may close this window.
|
||||
</Typography>
|
||||
:
|
||||
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
|
||||
{disabledButtons ? "Answered. You may close this window." : ""}
|
||||
</Typography>
|
||||
<Fade in={true} timeout={2500}>
|
||||
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
|
||||
{disabledButtons ? "Answered. You may close this window." : ""}
|
||||
</Typography>
|
||||
</Fade>
|
||||
}
|
||||
|
||||
{disabledButtons ? null :
|
||||
@@ -1387,25 +1524,47 @@ const RunWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}>
|
||||
<Button fullWidth id="continue_execution" variant="contained" disabled={!handleValidateForm(executionArgument) || disabledButtons} color="primary" style={{flex: 1,}} onClick={() => {
|
||||
setButtonClicked("FINISHED")
|
||||
setExecutionData({
|
||||
status: "FINISHED",
|
||||
})
|
||||
<Button
|
||||
fullWidth
|
||||
id="continue_execution"
|
||||
variant="contained"
|
||||
disabled={!handleValidateForm(executionArgument) || disabledButtons}
|
||||
color="primary"
|
||||
style={{
|
||||
flex: 1,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={() => {
|
||||
setButtonClicked("FINISHED")
|
||||
setExecutionData({
|
||||
status: "FINISHED",
|
||||
})
|
||||
|
||||
onSubmit(null, execution_id, authorization, true)
|
||||
}}>Continue</Button>
|
||||
onSubmit(null, execution_id, authorization, true)
|
||||
}}>
|
||||
Continue</Button>
|
||||
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
|
||||
or
|
||||
</Typography>
|
||||
<Button fullWidth id="abort_execution" variant="contained" disabled={!handleValidateForm(executionArgument) || disabledButtons} color="primary" style={{ flex: 1, }} onClick={() => {
|
||||
setButtonClicked("ABORTED")
|
||||
setExecutionData({
|
||||
status: "ABORTED",
|
||||
})
|
||||
<Button
|
||||
fullWidth
|
||||
id="abort_execution"
|
||||
variant="outlined"
|
||||
disabled={!handleValidateForm(executionArgument) || disabledButtons}
|
||||
color="primary"
|
||||
style={{
|
||||
flex: 1,
|
||||
textTransform: "none",
|
||||
}} onClick={() => {
|
||||
setButtonClicked("ABORTED")
|
||||
setExecutionData({
|
||||
status: "ABORTED",
|
||||
})
|
||||
|
||||
onSubmit(null, execution_id, authorization, false)
|
||||
}}>Stop</Button>
|
||||
onSubmit(null, execution_id, authorization, false)
|
||||
}}>
|
||||
Stop
|
||||
</Button>
|
||||
</div>
|
||||
</span>
|
||||
:
|
||||
@@ -1416,6 +1575,9 @@ const RunWorkflow = (defaultprops) => {
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={!handleValidateForm(executionArgument) || executionLoading}
|
||||
style={{
|
||||
textTransform: "none",
|
||||
}}
|
||||
>
|
||||
{executionLoading ?
|
||||
<CircularProgress color="secondary" style={{color: "white",}} />
|
||||
@@ -1601,7 +1763,10 @@ const RunWorkflow = (defaultprops) => {
|
||||
disabled={workflow.id === undefined || workflow.id === null}
|
||||
variant={"outlined"}
|
||||
color={"secondary"}
|
||||
style={{marginRight: 10, }}
|
||||
style={{
|
||||
marginRight: 10,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={() => {
|
||||
window.open(`/workflows/${workflow.id}`, "_blank")
|
||||
}}
|
||||
@@ -1614,7 +1779,10 @@ const RunWorkflow = (defaultprops) => {
|
||||
disabled={workflow.id === undefined || workflow.id === null}
|
||||
variant={workflow.sharing === "form" ? "outlined" : "contained"}
|
||||
color={"secondary"}
|
||||
style={{marginRight: 10, }}
|
||||
style={{
|
||||
marginRight: 10,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={() => {
|
||||
setSharingOpen(true)
|
||||
}}
|
||||
@@ -1636,7 +1804,9 @@ const RunWorkflow = (defaultprops) => {
|
||||
disabled={workflow.id === undefined || workflow.id === null}
|
||||
variant={"contained"}
|
||||
color={"primary"}
|
||||
style={{}}
|
||||
style={{
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={() => {
|
||||
setEditWorkflowModalOpen(true)
|
||||
}}
|
||||
|
||||
@@ -527,6 +527,8 @@ const Settings = (props) => {
|
||||
};
|
||||
|
||||
const generateApikey = () => {
|
||||
toast.info("Generating new API key. This may take a bit.");
|
||||
|
||||
fetch(globalUrl + "/api/v1/generateapikey", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -538,7 +540,7 @@ const Settings = (props) => {
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
|
||||
}
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
@@ -836,7 +838,12 @@ const Settings = (props) => {
|
||||
variant="outlined"
|
||||
/>
|
||||
<Button
|
||||
style={{ width: "100%", height: "40px", marginTop: "10px" }}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "40px",
|
||||
marginTop: "10px",
|
||||
textTransform: "none",
|
||||
}}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => generateApikey()}
|
||||
|
||||
@@ -661,7 +661,7 @@ const Workflows2 = (props) => {
|
||||
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1);
|
||||
const [isLoadingWorkflow, setIsLoadingWorkflow] = useState(false);
|
||||
const [isLoadingPublicWorkflow, setIsLoadingPublicWorkflow] = useState(false);
|
||||
const [view, setView] = useState("grid");
|
||||
const [view, setView] = useState(localStorage?.getItem("workflowView") || "grid");
|
||||
const classes = useStyles(theme)
|
||||
const imgSize = 60;
|
||||
|
||||
@@ -812,9 +812,10 @@ const Workflows2 = (props) => {
|
||||
|
||||
}
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io";
|
||||
//const isCloud =
|
||||
// window.location.host === "localhost:3002" ||
|
||||
// window.location.host === "shuffler.io";
|
||||
const isCloud = false
|
||||
|
||||
const findWorkflow = (filters) => {
|
||||
console.log("Using filters: ", filters)
|
||||
@@ -2074,55 +2075,6 @@ const Workflows2 = (props) => {
|
||||
};
|
||||
|
||||
const hasWorkflows = workflows === undefined || workflows === null || workflows.length === 0
|
||||
const NewWorkflowPaper = () => {
|
||||
const [hover, setHover] = React.useState(false);
|
||||
|
||||
const innerColor = "rgba(255,255,255,0.3)"
|
||||
|
||||
const setupPaperStyle = {
|
||||
minHeight: paperAppStyle.minHeight,
|
||||
maxWidth: "100%",
|
||||
minWidth: paperAppStyle.width,
|
||||
color: innerColor,
|
||||
padding: paperAppStyle.padding,
|
||||
display: "flex",
|
||||
boxSizing: "border-box",
|
||||
position: "relative",
|
||||
border: hasWorkflows ? `2px solid #f85a3e` : `2px solid ${innerColor}`,
|
||||
cursor: "pointer",
|
||||
backgroundColor: hover ? "rgba(39,41,45,0.5)" : "rgba(39,41,45,1)",
|
||||
borderRadius: paperAppStyle.borderRadius,
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid item xs={isMobile ? 12 : hasWorkflows ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
|
||||
<Paper
|
||||
square
|
||||
style={setupPaperStyle}
|
||||
onClick={() => {
|
||||
setModalOpen(true)
|
||||
setIsEditing(false)
|
||||
}}
|
||||
onMouseOver={() => {
|
||||
setHover(true);
|
||||
}}
|
||||
onMouseOut={() => {
|
||||
setHover(false);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={`New Workflow`} placement="bottom">
|
||||
<span style={{ textAlign: "center", minWidth: 240, margin: "auto" }}>
|
||||
<AddCircleIcon style={{ height: 65, width: 65 }} />
|
||||
<Typography variant="h6" style={{ color: innerColor, margin: "auto" }}>
|
||||
New Workflow
|
||||
</Typography>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Paper>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const getWorkflowAppgroup = (data) => {
|
||||
if (currTab !== 2) {
|
||||
if (data.actions === undefined || data.actions === null) {
|
||||
@@ -2517,21 +2469,24 @@ const Workflows2 = (props) => {
|
||||
/>
|
||||
</Tooltip>
|
||||
: null}
|
||||
|
||||
<Grid
|
||||
item
|
||||
style={{ display: "flex", flexDirection: "column", width: "100%", fontFamily: theme?.typography?.fontFamily }}
|
||||
>
|
||||
<Grid item style={{ display: "flex", maxHeight: 34 }}>
|
||||
<Tooltip title={`Org "${orgName}". Click to edit image.`} placement="bottom">
|
||||
<div
|
||||
styl={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
navigate("/admin")
|
||||
}}
|
||||
>
|
||||
{image}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{currTab === 2 ? null :
|
||||
<Tooltip title={`Org "${orgName}". Click to edit image.`} placement="bottom">
|
||||
<div
|
||||
styl={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
navigate("/admin")
|
||||
}}
|
||||
>
|
||||
{image}
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip arrow
|
||||
onMouseEnter={() => {
|
||||
/*
|
||||
@@ -2610,10 +2565,19 @@ const Workflows2 = (props) => {
|
||||
>
|
||||
<Link
|
||||
to={
|
||||
type === "public" ? parsedUrl : data.workflow_as_code ? `/workflows/${data.id}/code` : `/workflows/${data.id}`
|
||||
currTab === 2 ? `https://shuffler.io${parsedUrl}` : type === "public" ? parsedUrl : data.workflow_as_code ? `/workflows/${data.id}/code` : `/workflows/${data.id}`
|
||||
}
|
||||
style={{ textDecoration: "none", color: "inherit" }}
|
||||
>
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
maxWidth: "90%",
|
||||
display: "block"
|
||||
}}
|
||||
target={currTab === 2 ? "_blank" : "_self"}
|
||||
>
|
||||
{parsedName}
|
||||
</Link>
|
||||
</Typography>
|
||||
@@ -4310,7 +4274,7 @@ const Workflows2 = (props) => {
|
||||
TabIndicatorProps={{ style: { display: 'none' } }}
|
||||
>
|
||||
<Tab
|
||||
label="Organization Workflows"
|
||||
label="Org Workflows"
|
||||
style={{
|
||||
...tabStyle,
|
||||
...(currTab === 0 ? tabActive : {})
|
||||
@@ -4331,6 +4295,19 @@ const Workflows2 = (props) => {
|
||||
...(currTab === 2 ? tabActive : {})
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tab
|
||||
label="Org Forms"
|
||||
onClick={() => {
|
||||
navigate("/forms")
|
||||
}}
|
||||
style={{
|
||||
...tabStyle,
|
||||
marginRight: 0,
|
||||
marginLeft: 25,
|
||||
...(currTab === 3 ? tabActive : {})
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
appVersion: "2.0.0"
|
||||
|
||||
@@ -2,8 +2,8 @@ apiVersion: v2
|
||||
name: shuffle
|
||||
description: A Helm chart for deploying Shuffle on Kubernetes
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: 0.0.0
|
||||
version: 0.0.0 # Set during publishing in GitHub actions
|
||||
appVersion: nightly # Overwritten during publishing in GitHub actions
|
||||
dependencies:
|
||||
- name: common
|
||||
version: ^2.23.0
|
||||
|
||||
@@ -148,6 +148,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
||||
| `backend.image.registry` | backend image registry | `ghcr.io` |
|
||||
| `backend.image.repository` | backend image repository | `shuffle/shuffle-backend` |
|
||||
| `backend.image.tag` | backend image tag (immutable tags are recommended, defaults to appVersion) | `""` |
|
||||
| `backend.image.digest` | backend image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` |
|
||||
| `backend.image.pullPolicy` | backend image pull policy | `IfNotPresent` |
|
||||
| `backend.image.pullSecrets` | backend image pull secrets | `[]` |
|
||||
@@ -184,8 +185,8 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `backend.podSecurityContext.fsGroup` | Set fsGroup in backend pods' Security Context | `1001` |
|
||||
| `backend.containerSecurityContext.enabled` | Enabled backend container' Security Context | `true` |
|
||||
| `backend.containerSecurityContext.seLinuxOptions` | Set SELinux options in backend container | `{}` |
|
||||
| `backend.containerSecurityContext.runAsUser` | Set runAsUser in backend container' Security Context | `1000` |
|
||||
| `backend.containerSecurityContext.runAsGroup` | Set runAsGroup in backend container' Security Context | `1000` |
|
||||
| `backend.containerSecurityContext.runAsUser` | Set runAsUser in backend container' Security Context | `1001` |
|
||||
| `backend.containerSecurityContext.runAsGroup` | Set runAsGroup in backend container' Security Context | `1001` |
|
||||
| `backend.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in backend container' Security Context | `true` |
|
||||
| `backend.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in backend container' Security Context | `true` |
|
||||
| `backend.containerSecurityContext.privileged` | Set privileged in backend container' Security Context | `false` |
|
||||
@@ -196,9 +197,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `backend.args` | Override default backend container args (useful when using custom images) | `[]` |
|
||||
| `backend.automountServiceAccountToken` | Mount Service Account token in backend pods | `true` |
|
||||
| `backend.hostAliases` | backend pods host aliases | `[]` |
|
||||
| `backend.daemonsetAnnotations` | Annotations for backend daemonset | `{}` |
|
||||
| `backend.deploymentAnnotations` | Annotations for backend deployment | `{}` |
|
||||
| `backend.statefulsetAnnotations` | Annotations for backend statefulset | `{}` |
|
||||
| `backend.podLabels` | Extra labels for backend pods | `{}` |
|
||||
| `backend.podAnnotations` | Annotations for backend pods | `{}` |
|
||||
| `backend.podAffinityPreset` | Pod affinity preset. Ignored if `backend.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
@@ -210,8 +209,6 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `backend.nodeSelector` | Node labels for backend pods assignment | `{}` |
|
||||
| `backend.tolerations` | Tolerations for backend pods assignment | `[]` |
|
||||
| `backend.updateStrategy.type` | backend deployment strategy type | `RollingUpdate` |
|
||||
| `backend.updateStrategy.type` | backend statefulset strategy type | `RollingUpdate` |
|
||||
| `backend.podManagementPolicy` | Pod management policy for backend statefulset | `OrderedReady` |
|
||||
| `backend.priorityClassName` | backend pods' priorityClassName | `""` |
|
||||
| `backend.topologySpreadConstraints` | Topology Spread Constraints for backend pod assignment spread across your cluster among failure-domains | `[]` |
|
||||
| `backend.schedulerName` | Name of the k8s scheduler (other than default) for backend pods | `""` |
|
||||
@@ -265,6 +262,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| `frontend.image.registry` | frontend image registry | `ghcr.io` |
|
||||
| `frontend.image.repository` | frontend image repository | `shuffle/shuffle-frontend` |
|
||||
| `frontend.image.tag` | frontend image tag (immutable tags are recommended, defaults to appVersion) | `""` |
|
||||
| `frontend.image.digest` | frontend image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` |
|
||||
| `frontend.image.pullPolicy` | frontend image pull policy | `IfNotPresent` |
|
||||
| `frontend.image.pullSecrets` | frontend image pull secrets | `[]` |
|
||||
@@ -302,8 +300,8 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `frontend.podSecurityContext.fsGroup` | Set fsGroup in frontend pods' Security Context | `1001` |
|
||||
| `frontend.containerSecurityContext.enabled` | Enabled frontend container' Security Context | `false` |
|
||||
| `frontend.containerSecurityContext.seLinuxOptions` | Set SELinux options in frontend container | `{}` |
|
||||
| `frontend.containerSecurityContext.runAsUser` | Set runAsUser in frontend container' Security Context | `101` |
|
||||
| `frontend.containerSecurityContext.runAsGroup` | Set runAsGroup in frontend container' Security Context | `101` |
|
||||
| `frontend.containerSecurityContext.runAsUser` | Set runAsUser in frontend container' Security Context | `1001` |
|
||||
| `frontend.containerSecurityContext.runAsGroup` | Set runAsGroup in frontend container' Security Context | `1001` |
|
||||
| `frontend.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in frontend container' Security Context | `true` |
|
||||
| `frontend.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in frontend container' Security Context | `true` |
|
||||
| `frontend.containerSecurityContext.privileged` | Set privileged in frontend container' Security Context | `false` |
|
||||
@@ -314,9 +312,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `frontend.args` | Override default frontend container args (useful when using custom images) | `[]` |
|
||||
| `frontend.automountServiceAccountToken` | Mount Service Account token in frontend pods | `false` |
|
||||
| `frontend.hostAliases` | frontend pods host aliases | `[]` |
|
||||
| `frontend.daemonsetAnnotations` | Annotations for frontend daemonset | `{}` |
|
||||
| `frontend.deploymentAnnotations` | Annotations for frontend deployment | `{}` |
|
||||
| `frontend.statefulsetAnnotations` | Annotations for frontend statefulset | `{}` |
|
||||
| `frontend.podLabels` | Extra labels for frontend pods | `{}` |
|
||||
| `frontend.podAnnotations` | Annotations for frontend pods | `{}` |
|
||||
| `frontend.podAffinityPreset` | Pod affinity preset. Ignored if `frontend.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
@@ -328,8 +324,6 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `frontend.nodeSelector` | Node labels for frontend pods assignment | `{}` |
|
||||
| `frontend.tolerations` | Tolerations for frontend pods assignment | `[]` |
|
||||
| `frontend.updateStrategy.type` | frontend deployment strategy type | `RollingUpdate` |
|
||||
| `frontend.updateStrategy.type` | frontend statefulset strategy type | `RollingUpdate` |
|
||||
| `frontend.podManagementPolicy` | Pod management policy for frontend statefulset | `OrderedReady` |
|
||||
| `frontend.priorityClassName` | frontend pods' priorityClassName | `""` |
|
||||
| `frontend.topologySpreadConstraints` | Topology Spread Constraints for frontend pod assignment spread across your cluster among failure-domains | `[]` |
|
||||
| `frontend.schedulerName` | Name of the k8s scheduler (other than default) for frontend pods | `""` |
|
||||
@@ -373,6 +367,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
|
||||
| `orborus.image.registry` | orborus image registry | `ghcr.io` |
|
||||
| `orborus.image.repository` | orborus image repository | `shuffle/shuffle-orborus` |
|
||||
| `orborus.image.tag` | orborus image tag (immutable tags are recommended, defaults to appVersion) | `""` |
|
||||
| `orborus.image.digest` | orborus image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` |
|
||||
| `orborus.image.pullPolicy` | orborus image pull policy | `IfNotPresent` |
|
||||
| `orborus.image.pullSecrets` | orborus image pull secrets | `[]` |
|
||||
@@ -409,8 +404,8 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `orborus.podSecurityContext.fsGroup` | Set fsGroup in orborus pods' Security Context | `1001` |
|
||||
| `orborus.containerSecurityContext.enabled` | Enabled orborus container' Security Context | `true` |
|
||||
| `orborus.containerSecurityContext.seLinuxOptions` | Set SELinux options in orborus container | `{}` |
|
||||
| `orborus.containerSecurityContext.runAsUser` | Set runAsUser in orborus container' Security Context | `101` |
|
||||
| `orborus.containerSecurityContext.runAsGroup` | Set runAsGroup in orborus container' Security Context | `101` |
|
||||
| `orborus.containerSecurityContext.runAsUser` | Set runAsUser in orborus container' Security Context | `1001` |
|
||||
| `orborus.containerSecurityContext.runAsGroup` | Set runAsGroup in orborus container' Security Context | `1001` |
|
||||
| `orborus.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in orborus container' Security Context | `true` |
|
||||
| `orborus.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in orborus container' Security Context | `true` |
|
||||
| `orborus.containerSecurityContext.privileged` | Set privileged in orborus container' Security Context | `false` |
|
||||
@@ -421,9 +416,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `orborus.args` | Override default orborus container args (useful when using custom images) | `[]` |
|
||||
| `orborus.automountServiceAccountToken` | Mount Service Account token in orborus pods | `true` |
|
||||
| `orborus.hostAliases` | orborus pods host aliases | `[]` |
|
||||
| `orborus.daemonsetAnnotations` | Annotations for orborus daemonset | `{}` |
|
||||
| `orborus.deploymentAnnotations` | Annotations for orborus deployment | `{}` |
|
||||
| `orborus.statefulsetAnnotations` | Annotations for orborus statefulset | `{}` |
|
||||
| `orborus.podLabels` | Extra labels for orborus pods | `{}` |
|
||||
| `orborus.podAnnotations` | Annotations for orborus pods | `{}` |
|
||||
| `orborus.podAffinityPreset` | Pod affinity preset. Ignored if `orborus.affinity` is set. Allowed values: `soft` or `hard` | `""` |
|
||||
@@ -435,8 +428,6 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `orborus.nodeSelector` | Node labels for orborus pods assignment | `{}` |
|
||||
| `orborus.tolerations` | Tolerations for orborus pods assignment | `[]` |
|
||||
| `orborus.updateStrategy.type` | orborus deployment strategy type | `RollingUpdate` |
|
||||
| `orborus.updateStrategy.type` | orborus statefulset strategy type | `RollingUpdate` |
|
||||
| `orborus.podManagementPolicy` | Pod management policy for orborus statefulset | `OrderedReady` |
|
||||
| `orborus.priorityClassName` | orborus pods' priorityClassName | `""` |
|
||||
| `orborus.topologySpreadConstraints` | Topology Spread Constraints for orborus pod assignment spread across your cluster among failure-domains | `[]` |
|
||||
| `orborus.schedulerName` | Name of the k8s scheduler (other than default) for orborus pods | `""` |
|
||||
@@ -481,6 +472,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
|
||||
| `worker.image.registry` | worker image registry | `ghcr.io` |
|
||||
| `worker.image.repository` | worker image repository | `shuffle/shuffle-worker` |
|
||||
| `worker.image.tag` | worker image tag (immutable tags are recommended, defaults to appVersion) | `""` |
|
||||
| `worker.image.digest` | worker image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` |
|
||||
| `worker.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` |
|
||||
| `worker.serviceAccount.name` | The name of the ServiceAccount to use. | `""` |
|
||||
@@ -582,6 +574,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
| `volumePermissions.enabled` | Enable init container that changes the owner/group of the PV mount point to `runAsUser:fsGroup` | `false` |
|
||||
| `volumePermissions.image.registry` | OS Shell + Utility image registry | `docker.io` |
|
||||
| `volumePermissions.image.repository` | OS Shell + Utility image repository | `bitnami/os-shell` |
|
||||
| `volumePermissions.image.tag` | OS Shell + Utility image tag (immutable tags are recommended) | `12-debian-12-r30` |
|
||||
| `volumePermissions.image.pullPolicy` | OS Shell + Utility image pull policy | `IfNotPresent` |
|
||||
| `volumePermissions.image.pullSecrets` | OS Shell + Utility image pull secrets | `[]` |
|
||||
| `volumePermissions.resourcesPreset` | Set init container resources according to one common preset (allowed values: none, nano, small, medium, large, xlarge, 2xlarge). This is ignored if volumePermissions.resources is set (volumePermissions.resources is recommended for production). | `nano` |
|
||||
@@ -605,3 +598,4 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
|
||||
|
||||
### Other Parameters
|
||||
|
||||
|
||||
|
||||
@@ -119,14 +119,14 @@ app.kubernetes.io/name: shuffle-app
|
||||
Return the proper image name (for the init container volume-permissions image)
|
||||
*/}}
|
||||
{{- define "shuffle.volumePermissions.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.volumePermissions.image "global" .Values.global ) -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.volumePermissions.image "global" .Values.global "chart" .Chart ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Return the proper Shuffle backend image name
|
||||
*/}}
|
||||
{{- define "shuffle.backend.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.backend.image "global" .Values.global ) -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.backend.image "global" .Values.global "chart" .Chart ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
@@ -140,7 +140,7 @@ Return the proper Docker Image Registry Secret Names for the backend pod
|
||||
Return the proper Shuffle frontend image name
|
||||
*/}}
|
||||
{{- define "shuffle.frontend.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.frontend.image "global" .Values.global ) -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.frontend.image "global" .Values.global "chart" .Chart ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
@@ -154,7 +154,7 @@ Return the proper Docker Image Registry Secret Names for the frontend pod
|
||||
Return the proper Shuffle orborus image name
|
||||
*/}}
|
||||
{{- define "shuffle.orborus.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.orborus.image "global" .Values.global ) -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.orborus.image "global" .Values.global "chart" .Chart ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
@@ -168,7 +168,7 @@ Return the proper Docker Image Registry Secret Names for the orborus pod
|
||||
Return the proper Shuffle worker image name
|
||||
*/}}
|
||||
{{- define "shuffle.worker.image" -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.worker.image "global" .Values.global ) -}}
|
||||
{{- include "common.images.image" ( dict "imageRoot" .Values.worker.image "global" .Values.global "chart" .Chart ) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
|
||||
@@ -155,6 +155,11 @@
|
||||
"description": "backend image repository",
|
||||
"default": "shuffle/shuffle-backend"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "backend image tag (immutable tags are recommended, defaults to appVersion)",
|
||||
"default": ""
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"description": "backend image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended)",
|
||||
@@ -367,12 +372,12 @@
|
||||
"runAsUser": {
|
||||
"type": "number",
|
||||
"description": "Set runAsUser in backend container' Security Context",
|
||||
"default": 1000
|
||||
"default": 1001
|
||||
},
|
||||
"runAsGroup": {
|
||||
"type": "number",
|
||||
"description": "Set runAsGroup in backend container' Security Context",
|
||||
"default": 1000
|
||||
"default": 1001
|
||||
},
|
||||
"runAsNonRoot": {
|
||||
"type": "boolean",
|
||||
@@ -444,21 +449,11 @@
|
||||
"default": [],
|
||||
"items": {}
|
||||
},
|
||||
"daemonsetAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for backend daemonset",
|
||||
"default": {}
|
||||
},
|
||||
"deploymentAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for backend deployment",
|
||||
"default": {}
|
||||
},
|
||||
"statefulsetAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for backend statefulset",
|
||||
"default": {}
|
||||
},
|
||||
"podLabels": {
|
||||
"type": "object",
|
||||
"description": "Extra labels for backend pods",
|
||||
@@ -521,16 +516,11 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "backend statefulset strategy type",
|
||||
"description": "backend deployment strategy type",
|
||||
"default": "RollingUpdate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"podManagementPolicy": {
|
||||
"type": "string",
|
||||
"description": "Pod management policy for backend statefulset",
|
||||
"default": "OrderedReady"
|
||||
},
|
||||
"priorityClassName": {
|
||||
"type": "string",
|
||||
"description": "backend pods' priorityClassName",
|
||||
@@ -839,6 +829,11 @@
|
||||
"description": "frontend image repository",
|
||||
"default": "shuffle/shuffle-frontend"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "frontend image tag (immutable tags are recommended, defaults to appVersion)",
|
||||
"default": ""
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"description": "frontend image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended)",
|
||||
@@ -1056,12 +1051,12 @@
|
||||
"runAsUser": {
|
||||
"type": "number",
|
||||
"description": "Set runAsUser in frontend container' Security Context",
|
||||
"default": 101
|
||||
"default": 1001
|
||||
},
|
||||
"runAsGroup": {
|
||||
"type": "number",
|
||||
"description": "Set runAsGroup in frontend container' Security Context",
|
||||
"default": 101
|
||||
"default": 1001
|
||||
},
|
||||
"runAsNonRoot": {
|
||||
"type": "boolean",
|
||||
@@ -1133,21 +1128,11 @@
|
||||
"default": [],
|
||||
"items": {}
|
||||
},
|
||||
"daemonsetAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for frontend daemonset",
|
||||
"default": {}
|
||||
},
|
||||
"deploymentAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for frontend deployment",
|
||||
"default": {}
|
||||
},
|
||||
"statefulsetAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for frontend statefulset",
|
||||
"default": {}
|
||||
},
|
||||
"podLabels": {
|
||||
"type": "object",
|
||||
"description": "Extra labels for frontend pods",
|
||||
@@ -1210,16 +1195,11 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "frontend statefulset strategy type",
|
||||
"description": "frontend deployment strategy type",
|
||||
"default": "RollingUpdate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"podManagementPolicy": {
|
||||
"type": "string",
|
||||
"description": "Pod management policy for frontend statefulset",
|
||||
"default": "OrderedReady"
|
||||
},
|
||||
"priorityClassName": {
|
||||
"type": "string",
|
||||
"description": "frontend pods' priorityClassName",
|
||||
@@ -1463,6 +1443,11 @@
|
||||
"description": "orborus image repository",
|
||||
"default": "shuffle/shuffle-orborus"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "orborus image tag (immutable tags are recommended, defaults to appVersion)",
|
||||
"default": ""
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"description": "orborus image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended)",
|
||||
@@ -1675,12 +1660,12 @@
|
||||
"runAsUser": {
|
||||
"type": "number",
|
||||
"description": "Set runAsUser in orborus container' Security Context",
|
||||
"default": 101
|
||||
"default": 1001
|
||||
},
|
||||
"runAsGroup": {
|
||||
"type": "number",
|
||||
"description": "Set runAsGroup in orborus container' Security Context",
|
||||
"default": 101
|
||||
"default": 1001
|
||||
},
|
||||
"runAsNonRoot": {
|
||||
"type": "boolean",
|
||||
@@ -1752,21 +1737,11 @@
|
||||
"default": [],
|
||||
"items": {}
|
||||
},
|
||||
"daemonsetAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for orborus daemonset",
|
||||
"default": {}
|
||||
},
|
||||
"deploymentAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for orborus deployment",
|
||||
"default": {}
|
||||
},
|
||||
"statefulsetAnnotations": {
|
||||
"type": "object",
|
||||
"description": "Annotations for orborus statefulset",
|
||||
"default": {}
|
||||
},
|
||||
"podLabels": {
|
||||
"type": "object",
|
||||
"description": "Extra labels for orborus pods",
|
||||
@@ -1829,16 +1804,11 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "orborus statefulset strategy type",
|
||||
"description": "orborus deployment strategy type",
|
||||
"default": "RollingUpdate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"podManagementPolicy": {
|
||||
"type": "string",
|
||||
"description": "Pod management policy for orborus statefulset",
|
||||
"default": "OrderedReady"
|
||||
},
|
||||
"priorityClassName": {
|
||||
"type": "string",
|
||||
"description": "orborus pods' priorityClassName",
|
||||
@@ -2092,6 +2062,11 @@
|
||||
"description": "worker image repository",
|
||||
"default": "shuffle/shuffle-worker"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "worker image tag (immutable tags are recommended, defaults to appVersion)",
|
||||
"default": ""
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"description": "worker image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended)",
|
||||
@@ -2591,6 +2566,11 @@
|
||||
"description": "OS Shell + Utility image repository",
|
||||
"default": "bitnami/os-shell"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "OS Shell + Utility image tag (immutable tags are recommended)",
|
||||
"default": "12-debian-12-r30"
|
||||
},
|
||||
"pullPolicy": {
|
||||
"type": "string",
|
||||
"description": "OS Shell + Utility image pull policy",
|
||||
|
||||
@@ -96,7 +96,7 @@ backend:
|
||||
## backend image
|
||||
## @param backend.image.registry backend image registry
|
||||
## @param backend.image.repository backend image repository
|
||||
## @skip backend.image.tag backend image tag (immutable tags are recommended)
|
||||
## @param backend.image.tag backend image tag (immutable tags are recommended, defaults to appVersion)
|
||||
## @param backend.image.digest backend image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended)
|
||||
## @param backend.image.pullPolicy backend image pull policy
|
||||
## @param backend.image.pullSecrets backend image pull secrets
|
||||
@@ -104,7 +104,7 @@ backend:
|
||||
image:
|
||||
registry: ghcr.io
|
||||
repository: shuffle/shuffle-backend
|
||||
tag: nightly
|
||||
tag: ""
|
||||
digest: ""
|
||||
## Specify a imagePullPolicy
|
||||
## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
|
||||
@@ -235,8 +235,8 @@ backend:
|
||||
containerSecurityContext:
|
||||
enabled: true
|
||||
seLinuxOptions: {}
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsNonRoot: true
|
||||
readOnlyRootFilesystem: true
|
||||
privileged: false
|
||||
@@ -260,18 +260,10 @@ backend:
|
||||
## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/
|
||||
##
|
||||
hostAliases: []
|
||||
## @param backend.daemonsetAnnotations Annotations for backend daemonset
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
daemonsetAnnotations: {}
|
||||
## @param backend.deploymentAnnotations Annotations for backend deployment
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
deploymentAnnotations: {}
|
||||
## @param backend.statefulsetAnnotations Annotations for backend statefulset
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
statefulsetAnnotations: {}
|
||||
## @param backend.podLabels Extra labels for backend pods
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
##
|
||||
@@ -321,22 +313,11 @@ backend:
|
||||
## ONLY FOR DEPLOYMENTS:
|
||||
## @param backend.updateStrategy.type backend deployment strategy type
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## @param backend.updateStrategy.type backend statefulset strategy type
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies
|
||||
##
|
||||
updateStrategy:
|
||||
## ONLY FOR DEPLOYMENTS:
|
||||
## Can be set to RollingUpdate or Recreate
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## Can be set to RollingUpdate or OnDelete
|
||||
##
|
||||
type: RollingUpdate
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## @param backend.podManagementPolicy Pod management policy for backend statefulset
|
||||
## Ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies
|
||||
##
|
||||
podManagementPolicy: OrderedReady
|
||||
## @param backend.priorityClassName backend pods' priorityClassName
|
||||
##
|
||||
priorityClassName: ""
|
||||
@@ -565,7 +546,7 @@ frontend:
|
||||
## frontend image
|
||||
## @param frontend.image.registry frontend image registry
|
||||
## @param frontend.image.repository frontend image repository
|
||||
## @skip frontend.image.tag frontend image tag (immutable tags are recommended)
|
||||
## @param frontend.image.tag frontend image tag (immutable tags are recommended, defaults to appVersion)
|
||||
## @param frontend.image.digest frontend image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended)
|
||||
## @param frontend.image.pullPolicy frontend image pull policy
|
||||
## @param frontend.image.pullSecrets frontend image pull secrets
|
||||
@@ -573,7 +554,7 @@ frontend:
|
||||
image:
|
||||
registry: ghcr.io
|
||||
repository: shuffle/shuffle-frontend
|
||||
tag: nightly
|
||||
tag: ""
|
||||
digest: ""
|
||||
## Specify a imagePullPolicy
|
||||
## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
|
||||
@@ -683,11 +664,12 @@ frontend:
|
||||
## @param frontend.podSecurityContext.fsGroup Set fsGroup in frontend pods' Security Context
|
||||
##
|
||||
podSecurityContext:
|
||||
enabled: false
|
||||
enabled: false # The default shuffle frontend image does not support running as non-root, because /etc/nginx/nginx.conf is written on startup
|
||||
fsGroupChangePolicy: Always
|
||||
sysctls: []
|
||||
supplementalGroups: []
|
||||
fsGroup: 1001
|
||||
|
||||
## Configure Container Security Context
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||
## @param frontend.containerSecurityContext.enabled Enabled frontend container' Security Context
|
||||
@@ -702,10 +684,10 @@ frontend:
|
||||
## @param frontend.containerSecurityContext.seccompProfile.type Set seccomp profile in frontend container
|
||||
##
|
||||
containerSecurityContext:
|
||||
enabled: false
|
||||
enabled: false # The default shuffle frontend image does not support running as non-root, because /etc/nginx/nginx.conf is written on startup
|
||||
seLinuxOptions: {}
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsNonRoot: true
|
||||
readOnlyRootFilesystem: true
|
||||
privileged: false
|
||||
@@ -728,18 +710,10 @@ frontend:
|
||||
## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/
|
||||
##
|
||||
hostAliases: []
|
||||
## @param frontend.daemonsetAnnotations Annotations for frontend daemonset
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
daemonsetAnnotations: {}
|
||||
## @param frontend.deploymentAnnotations Annotations for frontend deployment
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
deploymentAnnotations: {}
|
||||
## @param frontend.statefulsetAnnotations Annotations for frontend statefulset
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
statefulsetAnnotations: {}
|
||||
## @param frontend.podLabels Extra labels for frontend pods
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
##
|
||||
@@ -786,25 +760,13 @@ frontend:
|
||||
## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
|
||||
##
|
||||
tolerations: []
|
||||
## ONLY FOR DEPLOYMENTS:
|
||||
## @param frontend.updateStrategy.type frontend deployment strategy type
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## @param frontend.updateStrategy.type frontend statefulset strategy type
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies
|
||||
##
|
||||
updateStrategy:
|
||||
## ONLY FOR DEPLOYMENTS:
|
||||
## Can be set to RollingUpdate or Recreate
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## Can be set to RollingUpdate or OnDelete
|
||||
##
|
||||
type: RollingUpdate
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## @param frontend.podManagementPolicy Pod management policy for frontend statefulset
|
||||
## Ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies
|
||||
##
|
||||
podManagementPolicy: OrderedReady
|
||||
## @param frontend.priorityClassName frontend pods' priorityClassName
|
||||
##
|
||||
priorityClassName: ""
|
||||
@@ -970,7 +932,7 @@ orborus:
|
||||
## orborus image
|
||||
## @param orborus.image.registry orborus image registry
|
||||
## @param orborus.image.repository orborus image repository
|
||||
## @skip orborus.image.tag orborus image tag (immutable tags are recommended)
|
||||
## @param orborus.image.tag orborus image tag (immutable tags are recommended, defaults to appVersion)
|
||||
## @param orborus.image.digest orborus image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended)
|
||||
## @param orborus.image.pullPolicy orborus image pull policy
|
||||
## @param orborus.image.pullSecrets orborus image pull secrets
|
||||
@@ -978,7 +940,7 @@ orborus:
|
||||
image:
|
||||
registry: ghcr.io
|
||||
repository: shuffle/shuffle-orborus
|
||||
tag: nightly
|
||||
tag: ""
|
||||
digest: ""
|
||||
## Specify a imagePullPolicy
|
||||
## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
|
||||
@@ -1107,8 +1069,8 @@ orborus:
|
||||
containerSecurityContext:
|
||||
enabled: true
|
||||
seLinuxOptions: {}
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsNonRoot: true
|
||||
readOnlyRootFilesystem: true
|
||||
privileged: false
|
||||
@@ -1132,18 +1094,10 @@ orborus:
|
||||
## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/
|
||||
##
|
||||
hostAliases: []
|
||||
## @param orborus.daemonsetAnnotations Annotations for orborus daemonset
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
daemonsetAnnotations: {}
|
||||
## @param orborus.deploymentAnnotations Annotations for orborus deployment
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
deploymentAnnotations: {}
|
||||
## @param orborus.statefulsetAnnotations Annotations for orborus statefulset
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
statefulsetAnnotations: {}
|
||||
## @param orborus.podLabels Extra labels for orborus pods
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
##
|
||||
@@ -1190,25 +1144,13 @@ orborus:
|
||||
## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
|
||||
##
|
||||
tolerations: []
|
||||
## ONLY FOR DEPLOYMENTS:
|
||||
## @param orborus.updateStrategy.type orborus deployment strategy type
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## @param orborus.updateStrategy.type orborus statefulset strategy type
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies
|
||||
##
|
||||
updateStrategy:
|
||||
## ONLY FOR DEPLOYMENTS:
|
||||
## Can be set to RollingUpdate or Recreate
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## Can be set to RollingUpdate or OnDelete
|
||||
##
|
||||
type: RollingUpdate
|
||||
## ONLY FOR STATEFULSETS:
|
||||
## @param orborus.podManagementPolicy Pod management policy for orborus statefulset
|
||||
## Ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies
|
||||
##
|
||||
podManagementPolicy: OrderedReady
|
||||
## @param orborus.priorityClassName orborus pods' priorityClassName
|
||||
##
|
||||
priorityClassName: ""
|
||||
@@ -1377,13 +1319,13 @@ worker:
|
||||
## worker image
|
||||
## @param worker.image.registry worker image registry
|
||||
## @param worker.image.repository worker image repository
|
||||
## @skip worker.image.tag worker image tag (immutable tags are recommended)
|
||||
## @param worker.image.tag worker image tag (immutable tags are recommended, defaults to appVersion)
|
||||
## @param worker.image.digest worker image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended)
|
||||
##
|
||||
image:
|
||||
registry: ghcr.io
|
||||
repository: shuffle/shuffle-worker
|
||||
tag: nightly
|
||||
tag: ""
|
||||
digest: ""
|
||||
|
||||
## ServiceAccount configuration
|
||||
@@ -1748,7 +1690,7 @@ volumePermissions:
|
||||
## ref: https://hub.docker.com/r/bitnami/os-shell/tags/
|
||||
## @param volumePermissions.image.registry OS Shell + Utility image registry
|
||||
## @param volumePermissions.image.repository OS Shell + Utility image repository
|
||||
## @skip volumePermissions.image.tag OS Shell + Utility image tag (immutable tags are recommended)
|
||||
## @param volumePermissions.image.tag OS Shell + Utility image tag (immutable tags are recommended)
|
||||
## @param volumePermissions.image.pullPolicy OS Shell + Utility image pull policy
|
||||
## @param volumePermissions.image.pullSecrets OS Shell + Utility image pull secrets
|
||||
##
|
||||
|
||||
@@ -10,7 +10,7 @@ require (
|
||||
github.com/docker/docker v27.5.0+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shuffle/shuffle-shared v0.8.18
|
||||
github.com/shuffle/shuffle-shared v0.8.35
|
||||
k8s.io/api v0.30.2
|
||||
k8s.io/apimachinery v0.30.2
|
||||
)
|
||||
|
||||
@@ -301,8 +301,8 @@ github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fc
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shuffle/shuffle-shared v0.8.18 h1:7f7cV+P2pr/g44i+AI8P0UheEe8oG8O2V8loBfe9YSw=
|
||||
github.com/shuffle/shuffle-shared v0.8.18/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI=
|
||||
github.com/shuffle/shuffle-shared v0.8.35 h1:3awc0TrsLLZiQeWD2XGIkTnFbczAG0cMfy1+cB/P7zg=
|
||||
github.com/shuffle/shuffle-shared v0.8.35/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI=
|
||||
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=
|
||||
|
||||
+183
-164
@@ -1,13 +1,8 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
Orborus exists to listen for new jobs which are deployed as workers.
|
||||
Orborus exists to listen for new jobs from Shuffle. This is to run workflows, pipelines, and other tasks.
|
||||
*/
|
||||
|
||||
// Potential issues:
|
||||
// Default network could be same as on the host
|
||||
// Ingress network may not exist (default)
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
@@ -60,7 +55,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// Starts jobs in bulk, so this could be increased
|
||||
// Starts jobs in bulk, so this could be increased or decreased based on who the user is
|
||||
var sleepTime = 2
|
||||
|
||||
// Making it work on low-end machines even during busy times :)
|
||||
@@ -75,9 +70,13 @@ var workerVersion = os.Getenv("SHUFFLE_WORKER_VERSION")
|
||||
var newWorkerImage = os.Getenv("SHUFFLE_WORKER_IMAGE")
|
||||
var dockerSwarmBridgeMTU = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_MTU")
|
||||
var dockerSwarmBridgeInterface = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_INTERFACE")
|
||||
var maxCPUPercent = 90
|
||||
|
||||
// Kubernetes settings
|
||||
var isKubernetes = os.Getenv("IS_KUBERNETES")
|
||||
var kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE")
|
||||
var maxCPUPercent = 90
|
||||
var workerServiceAccountName = os.Getenv("SHUFFLE_WORKER_SERVICE_ACCOUNT_NAME")
|
||||
var appServiceAccountName = os.Getenv("SHUFFLE_APP_SERVICE_ACCOUNT_NAME")
|
||||
|
||||
// var baseimagename = "docker.pkg.github.com/shuffle/shuffle"
|
||||
// var baseimagename = "ghcr.io/frikky"
|
||||
@@ -217,8 +216,7 @@ func skipCheckInCleanup(name string) bool {
|
||||
func cleanupExistingNodes(ctx context.Context) error {
|
||||
|
||||
if isKubernetes == "true" {
|
||||
// of course, this doesn't clean up "nodes" but
|
||||
// rather pods, services, roles etc.
|
||||
// Cleanup all workers created by orborus and all apps created by workers.
|
||||
|
||||
if kubernetesNamespace == "" {
|
||||
kubernetesNamespace = "default"
|
||||
@@ -230,62 +228,38 @@ func cleanupExistingNodes(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all pods
|
||||
pods, err := clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed listing pods: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
// check if pod.Name starts with:
|
||||
// "backend-", "frontend-", "orborus-", "opensearch-" or "memcached-"
|
||||
if skipCheckInCleanup(pod.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := clientset.CoreV1().Pods(kubernetesNamespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed deleting pod %s: %s", pod.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all services
|
||||
services, err := clientset.CoreV1().Services(kubernetesNamespace).List(context.Background(), metav1.ListOptions{})
|
||||
services, err := clientset.CoreV1().Services(kubernetesNamespace).List(context.Background(), metav1.ListOptions{
|
||||
LabelSelector: "app.kubernetes.io/name in (shuffle-worker, shuffle-app),app.kubernetes.io/managed-by in (shuffle-orborus, shuffle-worker)",
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed listing services: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range services.Items {
|
||||
if skipCheckInCleanup(service.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed deleting service %s: %s", service.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
deployments, err := clientset.AppsV1().Deployments(kubernetesNamespace).List(context.Background(), metav1.ListOptions{})
|
||||
deployments, err := clientset.AppsV1().Deployments(kubernetesNamespace).List(context.Background(), metav1.ListOptions{
|
||||
LabelSelector: "app.kubernetes.io/name in (shuffle-worker, shuffle-app),app.kubernetes.io/managed-by in (shuffle-orborus, shuffle-worker)",
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed listing deployments: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, deployment := range deployments.Items {
|
||||
if skipCheckInCleanup(deployment.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := clientset.AppsV1().Deployments(kubernetesNamespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed deleting deployment %s: %s", deployment.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Cleaned up all pods and services in namespace %s. Waiting 10 seconds for cleanup to reflect", kubernetesNamespace)
|
||||
log.Printf("[INFO] Cleaned up all services and deployments in namespace %s. Waiting 10 seconds for cleanup to reflect", kubernetesNamespace)
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
@@ -457,19 +431,19 @@ func deployServiceWorkers(image string) {
|
||||
}
|
||||
|
||||
/*
|
||||
isMemcachedRunning, err := checkMemcached(ctx, dockercli)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed checking memcached: %s", err)
|
||||
}
|
||||
if isMemcachedRunning == false {
|
||||
log.Printf("[ERROR] Memcached is not running. Will try to deploy it.")
|
||||
deployMemcached(dockercli)
|
||||
}
|
||||
isMemcachedRunning, err := checkMemcached(ctx, dockercli)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed checking memcached: %s", err)
|
||||
}
|
||||
if isMemcachedRunning == false {
|
||||
log.Printf("[ERROR] Memcached is not running. Will try to deploy it.")
|
||||
deployMemcached(dockercli)
|
||||
}
|
||||
|
||||
ip := "shuffle-cache"
|
||||
if len(os.Getenv("SHUFFLE_MEMCACHED")) == 0 {
|
||||
os.Setenv("SHUFFLE_MEMCACHED", fmt.Sprintf("%s:11211", ip))
|
||||
}
|
||||
ip := "shuffle-cache"
|
||||
if len(os.Getenv("SHUFFLE_MEMCACHED")) == 0 {
|
||||
os.Setenv("SHUFFLE_MEMCACHED", fmt.Sprintf("%s:11211", ip))
|
||||
}
|
||||
*/
|
||||
|
||||
defaultNetworkAttach := false
|
||||
@@ -725,7 +699,6 @@ func deployServiceWorkers(image string) {
|
||||
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] Successfully deployed workers with %d replica(s) on %d node(s)", replicas, cnt)
|
||||
//time.Sleep(time.Duration(10) * time.Second)
|
||||
//log.Printf("[DEBUG] Servicecreate request: %#v %#v", service, err)
|
||||
} else {
|
||||
if !strings.Contains(fmt.Sprintf("%s", err), "Already Exists") && !strings.Contains(fmt.Sprintf("%s", err), "is already in use by service") {
|
||||
@@ -769,10 +742,10 @@ func handleBackendImageDownload(ctx context.Context, images string) error {
|
||||
|
||||
// Remove the image
|
||||
handled := []string{}
|
||||
log.Printf("[DEBUG] Removing existing image (s): %s. Waiting 30 seconds before starting to ensure backend has the latest images built and ready to distribute.", images)
|
||||
//time.Sleep(time.Duration(30) * time.Second)
|
||||
|
||||
//log.Printf("[DEBUG] Removing existing image (s): %s", images)
|
||||
newImages := []string{}
|
||||
|
||||
successful := []string{}
|
||||
for _, curimage := range strings.Split(images, ",") {
|
||||
curimage = strings.TrimSpace(curimage)
|
||||
if shuffle.ArrayContains(handled, curimage) {
|
||||
@@ -787,25 +760,36 @@ func handleBackendImageDownload(ctx context.Context, images string) error {
|
||||
newImages = append(newImages, curimage)
|
||||
|
||||
// Force remove the current image to avoid cached layers
|
||||
_, err := dockercli.ImageRemove(ctx, curimage, image.RemoveOptions{
|
||||
Force: true,
|
||||
PruneChildren: true,
|
||||
})
|
||||
if swarmConfig == "run" || swarmConfig == "swarm" {
|
||||
_, err := dockercli.ImageRemove(ctx, curimage, image.RemoveOptions{
|
||||
Force: true,
|
||||
PruneChildren: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed removing image for re-download: %s", err)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed removing image for re-download: %s", err)
|
||||
} else {
|
||||
log.Printf("[DEBUG] Removed image: %s", curimage)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[DEBUG] Removed image: %s", curimage)
|
||||
//log.Printf("[DEBUG] Skipping image removal for %s as swarmConfig is not set to run or swarm. Value: %#v", curimage, swarmConfig)
|
||||
}
|
||||
|
||||
err = shuffle.DownloadDockerImageBackend(&http.Client{Timeout: imagedownloadTimeout}, curimage)
|
||||
err := shuffle.DownloadDockerImageBackend(&http.Client{Timeout: imagedownloadTimeout}, curimage)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed downloading image: %s", err)
|
||||
//log.Printf("[ERROR] Failed downloading image: %s", err)
|
||||
} else {
|
||||
log.Printf("[DEBUG] Downloaded image: %s", curimage)
|
||||
//log.Printf("[DEBUG] Downloaded image: %s", curimage)
|
||||
successful = append(successful, curimage)
|
||||
}
|
||||
}
|
||||
|
||||
if len(successful) == 0 {
|
||||
log.Printf("[ERROR] Failed downloading image copies: %s. This means the app may not have been updated.", strings.Join(handled, ", "))
|
||||
} else {
|
||||
log.Printf("[DEBUG] Successfully downloaded image copies: %s", strings.Join(successful, ", "))
|
||||
}
|
||||
|
||||
if swarmConfig == "run" || swarmConfig == "swarm" {
|
||||
log.Printf("[DEBUG] Should update service with new image after updating(s): %s. \n\nBETA REPLACEMENT IMPLEMENTATION: Contact support@shuffler.io for support.", strings.Join(newImages, "\n"))
|
||||
|
||||
@@ -853,7 +837,7 @@ func handleBackendImageDownload(ctx context.Context, images string) error {
|
||||
if !strings.Contains(fmt.Sprintf("%s", resp), "error") {
|
||||
break
|
||||
} else {
|
||||
found = true
|
||||
found = true
|
||||
log.Printf("[ERROR] Failed updating service %s with the new image %s: %s. Resp: %#v", service.Spec.Annotations.Name, image, err, resp)
|
||||
}
|
||||
}
|
||||
@@ -1010,6 +994,10 @@ func deployK8sWorker(image string, identifier string, env []string) error {
|
||||
env = append(env, fmt.Sprintf("SHUFFLE_USE_GHCR_OVERRIDE_FOR_AUTODEPLOY=%s", os.Getenv("SHUFFLE_USE_GHCR_OVERRIDE_FOR_AUTODEPLOY")))
|
||||
}
|
||||
|
||||
if len(appServiceAccountName) > 0 {
|
||||
env = append(env, fmt.Sprintf("SHUFFLE_APP_SERVICE_ACCOUNT_NAME=%s", appServiceAccountName))
|
||||
}
|
||||
|
||||
clientset, _, err := shuffle.GetKubernetesClient()
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Error getting kubernetes client:", err)
|
||||
@@ -1018,18 +1006,6 @@ func deployK8sWorker(image string, identifier string, env []string) error {
|
||||
|
||||
//env = append(env, fmt.Sprintf("KUBERNETES_CONFIG=%s", config.String()))
|
||||
|
||||
// FIXME: When a service account is used, the account is also mounted in the pod
|
||||
// The volume mount location is:
|
||||
// /var/run/secrets/kubernetes.io/serviceaccount
|
||||
|
||||
// Look for if there is a default service account in use
|
||||
if len(os.Getenv("KUBERNETES_SERVICE_ACCOUNT")) > 0 {
|
||||
log.Printf("[DEBUG] Using Kubernetes service account %s", os.Getenv("KUBERNETES_SERVICE_ACCOUNT"))
|
||||
env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_ACCOUNT=%s", os.Getenv("KUBERNETES_SERVICE_ACCOUNT")))
|
||||
|
||||
// use k8s downward API to find it if we are in a pod
|
||||
}
|
||||
|
||||
// Check if namespace exist as variable. If so, make it
|
||||
if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 && !namespacemade {
|
||||
kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE")
|
||||
@@ -1088,10 +1064,21 @@ func deployK8sWorker(image string, identifier string, env []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
containerLabels := map[string]string{
|
||||
labels := map[string]string{
|
||||
"app.kubernetes.io/name": "shuffle-worker",
|
||||
"app.kubernetes.io/instance": identifier,
|
||||
// "app.kubernetes.io/version": "",
|
||||
"app.kubernetes.io/part-of": "shuffle",
|
||||
"app.kubernetes.io/managed-by": "shuffle-orborus",
|
||||
// Keep legacy labels for backward compatibility
|
||||
"container": "shuffle-worker",
|
||||
}
|
||||
|
||||
matchLabels := map[string]string{
|
||||
"app.kubernetes.io/name": "shuffle-worker",
|
||||
"app.kubernetes.io/instance": identifier,
|
||||
}
|
||||
|
||||
containerAttachment := corev1.Container{
|
||||
Name: identifier,
|
||||
Image: kubernetesImage,
|
||||
@@ -1194,22 +1181,24 @@ func deployK8sWorker(image string, identifier string, env []string) error {
|
||||
|
||||
deployment := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: identifier,
|
||||
Name: identifier,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &replicaNumberInt32,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: containerLabels,
|
||||
MatchLabels: matchLabels,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: containerLabels,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
containerAttachment,
|
||||
},
|
||||
DNSPolicy: corev1.DNSClusterFirst,
|
||||
DNSPolicy: corev1.DNSClusterFirst,
|
||||
ServiceAccountName: workerServiceAccountName,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1224,10 +1213,11 @@ func deployK8sWorker(image string, identifier string, env []string) error {
|
||||
// kubectl expose deployment shuffle-workers --type=NodePort --port=33333 --target-port=33333
|
||||
service := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: identifier,
|
||||
Name: identifier,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Selector: containerLabels,
|
||||
Selector: matchLabels,
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Protocol: "TCP",
|
||||
@@ -1250,7 +1240,6 @@ func deployK8sWorker(image string, identifier string, env []string) error {
|
||||
|
||||
func deployWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) error {
|
||||
|
||||
|
||||
if len(os.Getenv("REGISTRY_URL")) > 0 && os.Getenv("REGISTRY_URL") != "" {
|
||||
env = append(env, fmt.Sprintf("REGISTRY_URL=%s", os.Getenv("REGISTRY_URL")))
|
||||
}
|
||||
@@ -1280,18 +1269,17 @@ func deployWorker(image string, identifier string, env []string, executionReques
|
||||
Resources: container.Resources{},
|
||||
}
|
||||
|
||||
certPath := "/certs"
|
||||
|
||||
// This is just to test the mounting locally so
|
||||
// I can control from what source I'm mounting
|
||||
// the certs to. Default behaviour is:
|
||||
// /certs:/certs.
|
||||
certPath := "/certs"
|
||||
if os.Getenv("SHUFFLE_CERT_PATH") != "" {
|
||||
certPath = os.Getenv("SHUFFLE_CERT_PATH")
|
||||
}
|
||||
|
||||
_, err := os.ReadDir(certPath)
|
||||
|
||||
if certPath != "" && err == nil {
|
||||
certVol := mount.Mount{
|
||||
Type: mount.TypeBind,
|
||||
@@ -1310,7 +1298,6 @@ func deployWorker(image string, identifier string, env []string, executionReques
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG")
|
||||
parsedUuid := uuid.NewV4()
|
||||
|
||||
@@ -1322,7 +1309,7 @@ func deployWorker(image string, identifier string, env []string, executionReques
|
||||
if isKubernetes != "true" {
|
||||
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
|
||||
|
||||
if strings.ToLower(cleanupEnv) != "false" {
|
||||
if strings.ToLower(cleanupEnv) == "true" {
|
||||
hostConfig.AutoRemove = true
|
||||
}
|
||||
}
|
||||
@@ -1351,11 +1338,11 @@ func deployWorker(image string, identifier string, env []string, executionReques
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Container create error(2): %s", err)
|
||||
log.Printf("[ERROR][%s] Container create error(2): %s", executionRequest.ExecutionId, err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Printf("[ERROR] Container create error: %s", err)
|
||||
log.Printf("[ERROR][%s] Container create error: %s", executionRequest.ExecutionId, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1380,47 +1367,47 @@ func deployWorker(image string, identifier string, env []string, executionReques
|
||||
identifier+"-2",
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to CREATE container (2): %s", err)
|
||||
log.Printf("[ERROR][%s] Failed to CREATE container (2): %s", executionRequest.ExecutionId, err)
|
||||
}
|
||||
|
||||
err = dockercli.ContainerStart(context.Background(), cont.ID, containerStartOptions)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to start container (2): %s", err)
|
||||
log.Printf("[ERROR][%s] Failed to start container (2): %s", executionRequest.ExecutionId, err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[ERROR] Failed initial container start. Quitting as this is NOT a simple network issue. Err: %s", err)
|
||||
log.Printf("[ERROR][%s] Failed initial container start. Quitting as this is NOT a simple network issue. Err: %s", executionRequest.ExecutionId, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to start worker container in environment '%s': %s", environment, err)
|
||||
log.Printf("[ERROR][%s] Failed to start worker container in environment '%s': %s", executionRequest.ExecutionId, environment, err)
|
||||
return err
|
||||
} else {
|
||||
log.Printf("[INFO][%s] Worker Container created (2). Environment %s: docker logs %s", executionRequest.ExecutionId, environment, cont.ID)
|
||||
log.Printf("[INFO][%s] Worker Container created (2). Runtime Location '%s': docker logs -f %s", executionRequest.ExecutionId, environment, cont.ID)
|
||||
}
|
||||
|
||||
stats, err := dockercli.ContainerInspect(ctx, cont.ID)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed checking worker '%s': %s", cont.ID, err)
|
||||
return nil
|
||||
log.Printf("[WARNING][%s] Failed checking worker '%s': %s", executionRequest.ExecutionId, cont.ID, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
containerStatus := stats.ContainerJSONBase.State.Status
|
||||
if containerStatus != "running" {
|
||||
log.Printf("[ERROR] Status of %s is %s. Should be running. Contact support@shuffler.io if this persists.", cont.ID, containerStatus)
|
||||
log.Printf("[ERROR][%s] Status of %s is %s. Should be running. Contact support@shuffler.io if this persists.", executionRequest.ExecutionId, cont.ID, containerStatus)
|
||||
}
|
||||
/*
|
||||
err = stopWorker(containerName)
|
||||
if err != nil {
|
||||
log.Printf("Failed stopping worker %s", execution.ExecutionId)
|
||||
return nil
|
||||
}
|
||||
/*
|
||||
err = stopWorker(containerName)
|
||||
if err != nil {
|
||||
log.Printf("Failed stopping worker %s", execution.ExecutionId)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = deployWorker(dockercli, workerImage, containerName, env)
|
||||
if err != nil {
|
||||
log.Printf("Failed executing worker %s in state %s", execution.ExecutionId, containerStatus)
|
||||
return nil
|
||||
err = deployWorker(dockercli, workerImage, containerName, env)
|
||||
if err != nil {
|
||||
log.Printf("Failed executing worker %s in state %s", execution.ExecutionId, containerStatus)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
log.Printf("[INFO][%s] New Worker created. Environment %s: docker logs %s", executionRequest.ExecutionId, environment, cont.ID)
|
||||
@@ -1468,31 +1455,43 @@ func initializeImages() {
|
||||
}
|
||||
|
||||
if baseimageregistry == "" {
|
||||
//baseimageregistry = "ghcr.io" // Github
|
||||
baseimageregistry = "docker.io" // Dockerhub
|
||||
baseimageregistry = "ghcr.io" // Github
|
||||
log.Printf("[DEBUG] Setting baseimageregistry to %#v", baseimageregistry)
|
||||
|
||||
if len(os.Getenv("REGISTRY_URL")) > 0 {
|
||||
baseimageregistry = os.Getenv("REGISTRY_URL")
|
||||
} else {
|
||||
os.Setenv("REGISTRY_URL", baseimageregistry)
|
||||
}
|
||||
|
||||
os.Setenv("SHUFFLE_BASE_IMAGE_REGISTRY", baseimageregistry)
|
||||
|
||||
log.Printf("[WARNING] Setting baseimageregistry to %#v", baseimageregistry)
|
||||
}
|
||||
|
||||
if baseimagename == "" {
|
||||
// FIXME: This is probably the problem for image names tbh
|
||||
//baseimagename = "shuffle" // Github (ghcr.io)
|
||||
baseimagename = "frikky/shuffle" // Dockerhub
|
||||
baseimagename = "shuffle" // Github (ghcr.io)
|
||||
log.Printf("[DEBUG] Setting baseimagename to %#v", baseimagename)
|
||||
|
||||
os.Setenv("SHUFFLE_BASE_IMAGE_NAME", baseimagename)
|
||||
log.Printf("[WARNING] Setting baseimagename to %#v", baseimagename)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Setting swarm config to %#v. Default is empty.", swarmConfig)
|
||||
|
||||
newWorker := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion)
|
||||
// This is now always static
|
||||
newWorker := fmt.Sprintf("ghcr.io/shuffle/shuffle-worker:%s", workerVersion)
|
||||
if len(newWorkerImage) > 0 {
|
||||
newWorker = newWorkerImage
|
||||
}
|
||||
|
||||
// check whether they are the same first
|
||||
|
||||
if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") != "true" {
|
||||
// Check whether they are the same first
|
||||
if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") == "false" {
|
||||
log.Printf("[DEBUG] Skipping image download as SHUFFLE_AUTO_IMAGE_DOWNLOAD is set to false")
|
||||
} else {
|
||||
images := []string{
|
||||
fmt.Sprintf("frikky/shuffle:app_sdk"),
|
||||
fmt.Sprintf("shuffle/shuffle:app_sdk"),
|
||||
fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", baseimageregistry, baseimagename, appSdkVersion),
|
||||
newWorker,
|
||||
}
|
||||
|
||||
@@ -1513,8 +1512,6 @@ func initializeImages() {
|
||||
log.Printf("[DEBUG] Successfully downloaded and built %s", image)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("[DEBUG] Skipping image download as SHUFFLE_AUTO_IMAGE_DOWNLOAD is set to true")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1931,7 +1928,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Handle Cleanup - made it cleanup by default
|
||||
if strings.ToLower(os.Getenv("SHUFFLE_CONTAINER_AUTO_CLEANUP")) != "false" {
|
||||
if strings.ToLower(os.Getenv("SHUFFLE_CONTAINER_AUTO_CLEANUP")) != "false" && os.Getenv("CLEANUP") == "" {
|
||||
cleanupEnv = "true"
|
||||
}
|
||||
|
||||
@@ -2008,7 +2005,7 @@ func main() {
|
||||
log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!")
|
||||
|
||||
initializeImages()
|
||||
workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion)
|
||||
workerImage := fmt.Sprintf("ghcr.io/shuffle/shuffle-worker:%s", workerVersion)
|
||||
if len(newWorkerImage) > 0 {
|
||||
workerImage = newWorkerImage
|
||||
}
|
||||
@@ -2119,6 +2116,7 @@ func main() {
|
||||
|
||||
// Marshal and set body
|
||||
orborusStats := getOrborusStats(ctx)
|
||||
|
||||
pipelinePayload, pipelineerr := sendPipelineHealthStatus()
|
||||
|
||||
if pipelineerr != nil {
|
||||
@@ -2248,7 +2246,7 @@ func main() {
|
||||
if incRequest.Type == "PIPELINE_CREATE" || incRequest.Type == "PIPELINE_START" || incRequest.Type == "PIPELINE_STOP" || incRequest.Type == "PIPELINE_DELETE" {
|
||||
|
||||
os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
|
||||
tenzirDisabled = false
|
||||
tenzirDisabled = false
|
||||
|
||||
// Running NEW or editing pipelines
|
||||
err := handlePipeline(incRequest)
|
||||
@@ -2258,12 +2256,9 @@ func main() {
|
||||
|
||||
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
||||
} else if incRequest.Type == "DOCKER_IMAGE_DOWNLOAD" {
|
||||
log.Printf("[INFO] Re-downloading new image(s): %#v", incRequest.ExecutionArgument)
|
||||
log.Printf("[INFO] Re-downloading new image(s) due to backend request: %#v", incRequest.ExecutionArgument)
|
||||
|
||||
if len(incRequest.ExecutionArgument) > 0 {
|
||||
// FIXME: Wait X seconds before running this as the image build may not be done yet. This is shitty, but may be ok to do in Orborus. Easy fix for the future: Just let it run through jobs 5-10 times before actually picking it up
|
||||
|
||||
// Run after 25 seconds in the goroutine instead
|
||||
go handleBackendImageDownload(ctx, incRequest.ExecutionArgument)
|
||||
} else {
|
||||
log.Printf("[ERROR] No image name provided for download. Removing job from queue.")
|
||||
@@ -2273,7 +2268,7 @@ func main() {
|
||||
|
||||
} else if incRequest.Type == "CATEGORY_UPDATE" {
|
||||
os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
|
||||
tenzirDisabled = false
|
||||
tenzirDisabled = false
|
||||
|
||||
err = handleFileCategoryChange()
|
||||
if err != nil {
|
||||
@@ -2318,7 +2313,7 @@ func main() {
|
||||
|
||||
// Manual command = overrides to allow starting of Tenzir from the frontend anyway.
|
||||
os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
|
||||
tenzirDisabled = false
|
||||
tenzirDisabled = false
|
||||
|
||||
// Removed either way
|
||||
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
||||
@@ -2328,7 +2323,7 @@ func main() {
|
||||
if strings.Contains(fmt.Sprintf("%s", err), "node available") {
|
||||
// Disabling until UI is updated
|
||||
os.Setenv("SHUFFLE_SKIP_PIPELINES", "true")
|
||||
tenzirDisabled = true
|
||||
tenzirDisabled = true
|
||||
|
||||
log.Printf("[ERROR] Failed to start tenzir, reason: %s", err)
|
||||
err = shuffle.CreateOrgNotification(
|
||||
@@ -2426,11 +2421,11 @@ func main() {
|
||||
}
|
||||
|
||||
if execution.Status == "ABORT" || execution.Status == "FAILED" {
|
||||
log.Printf("[INFO] Executionstatus issue: ", execution.Status)
|
||||
log.Printf("[INFO][%s] Executionstatus issue: ", execution.ExecutionId, execution.Status)
|
||||
}
|
||||
|
||||
if shuffle.ArrayContains(executionIds, execution.ExecutionId) {
|
||||
log.Printf("[INFO] Execution already handled (rerun of old executions?): %s", execution.ExecutionId)
|
||||
log.Printf("[INFO][%s] Execution already handled (rerunning old execution)", execution.ExecutionId)
|
||||
toBeRemoved.Data = append(toBeRemoved.Data, execution)
|
||||
|
||||
// Should check when last this was ran, and if it's more than 10 minutes ago and it's not finished, we should run it again?
|
||||
@@ -2490,7 +2485,7 @@ func main() {
|
||||
|
||||
// Look for volume binds
|
||||
if len(os.Getenv("SHUFFLE_VOLUME_BINDS")) > 0 {
|
||||
log.Printf("[DEBUG] Added volume binds: %s", os.Getenv("SHUFFLE_VOLUME_BINDS"))
|
||||
//log.Printf("[DEBUG] Added volume binds: %s", os.Getenv("SHUFFLE_VOLUME_BINDS"))
|
||||
env = append(env, fmt.Sprintf("SHUFFLE_VOLUME_BINDS=%s", os.Getenv("SHUFFLE_VOLUME_BINDS")))
|
||||
}
|
||||
|
||||
@@ -2520,10 +2515,24 @@ func main() {
|
||||
toBeRemoved.Data = append(toBeRemoved.Data, execution)
|
||||
executionIds = append(executionIds, execution.ExecutionId)
|
||||
} else {
|
||||
log.Printf("[WARNING] Execution ID '%s' failed to deploy: %s", execution.ExecutionId, err)
|
||||
log.Printf("[WARNING][%s] Failed to deploy: %s", execution.ExecutionId, err)
|
||||
|
||||
if strings.Contains(err.Error(), "already exists") {
|
||||
toBeRemoved.Data = append(toBeRemoved.Data, execution)
|
||||
executionIds = append(executionIds, execution.ExecutionId)
|
||||
} else if strings.Contains(err.Error(), "No such image") {
|
||||
// Download the image
|
||||
|
||||
if isKubernetes == "true" {
|
||||
log.Printf("[DEBUG] Skipping image pull of '%s' because Kubernetes does it in realtime instead", workerImage)
|
||||
} else {
|
||||
log.Printf("[DEBUG] Re-pulling image %s as it doesn't exist, and is necessary for worker to run (autofix)", workerImage)
|
||||
pullOptions := image.PullOptions{}
|
||||
_, err = dockercli.ImagePull(ctx, workerImage, pullOptions)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to pull image %s: %s", workerImage, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2647,7 +2656,7 @@ func deployTenzirNode() error {
|
||||
// return errors.New("Pipelines are disabled by user with SHUFFLE_SKIP_PIPELINES")
|
||||
//log.Printf("[INFO] Pipelines are enabled by user")
|
||||
} else {
|
||||
return errors.New("Pipelines are disabled by user with SHUFFLE_SKIP_PIPELINES")
|
||||
return errors.New("Pipelines are disabled by user with SHUFFLE_SKIP_PIPELINES")
|
||||
}
|
||||
|
||||
if isKubernetes == "true" {
|
||||
@@ -2847,12 +2856,12 @@ func createAndStartTenzirNode(ctx context.Context, containerName, imageName stri
|
||||
},
|
||||
}
|
||||
|
||||
// FIXME: Is this necessary? Seems to screw up networking:
|
||||
// FIXME: Is this necessary? Seems to screw up networking:
|
||||
// conflicting options: hostname and the network mode
|
||||
/*
|
||||
if isKubernetes != "true" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" {
|
||||
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
|
||||
}
|
||||
if isKubernetes != "true" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" {
|
||||
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
|
||||
}
|
||||
*/
|
||||
|
||||
resp, err := dockercli.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName)
|
||||
@@ -3060,7 +3069,6 @@ func createPipeline(command, identifier string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed reading response body: %s", err)
|
||||
@@ -3078,8 +3086,8 @@ func createPipeline(command, identifier string) (string, error) {
|
||||
}
|
||||
|
||||
type PipelineResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
@@ -3215,10 +3223,28 @@ func deletePipeline(pipelineId string) error {
|
||||
func listPipelines() ([]shuffle.PipelineInfo, error) {
|
||||
responseData := shuffle.PipelineInfoWrapper{}
|
||||
|
||||
if tenzirDisabled {
|
||||
return responseData.Pipelines, errors.New("Tenzir is disabled")
|
||||
}
|
||||
|
||||
var reqBody []byte
|
||||
url := fmt.Sprintf("%s/api/v0/pipeline/list", pipelineUrl)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(reqBody))
|
||||
client := http.Client{
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(
|
||||
"POST",
|
||||
url,
|
||||
bytes.NewBuffer(reqBody),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return responseData.Pipelines, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return responseData.Pipelines, err
|
||||
}
|
||||
@@ -3302,7 +3328,7 @@ func handleFileCategoryChange() error {
|
||||
tenzirStorageFolder = "/tmp/"
|
||||
}
|
||||
|
||||
tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/")
|
||||
tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/")
|
||||
sigmaPath := fmt.Sprintf("%s/sigma_rules", tenzirStorageFolder)
|
||||
err = extractZIP("files.zip", sigmaPath)
|
||||
if err != nil {
|
||||
@@ -3310,7 +3336,6 @@ func handleFileCategoryChange() error {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
log.Printf("[DEBUG] Detection files copied to '%s' successfully.", sigmaPath)
|
||||
|
||||
return nil
|
||||
@@ -3399,7 +3424,7 @@ func removeFileCategory() error {
|
||||
tenzirStorageFolder = "/tmp/"
|
||||
}
|
||||
|
||||
tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/")
|
||||
tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/")
|
||||
|
||||
//sigmaPath := "/var/lib/tenzir/sigma_rules/*"
|
||||
sigmaPath := fmt.Sprintf("%s/sigma_rules", tenzirStorageFolder)
|
||||
@@ -3446,6 +3471,10 @@ func sendPipelineHealthStatus() (shuffle.LakeConfig, error) {
|
||||
Pipelines: []shuffle.PipelineInfoMini{},
|
||||
}
|
||||
|
||||
if tenzirDisabled {
|
||||
return pipelinePayload, nil
|
||||
}
|
||||
|
||||
// To not spam down the list API too much
|
||||
randint := rand.Intn(5)
|
||||
if len(pipelines) == 0 || randint == 0 {
|
||||
@@ -3468,10 +3497,6 @@ func sendPipelineHealthStatus() (shuffle.LakeConfig, error) {
|
||||
pipelinePayload.Pipelines = pipelines
|
||||
}
|
||||
|
||||
if tenzirDisabled {
|
||||
return pipelinePayload, nil
|
||||
}
|
||||
|
||||
err := deployTenzirNode()
|
||||
if err != nil {
|
||||
if (!strings.Contains(err.Error(), "SHUFFLE_SKIP_PIPELINES") && !strings.Contains(err.Error(), "Kubernetes not implemented for Tenzir node")) && !strings.Contains(err.Error(), "Tenzir Node is already running") && !strings.Contains(err.Error(), "docker daemon") {
|
||||
@@ -3564,9 +3589,8 @@ func getRunningWorkers(ctx context.Context, workerTimeout int) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
labelSelector := "app=shuffle-worker"
|
||||
pods, podErr := clientset.CoreV1().Pods(kubernetesNamespace).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: labelSelector,
|
||||
LabelSelector: "app.kubernetes.io/name=shuffle-worker",
|
||||
})
|
||||
if podErr != nil {
|
||||
log.Printf("[ERROR] Failed getting running workers: %s", podErr)
|
||||
@@ -3655,15 +3679,12 @@ func zombiecheck(ctx context.Context, workerTimeout int) error {
|
||||
All: true,
|
||||
})
|
||||
|
||||
//log.Printf("Len: %d", len(containers))
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed creating Containerlist: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
containerNames := map[string]string{}
|
||||
|
||||
stopContainers := []string{}
|
||||
removeContainers := []string{}
|
||||
log.Printf("[INFO] Baseimage: %s, Workertimeout: %d", baseimagename, int64(workerTimeout))
|
||||
@@ -3812,8 +3833,7 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest, image string,
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed creating worker request: %s", err)
|
||||
if strings.Contains(fmt.Sprintf("%s", err), "connection refused") || strings.Contains(fmt.Sprintf("%s", err), "EOF") {
|
||||
workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion)
|
||||
|
||||
workerImage := fmt.Sprintf("ghcr.io/shuffle/shuffle-worker:%s", workerVersion)
|
||||
if len(newWorkerImage) > 0 {
|
||||
workerImage = newWorkerImage
|
||||
}
|
||||
@@ -3837,8 +3857,7 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest, image string,
|
||||
log.Printf("[ERROR] Error running worker request to %s (1): %s", streamUrl, err)
|
||||
|
||||
if strings.Contains(fmt.Sprintf("%s", err), "connection refused") || strings.Contains(fmt.Sprintf("%s", err), "EOF") {
|
||||
workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion)
|
||||
|
||||
workerImage := fmt.Sprintf("ghcr.io/shuffle/shuffle-worker:%s", workerVersion)
|
||||
if len(newWorkerImage) > 0 {
|
||||
workerImage = newWorkerImage
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
github.com/docker/docker v27.5.0+incompatible
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shuffle/shuffle-shared v0.8.18
|
||||
github.com/shuffle/shuffle-shared v0.8.35
|
||||
k8s.io/api v0.30.2
|
||||
k8s.io/apimachinery v0.30.2
|
||||
k8s.io/client-go v0.30.2
|
||||
|
||||
@@ -294,8 +294,8 @@ github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fc
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shuffle/shuffle-shared v0.8.18 h1:7f7cV+P2pr/g44i+AI8P0UheEe8oG8O2V8loBfe9YSw=
|
||||
github.com/shuffle/shuffle-shared v0.8.18/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI=
|
||||
github.com/shuffle/shuffle-shared v0.8.35 h1:3awc0TrsLLZiQeWD2XGIkTnFbczAG0cMfy1+cB/P7zg=
|
||||
github.com/shuffle/shuffle-shared v0.8.35/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI=
|
||||
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=
|
||||
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
|
||||
// This is for automatic removal of certain code :)
|
||||
@@ -56,6 +56,7 @@ var logsDisabled = os.Getenv("SHUFFLE_LOGS_DISABLED")
|
||||
var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP"))
|
||||
var swarmNetworkName = os.Getenv("SHUFFLE_SWARM_NETWORK_NAME")
|
||||
var dockerApiVersion = strings.ToLower(os.Getenv("DOCKER_API_VERSION"))
|
||||
var appServiceAccountName = os.Getenv("SHUFFLE_APP_SERVICE_ACCOUNT_NAME")
|
||||
|
||||
var kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE")
|
||||
var executionCount int64
|
||||
@@ -104,9 +105,9 @@ var window = shuffle.NewTimeWindow(10 * time.Second)
|
||||
|
||||
// Images to be autodeployed in the latest version of Shuffle.
|
||||
var autoDeploy = map[string]string{
|
||||
"http:1.4.0": "frikky/shuffle:http_1.4.0",
|
||||
"shuffle-tools:1.2.0": "frikky/shuffle:shuffle-tools_1.2.0",
|
||||
"shuffle-subflow:1.1.0": "frikky/shuffle:shuffle-subflow_1.1.0",
|
||||
"http:1.4.0": "frikky/shuffle:http_1.4.0",
|
||||
"shuffle-tools:1.2.0": "frikky/shuffle:shuffle-tools_1.2.0",
|
||||
"shuffle-subflow:1.1.0": "frikky/shuffle:shuffle-subflow_1.1.0",
|
||||
// "shuffle-tools-fork:1.0.0": "frikky/shuffle:shuffle-tools-fork_1.0.0",
|
||||
}
|
||||
|
||||
@@ -298,7 +299,7 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason
|
||||
}
|
||||
|
||||
// Might not be necessary because of cleanupEnv hostconfig autoremoval
|
||||
if cleanupEnv == "true" && (os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm") {
|
||||
if strings.ToLower(cleanupEnv) == "true" && (os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm") {
|
||||
/*
|
||||
ctx := context.Background()
|
||||
dockercli, err := dockerclient.NewEnvClient()
|
||||
@@ -482,9 +483,24 @@ func deployk8sApp(image string, identifier string, env []string) error {
|
||||
|
||||
//fix naming convention
|
||||
// podUuid := uuid.NewV4().String()
|
||||
// podName := fmt.Sprintf("%s-%s", value, podUuid)
|
||||
// name := fmt.Sprintf("%s-%s", value, podUuid)
|
||||
// replace identifier "_" with "-"
|
||||
podName := strings.ReplaceAll(identifier, "_", "-")
|
||||
name := strings.ReplaceAll(identifier, "_", "-")
|
||||
|
||||
labels := map[string]string{
|
||||
"app.kubernetes.io/name": "shuffle-app",
|
||||
"app.kubernetes.io/instance": name,
|
||||
// "app.kubernetes.io/version": "",
|
||||
"app.kubernetes.io/part-of": "shuffle",
|
||||
"app.kubernetes.io/managed-by": "shuffle-worker",
|
||||
// Keep legacy labels for backward compatibility
|
||||
"app": name,
|
||||
}
|
||||
|
||||
matchLabels := map[string]string{
|
||||
"app.kubernetes.io/name": "shuffle-app",
|
||||
"app.kubernetes.io/instance": name,
|
||||
}
|
||||
|
||||
// pod := &corev1.Pod{
|
||||
// ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -564,20 +580,17 @@ func deployk8sApp(image string, identifier string, env []string) error {
|
||||
|
||||
deployment := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Name: name,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &replicaNumberInt32,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": podName,
|
||||
},
|
||||
MatchLabels: matchLabels,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": podName,
|
||||
},
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
@@ -587,6 +600,8 @@ func deployk8sApp(image string, identifier string, env []string) error {
|
||||
Env: buildEnvVars(envMap),
|
||||
},
|
||||
},
|
||||
DNSPolicy: corev1.DNSClusterFirst,
|
||||
ServiceAccountName: appServiceAccountName,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -601,12 +616,11 @@ func deployk8sApp(image string, identifier string, env []string) error {
|
||||
// kubectl expose deployment {podName} --type=NodePort --port=80 --target-port=80
|
||||
service := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Name: name,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Selector: map[string]string{
|
||||
"app": podName,
|
||||
},
|
||||
Selector: matchLabels,
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Protocol: "TCP",
|
||||
@@ -870,7 +884,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
|
||||
}
|
||||
|
||||
// Removing because log extraction should happen first
|
||||
if cleanupEnv == "true" {
|
||||
if strings.ToLower(cleanupEnv) == "true" {
|
||||
hostConfig.AutoRemove = true
|
||||
}
|
||||
|
||||
@@ -880,19 +894,30 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
|
||||
if len(volumeBindString) > 0 {
|
||||
volumeBindSplit := strings.Split(volumeBindString, ",")
|
||||
for _, volumeBind := range volumeBindSplit {
|
||||
if strings.Contains(volumeBind, ":") {
|
||||
volumeBinds = append(volumeBinds, volumeBind)
|
||||
} else {
|
||||
log.Printf("[ERROR] Volume bind '%s' is invalid.", volumeBind)
|
||||
if volumeBind == "srcfolder=dstfolder" || volumeBind == "srcfolder:dstfolder" || volumeBind == "/srcfolder:/dstfolder" {
|
||||
log.Printf("[DEBUG] Volume bind '%s' is invalid.", volumeBind)
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(volumeBind, "/") {
|
||||
log.Printf("[ERROR] Volume bind '%s' is invalid. Use absolute paths.", volumeBind)
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(volumeBind, ":") {
|
||||
log.Printf("[ERROR] Volume bind '%s' is invalid. Use absolute paths with colon inbetween them (/srcpath:dstpath/", volumeBind)
|
||||
continue
|
||||
}
|
||||
|
||||
volumeBinds = append(volumeBinds, volumeBind)
|
||||
}
|
||||
}
|
||||
|
||||
// Add more volume binds if possible
|
||||
if len(volumeBinds) > 0 {
|
||||
log.Printf("[DEBUG] Setting up binds for container. Got %d volume binds.", len(volumeBinds))
|
||||
|
||||
hostConfig.Binds = volumeBinds
|
||||
// Only use mounts, not direct binds
|
||||
hostConfig.Binds = []string{}
|
||||
hostConfig.Mounts = []mount.Mount{}
|
||||
for _, bind := range volumeBinds {
|
||||
if !strings.Contains(bind, ":") || strings.Contains(bind, "..") || strings.HasPrefix(bind, "~") {
|
||||
@@ -900,15 +925,27 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Appending bind %s to app container", bind)
|
||||
log.Printf("[DEBUG] Appending bind %s to App container", bind)
|
||||
bindSplit := strings.Split(bind, ":")
|
||||
sourceFolder := bindSplit[0]
|
||||
destinationFolder := bindSplit[1]
|
||||
hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{
|
||||
|
||||
readOnly := false
|
||||
if len(bindSplit) > 2 {
|
||||
mode := bindSplit[2]
|
||||
if mode == "ro" {
|
||||
readOnly = true
|
||||
}
|
||||
}
|
||||
|
||||
builtMount := mount.Mount{
|
||||
Type: mount.TypeBind,
|
||||
Source: sourceFolder,
|
||||
Target: destinationFolder,
|
||||
})
|
||||
ReadOnly: readOnly,
|
||||
}
|
||||
|
||||
hostConfig.Mounts = append(hostConfig.Mounts, builtMount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,7 +991,8 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
|
||||
|
||||
func cleanupKubernetesExecution(clientset *kubernetes.Clientset, workflowExecution shuffle.WorkflowExecution, namespace string) error {
|
||||
// workerName := fmt.Sprintf("worker-%s", workflowExecution.ExecutionId)
|
||||
labelSelector := fmt.Sprintf("app=shuffle-app,executionId=%s", workflowExecution.ExecutionId)
|
||||
// FIXME: The executionId label is currently not set
|
||||
labelSelector := fmt.Sprintf("app.kubernetes.io/name=shuffle-app,executionId=%s", workflowExecution.ExecutionId)
|
||||
|
||||
podList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{
|
||||
LabelSelector: labelSelector,
|
||||
@@ -1035,17 +1073,24 @@ func DeployContainer(ctx context.Context, cli *dockerclient.Client, config *cont
|
||||
err = cli.ContainerStart(ctx, cont.ID, container.StartOptions{})
|
||||
if err != nil {
|
||||
if strings.Contains(fmt.Sprintf("%s", err), "cannot join network") || strings.Contains(fmt.Sprintf("%s", err), "No such container") {
|
||||
// Remove the "CREATED" one from the previous:
|
||||
removeErr := cli.ContainerRemove(ctx, cont.ID, container.RemoveOptions{})
|
||||
if removeErr != nil {
|
||||
log.Printf("[ERROR] Failed to remove container %s: %s", cont.ID, removeErr)
|
||||
}
|
||||
|
||||
log.Printf("[WARNING] Failed deploying App on first attempt: %s. Removing some HostConfig configs.", err)
|
||||
parsedUuid := uuid.NewV4()
|
||||
identifier = fmt.Sprintf("%s-%s-nonetwork", identifier, parsedUuid)
|
||||
hostConfig = &container.HostConfig{
|
||||
LogConfig: container.LogConfig{
|
||||
Type: "json-file",
|
||||
Config: map[string]string{
|
||||
"max-size": "10m",
|
||||
},
|
||||
|
||||
hostConfig.NetworkMode = container.NetworkMode("")
|
||||
hostConfig.LogConfig = container.LogConfig{
|
||||
Type: "json-file",
|
||||
Config: map[string]string{
|
||||
"max-size": "10m",
|
||||
},
|
||||
Resources: container.Resources{},
|
||||
}
|
||||
hostConfig.Resources = container.Resources{}
|
||||
|
||||
cont, err = cli.ContainerCreate(
|
||||
context.Background(),
|
||||
@@ -1067,12 +1112,12 @@ func DeployContainer(ctx context.Context, cli *dockerclient.Client, config *cont
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Running secondary check without network with worker")
|
||||
//log.Printf("[DEBUG] Running secondary check without network with worker")
|
||||
err = cli.ContainerStart(ctx, cont.ID, container.StartOptions{})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to start container in environment %s: %s", environment, err)
|
||||
log.Printf("[ERROR] Failed to start container (2) in runtime location %s: %s", environment, err)
|
||||
|
||||
cacheErr := shuffle.DeleteCache(ctx, actionExecId)
|
||||
if cacheErr != nil {
|
||||
@@ -1232,6 +1277,7 @@ func getWorkerURLs() ([]string, error) {
|
||||
}
|
||||
|
||||
func askOtherWorkersToDownloadImage(image string) {
|
||||
// Why wouldn't it happen on swarm? Hmm
|
||||
if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" {
|
||||
return
|
||||
}
|
||||
@@ -1474,10 +1520,6 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
|
||||
env = append(env, fmt.Sprintf("SHUFFLE_APP_SDK_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")))
|
||||
}
|
||||
|
||||
// Fixes issue:
|
||||
// standard_go init_linux.go:185: exec user process caused "argument list too long"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083
|
||||
|
||||
// FIXME: Ensure to NEVER do this anymore
|
||||
// This potentially breaks too much stuff. Better to have the app poll the data.
|
||||
_ = executionData
|
||||
@@ -1501,9 +1543,11 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
|
||||
fmt.Sprintf("%s:%s_%s", baseimagename, parsedAppname, action.AppVersion),
|
||||
}
|
||||
|
||||
// If cleanup is set, it should run for efficiency
|
||||
// This is the weirdest shit ever looking back at
|
||||
// Needs optimization lol
|
||||
|
||||
pullOptions := image.PullOptions{}
|
||||
if cleanupEnv == "true" {
|
||||
if strings.ToLower(cleanupEnv) == "true" {
|
||||
err = deployApp(dockercli, images[0], identifier, env, workflowExecution, action)
|
||||
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
|
||||
if strings.Contains(err.Error(), "exited prematurely") {
|
||||
|
||||
Reference in New Issue
Block a user