Merge pull request #1676 from Shuffle/nightly

2.0.2
This commit is contained in:
Frikky
2025-04-03 23:14:49 +02:00
committed by GitHub
45 changed files with 2673 additions and 1858 deletions
@@ -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 }}
+12 -7
View File
@@ -33,16 +33,21 @@ jobs:
sudo apt-get install helm -y --no-install-recommends sudo apt-get install helm -y --no-install-recommends
- name: Set versions - name: Set versions
id: set_versions
run: | run: |
if [[ ${{ github.event_name }} == 'release' ]]; then if [[ ${{ github.event_name }} == 'release' ]]; then
CHART_VERSION="${{ github.event.release.tag_name }}" TAG_NAME="${{ github.event.release.tag_name }}"
APP_VERSION="${{ github.event.release.tag_name }}"
# Remove the v prefix
VERSION=${TAG_NAME#v}
APP_VERSION="${VERSION}"
CHART_VERSION="${VERSION}"
else else
CHART_VERSION="0.0.0-nightly-untagged-latest"
APP_VERSION="nightly" APP_VERSION="nightly"
CHART_VERSION="0.0.0-nightly-untagged-latest"
fi fi
echo "APP_VERSION set to ${APP_VERSION}"
echo "CHART_VERSION set to ${CHART_VERSION}. Validating..." echo "CHART_VERSION set to ${CHART_VERSION}. Validating..."
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string # https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
@@ -55,8 +60,8 @@ jobs:
exit 1; exit 1;
fi fi
echo "CHART_VERSION=${CHART_VERSION}" >> $GITHUB_OUTPUT echo "CHART_VERSION=${CHART_VERSION}" >> "$GITHUB_ENV"
echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_OUTPUT echo "APP_VERSION=${APP_VERSION}" >> "$GITHUB_ENV"
- name: Update helm dependencies - name: Update helm dependencies
run: helm dependency update ./functions/kubernetes/charts/shuffle run: helm dependency update ./functions/kubernetes/charts/shuffle
@@ -68,4 +73,4 @@ jobs:
run: helm registry login ghcr.io --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} run: helm registry login ghcr.io --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }}
- name: Push helm chart - name: Push helm chart
run: helm push ./functions/kubernetes/charts/shuffle-*.tgz oci://ghcr.io/shuffle/shuffle/charts run: helm push ./functions/kubernetes/charts/shuffle-*.tgz oci://ghcr.io/shuffle/charts
+56 -20
View File
@@ -604,11 +604,27 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
// return // return
//} //}
body, err := ioutil.ReadAll(request.Body) var err error
if err != nil { body := []byte{}
resp.WriteHeader(401) //log.Printf("IMAGE REQUEST BODY: %#v", request.Body)
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`)) if request.Body == nil || request.Body == http.NoBody {
return // Check for the image query, otherwise we skip everything
imageQuery := request.URL.Query().Get("image")
if len(imageQuery) == 0 {
resp.WriteHeader(400)
resp.Write([]byte(`{"success": false, "reason": "No image query found"}`))
return
}
body = []byte(fmt.Sprintf(`{"name": "%s"}`, imageQuery))
} else {
body, err = ioutil.ReadAll(request.Body)
if err != nil {
resp.WriteHeader(400)
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`))
return
}
} }
// This has to be done in a weird way because Datastore doesn't // This has to be done in a weird way because Datastore doesn't
@@ -630,28 +646,48 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
return return
} }
img := image.Summary{}
img2 := image.Summary{}
tagFound := ""
tagFound2 := ""
// Old way of doing it
//alternativeNameSplit := strings.Split(version.Name, "/")
//alternativeName := version.Name
//if len(alternativeNameSplit) == 3 {
// alternativeName = strings.Join(alternativeNameSplit[1:3], "/")
//}
appname, baseAppname, appnameSplit2, err := shuffle.GetAppNameSplit(version)
if err != nil {
log.Printf("[ERROR] Failed getting appname split: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't get the right docker image name"}`)))
return
}
if len(version.Name) == 0 {
log.Printf("[ERROR] No image name provided for download: %s", version.Name)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "No image name"}`)))
return
}
log.Printf("[INFO] Trying to download image: '%s'. Appname: '%s'. BaseAppname: '%s', Split2: %s", version.Name, appname, baseAppname, appnameSplit2)
alternativeName := appname
ctx := context.Background() ctx := context.Background()
images, err := dockercli.ImageList(ctx, image.ListOptions{ images, err := dockercli.ImageList(ctx, image.ListOptions{
All: true, All: true,
}) })
img := image.Summary{}
tagFound := ""
img2 := image.Summary{}
tagFound2 := ""
alternativeNameSplit := strings.Split(version.Name, "/")
alternativeName := version.Name
if len(alternativeNameSplit) == 3 {
alternativeName = strings.Join(alternativeNameSplit[1:3], "/")
}
log.Printf("[INFO] Trying to download image: %s. Alt: %s", version.Name, alternativeName)
for _, image := range images { for _, image := range images {
for _, tag := range image.RepoTags { for _, tag := range image.RepoTags {
//log.Printf("[DEBUG] Tag: %s", tag) if strings.Contains(tag, "<none>") {
continue
}
if strings.ToLower(tag) == strings.ToLower(version.Name) { if strings.ToLower(tag) == strings.ToLower(version.Name) {
img = image img = image
tagFound = tag tagFound = tag
+1 -1
View File
@@ -20,7 +20,7 @@ require (
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/h2non/filetype v1.1.3 github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.8.18 github.com/shuffle/shuffle-shared v0.8.35
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.36.0
google.golang.org/api v0.176.1 google.golang.org/api v0.176.1
google.golang.org/grpc v1.68.1 google.golang.org/grpc v1.68.1
+2 -2
View File
@@ -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/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 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shuffle/shuffle-shared v0.8.18 h1:7f7cV+P2pr/g44i+AI8P0UheEe8oG8O2V8loBfe9YSw= github.com/shuffle/shuffle-shared v0.8.35 h1:3awc0TrsLLZiQeWD2XGIkTnFbczAG0cMfy1+cB/P7zg=
github.com/shuffle/shuffle-shared v0.8.18/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI= 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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+18 -5
View File
@@ -3543,7 +3543,12 @@ func handleCloudJob(job shuffle.CloudSyncJob) error {
return err return err
} }
redirectDomain := "localhost:5001" backendPort := os.Getenv("BACKEND_PORT")
if backendPort == "" {
backendPort = "5001"
}
redirectDomain := fmt.Sprintf("localhost:%s", backendPort)
redirectUrl := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain) redirectUrl := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain)
outlookClient, _, err := shuffle.GetOutlookClient(ctx, "", hook.OauthToken, redirectUrl) outlookClient, _, err := shuffle.GetOutlookClient(ctx, "", hook.OauthToken, redirectUrl)
if err != nil { if err != nil {
@@ -4197,20 +4202,26 @@ func runInitEs(ctx context.Context) {
cleanupJob := func() func() { cleanupJob := func() func() {
return func() { return func() {
log.Printf("[INFO] Running schedule for cleaning up or re-running unfinished workflows in %d environments.", len(environments)) //log.Printf("[INFO] Running schedule for cleaning up or re-running unfinished workflows in %d environments.", len(environments))
backendPort := os.Getenv("BACKEND_PORT")
if backendPort == "" {
backendPort = "5001"
}
for _, environment := range environments { for _, environment := range environments {
// Allowed without PROXY management as it's localhost // Allowed without PROXY management as it's localhost
// client := shuffle.GetExternalClient(syncUrl) // client := shuffle.GetExternalClient(syncUrl)
httpClient := &http.Client{} httpClient := &http.Client{}
url := fmt.Sprintf("http://localhost:5001/api/v1/environments/%s/stop", environment) url := fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/stop", backendPort, environment)
req, err := http.NewRequest( req, err := http.NewRequest(
"GET", "GET",
url, url,
nil, nil,
) )
// FIXME: This will stop working of the user rotates their key lol
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, parsedApikey)) req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, parsedApikey))
if err != nil { if err != nil {
log.Printf("[ERROR] Failed CREATING environment request for %s: %s", environment, err) log.Printf("[ERROR] Failed CREATING environment request for %s: %s", environment, err)
@@ -4231,7 +4242,7 @@ func runInitEs(ctx context.Context) {
} }
log.Printf("[DEBUG] Successfully ran workflow cleanup request for %s. Body: %s", environment, string(respBody)) 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( req, err = http.NewRequest(
"GET", "GET",
url, url,
@@ -4256,7 +4267,8 @@ func runInitEs(ctx context.Context) {
log.Printf("[ERROR] Failed setting respbody %s", err) log.Printf("[ERROR] Failed setting respbody %s", err)
continue 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 == "" { if innerPort == "" {
log.Printf("[DEBUG] Running on %s:5001", hostname) log.Printf("[DEBUG] Running on %s:5001", hostname)
log.Fatal(http.ListenAndServe(":5001", nil)) log.Fatal(http.ListenAndServe(":5001", nil))
os.Setenv("BACKEND_PORT", "5001")
} else { } else {
log.Printf("[DEBUG] Running on %s:%s", hostname, innerPort) log.Printf("[DEBUG] Running on %s:%s", hostname, innerPort)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", innerPort), nil)) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", innerPort), nil))
+16 -517
View File
@@ -22,9 +22,6 @@ import (
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
//gyaml "github.com/ghodss/yaml"
"github.com/h2non/filetype" "github.com/h2non/filetype"
uuid "github.com/satori/go.uuid" 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/storage/memory"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
http2 "github.com/go-git/go-git/v5/plumbing/transport/http" 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" gyaml "github.com/ghodss/yaml"
) )
@@ -208,7 +197,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
} }
// remove items from DB // remove items from DB
parsedId := fmt.Sprintf("workflowqueue-%s", id) parsedId := strings.ReplaceAll(fmt.Sprintf("workflowqueue-%s", id), " ", "-")
ids := []string{} ids := []string{}
for _, execution := range removeExecutionRequests.Data { for _, execution := range removeExecutionRequests.Data {
ids = append(ids, execution.ExecutionId) ids = append(ids, execution.ExecutionId)
@@ -985,7 +974,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
if err != nil { if err != nil {
log.Printf("[ERROR] Failed to list child workflows: %s", err) log.Printf("[ERROR] Failed to list child workflows: %s", err)
} else { } 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 // Find cookies and append them to request.Header to replicate current request as closely as possible
for _, childWorkflow := range childWorkflows { 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 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) err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err) log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err)
@@ -1652,7 +1140,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
environments := execInfo.Environments environments := execInfo.Environments
var allEnvs []shuffle.Environment var allEnvs []shuffle.Environment
if len(workflowExecution.ExecutionOrg) > 0 { if len(workflowExecution.ExecutionOrg) > 0 {
//log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg)
allEnvironments, err := shuffle.GetEnvironments(ctx, workflowExecution.ExecutionOrg) allEnvironments, err := shuffle.GetEnvironments(ctx, workflowExecution.ExecutionOrg)
if err != nil { if err != nil {
@@ -3557,9 +3044,21 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
return return
} }
time.Sleep(2 * time.Second) shouldRerun := false
log.Printf("[INFO] Starting validation of execution %s", workflowExecution.ExecutionId) 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) returnBody := shuffle.HandleRetValidation(ctx, workflowExecution, 1)
returnBytes, err := json.Marshal(returnBody) returnBytes, err := json.Marshal(returnBody)
if err != nil { if err != nil {
+3
View File
@@ -33,6 +33,9 @@ const AdminNavBar = (props) => {
const navigate = useNavigate(); const navigate = useNavigate();
//const leadinfo = selectedOrganization.lead_info === undefined || selectedOrganization.lead_info === null || selectedOrganization.lead_info === "" ? "" : JSON.stringify(selectedOrganization.lead_info)
//const isPartner = leadinfo.includes("partner")
useEffect(() => { useEffect(() => {
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const tabName = queryParams.get('tab'); const tabName = queryParams.get('tab');
+4 -4
View File
@@ -891,7 +891,7 @@ const Billing = memo((props) => {
</span> </span>
: null} : null}
{showSupport ? {/* {showSupport ?
<Button variant="outlined" color="primary" style={{ marginTop: 20, marginBottom: 10, }} onClick={() => { <Button variant="outlined" color="primary" style={{ marginTop: 20, marginBottom: 10, }} onClick={() => {
if (window.drift !== undefined) { if (window.drift !== undefined) {
//window.drift.api.startInteraction({ interactionId: 340045 }) //window.drift.api.startInteraction({ interactionId: 340045 })
@@ -902,7 +902,7 @@ const Billing = memo((props) => {
}}> }}>
Get Support Get Support
</Button> </Button>
: null} : null} */}
</div> </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", 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%", }}> <div style={{ display: "flex", flexDirection: "column", width: "100%", }}>
{isCloud && {/* {isCloud &&
selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== undefined &&
selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions !== null &&
selectedOrganization.subscriptions.length > 0 && 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, }}> <div style={{ display: "flex", flexDirection: "row", width: "100%", marginTop: 20, marginBottom: 20, maxWidth: 860, }}>
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null : {isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null :
+10 -6
View File
@@ -151,13 +151,16 @@ const CacheView = memo((props) => {
if (fileCategories.length === 1 && fileCategories[0] === "default") { if (fileCategories.length === 1 && fileCategories[0] === "default") {
var newcategories = ["default"] var newcategories = ["default"]
for (var key in responseJson.keys) { 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)) { var category = responseJson.keys[key].category
newcategories.push(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) setFileCategories(newcategories)
} }
} }
@@ -357,6 +360,7 @@ const CacheView = memo((props) => {
<span style={{ color: "white" }}> <span style={{ color: "white" }}>
{ editCache ? "Edit Key" : "Add Key"}{selectedCategory === "" || selectedCategory === "default" ? "" : ` in category '${selectedCategory}'`} { editCache ? "Edit Key" : "Add Key"}{selectedCategory === "" || selectedCategory === "default" ? "" : ` in category '${selectedCategory}'`}
</span> </span>
</DialogTitle> </DialogTitle>
<div style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: "#212121", }}> <div style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: "#212121", }}>
Key Key
@@ -1041,13 +1045,13 @@ const CacheView = memo((props) => {
</span> </span>
</Tooltip> </Tooltip>
<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"} aria-label={"Delete"}
> >
<span> <span>
<IconButton <IconButton
style={{ padding: "6px" }} style={{ padding: "6px" }}
disabled={data.org_id !== selectedOrganization.id ? true : false} disabled={selectedOrganization?.id === undefined ? false : data.org_id !== selectedOrganization.id ? true : false}
onClick={() => { onClick={() => {
deleteCache(orgId, data.key); deleteCache(orgId, data.key);
//deleteFile(orgId); //deleteFile(orgId);
+2
View File
@@ -545,6 +545,8 @@ const EditWorkflow = (props) => {
}} }}
> >
<FormControlLabel value="test" control={<Radio />} label="Test" /> <FormControlLabel value="test" control={<Radio />} label="Test" />
<FormControlLabel value="staging" control={<Radio />} label="Staging" />
<FormControlLabel value="preprod" control={<Radio />} label="Pre-production" />
<FormControlLabel value="production" control={<Radio />} label="Production" /> <FormControlLabel value="production" control={<Radio />} label="Production" />
</RadioGroup> </RadioGroup>
+8 -6
View File
@@ -774,8 +774,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
}, [window?.location?.pathname]); }, [window?.location?.pathname]);
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 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 ( return (
<div <div
style={{ style={{
@@ -836,11 +836,13 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
} }
}} }}
> >
<Link to="/"> <Link to={isCloud && !showPartnerLogo ? "/" : "/workflows"}>
<img <img
src={ShuffleLogo} src={
showPartnerLogo ? userdata?.active_org?.image : ShuffleLogo
}
alt="Shuffle Logo" alt="Shuffle Logo"
style={{ width: 24, height: 24 }} style={{ width: showPartnerLogo ? 30 : 24, height: showPartnerLogo ? 30 : 24 }}
/> />
</Link> </Link>
</Tooltip> </Tooltip>
@@ -1611,7 +1613,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
}} }}
> >
{expandLeftNav && {userdata?.licensed !== true && !userdata?.org_status?.includes("integration_partner") && expandLeftNav &&
<Button <Button
variant="outlined" variant="outlined"
style={{marginBottom: 15, borderWidth: 2, }} style={{marginBottom: 15, borderWidth: 2, }}
+447 -203
View File
@@ -25,6 +25,8 @@ import {
CardContent, CardContent,
ButtonGroup, ButtonGroup,
DialogContentText, DialogContentText,
ToggleButton,
ToggleButtonGroup,
} from "@mui/material"; } from "@mui/material";
import { useNavigate, Link } from "react-router-dom"; import { useNavigate, Link } from "react-router-dom";
@@ -87,29 +89,80 @@ const LicencePopup = (props) => {
const [calculatedCores, setCalculatedCores] = useState('600') const [calculatedCores, setCalculatedCores] = useState('600')
const [onpremSelectedValue, setOnpremSelectedValue] = useState(8) 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) : "" 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 = { const paperStyle = {
padding: 20, padding: 20,
paddingBottom: 30,
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
height: "100%" height: "100%"
} }
billingInfo.subscription = { billingInfo.subscription = {
"active": true, "active": true,
"name": "Pay as you go", "name": userdata?.app_execution_limit === 10000 ? "10,000 App Runs" : "2,000 App Runs",
"price": typecost_single, "price": "Free",
"currency": "USD", "currency": "Free",
"currency_text": "$", "currency_text": "",
"interval": "app run / month", "interval": "",
"description": "Pay as you go", "description": "Pay as you go",
"features": [ "features": [
"Basic Support", "Community Support",
"Includes 10.000 app run/month for free. ", `Includes ${userdata?.app_execution_limit === 10000 ? "10,000" : "2,000"} app run/month for free. `,
"Pay for what you use with no minimum commitment and cancel anytime." "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) => { const sendSignatureRequest = (subscription) => {
@@ -151,32 +204,32 @@ const LicencePopup = (props) => {
const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed) const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed)
const [hovered, setHovered] = React.useState(false) const [hovered, setHovered] = React.useState(false)
const [newBillingEmail, setNewBillingEmail] = useState(''); const [newBillingEmail, setNewBillingEmail] = useState('');
var top_text = "Base Cloud Access" var top_text = "Starter Plan"
if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) { // if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) {
subscription.name = "Enterprise" // subscription.name = "Enterprise"
subscription.currency_text = "$" // subscription.currency_text = "$"
subscription.price = subscription.level * 180 // subscription.price = subscription.level * 180
subscription.limit = subscription.level * 100000 // subscription.limit = subscription.level * 100000
subscription.interval = subscription.recurrence // subscription.interval = subscription.recurrence
subscription.features = [ // subscription.features = [
"Includes " + subscription.limit + " app runs/month. ", // "Includes " + subscription.limit + " app runs/month. ",
"Multi-Tenancy and Region-Selection", // "Multi-Tenancy and Region-Selection",
"And all other features from /pricing", // "And all other features from /pricing",
] // ]
} // }
if (userdata?.app_execution_limit >= 300000) { // if (userdata?.app_execution_limit >= 300000) {
subscription.name = "Enterprise" // subscription.name = "Enterprise"
subscription.currency_text = "$" // subscription.currency_text = "$"
subscription.price = typecost_single // subscription.price = typecost_single
subscription.limit = userdata?.app_execution_limit // subscription.limit = userdata?.app_execution_limit
subscription.interval = "app run / month" // subscription.interval = "app run / month"
subscription.features = [ // subscription.features = [
"Includes " + (userdata?.app_execution_limit/1000) + "K app runs/month. ", // "Includes " + (userdata?.app_execution_limit/1000) + "K app runs/month. ",
"Multi-Tenancy and Region-Selection", // "Multi-Tenancy and Region-Selection",
"And all other features from /pricing", // "And all other features from /pricing",
] // ]
} // }
var newPaperstyle = JSON.parse(JSON.stringify(paperStyle)) var newPaperstyle = JSON.parse(JSON.stringify(paperStyle))
@@ -189,12 +242,12 @@ const LicencePopup = (props) => {
var showSupport = false var showSupport = false
if (subscription.name.includes("default")) { if (subscription.name.includes("default")) {
top_text = "Custom Contract" top_text = "Custom Contract"
newPaperstyle.border = "1px solid #f85a3e" // newPaperstyle.border = "1px solid #f85a3e"
showSupport = true showSupport = true
} }
if (subscription.name.includes("App Run Units")) { if (subscription.name.includes("App Run Units")) {
top_text = "Cloud Access" top_text = "Scale Plan"
showSupport = true showSupport = true
} }
@@ -357,7 +410,11 @@ const LicencePopup = (props) => {
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </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" }}> <div style={{ display: "flex" }}>
{top_text === "Base Cloud Access" && userdata.has_card_available === true && !isScale ? {top_text === "Base Cloud Access" && userdata.has_card_available === true && !isScale ?
<Chip <Chip
@@ -381,7 +438,7 @@ const LicencePopup = (props) => {
color="primary" color="primary"
/> />
: null} : 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} {top_text}
</Typography> </Typography>
@@ -396,7 +453,7 @@ const LicencePopup = (props) => {
}} }}
/> />
: null} : null}
{isCloud && highlight === true && top_text !== "Base Cloud Access" ? {isCloud && highlight === true && top_text !== "Starter Plan" ?
<Tooltip <Tooltip
title="Sign EULA" title="Sign EULA"
placement="top" placement="top"
@@ -426,7 +483,7 @@ const LicencePopup = (props) => {
{subscription.currency_text}{subscription.price} {subscription.currency_text}{subscription.price}
</Typography> </Typography>
<Typography variant="body1" color="textSecondary" style={{ marginLeft: 10, marginTop: 15, marginBottom: 10 }}> <Typography variant="body1" color="textSecondary" style={{ marginLeft: 10, marginTop: 15, marginBottom: 10 }}>
/ {subscription.interval} {subscription.interval.length > 0 ? `/ ${subscription.interval}` : ""}
</Typography> </Typography>
</div> </div>
: null} : null}
@@ -517,23 +574,17 @@ const LicencePopup = (props) => {
: null} : null}
</ul> </ul>
<Typography variant="body2" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}> <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 ? isCloud ?
userdata?.app_execution_limit && userdata?.app_execution_limit >= 300000 ? userdata?.app_execution_limit && userdata?.app_execution_limit !== 10000 ?
"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 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 not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.` `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.` `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> </Typography>
{isCloud && (userdata.has_card_available === true || selectedOrganization?.Billing?.Email?.length > 0 )? {/* {isCloud && (userdata.has_card_available === true || selectedOrganization?.Billing?.Email?.length > 0 )?
<div> <div>
<span>Billing email: {BillingEmail}</span> <span>Billing email: {BillingEmail}</span>
<Button <Button
@@ -611,15 +662,14 @@ const LicencePopup = (props) => {
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</div> </div>
: null} : null} */}
</div> </div>
{isCloud ? ( {isCloud ? (
<Button <Button
fullWidth fullWidth
disabled={false}
color="primary" color="primary"
style={{ style={{
marginTop: !userdata.has_card_available ? 20 : 10, marginTop: !userdata.has_card_available ? 25 : 10,
borderRadius: 4, borderRadius: 4,
height: 40, height: 40,
fontSize: 16, fontSize: 16,
@@ -630,20 +680,15 @@ const LicencePopup = (props) => {
}} }}
onClick={() => { onClick={() => {
if (isCloud) { console.log("Subscription: ", subscription.name)
handlePayasyougo(userdata, selectedOrganization, BillingEmail) if(!subscription.name.includes("App Run Units")) {
//navigate("/pricing?tab=cloud&highlight=true") window.open("https://discord.gg/B2CBzUm", "_blank")
} else { } else {
//window.open("https://shuffler.io/pricing?tab=onprem&highlight=true", "_blank") window.open("mailto:support@shuffler.io", "_blank")
handlePayasyougo() }
}
}} }}
> >
{userdata.has_card_available === true ? Get Support
"Manage Card Details"
:
"Add Card Details"
}
</Button> ) : null} </Button> ) : null}
<Button <Button
variant="outlined" variant="outlined"
@@ -709,42 +754,42 @@ const LicencePopup = (props) => {
) )
} }
useEffect(() => { // useEffect(() => {
console.log("New variant: ", shuffleVariant) // console.log("New variant: ", shuffleVariant)
if (shuffleVariant === 1) { // if (shuffleVariant === 1) {
setCalculatedCost("$960") // setCalculatedCost("$960")
setSelectedValue(8) // setSelectedValue(8)
} else { // } else {
if (userdata && userdata?.app_execution_limit) { // if (userdata && userdata?.app_execution_limit) {
if (userdata.app_execution_limit >= 300000 && userdata.app_execution_limit < 400000) { // if (userdata.app_execution_limit >= 30000 && userdata.app_execution_limit < 40000) {
setSelectedValue(400) // setSelectedValue(400)
setCalculatedCost("$1280") // setCalculatedCost("$1280")
}else if (userdata?.app_execution_limit >= 400000 && userdata?.app_execution_limit < 500000) { // }else if (userdata?.app_execution_limit >= 40000 && userdata?.app_execution_limit < 50000) {
setSelectedValue(500) // setSelectedValue(500)
setCalculatedCost("$1600") // setCalculatedCost("$1600")
} else if (userdata?.app_execution_limit >= 500000 && userdata?.app_execution_limit < 600000) { // } else if (userdata?.app_execution_limit >= 500000 && userdata?.app_execution_limit < 600000) {
setSelectedValue(600) // setSelectedValue(600)
setCalculatedCost("$1920") // setCalculatedCost("$1920")
} else if (userdata?.app_execution_limit >= 600000 && userdata?.app_execution_limit < 700000) { // } else if (userdata?.app_execution_limit >= 60000 && userdata?.app_execution_limit < 70000) {
setSelectedValue(700) // setSelectedValue(700)
setCalculatedCost("$2240") // setCalculatedCost("$2240")
} else if (userdata?.app_execution_limit >= 700000 && userdata?.app_execution_limit < 800000) { // } else if (userdata?.app_execution_limit >= 70000 && userdata?.app_execution_limit < 80000) {
setSelectedValue(800) // setSelectedValue(800)
setCalculatedCost("$2560") // setCalculatedCost("$2560")
} else if (userdata?.app_execution_limit >= 800000 && userdata?.app_execution_limit < 900000) { // } else if (userdata?.app_execution_limit >= 80000 && userdata?.app_execution_limit < 90000) {
setSelectedValue(900) // setSelectedValue(900)
setCalculatedCost("$2880") // setCalculatedCost("$2880")
}else { // }else {
setCalculatedCost("$960") // setCalculatedCost("$960")
setSelectedValue(300) // setSelectedValue(300)
} // }
}else { // }else {
setCalculatedCost("$960") // setCalculatedCost("$960")
setSelectedValue(300) // setSelectedValue(300)
} // }
} // }
}, [shuffleVariant]) // }, [userdata])
if (typeof window === 'undefined' || window.location === undefined) { if (typeof window === 'undefined' || window.location === undefined) {
return null return null
@@ -900,71 +945,121 @@ const LicencePopup = (props) => {
} }
console.log("Priceitem: ", shuffleVariant) 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 = () => { const isLoggedInHandler = () => {
if (calculatedCost === payasyougo) { var priceItem;
handlePayasyougo(props.userdata) if (window.location.origin === "https://shuffler.io" || window.location.origin === "https://sandbox.shuffler.io") {
return 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 = console.log("Selected Organization: ", selectedOrganization.subscriptions)
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: "",
})
})
}
return ( return (
<div> <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}> <Grid item xs={8}>
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? {selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0 ?
<SubscriptionObject <SubscriptionObject
index={0} index={0}
globalUrl={globalUrl} globalUrl={globalUrl}
@@ -976,7 +1071,29 @@ const LicencePopup = (props) => {
subscription={billingInfo.subscription} subscription={billingInfo.subscription}
highlight={selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0} 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", }}> <span style={{ display: "flex", }}>
<SubscriptionObject <SubscriptionObject
index={0} index={0}
@@ -1044,61 +1161,184 @@ const LicencePopup = (props) => {
}) })
: null} */} : null} */}
</Grid> </Grid>
<Grid item xs={8}> <Grid item xs={8} sx={{ height: "100%" }}>
<Grid style={{}}> <Grid style={{}}>
{errorMessage.length > 0 ? <Typography variant="h4">Error: {errorMessage}</Typography> : null} {errorMessage.length > 0 ? <Typography variant="h4">Error: {errorMessage}</Typography> : null}
<Card style={{ <Card style={{
padding: 20, padding: 20,
borderRadius: theme.palette?.borderRadius, background:
border: !isScale ? "1px solid #f85a3e" : 'none', "linear-gradient(to right, #212121, #212121) padding-box, linear-gradient(90deg, #F86744 0%, #F34475 100%) border-box",
borderWidth: "2px",
borderStyle: "solid",
borderColor: "transparent",
}}> }}>
<div> <div
{ !isScale && <Button style={{ backgroundColor: 'rgba(255, 132, 68, 0.2)', color: "#FF8444", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', }} 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" 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> </div>
<Typography variant="h6" style={{ marginTop: 10, marginBottom: 10, flex: 5, }}>{shuffleVariant === 1 ? "Scale" : "Enterprise"}</Typography>
<Divider /> <Divider />
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20 }}> <Typography variant="body1" color="textSecondary" style={{ marginTop: 20 }}>
{shuffleVariant === 0 ? App Runs Units
"SaaS / Cloud - Per Month"
:
"Open Source + Scale License"
}
</Typography> </Typography>
<Typography style={{ minHeight: 46, cursor: calculatedCores === "Get A Quote" ? "pointer" : "inherit", }} onClick={() => { <div style={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 10 , marginTop: 10 }}>
<Typography style={{
if (calculatedCores === "Get A Quote") { fontSize: 24,
console.log("Clicked on get a quote") marginTop: 7,
if (window.drift !== undefined) { marginBottom: 10,
window.drift.api.startInteraction({ interactionId: 340785 }) fontWeight: "500",
} }}
} >
}}>{calculatedCost}</Typography> {scaleValue > 300 ? "Let's Talk" : `$${getPrice(32) * (scaleValue / 10)}`}
<Typography variant="body1" color="textSecondary" style={{}}>For {shuffleVariant === 1 ? `${selectedValue} CPU cores` : `${selectedValue}k App Runs`}: </Typography> </Typography>
<div style={{ textAlign: "center" }}> <Typography
color="text.secondary"
<Slider sx={{
aria-label="Small steps" fontSize: "14px",
style={{ width: "80%", margin: "auto" }} marginBottom: "-2px",
onChange={(event, newValue) => { marginLeft: scaleValue > 300 ? 1 : 0,
handleChange(event, newValue)
}} }}
marks >
value={selectedValue} {scaleValue > 300 ? `for ${scaleValue > 500 ? "500k+" : `${scaleValue}k`} App Runs` : `/month for ${scaleValue}k App Runs`}
step={shuffleVariant === 0 ? 100 : 4} </Typography>
min={shuffleVariant === 0 ? 100 : 8}
max={shuffleVariant === 0 ? 1000 : 32}
valueLabelDisplay="auto"
/>
</div> </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>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<span>{defaultTaskIcon}</span> <span>{defaultTaskIcon}</span>
<Typography style={{ fontSize: 14, marginLeft: 8 }}>Priority Support</Typography> <Typography style={{ fontSize: 14, marginLeft: 8 }}>Standard Email Support</Typography>
</div> </div>
<Divider /> <Divider />
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
@@ -1117,7 +1357,7 @@ const LicencePopup = (props) => {
<Divider /> <Divider />
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<span>{defaultTaskIcon}</span> <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> </div>
<div style={{ marginTop: 20, }} /> <div style={{ marginTop: 20, }} />
@@ -1136,7 +1376,7 @@ const LicencePopup = (props) => {
navigate("/pricing") navigate("/pricing")
} else { } else {
window.open("https://shuffler.io/pricing?tab=onprem", "_blank") window.open("https://shuffler.io/pricing?tab=Self-Hosted", "_blank")
} }
}} }}
color="primary" color="primary"
@@ -1149,6 +1389,10 @@ const LicencePopup = (props) => {
style={{ borderRadius: 4, textTransform: "capitalize", color: "#1a1a1a", backgroundColor: "#ff8544", width: "100%", fontSize: 16}} style={{ borderRadius: 4, textTransform: "capitalize", color: "#1a1a1a", backgroundColor: "#ff8544", width: "100%", fontSize: 16}}
onClick={() => { onClick={() => {
if (isCloud) { if (isCloud) {
if(scaleValue > 300){
navigate("/contact?category=cloud_enterprise_plan")
return;
}
ReactGA.event({ ReactGA.event({
category: "header", category: "header",
action: "upgread_clicks_popup", action: "upgread_clicks_popup",
@@ -1170,7 +1414,7 @@ const LicencePopup = (props) => {
}} }}
color="primary" color="primary"
> >
Upgrade {scaleValue > 300 ? "Let's Talk" : "Upgrade"}
</Button> </Button>
</div> </div>
</DialogActions> </DialogActions>
+3 -3
View File
@@ -61,7 +61,7 @@ const menuData = {
description: description:
"Connect and run actions seamlessly between different platforms.", "Connect and run actions seamlessly between different platforms.",
icon: "/images/logos/singul.svg", icon: "/images/logos/singul.svg",
path: "https://singul.io/", path: "https://singul-docs.gitbook.io/singul/getting-started",
gaData: { gaData: {
category: "navbar", category: "navbar",
action: "products_click", action: "products_click",
@@ -1247,7 +1247,7 @@ const Navbar = (props) => {
letterSpacing: '0.5px', letterSpacing: '0.5px',
}} }}
> >
Coming Soon Beta: Coming Soon
</Typography> </Typography>
)} )}
</Box> </Box>
@@ -1370,7 +1370,7 @@ const Navbar = (props) => {
label: "go_to_pricing", label: "go_to_pricing",
}) })
} else { } else {
window.open("https://shuffler.io/pricing", '_blank'); window.open("https://shuffler.io/pricing?env=Self-Hosted", '_blank');
return; return;
} }
}} }}
@@ -538,7 +538,7 @@ const OrgHeaderexpandedNew = (props) => {
renderValue={(selected) => selected.join(', ')} renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps} MenuProps={MenuProps}
> >
{["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "old customer", "old lead"].map((name) => ( {["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "integration partner", "distribution partner", "service partner", "old customer", "old lead"].map((name) => (
<MenuItem key={name} value={name}> <MenuItem key={name} value={name}>
<Checkbox checked={selectedStatus.indexOf(name) > -1} /> <Checkbox checked={selectedStatus.indexOf(name) > -1} />
<ListItemText primary={name} /> <ListItemText primary={name} />
+2 -2
View File
@@ -120,7 +120,7 @@ const OrganizationTab = (props) => {
isLoaded={isLoaded} isLoaded={isLoaded}
/> />
); );
case 'branding(beta)': case 'branding':
return <Branding return <Branding
isCloud={isCloud} isCloud={isCloud}
userdata={userdata} userdata={userdata}
@@ -140,7 +140,7 @@ const OrganizationTab = (props) => {
return ( return (
<div style={{ height: "100%", width: "100%", color: '#FFFFFF', backgroundColor: '#212121', borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", boxSizing: 'border-box' }}> <div style={{ height: "100%", width: "100%", color: '#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' }}> <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 <Tooltip
key={index} key={index}
title={ title={
+101 -14
View File
@@ -179,6 +179,7 @@ const ParsedAction = (props) => {
suborgWorkflows, suborgWorkflows,
originalWorkflow, originalWorkflow,
runFromHere,
} = props; } = props;
let navigate = useNavigate() let navigate = useNavigate()
@@ -233,11 +234,16 @@ const ParsedAction = (props) => {
// auth, required, optional // auth, required, optional
var changed = false var changed = false
if (selectedActionParameters === undefined || selectedActionParameters === null || selectedActionParameters.length === 0) { if (selectedActionParameters === undefined || selectedActionParameters === null || selectedActionParameters.length === 0) {
console.log("Returning because no params")
return return
} }
if (selectedApp !== undefined && selectedApp !== null && selectedApp.generated !== true) { if (selectedApp !== undefined && selectedApp !== null && selectedApp.generated !== true) {
return
if (isAgent || isIntegration) {
} else {
return
}
} }
// Fixing required fields with a shitty structure :) // Fixing required fields with a shitty structure :)
@@ -346,13 +352,23 @@ const ParsedAction = (props) => {
// 3. generated fields & all else // 3. generated fields & all else
const newparams = auth var newparams = auth
.concat(bodyfield) .concat(bodyfield)
.concat(required) .concat(required)
.concat(generated_optional) .concat(generated_optional)
.concat(special_optional) .concat(special_optional)
.concat(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 = [] var newkeyorder = []
for (let paramkey in newparams) { for (let paramkey in newparams) {
//console.log("Param: ", newparams[paramkey]) //console.log("Param: ", newparams[paramkey])
@@ -666,11 +682,14 @@ const ParsedAction = (props) => {
// Process workflowExecutions // Process workflowExecutions
if (workflowExecutions.length > 0) { if (workflowExecutions.length > 0) {
var appended = false
var foundvalue = ""
for (let execution of workflowExecutions) { for (let execution of workflowExecutions) {
const execArg = execution.execution_argument; const execArg = execution.execution_argument;
if (execArg && execArg.length > 0) { if (execArg && execArg.length > 0) {
const valid = validateJson(execArg); const valid = validateJson(execArg);
if (valid.valid) { if (valid.valid) {
appended = true
newActionList.push({ newActionList.push({
type: "Runtime Argument", type: "Runtime Argument",
name: "Runtime Argument", name: "Runtime Argument",
@@ -681,9 +700,23 @@ const ParsedAction = (props) => {
}) })
break 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 // Add default Runtime Argument if none were added
@@ -772,6 +805,8 @@ const ParsedAction = (props) => {
} }
labels.push(parentNode.label); labels.push(parentNode.label);
var secondaryExample = ""
let exampleData = parentNode.example ?? ""; let exampleData = parentNode.example ?? "";
if (parentNode?.app_name === "http") { if (parentNode?.app_name === "http") {
exampleData = "" exampleData = ""
@@ -782,9 +817,22 @@ const ParsedAction = (props) => {
const foundResult = exec.results?.find(result => result?.action?.id === parentNode?.id); const foundResult = exec.results?.find(result => result?.action?.id === parentNode?.id);
if (foundResult) { if (foundResult) {
const valid = validateJson(foundResult.result); const valid = validateJson(foundResult.result);
if (valid.valid && valid.result.success !== false) { if (valid.valid) {
exampleData = valid.result
break // 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) { if (parentNode.label === undefined) {
@@ -1162,7 +1214,9 @@ const ParsedAction = (props) => {
selectedAction.parameters[1].value = splitparsed[1] selectedAction.parameters[1].value = splitparsed[1]
if (splitparsed.length > 2) { 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(".#")) { } 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.") 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> </Tooltip>
</IconButton> </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") ? {(selectedAction?.generated === true && selectedAction?.app_version === "1.0.0") || (selectedAction?.app_name === "Shuffle Tools" && selectedAction?.app_version !== "1.2.0") ?
<Button <Button
variant="contained" variant="contained"
@@ -1875,12 +1961,12 @@ const ParsedAction = (props) => {
toast.success("Changed version of all nodes to " + event.target.value) toast.success("Changed version of all nodes to " + event.target.value)
}} }}
style={{ style={{
marginTop: 10, position: "absolute",
top: 10, right: 10,
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.surfaceColor,
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: "white",
height: 35, height: 35,
marginleft: 10,
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
}} }}
SelectDisplayProps={{ SelectDisplayProps={{
@@ -4120,15 +4206,12 @@ const ParsedAction = (props) => {
fullWidth fullWidth
id={"rightside_field_" + count} id={"rightside_field_" + count}
onChange={(e) => { onChange={(e) => {
console.log("MULTI SELECT: ", multi, e.target.value)
changeActionParameter(e, count, data); changeActionParameter(e, count, data);
setUpdate(Math.random()); setUpdate(Math.random());
}} }}
style={{ style={{
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.platformColor,
color: "white", height: 40,
height: "50px",
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
}} }}
> >
@@ -4525,6 +4608,7 @@ const ParsedAction = (props) => {
minWidth: 250, minWidth: 250,
maxWidth: 250, maxWidth: 250,
marginRight: 0, marginRight: 0,
paddingLeft: 12,
}} }}
value={innerdata} value={innerdata}
onMouseOver={() => handleMouseover()} onMouseOver={() => handleMouseover()}
@@ -4685,8 +4769,9 @@ const ParsedAction = (props) => {
</Tooltip> </Tooltip>
: null} : 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 <OpenInFullIcon
style={{ color: "rgba(255,255,255,0.7)", cursor: "pointer", margin: multiline ? 5 : 0, height: 20, width: 20, }} style={{ color: "rgba(255,255,255,0.7)", cursor: "pointer", margin: multiline ? 5 : 0, height: 20, width: 20, }}
onMouseOver={(event) => { onMouseOver={(event) => {
@@ -4728,6 +4813,7 @@ const ParsedAction = (props) => {
}} }}
/> />
</Tooltip> </Tooltip>
}
</div> </div>
@@ -4737,6 +4823,7 @@ const ParsedAction = (props) => {
showDropdownNumber === count && showDropdownNumber === count &&
data.variant === "STATIC_VALUE" && data.variant === "STATIC_VALUE" &&
jsonList.length > 0 ? ( jsonList.length > 0 ? (
<FormControl fullWidth style={{ marginTop: 0 }}> <FormControl fullWidth style={{ marginTop: 0 }}>
<InputLabel <InputLabel
id="action-autocompleter" id="action-autocompleter"
+2 -1
View File
@@ -837,12 +837,13 @@ const NotificationItem = memo((props) => {
color="secondary" color="secondary"
style={{ style={{
height: 50, height: 50,
textTransform: "none",
}} }}
onClick={() => { onClick={() => {
dismissNotification(data.id); dismissNotification(data.id);
}} }}
> >
Mark Read Mark as Read
</Button> </Button>
) : null} ) : null}
+98 -13
View File
@@ -46,6 +46,7 @@ import {
DragIndicator as DragIndicatorIcon, DragIndicator as DragIndicatorIcon,
RestartAlt as RestartAltIcon, RestartAlt as RestartAltIcon,
ArrowForward as ArrowForwardIcon, ArrowForward as ArrowForwardIcon,
KeyboardReturn as KeyboardReturnIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
@@ -94,9 +95,12 @@ const pythonFilters = [
{ "name": "Get full execution details", "value": `print(self.full_execution)`, "example": `` }, { "name": "Get full execution details", "value": `print(self.full_execution)`, "example": `` },
{ "name": "Use files", "value": `# Create a sample file\nfiles = [{\n \"filename\": \"test.txt\",\n \"data\": \"Testdata\"\n}]\nret = self.set_files(files)\n\n# Get the content of the file from Shuffle storage\n# Originally a byte string in the \"data\" key\nfile_content = (self.get_file(ret[0])[\"data\"]).decode()\nprint(file_content)`, "example": `` }, { "name": "Use files", "value": `# Create a sample file\nfiles = [{\n \"filename\": \"test.txt\",\n \"data\": \"Testdata\"\n}]\nret = self.set_files(files)\n\n# Get the content of the file from Shuffle storage\n# Originally a byte string in the \"data\" key\nfile_content = (self.get_file(ret[0])[\"data\"]).decode()\nprint(file_content)`, "example": `` },
{ "name": "Use datastore", "value": `key = \"testkey\"\nvalue = \"The value of the testkey\"\n\nself.set_cache(key, value)\n\n# Print the details of the key after it's been updated\n# To get the value, use self.get_cache(key)[\"value\"]\nprint(self.get_cache(key))`, "example": `` }, { "name": "Use datastore", "value": `key = \"testkey\"\nvalue = \"The value of the testkey\"\n\nself.set_key(key, value)\n\n# Print the details of the key after it's been updated\n# To get the value, use self.get_key(key)[\"value\"]\nprint(self.get_key(key))`, "example": `` },
{ "name": "Run an App Action", "value": `response = shuffle.run_app(app_id="app", action="action_name", auth="authentication_id", params={})\nprint(response)`, "example": ``, "disabled": true, },
{ "name": "Run a Singul AI Action", "value": `response = singul.create_ticket(app="jira/iris/ticketingsystem", fields={"title": "Test ticket!"})\nprint(response)`, "example": ``, "disabled": true, }, { "name": "Run a Subflow", "value": `response = shuffle.run_workflow(workflow_id="", start_command="Runtime arg here!", wait=True)\nprint(response)`, "example": ``, "disabled": false, },
{ "name": "Run an App Action", "value": `response = shuffle.run_app(app_id="app", action="action_name", auth="authentication_id", params={})\nprint(response)`, "example": ``, "disabled": false, },
{ "name": "Run a Singul AI Action", "value": `response = singul.cases.create_ticket(app="jira", fields={"title": "Test ticket!"})\nprint(response)`, "example": ``, "disabled": false, },
] ]
@@ -299,8 +303,19 @@ const CodeEditor = (props) => {
setMainVariables(tmpVariables) setMainVariables(tmpVariables)
} }
const handleKeyDown = (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
event.preventDefault()
const tryItButton = document.getElementById("try-it-button")
if (tryItButton !== undefined && tryItButton !== null) {
tryItButton.click()
}
}
}
// Remove the original useEffect for actionlist since we'll update on action/trigger changes // Remove the original useEffect for actionlist since we'll update on action/trigger changes
useEffect(() => { useEffect(() => {
document.addEventListener("keydown", handleKeyDown)
updateAvailableVariables(actionlist) updateAvailableVariables(actionlist)
}, []) }, [])
@@ -887,6 +902,8 @@ const CodeEditor = (props) => {
// Whelp this is inefficient af. Single loop pls // Whelp this is inefficient af. Single loop pls
// When the found array is empty. // When the found array is empty.
if (found !== null && found !== undefined) { if (found !== null && found !== undefined) {
//console.log("FOUND: ", found)
try { try {
for (var i = 0; i < found.length; i++) { for (var i = 0; i < found.length; i++) {
try { try {
@@ -921,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++) { for (var j = 0; j < actionlist.length; j++) {
if (fixedVariable.slice(1,).toLowerCase() !== actionlist[j].autocomplete.toLowerCase()) { if (fixedVariable.slice(1,).toLowerCase() !== actionlist[j].autocomplete.toLowerCase()) {
continue continue
} }
// Look for the location of found[i] in the input, as to make sure to skip parts of the input in the replace. Find ALL spots for it
valuefound = true valuefound = true
var newvalue = ""
try { try {
if (typeof actionlist[j].example === "object") {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1); if (typeof actionlist[j].example === "object") {
newvalue = JSON.stringify(actionlist[j].example)
} else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) { } else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
newvalue = JSON.stringify(actionlist[j].example)
} else { } else {
const newExample = fixStringInput(actionlist[j].example) const newExample = fixStringInput(actionlist[j].example)
input = input.replace(found[i], newExample, -1)
newvalue = newExample
} }
} catch (e) { } catch (e) {
input = input.replace(found[i], actionlist[j].example, -1) newvalue = actionlist[j].example
} }
try {
console.log("REPLACE: ", foundlocation, fixedVariable, newvalue)
if (newvalue !== "") {
if (foundlocation === -1) {
input = input.replace(fixedVariable, newvalue, 1)
} else {
// Ensures we don't just randomly replace the first value we find
const replacedSlice = input.slice(foundlocation, input.length).replace(fixedVariable, newvalue, 1)
input = input.slice(0, foundlocation) + replacedSlice
}
}
} catch (e) {
console.log("Replace error: ", e)
}
} }
if (!valuefound) { if (!valuefound) {
@@ -1721,13 +1782,13 @@ const CodeEditor = (props) => {
color="secondary" color="secondary"
style={{ style={{
textTransform: "none", textTransform: "none",
width: 120, width: 145,
}} }}
onClick={(event) => { onClick={(event) => {
setAnchorEl3(event.currentTarget); setAnchorEl3(event.currentTarget);
}} }}
> >
Python Code Python Examples
</Button> </Button>
<Button <Button
id="basic-button" id="basic-button"
@@ -1762,7 +1823,12 @@ const CodeEditor = (props) => {
> >
{pythonFilters.map((item, index) => { {pythonFilters.map((item, index) => {
return ( return (
<MenuItem key={index} onClick={() => { <MenuItem
style={{
borderTop: item.name === "Use files" || (item.name.toLowerCase().includes("run") && item.name.toLowerCase().includes("subflow")) ? "2px solid rgba(255,255,255,0.3)" : "none",
}}
key={index} onClick={() => {
if (item.disabled) { if (item.disabled) {
toast.error("This feature may not work in your environment until you update your Shuffle Tools app.", { autoClose: 10000 }) toast.error("This feature may not work in your environment until you update your Shuffle Tools app.", { autoClose: 10000 })
} }
@@ -2270,6 +2336,7 @@ const CodeEditor = (props) => {
<div style={{}}> <div style={{}}>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top"> <Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
<Button <Button
id="try-it-button"
variant="outlined" variant="outlined"
disabled={executing} disabled={executing}
color="primary" color="primary"
@@ -2283,11 +2350,13 @@ const CodeEditor = (props) => {
zIndex: 1200, zIndex: 1200,
fontWeight: 500, fontWeight: 500,
fontSize: 14, fontSize: 14,
textTransform: "none",
backgroundColor: "rgba(33, 33, 33, 0.95)", backgroundColor: "rgba(33, 33, 33, 0.95)",
backdropFilter: "blur(8px)", backdropFilter: "blur(8px)",
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)", boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)",
transition: "all 0.2s ease", transition: "all 0.2s ease",
borderRadius: "4px", paddingRight: 20,
borderRadius: theme.palette?.borderRadius,
"&:hover": { "&:hover": {
backgroundColor: "rgba(45, 45, 45, 0.95)", backgroundColor: "rgba(45, 45, 45, 0.95)",
transform: "translateY(-1px)", transform: "translateY(-1px)",
@@ -2304,7 +2373,23 @@ const CodeEditor = (props) => {
{executing ? {executing ?
<CircularProgress style={{ height: 18, width: 18, }} /> <CircularProgress style={{ height: 18, width: 18, }} />
: :
<span>{selectedAction === undefined ? "Try it" : selectedAction.name === "execute_python" ? "Run Python Code" : selectedAction.name === "execute_bash" ? "Run Bash" : "Try it"}<PlayArrowIcon style={{ height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} /> </span> <span>
<PlayArrowIcon style={{ height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} />
{selectedAction === undefined ? "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> </Button>
</Tooltip> </Tooltip>
+492 -399
View File
@@ -30,6 +30,8 @@ import {
Apps as AppsIcon, Apps as AppsIcon,
Business as BusinessIcon, Business as BusinessIcon,
Flag, Flag,
ArrowDropDown as ArrowDropDownIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -54,9 +56,12 @@ const TenantsTab = memo((props) => {
const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false); const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false);
const [modalOpen, setModalOpen] = React.useState(false); const [modalOpen, setModalOpen] = React.useState(false);
const [parentOrg, setParentOrg] = React.useState(null); 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 [loadOrgs, setLoadOrgs] = React.useState(true);
const [, forceUpdate] = React.useState(); const [, forceUpdate] = React.useState();
const [suborglistOpen, setSuborglistOpen] = React.useState(false);
const [allTenantsOpen, setAllTenantsOpen] = React.useState(false);
const itemColor = "black"; const itemColor = "black";
useEffect(() => { useEffect(() => {
@@ -82,9 +87,10 @@ const TenantsTab = memo((props) => {
} }
} }
setParentOrgFlag(regionCode); setParentOrgFlag(regionCode);
setParentOrgRegionName(regiontag);
} }
} }
}, [parentOrg]); }, [parentOrg, parentOrgFlag]);
var syncList = [ var syncList = [
{ {
@@ -164,7 +170,8 @@ const TenantsTab = memo((props) => {
regionCode = "ca"; regionCode = "ca";
} }
} }
setParentOrgFlag(regionCode); setParentOrgFlag(regionCode);
setParentOrgRegionName(regiontag);
} }
} }
}) })
@@ -479,8 +486,8 @@ const TenantsTab = memo((props) => {
const createSubOrg = (currentOrgId, name) => { const createSubOrg = (currentOrgId, name) => {
const data = { name: name, org_id: currentOrgId }; const data = { name: name, org_id: currentOrgId };
console.log(data);
const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org`; const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org`;
setSuborglistOpen(true)
fetch(url, { fetch(url, {
mode: "cors", mode: "cors",
@@ -788,7 +795,7 @@ const TenantsTab = memo((props) => {
Create, manage and change to sub-organizations (tenants)! {" "} Create, manage and change to sub-organizations (tenants)! {" "}
{isCloud {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." ? "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."
: ''} : ''}&nbsp;
<a <a
href="/docs/organizations" href="/docs/organizations"
target="_blank" target="_blank"
@@ -1026,7 +1033,7 @@ const TenantsTab = memo((props) => {
src={`https://flagcdn.com/w20/${parentOrgFlag}.png`} src={`https://flagcdn.com/w20/${parentOrgFlag}.png`}
style={{ width: "30px", height: "20px", marginRight: "5px" }} style={{ width: "30px", height: "20px", marginRight: "5px" }}
/> />
<ListItemText primary={parentOrgFlag?.toUpperCase()} /> <ListItemText primary={parentOrgRegionName?.toUpperCase()} />
</div> </div>
} }
style={{ display: "table-cell", padding: 8, verticalAlign: "middle" }} style={{ display: "table-cell", padding: 8, verticalAlign: "middle" }}
@@ -1147,168 +1154,212 @@ const TenantsTab = memo((props) => {
overflowX: "auto", overflowX: "auto",
paddingBottom: 0, paddingBottom: 0,
}}> }}>
<ListItem {!suborglistOpen ?
style={{ <ListItem
width: "100%", style={{
display: "table-row", width: "100%",
padding: "0px 8px 8px 8px", display: "table-row",
whiteSpace: "nowrap", padding: "0px 8px 8px 8px",
textOverflow: "ellipsis", whiteSpace: "nowrap",
verticalAlign: "middle", textOverflow: "ellipsis",
}}> verticalAlign: "middle",
<ListItemText itemAlign: "center",
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")) { <ListItemText
const namesplit = regionsplit[0].split("/"); primary={
regiontag = namesplit[namesplit.length - 1]; <Button
if (regiontag === "california") { fullWidth
regiontag = "US"; variant="secondary"
regionCode = "us"; style={{
} else if (regiontag === "frankfurt") { textTransform: 'none',
regiontag = "EU-2"; }}
regionCode = "eu"; onClick={() => setSuborglistOpen(true)}
} else if (regiontag === "ca") { >
regiontag = "CA"; Show Sub-Organizations <ArrowDropDownIcon />
regionCode = "ca"; </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 ( return (
<ListItem key={index} style={{ backgroundColor: index % 2 === 0 ? '#1A1A1A' : '#212121', width: "100%", borderBottomLeftRadius: 8, display:'table-row', borderBottomRightRadius: 8 }}> <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, <ListItemText primary={<img alt={data?.name} src={data.image || theme.palette.defaultImage} style={imageStyle} />} style={{ width: 100,
minWidth: 100, minWidth: 100,
maxWidth: 100, maxWidth: 100,
display: "table-cell", display: "table-cell",
padding: "8px 8px 8px 20px", padding: "8px 8px 8px 20px",
textAlign: "center", }} /> textAlign: "center", }} />
<ListItemText primary={data.name} style={{ minWidth: 100, <ListItemText primary={data.name} style={{ minWidth: 100,
maxWidth: 300, maxWidth: 300,
overflow: "hidden", overflow: "hidden",
whiteSpace: "nowrap", whiteSpace: "nowrap",
textOverflow: "ellipsis", textOverflow: "ellipsis",
display: "table-cell", display: "table-cell",
padding: 8, padding: 8,
verticalAlign: "middle", verticalAlign: "middle",
textAlign: "center", }} /> textAlign: "center", }} />
{isCloud && ( {isCloud && (
<ListItemText <ListItemText
primary={ primary={
<div style={{ display: "flex", alignItems: "center" }}> <div style={{ display: "flex", alignItems: "center" }}>
<img <img
alt={regiontag} alt={regiontag}
src={`https://flagcdn.com/w20/${regionCode}.png`} src={`https://flagcdn.com/w20/${regionCode}.png`}
style={{ width: "30px", height: "20px", marginRight: "5px" }} style={{ width: "30px", height: "20px", marginRight: "5px" }}
/> />
<ListItemText primary={regiontag?.toUpperCase()} /> <ListItemText primary={regiontag?.toUpperCase()} />
</div> </div>
} }
style={{ display: "table-cell", padding: 8, verticalAlign: "middle" }} style={{ display: "table-cell", padding: 8, verticalAlign: "middle" }}
/> />
)} )}
<ListItemText primary={data.id} style={{ minWidth: 300, <ListItemText primary={data.id} style={{ minWidth: 300,
maxWidth: 300, maxWidth: 300,
display: "table-cell", display: "table-cell",
padding: 8, padding: 8,
verticalAlign: "middle", }} /> 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> </List>
</div> </div>
</div> </div>
@@ -1363,247 +1414,289 @@ const TenantsTab = memo((props) => {
paddingBottom: 0, paddingBottom: 0,
}} }}
> >
<ListItem {!allTenantsOpen ?
style={{ <ListItem
width: "100%", style={{
display: "table-row", width: "100%",
padding: "0px 8px 8px 8px", display: "table-row",
whiteSpace: "nowrap", padding: "0px 8px 8px 8px",
textOverflow: "ellipsis", whiteSpace: "nowrap",
verticalAlign: "middle", textOverflow: "ellipsis",
}} verticalAlign: "middle",
> itemAlign: "center",
<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>
{userdata?.orgs?.length <= 0 ? ( }}
[...Array(6)].map((_, rowIndex) => ( >
<ListItem <ListItemText
key={rowIndex} primary={
style={{ <Button
display: "table-row", fullWidth
backgroundColor: "#212121", variant="secondary"
}} style={{
> textTransform: 'none',
{Array(7) }}
.fill() onClick={() => setAllTenantsOpen(true)}
.map((_, colIndex) => ( >
<ListItemText Show ALL your tenants <ArrowDropDownIcon />
key={colIndex} </Button>
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";
} }
} 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 ( {userdata?.orgs?.length <= 0 ? (
<ListItem [...Array(6)].map((_, rowIndex) => (
key={index} <ListItem
style={{ key={rowIndex}
display: "table-row", style={{
verticalAlign: "middle", display: "table-row",
padding: 8, backgroundColor: "#212121",
backgroundColor: index % 2 === 0 ? "#1A1A1A" : "#212121", }}
borderBottomLeftRadius: >
userdata?.orgs?.length - 1 === index ? 8 : 0, {Array(7)
borderBottomRightRadius: .fill()
userdata?.orgs?.length - 1 === index ? 8 : 0, .map((_, colIndex) => (
}} <ListItemText
> key={colIndex}
<ListItemText style={{
primary={ display: "table-cell",
<img padding: "8px",
alt={data.name} }}
src={data.image || theme.palette.defaultImage} >
style={imageStyle} <Skeleton
/> variant="text"
} animation="wave"
style={{ sx={{
width: 100, backgroundColor: "#1a1a1a",
minWidth: 100, height: "20px",
maxWidth: 100, borderRadius: "4px",
display: "table-cell", }}
padding: "8px 8px 8px 20px", />
textAlign: "center", </ListItemText>
}} ))}
/> </ListItem>
<ListItemText ))
primary={data?.name} ) : (
style={{ userdata?.orgs?.length > 0 &&
minWidth: 100, userdata.orgs.map((data, index) => {
maxWidth: 300, let regiontag = "UK";
overflow: "hidden", let regionCode = "gb";
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} /> if (data.region_url?.length > 0) {
</div> const regionsplit = data.region_url.split(".");
} if (regionsplit.length > 2 && !regionsplit[0].includes("shuffler")) {
style={{ const namesplit = regionsplit[0].split("/");
display: "table-cell", regiontag = namesplit[namesplit.length - 1];
padding: 8,
verticalAlign: "middle", if (regiontag === "california") {
}} regiontag = "US";
></ListItemText> regionCode = "us";
) : null} } else if (regiontag === "frankfurt") {
<ListItemText regiontag = "EU-2";
primary={data.id} regionCode = "eu";
style={{ } else if (regiontag === "ca") {
minWidth: 300, regiontag = "CA";
maxWidth: 300, regionCode = "ca";
display: "table-cell", }
padding: 8, }
verticalAlign: "middle", }
}}
/> return (
<ListItemText <ListItem
primary={ key={index}
<Button style={{
variant="outlined" display: "table-row",
style={{ verticalAlign: "middle",
whiteSpace: "nowrap", padding: 8,
textTransform: "none", backgroundColor: index % 2 === 0 ? "#1A1A1A" : "#212121",
fontSize: 16, borderBottomLeftRadius:
}} userdata?.orgs?.length - 1 === index ? 8 : 0,
disabled={data?.id === userdata?.active_org?.id} borderBottomRightRadius:
onClick={() => { userdata?.orgs?.length - 1 === index ? 8 : 0,
handleClickChangeOrg(data?.id); }}
}} >
> <ListItemText
Change Active Org primary={
</Button> <img
} alt={data.name}
style={{ src={data.image || theme.palette.defaultImage}
display: "table-cell", style={imageStyle}
padding: 8, />
verticalAlign: "middle", }
}} style={{
></ListItemText> width: 100,
</ListItem> 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> </List>
</div> </div>
</div> </div>
@@ -248,6 +248,14 @@ const UserManagmentTab = memo((props) => {
return; return;
} }
if (event.target.value.includes("ALL")) {
toast.info("Adding to available all sub-organizations. This may take a minute.")
event.target.value = selectedOrganization.child_orgs.map((org) => org.id)
} else if (event.target.value.includes("None")) {
toast.info("Removing from all sub-organizations. This may take a minute")
event.target.value = []
}
console.log("event: ", event.target.value); console.log("event: ", event.target.value);
setMatchingOrganizations(event.target.value); setMatchingOrganizations(event.target.value);
// Workaround for empty orgs // Workaround for empty orgs
@@ -286,6 +294,14 @@ const UserManagmentTab = memo((props) => {
}} }}
MenuProps={MenuProps} MenuProps={MenuProps}
> >
<MenuItem key={-2} value={"None"}>
<Checkbox checked={false} />
<ListItemText primary={"None"} />
</MenuItem>
<MenuItem key={-1} value={"ALL"}>
<Checkbox checked={false} />
<ListItemText primary={"ALL"} />
</MenuItem>
{selectedOrganization.child_orgs.map((org, index) => ( {selectedOrganization.child_orgs.map((org, index) => (
<MenuItem key={index} value={org.id}> <MenuItem key={index} value={org.id}>
<Checkbox checked={matchingOrganizations.indexOf(org.id) > -1} /> <Checkbox checked={matchingOrganizations.indexOf(org.id) > -1} />
@@ -608,8 +608,6 @@ const WorkflowValidationTimeline = (props) => {
const ballsize = 8 const ballsize = 8
const topMargin = 20 const topMargin = 20
console.log("CHIP: ", index, chipColor, chipBackground)
const chipStyle = { const chipStyle = {
height: 40, height: 40,
minWidth: 125, minWidth: 125,
+39 -6
View File
@@ -95,23 +95,56 @@ const data = [
{ {
selector: `node[type="COMMENT"]`, selector: `node[type="COMMENT"]`,
css: { css: {
label: function(element) { label: function (element) {
return element.data("label") return element.data("label")
}, },
shape: "roundrectangle", shape: "roundrectangle",
color: "data(color)", color: "data(color)",
width: "data(width)", width: "data(width)",
height: "data(height)", height: "data(height)",
padding: "0px", padding: "5px",
margin: "0px", margin: "0px",
"background-color": "data(backgroundcolor)", "background-color": "data(backgroundcolor)",
"background-image": "data(backgroundimage)", "background-image": "data(backgroundimage)",
"border-color": "#ffffff", "border-color": "#ffffff",
"text-margin-x": "0px", "text-margin-x": "data(textMarginX)",
"text-margin-y": "data(textMarginY)",
"z-index": 4999, "z-index": 4999,
"border-radius": "5px", "border-radius": "5px",
"background-opacity": "0.5", "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
View File
@@ -27,7 +27,7 @@ const theme = createTheme(adaptV4Theme({
distributionColor: "#40E0D0", distributionColor: "#40E0D0",
green: "#5cc879", green: "#5cc879",
borderRadius: 10, borderRadius: 8,
defaultBorder: "1px solid rgba(255,255,255,0.3)", defaultBorder: "1px solid rgba(255,255,255,0.3)",
//jsonTheme: "brewer", //jsonTheme: "brewer",
@@ -112,39 +112,39 @@ const theme = createTheme(adaptV4Theme({
MuiCssBaseline: { MuiCssBaseline: {
styleOverrides: ` styleOverrides: `
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 300; font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'); src: local('Inter Light'), local('Inter-Light');
} }
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 400; font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'); src: local('Inter Regular'), local('Inter-Regular');
} }
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 500; font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'); src: local('Inter Medium'), local('Inter-Medium');
} }
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 600; font-weight: 600;
src: local('Roboto SemiBold'), local('Roboto-SemiBold'); src: local('Inter SemiBold'), local('Inter-SemiBold');
} }
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 700; font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'); src: local('Inter Bold'), local('Inter-Bold');
} }
`, `,
}, },
+12
View File
@@ -76,6 +76,18 @@ const Admin2 = (props) => {
leads.push("tech partner"); 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) { if (responseJson.lead_info.creator) {
leads.push("creator"); leads.push("creator");
} }
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -943,8 +943,8 @@ const AppCreator = (defaultprops) => {
if (newaction.url !== undefined && newaction.url !== null && newaction.url.includes("_shuffle_replace_")) { if (newaction.url !== undefined && newaction.url !== null && newaction.url.includes("_shuffle_replace_")) {
//const regex = /_shuffle_replace_\d/i; //const regex = /_shuffle_replace_\d/i;
const regex = /_shuffle_replace_\d+/i const regex = /_shuffle_replace_\d+/i
const newurl = newaction.url.replaceAll(new RegExp(regex, 'g'), "")
newaction.url = newaction.url.replaceAll(new RegExp(regex, 'g'), "") newaction.url = newurl
} }
// Finding category // Finding category
@@ -956,7 +956,6 @@ const AppCreator = (defaultprops) => {
if (pathsplit[splitkey].includes("_shuffle_replace_")) { if (pathsplit[splitkey].includes("_shuffle_replace_")) {
//const regex = /_shuffle_replace_\d/i; //const regex = /_shuffle_replace_\d/i;
const regex = /_shuffle_replace_\d+/i const regex = /_shuffle_replace_\d+/i
//console.log("NEW: ",
pathsplit[splitkey] = pathsplit[splitkey].replaceAll(new RegExp(regex, 'g'), "") pathsplit[splitkey] = pathsplit[splitkey].replaceAll(new RegExp(regex, 'g'), "")
} }
@@ -2037,7 +2036,7 @@ const AppCreator = (defaultprops) => {
for (let actionkey in actions) { for (let actionkey in actions) {
var item = JSON.parse(JSON.stringify(actions[actionkey])) var item = JSON.parse(JSON.stringify(actions[actionkey]))
if (item.errors.length > 0) { 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) { if (item.name === undefined && item.description !== undefined) {
@@ -3858,7 +3857,7 @@ const AppCreator = (defaultprops) => {
if (currentAction.url === "" && actions !== undefined && actions !== null && actions.length > 0) { if (currentAction.url === "" && actions !== undefined && actions !== null && actions.length > 0) {
for (var i = 0; i < actions.length; i++) { for (var i = 0; i < actions.length; i++) {
if (actions[i].name.toLowerCase() === e.target.value.toLowerCase()) { 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 break
} }
} }
+4 -5
View File
@@ -1088,14 +1088,13 @@ const AppExplorer = (props) => {
setNewWorkflowTags(newWorkflowTags); setNewWorkflowTags(newWorkflowTags);
} }
// This is annoying (: var securitySchemes = data?.components?.securityDefinitions;
var securitySchemes = data.components.securityDefinitions;
if (securitySchemes === undefined) { if (securitySchemes === undefined) {
securitySchemes = data.securitySchemes; securitySchemes = data?.securitySchemes;
} }
if (securitySchemes === undefined) { if (securitySchemes === undefined) {
securitySchemes = data.components.securitySchemes; securitySchemes = data?.components?.securitySchemes;
} }
const allowedfunctions = [ const allowedfunctions = [
@@ -1681,7 +1680,7 @@ const AppExplorer = (props) => {
setExecutionResult({ setExecutionResult({
valid: false, valid: false,
result: 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.",
}); });
} }
+13 -5
View File
@@ -343,10 +343,15 @@ const Docs = (defaultprops) => {
return return
} }
console.log("PROPKEY: ", propkey)
if (location.pathname.includes("/docs/")) { if (location.pathname.includes("/docs/")) {
if (propkey === "cookie_policy" || propkey === "compliance" || propkey === "privacy_policy" || propkey === "terms_of_service") { if (propkey === "cookie_policy" || propkey === "compliance" || propkey === "privacy_policy" || propkey === "terms_of_service") {
navigate(`/legal/${propkey}`) navigate(`/legal/${propkey}`)
} }
if (propkey === "app_creation") {
navigate('/docs/apps#app-creation-introduction')
}
} }
}, [location]); }, [location]);
@@ -932,7 +937,7 @@ const Docs = (defaultprops) => {
} }
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.includes("404: Not Found") && !isArticlePage) { if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.includes("404: Not Found") && !isArticlePage) {
navigate("/docs") //navigate("/docs")
return return
} }
@@ -1109,6 +1114,7 @@ const Docs = (defaultprops) => {
marginTop: 25, 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 = const mainpageInfo =
<div style={{ <div style={{
color: "rgba(255, 255, 255, 0.65)", color: "rgba(255, 255, 255, 0.65)",
@@ -1124,10 +1130,12 @@ const Docs = (defaultprops) => {
<Typography variant="h4" style={{ textAlign: "center", marginTop: 20 }}> <Typography variant="h4" style={{ textAlign: "center", marginTop: 20 }}>
Documentation Documentation
</Typography> </Typography>
<div style={{ display: "flex", marginTop: 25, }}> {showPartnerLogo === true ? null :
<CustomButton title="Talk to Support" icon=<img src="/images/Shuffle_logo_new.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette?.borderRadius, }} /> /> <div style={{ display: "flex", marginTop: 25, }}>
<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" /> <CustomButton title="Talk to Support" icon=<img src="/images/Shuffle_logo_new.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette?.borderRadius, }} /> />
</div> <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" }}> <div style={{ textAlign: "left" }}>
<Typography variant="h6" style={headerStyle} >Tutorial</Typography> <Typography variant="h6" style={headerStyle} >Tutorial</Typography>
+11 -3
View File
@@ -323,9 +323,17 @@ const LoginPage = props => {
if (document !== undefined) { if (document !== undefined) {
if (register) { if (register) {
document.title = "Login to Shuffle SaaS" if (isCloud) {
document.title = "Login to Shuffle SaaS"
} else {
document.title = "Login to Shuffle"
}
} else { } 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", padding: "40px",
flex: 1, flex: 1,
maxWidth: isMobile ? "100%" : 410, maxWidth: isMobile ? "100%" : 410,
minWidth: 410, minWidth: isCloud ? 410 : 475,
background: "#212121", background: "#212121",
borderRadius: "12px", borderRadius: "12px",
display: "flex", display: "flex",
+194 -24
View File
@@ -19,6 +19,7 @@ import RecentWorkflow from "../components/RecentWorkflow.jsx";
import { import {
Tooltip, Tooltip,
Fade,
Select, Select,
IconButton, IconButton,
CircularProgress, CircularProgress,
@@ -33,6 +34,7 @@ import {
DialogTitle, DialogTitle,
DialogContent, DialogContent,
MenuItem, MenuItem,
Autocomplete,
} from '@mui/material'; } from '@mui/material';
import { import {
@@ -44,6 +46,7 @@ import {
LockOpen as LockOpenIcon, LockOpen as LockOpenIcon,
OpenInNew as OpenInNewIcon, OpenInNew as OpenInNewIcon,
Edit as EditIcon, Edit as EditIcon,
Polyline as PolylineIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
const hrefStyle = { const hrefStyle = {
@@ -74,6 +77,7 @@ const RunWorkflow = (defaultprops) => {
const [sharingOpen, setSharingOpen] = React.useState(false) const [sharingOpen, setSharingOpen] = React.useState(false)
const [realtimeMarkdown, setRealtimeMarkdown] = React.useState("") const [realtimeMarkdown, setRealtimeMarkdown] = React.useState("")
const [forms, setForms] = React.useState([]) const [forms, setForms] = React.useState([])
const [workflows, setWorkflows] = React.useState([])
const [boxWidth, setBoxWidth] = React.useState(500) const [boxWidth, setBoxWidth] = React.useState(500)
const [inputQuestions, setInputQuestions] = React.useState([]) const [inputQuestions, setInputQuestions] = React.useState([])
@@ -180,6 +184,37 @@ const RunWorkflow = (defaultprops) => {
return true 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 loadForms = (orgId) => {
const url = `${globalUrl}/api/v1/orgs/${orgId}/forms` const url = `${globalUrl}/api/v1/orgs/${orgId}/forms`
fetch(url, { fetch(url, {
@@ -462,6 +497,8 @@ const RunWorkflow = (defaultprops) => {
} else { } else {
console.log("Started execution") console.log("Started execution")
start()
setExecutionRunning(true);
if (answer !== undefined && answer !== null) { if (answer !== undefined && answer !== null) {
console.log("Skipping start") console.log("Skipping start")
} else { } else {
@@ -875,6 +912,7 @@ const RunWorkflow = (defaultprops) => {
// Just use this one? // 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}`; 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) loadForms(orgId)
fetch(url, { fetch(url, {
@@ -1055,7 +1093,7 @@ const RunWorkflow = (defaultprops) => {
if (parsedresult.click_info !== undefined && parsedresult.click_info !== null) { 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) { 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 { } else {
@@ -1149,10 +1187,100 @@ const RunWorkflow = (defaultprops) => {
No Forms Found No Forms Found
</Typography> </Typography>
<Typography variant="body1" color="textSecondary"> <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. <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> </Typography>
</div> </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> </div>
) )
} }
@@ -1325,7 +1453,8 @@ const RunWorkflow = (defaultprops) => {
})} })}
</div> </div>
: :
answer !== undefined && answer !== null ? null : (answer !== undefined && answer !== null) || message !== "" ? null :
<span> <span>
Runtime Argument Runtime Argument
<div style={{marginBottom: 5}}> <div style={{marginBottom: 5}}>
@@ -1334,7 +1463,13 @@ const RunWorkflow = (defaultprops) => {
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }} style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
multiLine multiLine
maxRows={2} maxRows={2}
type="text"
autoComplete="off"
InputProps={{ InputProps={{
autocomplete: "off",
form: {
autocomplete: "off",
},
style:{ style:{
height: "50px", height: "50px",
color: "white", color: "white",
@@ -1375,9 +1510,11 @@ const RunWorkflow = (defaultprops) => {
{message}. You may close this window. {message}. You may close this window.
</Typography> </Typography>
: :
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}> <Fade in={true} timeout={2500}>
{disabledButtons ? "Answered. You may close this window." : ""} <Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
</Typography> {disabledButtons ? "Answered. You may close this window." : ""}
</Typography>
</Fade>
} }
{disabledButtons ? null : {disabledButtons ? null :
@@ -1387,25 +1524,47 @@ const RunWorkflow = (defaultprops) => {
} }
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}> <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={() => { <Button
setButtonClicked("FINISHED") fullWidth
setExecutionData({ id="continue_execution"
status: "FINISHED", 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) onSubmit(null, execution_id, authorization, true)
}}>Continue</Button> }}>
Continue</Button>
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}> <Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
&nbsp;or&nbsp; &nbsp;or&nbsp;
</Typography> </Typography>
<Button fullWidth id="abort_execution" variant="contained" disabled={!handleValidateForm(executionArgument) || disabledButtons} color="primary" style={{ flex: 1, }} onClick={() => { <Button
setButtonClicked("ABORTED") fullWidth
setExecutionData({ id="abort_execution"
status: "ABORTED", 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) onSubmit(null, execution_id, authorization, false)
}}>Stop</Button> }}>
Stop
</Button>
</div> </div>
</span> </span>
: :
@@ -1416,6 +1575,9 @@ const RunWorkflow = (defaultprops) => {
color="primary" color="primary"
fullWidth fullWidth
disabled={!handleValidateForm(executionArgument) || executionLoading} disabled={!handleValidateForm(executionArgument) || executionLoading}
style={{
textTransform: "none",
}}
> >
{executionLoading ? {executionLoading ?
<CircularProgress color="secondary" style={{color: "white",}} /> <CircularProgress color="secondary" style={{color: "white",}} />
@@ -1601,7 +1763,10 @@ const RunWorkflow = (defaultprops) => {
disabled={workflow.id === undefined || workflow.id === null} disabled={workflow.id === undefined || workflow.id === null}
variant={"outlined"} variant={"outlined"}
color={"secondary"} color={"secondary"}
style={{marginRight: 10, }} style={{
marginRight: 10,
textTransform: "none",
}}
onClick={() => { onClick={() => {
window.open(`/workflows/${workflow.id}`, "_blank") window.open(`/workflows/${workflow.id}`, "_blank")
}} }}
@@ -1614,7 +1779,10 @@ const RunWorkflow = (defaultprops) => {
disabled={workflow.id === undefined || workflow.id === null} disabled={workflow.id === undefined || workflow.id === null}
variant={workflow.sharing === "form" ? "outlined" : "contained"} variant={workflow.sharing === "form" ? "outlined" : "contained"}
color={"secondary"} color={"secondary"}
style={{marginRight: 10, }} style={{
marginRight: 10,
textTransform: "none",
}}
onClick={() => { onClick={() => {
setSharingOpen(true) setSharingOpen(true)
}} }}
@@ -1636,7 +1804,9 @@ const RunWorkflow = (defaultprops) => {
disabled={workflow.id === undefined || workflow.id === null} disabled={workflow.id === undefined || workflow.id === null}
variant={"contained"} variant={"contained"}
color={"primary"} color={"primary"}
style={{}} style={{
textTransform: "none",
}}
onClick={() => { onClick={() => {
setEditWorkflowModalOpen(true) setEditWorkflowModalOpen(true)
}} }}
+9 -2
View File
@@ -527,6 +527,8 @@ const Settings = (props) => {
}; };
const generateApikey = () => { const generateApikey = () => {
toast.info("Generating new API key. This may take a bit.");
fetch(globalUrl + "/api/v1/generateapikey", { fetch(globalUrl + "/api/v1/generateapikey", {
method: "GET", method: "GET",
headers: { headers: {
@@ -538,7 +540,7 @@ const Settings = (props) => {
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!"); console.log("Status not 200 for WORKFLOW EXECUTION :O!");
} }
return response.json(); return response.json();
}) })
@@ -836,7 +838,12 @@ const Settings = (props) => {
variant="outlined" variant="outlined"
/> />
<Button <Button
style={{ width: "100%", height: "40px", marginTop: "10px" }} style={{
width: "100%",
height: "40px",
marginTop: "10px",
textTransform: "none",
}}
variant="outlined" variant="outlined"
color="primary" color="primary"
onClick={() => generateApikey()} onClick={() => generateApikey()}
+44 -67
View File
@@ -661,7 +661,7 @@ const Workflows2 = (props) => {
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1); const [mouseHoverIndex, setMouseHoverIndex] = useState(-1);
const [isLoadingWorkflow, setIsLoadingWorkflow] = useState(false); const [isLoadingWorkflow, setIsLoadingWorkflow] = useState(false);
const [isLoadingPublicWorkflow, setIsLoadingPublicWorkflow] = 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 classes = useStyles(theme)
const imgSize = 60; const imgSize = 60;
@@ -812,9 +812,10 @@ const Workflows2 = (props) => {
} }
const isCloud = //const isCloud =
window.location.host === "localhost:3002" || // window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io"; // window.location.host === "shuffler.io";
const isCloud = false
const findWorkflow = (filters) => { const findWorkflow = (filters) => {
console.log("Using filters: ", filters) console.log("Using filters: ", filters)
@@ -2074,55 +2075,6 @@ const Workflows2 = (props) => {
}; };
const hasWorkflows = workflows === undefined || workflows === null || workflows.length === 0 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) => { const getWorkflowAppgroup = (data) => {
if (currTab !== 2) { if (currTab !== 2) {
if (data.actions === undefined || data.actions === null) { if (data.actions === undefined || data.actions === null) {
@@ -2517,21 +2469,24 @@ const Workflows2 = (props) => {
/> />
</Tooltip> </Tooltip>
: null} : null}
<Grid <Grid
item item
style={{ display: "flex", flexDirection: "column", width: "100%", fontFamily: theme?.typography?.fontFamily }} style={{ display: "flex", flexDirection: "column", width: "100%", fontFamily: theme?.typography?.fontFamily }}
> >
<Grid item style={{ display: "flex", maxHeight: 34 }}> <Grid item style={{ display: "flex", maxHeight: 34 }}>
<Tooltip title={`Org "${orgName}". Click to edit image.`} placement="bottom"> {currTab === 2 ? null :
<div <Tooltip title={`Org "${orgName}". Click to edit image.`} placement="bottom">
styl={{ cursor: "pointer" }} <div
onClick={() => { styl={{ cursor: "pointer" }}
navigate("/admin") onClick={() => {
}} navigate("/admin")
> }}
{image} >
</div> {image}
</Tooltip> </div>
</Tooltip>
}
<Tooltip arrow <Tooltip arrow
onMouseEnter={() => { onMouseEnter={() => {
/* /*
@@ -2610,10 +2565,19 @@ const Workflows2 = (props) => {
> >
<Link <Link
to={ 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} {parsedName}
</Link> </Link>
</Typography> </Typography>
@@ -4310,7 +4274,7 @@ const Workflows2 = (props) => {
TabIndicatorProps={{ style: { display: 'none' } }} TabIndicatorProps={{ style: { display: 'none' } }}
> >
<Tab <Tab
label="Organization Workflows" label="Org Workflows"
style={{ style={{
...tabStyle, ...tabStyle,
...(currTab === 0 ? tabActive : {}) ...(currTab === 0 ? tabActive : {})
@@ -4331,6 +4295,19 @@ const Workflows2 = (props) => {
...(currTab === 2 ? tabActive : {}) ...(currTab === 2 ? tabActive : {})
}} }}
/> />
<Tab
label="Org Forms"
onClick={() => {
navigate("/forms")
}}
style={{
...tabStyle,
marginRight: 0,
marginLeft: 25,
...(currTab === 3 ? tabActive : {})
}}
/>
</Tabs> </Tabs>
</div> </div>
+2 -2
View File
@@ -15,10 +15,10 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # 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. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # 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 # 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 # 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. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "1.16.0" appVersion: "2.0.0"
@@ -2,8 +2,8 @@ apiVersion: v2
name: shuffle name: shuffle
description: A Helm chart for deploying Shuffle on Kubernetes description: A Helm chart for deploying Shuffle on Kubernetes
type: application type: application
version: 0.0.0 version: 0.0.0 # Set during publishing in GitHub actions
appVersion: 0.0.0 appVersion: nightly # Overwritten during publishing in GitHub actions
dependencies: dependencies:
- name: common - name: common
version: ^2.23.0 version: ^2.23.0
+12 -18
View File
@@ -148,6 +148,7 @@ SHUFFLE_ENCRYPTION_MODIFIER: "MyShuffleEncryptionModifier"
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `backend.image.registry` | backend image registry | `ghcr.io` | | `backend.image.registry` | backend image registry | `ghcr.io` |
| `backend.image.repository` | backend image repository | `shuffle/shuffle-backend` | | `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.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.pullPolicy` | backend image pull policy | `IfNotPresent` |
| `backend.image.pullSecrets` | backend image pull secrets | `[]` | | `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.podSecurityContext.fsGroup` | Set fsGroup in backend pods' Security Context | `1001` |
| `backend.containerSecurityContext.enabled` | Enabled backend container' Security Context | `true` | | `backend.containerSecurityContext.enabled` | Enabled backend container' Security Context | `true` |
| `backend.containerSecurityContext.seLinuxOptions` | Set SELinux options in backend container | `{}` | | `backend.containerSecurityContext.seLinuxOptions` | Set SELinux options in backend container | `{}` |
| `backend.containerSecurityContext.runAsUser` | Set runAsUser 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 | `1000` | | `backend.containerSecurityContext.runAsGroup` | Set runAsGroup in backend container' Security Context | `1001` |
| `backend.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in backend container' Security Context | `true` | | `backend.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in backend container' Security Context | `true` |
| `backend.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in backend container' Security Context | `true` | | `backend.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in backend container' Security Context | `true` |
| `backend.containerSecurityContext.privileged` | Set privileged in backend container' Security Context | `false` | | `backend.containerSecurityContext.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.args` | Override default backend container args (useful when using custom images) | `[]` |
| `backend.automountServiceAccountToken` | Mount Service Account token in backend pods | `true` | | `backend.automountServiceAccountToken` | Mount Service Account token in backend pods | `true` |
| `backend.hostAliases` | backend pods host aliases | `[]` | | `backend.hostAliases` | backend pods host aliases | `[]` |
| `backend.daemonsetAnnotations` | Annotations for backend daemonset | `{}` |
| `backend.deploymentAnnotations` | Annotations for backend deployment | `{}` | | `backend.deploymentAnnotations` | Annotations for backend deployment | `{}` |
| `backend.statefulsetAnnotations` | Annotations for backend statefulset | `{}` |
| `backend.podLabels` | Extra labels for backend pods | `{}` | | `backend.podLabels` | Extra labels for backend pods | `{}` |
| `backend.podAnnotations` | Annotations for backend pods | `{}` | | `backend.podAnnotations` | Annotations for backend pods | `{}` |
| `backend.podAffinityPreset` | Pod affinity preset. Ignored if `backend.affinity` is set. Allowed values: `soft` or `hard` | `""` | | `backend.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.nodeSelector` | Node labels for backend pods assignment | `{}` |
| `backend.tolerations` | Tolerations for backend pods assignment | `[]` | | `backend.tolerations` | Tolerations for backend pods assignment | `[]` |
| `backend.updateStrategy.type` | backend deployment strategy type | `RollingUpdate` | | `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.priorityClassName` | backend pods' priorityClassName | `""` |
| `backend.topologySpreadConstraints` | Topology Spread Constraints for backend pod assignment spread across your cluster among failure-domains | `[]` | | `backend.topologySpreadConstraints` | Topology Spread Constraints for backend pod assignment spread across your cluster among failure-domains | `[]` |
| `backend.schedulerName` | Name of the k8s scheduler (other than default) for backend pods | `""` | | `backend.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.registry` | frontend image registry | `ghcr.io` |
| `frontend.image.repository` | frontend image repository | `shuffle/shuffle-frontend` | | `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.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.pullPolicy` | frontend image pull policy | `IfNotPresent` |
| `frontend.image.pullSecrets` | frontend image pull secrets | `[]` | | `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.podSecurityContext.fsGroup` | Set fsGroup in frontend pods' Security Context | `1001` |
| `frontend.containerSecurityContext.enabled` | Enabled frontend container' Security Context | `false` | | `frontend.containerSecurityContext.enabled` | Enabled frontend container' Security Context | `false` |
| `frontend.containerSecurityContext.seLinuxOptions` | Set SELinux options in frontend container | `{}` | | `frontend.containerSecurityContext.seLinuxOptions` | Set SELinux options in frontend container | `{}` |
| `frontend.containerSecurityContext.runAsUser` | Set runAsUser 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 | `101` | | `frontend.containerSecurityContext.runAsGroup` | Set runAsGroup in frontend container' Security Context | `1001` |
| `frontend.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in frontend container' Security Context | `true` | | `frontend.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in frontend container' Security Context | `true` |
| `frontend.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in frontend container' Security Context | `true` | | `frontend.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in frontend container' Security Context | `true` |
| `frontend.containerSecurityContext.privileged` | Set privileged in frontend container' Security Context | `false` | | `frontend.containerSecurityContext.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.args` | Override default frontend container args (useful when using custom images) | `[]` |
| `frontend.automountServiceAccountToken` | Mount Service Account token in frontend pods | `false` | | `frontend.automountServiceAccountToken` | Mount Service Account token in frontend pods | `false` |
| `frontend.hostAliases` | frontend pods host aliases | `[]` | | `frontend.hostAliases` | frontend pods host aliases | `[]` |
| `frontend.daemonsetAnnotations` | Annotations for frontend daemonset | `{}` |
| `frontend.deploymentAnnotations` | Annotations for frontend deployment | `{}` | | `frontend.deploymentAnnotations` | Annotations for frontend deployment | `{}` |
| `frontend.statefulsetAnnotations` | Annotations for frontend statefulset | `{}` |
| `frontend.podLabels` | Extra labels for frontend pods | `{}` | | `frontend.podLabels` | Extra labels for frontend pods | `{}` |
| `frontend.podAnnotations` | Annotations for frontend pods | `{}` | | `frontend.podAnnotations` | Annotations for frontend pods | `{}` |
| `frontend.podAffinityPreset` | Pod affinity preset. Ignored if `frontend.affinity` is set. Allowed values: `soft` or `hard` | `""` | | `frontend.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.nodeSelector` | Node labels for frontend pods assignment | `{}` |
| `frontend.tolerations` | Tolerations for frontend pods assignment | `[]` | | `frontend.tolerations` | Tolerations for frontend pods assignment | `[]` |
| `frontend.updateStrategy.type` | frontend deployment strategy type | `RollingUpdate` | | `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.priorityClassName` | frontend pods' priorityClassName | `""` |
| `frontend.topologySpreadConstraints` | Topology Spread Constraints for frontend pod assignment spread across your cluster among failure-domains | `[]` | | `frontend.topologySpreadConstraints` | Topology Spread Constraints for frontend pod assignment spread across your cluster among failure-domains | `[]` |
| `frontend.schedulerName` | Name of the k8s scheduler (other than default) for frontend pods | `""` | | `frontend.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.registry` | orborus image registry | `ghcr.io` |
| `orborus.image.repository` | orborus image repository | `shuffle/shuffle-orborus` | | `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.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.pullPolicy` | orborus image pull policy | `IfNotPresent` |
| `orborus.image.pullSecrets` | orborus image pull secrets | `[]` | | `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.podSecurityContext.fsGroup` | Set fsGroup in orborus pods' Security Context | `1001` |
| `orborus.containerSecurityContext.enabled` | Enabled orborus container' Security Context | `true` | | `orborus.containerSecurityContext.enabled` | Enabled orborus container' Security Context | `true` |
| `orborus.containerSecurityContext.seLinuxOptions` | Set SELinux options in orborus container | `{}` | | `orborus.containerSecurityContext.seLinuxOptions` | Set SELinux options in orborus container | `{}` |
| `orborus.containerSecurityContext.runAsUser` | Set runAsUser 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 | `101` | | `orborus.containerSecurityContext.runAsGroup` | Set runAsGroup in orborus container' Security Context | `1001` |
| `orborus.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in orborus container' Security Context | `true` | | `orborus.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in orborus container' Security Context | `true` |
| `orborus.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in orborus container' Security Context | `true` | | `orborus.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in orborus container' Security Context | `true` |
| `orborus.containerSecurityContext.privileged` | Set privileged in orborus container' Security Context | `false` | | `orborus.containerSecurityContext.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.args` | Override default orborus container args (useful when using custom images) | `[]` |
| `orborus.automountServiceAccountToken` | Mount Service Account token in orborus pods | `true` | | `orborus.automountServiceAccountToken` | Mount Service Account token in orborus pods | `true` |
| `orborus.hostAliases` | orborus pods host aliases | `[]` | | `orborus.hostAliases` | orborus pods host aliases | `[]` |
| `orborus.daemonsetAnnotations` | Annotations for orborus daemonset | `{}` |
| `orborus.deploymentAnnotations` | Annotations for orborus deployment | `{}` | | `orborus.deploymentAnnotations` | Annotations for orborus deployment | `{}` |
| `orborus.statefulsetAnnotations` | Annotations for orborus statefulset | `{}` |
| `orborus.podLabels` | Extra labels for orborus pods | `{}` | | `orborus.podLabels` | Extra labels for orborus pods | `{}` |
| `orborus.podAnnotations` | Annotations for orborus pods | `{}` | | `orborus.podAnnotations` | Annotations for orborus pods | `{}` |
| `orborus.podAffinityPreset` | Pod affinity preset. Ignored if `orborus.affinity` is set. Allowed values: `soft` or `hard` | `""` | | `orborus.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.nodeSelector` | Node labels for orborus pods assignment | `{}` |
| `orborus.tolerations` | Tolerations for orborus pods assignment | `[]` | | `orborus.tolerations` | Tolerations for orborus pods assignment | `[]` |
| `orborus.updateStrategy.type` | orborus deployment strategy type | `RollingUpdate` | | `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.priorityClassName` | orborus pods' priorityClassName | `""` |
| `orborus.topologySpreadConstraints` | Topology Spread Constraints for orborus pod assignment spread across your cluster among failure-domains | `[]` | | `orborus.topologySpreadConstraints` | Topology Spread Constraints for orborus pod assignment spread across your cluster among failure-domains | `[]` |
| `orborus.schedulerName` | Name of the k8s scheduler (other than default) for orborus pods | `""` | | `orborus.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.registry` | worker image registry | `ghcr.io` |
| `worker.image.repository` | worker image repository | `shuffle/shuffle-worker` | | `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.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.create` | Specifies whether a ServiceAccount should be created | `true` |
| `worker.serviceAccount.name` | The name of the ServiceAccount to use. | `""` | | `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.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.registry` | OS Shell + Utility image registry | `docker.io` |
| `volumePermissions.image.repository` | OS Shell + Utility image repository | `bitnami/os-shell` | | `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.pullPolicy` | OS Shell + Utility image pull policy | `IfNotPresent` |
| `volumePermissions.image.pullSecrets` | OS Shell + Utility image pull secrets | `[]` | | `volumePermissions.image.pullSecrets` | OS Shell + Utility image pull secrets | `[]` |
| `volumePermissions.resourcesPreset` | Set init container resources according to one common preset (allowed values: none, nano, small, medium, large, xlarge, 2xlarge). This is ignored if volumePermissions.resources is set (volumePermissions.resources is recommended for production). | `nano` | | `volumePermissions.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 ### Other Parameters
@@ -119,14 +119,14 @@ app.kubernetes.io/name: shuffle-app
Return the proper image name (for the init container volume-permissions image) Return the proper image name (for the init container volume-permissions image)
*/}} */}}
{{- define "shuffle.volumePermissions.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 -}} {{- end -}}
{{/* {{/*
Return the proper Shuffle backend image name Return the proper Shuffle backend image name
*/}} */}}
{{- define "shuffle.backend.image" -}} {{- 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 -}} {{- end -}}
{{/* {{/*
@@ -140,7 +140,7 @@ Return the proper Docker Image Registry Secret Names for the backend pod
Return the proper Shuffle frontend image name Return the proper Shuffle frontend image name
*/}} */}}
{{- define "shuffle.frontend.image" -}} {{- 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 -}} {{- end -}}
{{/* {{/*
@@ -154,7 +154,7 @@ Return the proper Docker Image Registry Secret Names for the frontend pod
Return the proper Shuffle orborus image name Return the proper Shuffle orborus image name
*/}} */}}
{{- define "shuffle.orborus.image" -}} {{- 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 -}} {{- end -}}
{{/* {{/*
@@ -168,7 +168,7 @@ Return the proper Docker Image Registry Secret Names for the orborus pod
Return the proper Shuffle worker image name Return the proper Shuffle worker image name
*/}} */}}
{{- define "shuffle.worker.image" -}} {{- 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 -}} {{- end -}}
{{/* {{/*
@@ -155,6 +155,11 @@
"description": "backend image repository", "description": "backend image repository",
"default": "shuffle/shuffle-backend" "default": "shuffle/shuffle-backend"
}, },
"tag": {
"type": "string",
"description": "backend image tag (immutable tags are recommended, defaults to appVersion)",
"default": ""
},
"digest": { "digest": {
"type": "string", "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)", "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": { "runAsUser": {
"type": "number", "type": "number",
"description": "Set runAsUser in backend container' Security Context", "description": "Set runAsUser in backend container' Security Context",
"default": 1000 "default": 1001
}, },
"runAsGroup": { "runAsGroup": {
"type": "number", "type": "number",
"description": "Set runAsGroup in backend container' Security Context", "description": "Set runAsGroup in backend container' Security Context",
"default": 1000 "default": 1001
}, },
"runAsNonRoot": { "runAsNonRoot": {
"type": "boolean", "type": "boolean",
@@ -444,21 +449,11 @@
"default": [], "default": [],
"items": {} "items": {}
}, },
"daemonsetAnnotations": {
"type": "object",
"description": "Annotations for backend daemonset",
"default": {}
},
"deploymentAnnotations": { "deploymentAnnotations": {
"type": "object", "type": "object",
"description": "Annotations for backend deployment", "description": "Annotations for backend deployment",
"default": {} "default": {}
}, },
"statefulsetAnnotations": {
"type": "object",
"description": "Annotations for backend statefulset",
"default": {}
},
"podLabels": { "podLabels": {
"type": "object", "type": "object",
"description": "Extra labels for backend pods", "description": "Extra labels for backend pods",
@@ -521,16 +516,11 @@
"properties": { "properties": {
"type": { "type": {
"type": "string", "type": "string",
"description": "backend statefulset strategy type", "description": "backend deployment strategy type",
"default": "RollingUpdate" "default": "RollingUpdate"
} }
} }
}, },
"podManagementPolicy": {
"type": "string",
"description": "Pod management policy for backend statefulset",
"default": "OrderedReady"
},
"priorityClassName": { "priorityClassName": {
"type": "string", "type": "string",
"description": "backend pods' priorityClassName", "description": "backend pods' priorityClassName",
@@ -839,6 +829,11 @@
"description": "frontend image repository", "description": "frontend image repository",
"default": "shuffle/shuffle-frontend" "default": "shuffle/shuffle-frontend"
}, },
"tag": {
"type": "string",
"description": "frontend image tag (immutable tags are recommended, defaults to appVersion)",
"default": ""
},
"digest": { "digest": {
"type": "string", "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)", "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": { "runAsUser": {
"type": "number", "type": "number",
"description": "Set runAsUser in frontend container' Security Context", "description": "Set runAsUser in frontend container' Security Context",
"default": 101 "default": 1001
}, },
"runAsGroup": { "runAsGroup": {
"type": "number", "type": "number",
"description": "Set runAsGroup in frontend container' Security Context", "description": "Set runAsGroup in frontend container' Security Context",
"default": 101 "default": 1001
}, },
"runAsNonRoot": { "runAsNonRoot": {
"type": "boolean", "type": "boolean",
@@ -1133,21 +1128,11 @@
"default": [], "default": [],
"items": {} "items": {}
}, },
"daemonsetAnnotations": {
"type": "object",
"description": "Annotations for frontend daemonset",
"default": {}
},
"deploymentAnnotations": { "deploymentAnnotations": {
"type": "object", "type": "object",
"description": "Annotations for frontend deployment", "description": "Annotations for frontend deployment",
"default": {} "default": {}
}, },
"statefulsetAnnotations": {
"type": "object",
"description": "Annotations for frontend statefulset",
"default": {}
},
"podLabels": { "podLabels": {
"type": "object", "type": "object",
"description": "Extra labels for frontend pods", "description": "Extra labels for frontend pods",
@@ -1210,16 +1195,11 @@
"properties": { "properties": {
"type": { "type": {
"type": "string", "type": "string",
"description": "frontend statefulset strategy type", "description": "frontend deployment strategy type",
"default": "RollingUpdate" "default": "RollingUpdate"
} }
} }
}, },
"podManagementPolicy": {
"type": "string",
"description": "Pod management policy for frontend statefulset",
"default": "OrderedReady"
},
"priorityClassName": { "priorityClassName": {
"type": "string", "type": "string",
"description": "frontend pods' priorityClassName", "description": "frontend pods' priorityClassName",
@@ -1463,6 +1443,11 @@
"description": "orborus image repository", "description": "orborus image repository",
"default": "shuffle/shuffle-orborus" "default": "shuffle/shuffle-orborus"
}, },
"tag": {
"type": "string",
"description": "orborus image tag (immutable tags are recommended, defaults to appVersion)",
"default": ""
},
"digest": { "digest": {
"type": "string", "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)", "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": { "runAsUser": {
"type": "number", "type": "number",
"description": "Set runAsUser in orborus container' Security Context", "description": "Set runAsUser in orborus container' Security Context",
"default": 101 "default": 1001
}, },
"runAsGroup": { "runAsGroup": {
"type": "number", "type": "number",
"description": "Set runAsGroup in orborus container' Security Context", "description": "Set runAsGroup in orborus container' Security Context",
"default": 101 "default": 1001
}, },
"runAsNonRoot": { "runAsNonRoot": {
"type": "boolean", "type": "boolean",
@@ -1752,21 +1737,11 @@
"default": [], "default": [],
"items": {} "items": {}
}, },
"daemonsetAnnotations": {
"type": "object",
"description": "Annotations for orborus daemonset",
"default": {}
},
"deploymentAnnotations": { "deploymentAnnotations": {
"type": "object", "type": "object",
"description": "Annotations for orborus deployment", "description": "Annotations for orborus deployment",
"default": {} "default": {}
}, },
"statefulsetAnnotations": {
"type": "object",
"description": "Annotations for orborus statefulset",
"default": {}
},
"podLabels": { "podLabels": {
"type": "object", "type": "object",
"description": "Extra labels for orborus pods", "description": "Extra labels for orborus pods",
@@ -1829,16 +1804,11 @@
"properties": { "properties": {
"type": { "type": {
"type": "string", "type": "string",
"description": "orborus statefulset strategy type", "description": "orborus deployment strategy type",
"default": "RollingUpdate" "default": "RollingUpdate"
} }
} }
}, },
"podManagementPolicy": {
"type": "string",
"description": "Pod management policy for orborus statefulset",
"default": "OrderedReady"
},
"priorityClassName": { "priorityClassName": {
"type": "string", "type": "string",
"description": "orborus pods' priorityClassName", "description": "orborus pods' priorityClassName",
@@ -2092,6 +2062,11 @@
"description": "worker image repository", "description": "worker image repository",
"default": "shuffle/shuffle-worker" "default": "shuffle/shuffle-worker"
}, },
"tag": {
"type": "string",
"description": "worker image tag (immutable tags are recommended, defaults to appVersion)",
"default": ""
},
"digest": { "digest": {
"type": "string", "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)", "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", "description": "OS Shell + Utility image repository",
"default": "bitnami/os-shell" "default": "bitnami/os-shell"
}, },
"tag": {
"type": "string",
"description": "OS Shell + Utility image tag (immutable tags are recommended)",
"default": "12-debian-12-r30"
},
"pullPolicy": { "pullPolicy": {
"type": "string", "type": "string",
"description": "OS Shell + Utility image pull policy", "description": "OS Shell + Utility image pull policy",
+18 -76
View File
@@ -96,7 +96,7 @@ backend:
## backend image ## backend image
## @param backend.image.registry backend image registry ## @param backend.image.registry backend image registry
## @param backend.image.repository backend image repository ## @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.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.pullPolicy backend image pull policy
## @param backend.image.pullSecrets backend image pull secrets ## @param backend.image.pullSecrets backend image pull secrets
@@ -104,7 +104,7 @@ backend:
image: image:
registry: ghcr.io registry: ghcr.io
repository: shuffle/shuffle-backend repository: shuffle/shuffle-backend
tag: nightly tag: ""
digest: "" digest: ""
## Specify a imagePullPolicy ## Specify a imagePullPolicy
## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
@@ -235,8 +235,8 @@ backend:
containerSecurityContext: containerSecurityContext:
enabled: true enabled: true
seLinuxOptions: {} seLinuxOptions: {}
runAsUser: 1000 runAsUser: 1001
runAsGroup: 1000 runAsGroup: 1001
runAsNonRoot: true runAsNonRoot: true
readOnlyRootFilesystem: true readOnlyRootFilesystem: true
privileged: false privileged: false
@@ -260,18 +260,10 @@ backend:
## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/
## ##
hostAliases: [] 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 ## @param backend.deploymentAnnotations Annotations for backend deployment
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
## ##
deploymentAnnotations: {} 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 ## @param backend.podLabels Extra labels for backend pods
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
## ##
@@ -321,22 +313,11 @@ backend:
## ONLY FOR DEPLOYMENTS: ## ONLY FOR DEPLOYMENTS:
## @param backend.updateStrategy.type backend deployment strategy type ## @param backend.updateStrategy.type backend deployment strategy type
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy ## 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: updateStrategy:
## ONLY FOR DEPLOYMENTS:
## Can be set to RollingUpdate or Recreate ## Can be set to RollingUpdate or Recreate
## ONLY FOR STATEFULSETS:
## Can be set to RollingUpdate or OnDelete
## ##
type: RollingUpdate 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 ## @param backend.priorityClassName backend pods' priorityClassName
## ##
priorityClassName: "" priorityClassName: ""
@@ -565,7 +546,7 @@ frontend:
## frontend image ## frontend image
## @param frontend.image.registry frontend image registry ## @param frontend.image.registry frontend image registry
## @param frontend.image.repository frontend image repository ## @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.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.pullPolicy frontend image pull policy
## @param frontend.image.pullSecrets frontend image pull secrets ## @param frontend.image.pullSecrets frontend image pull secrets
@@ -573,7 +554,7 @@ frontend:
image: image:
registry: ghcr.io registry: ghcr.io
repository: shuffle/shuffle-frontend repository: shuffle/shuffle-frontend
tag: nightly tag: ""
digest: "" digest: ""
## Specify a imagePullPolicy ## Specify a imagePullPolicy
## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## 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 ## @param frontend.podSecurityContext.fsGroup Set fsGroup in frontend pods' Security Context
## ##
podSecurityContext: 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 fsGroupChangePolicy: Always
sysctls: [] sysctls: []
supplementalGroups: [] supplementalGroups: []
fsGroup: 1001 fsGroup: 1001
## Configure Container Security Context ## Configure Container Security Context
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container ## 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 ## @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 ## @param frontend.containerSecurityContext.seccompProfile.type Set seccomp profile in frontend container
## ##
containerSecurityContext: 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: {} seLinuxOptions: {}
runAsUser: 101 runAsUser: 1001
runAsGroup: 101 runAsGroup: 1001
runAsNonRoot: true runAsNonRoot: true
readOnlyRootFilesystem: true readOnlyRootFilesystem: true
privileged: false privileged: false
@@ -728,18 +710,10 @@ frontend:
## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/
## ##
hostAliases: [] 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 ## @param frontend.deploymentAnnotations Annotations for frontend deployment
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
## ##
deploymentAnnotations: {} 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 ## @param frontend.podLabels Extra labels for frontend pods
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ ## 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/ ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
## ##
tolerations: [] tolerations: []
## ONLY FOR DEPLOYMENTS:
## @param frontend.updateStrategy.type frontend deployment strategy type ## @param frontend.updateStrategy.type frontend deployment strategy type
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy ## 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: updateStrategy:
## ONLY FOR DEPLOYMENTS:
## Can be set to RollingUpdate or Recreate ## Can be set to RollingUpdate or Recreate
## ONLY FOR STATEFULSETS:
## Can be set to RollingUpdate or OnDelete
## ##
type: RollingUpdate 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 ## @param frontend.priorityClassName frontend pods' priorityClassName
## ##
priorityClassName: "" priorityClassName: ""
@@ -970,7 +932,7 @@ orborus:
## orborus image ## orborus image
## @param orborus.image.registry orborus image registry ## @param orborus.image.registry orborus image registry
## @param orborus.image.repository orborus image repository ## @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.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.pullPolicy orborus image pull policy
## @param orborus.image.pullSecrets orborus image pull secrets ## @param orborus.image.pullSecrets orborus image pull secrets
@@ -978,7 +940,7 @@ orborus:
image: image:
registry: ghcr.io registry: ghcr.io
repository: shuffle/shuffle-orborus repository: shuffle/shuffle-orborus
tag: nightly tag: ""
digest: "" digest: ""
## Specify a imagePullPolicy ## Specify a imagePullPolicy
## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
@@ -1107,8 +1069,8 @@ orborus:
containerSecurityContext: containerSecurityContext:
enabled: true enabled: true
seLinuxOptions: {} seLinuxOptions: {}
runAsUser: 101 runAsUser: 1001
runAsGroup: 101 runAsGroup: 1001
runAsNonRoot: true runAsNonRoot: true
readOnlyRootFilesystem: true readOnlyRootFilesystem: true
privileged: false privileged: false
@@ -1132,18 +1094,10 @@ orborus:
## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/
## ##
hostAliases: [] 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 ## @param orborus.deploymentAnnotations Annotations for orborus deployment
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
## ##
deploymentAnnotations: {} 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 ## @param orborus.podLabels Extra labels for orborus pods
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ ## 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/ ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
## ##
tolerations: [] tolerations: []
## ONLY FOR DEPLOYMENTS:
## @param orborus.updateStrategy.type orborus deployment strategy type ## @param orborus.updateStrategy.type orborus deployment strategy type
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy ## 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: updateStrategy:
## ONLY FOR DEPLOYMENTS:
## Can be set to RollingUpdate or Recreate ## Can be set to RollingUpdate or Recreate
## ONLY FOR STATEFULSETS:
## Can be set to RollingUpdate or OnDelete
## ##
type: RollingUpdate 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 ## @param orborus.priorityClassName orborus pods' priorityClassName
## ##
priorityClassName: "" priorityClassName: ""
@@ -1377,13 +1319,13 @@ worker:
## worker image ## worker image
## @param worker.image.registry worker image registry ## @param worker.image.registry worker image registry
## @param worker.image.repository worker image repository ## @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) ## @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: image:
registry: ghcr.io registry: ghcr.io
repository: shuffle/shuffle-worker repository: shuffle/shuffle-worker
tag: nightly tag: ""
digest: "" digest: ""
## ServiceAccount configuration ## ServiceAccount configuration
@@ -1748,7 +1690,7 @@ volumePermissions:
## ref: https://hub.docker.com/r/bitnami/os-shell/tags/ ## ref: https://hub.docker.com/r/bitnami/os-shell/tags/
## @param volumePermissions.image.registry OS Shell + Utility image registry ## @param volumePermissions.image.registry OS Shell + Utility image registry
## @param volumePermissions.image.repository OS Shell + Utility image repository ## @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.pullPolicy OS Shell + Utility image pull policy
## @param volumePermissions.image.pullSecrets OS Shell + Utility image pull secrets ## @param volumePermissions.image.pullSecrets OS Shell + Utility image pull secrets
## ##
+1 -1
View File
@@ -10,7 +10,7 @@ require (
github.com/docker/docker v27.5.0+incompatible github.com/docker/docker v27.5.0+incompatible
github.com/docker/go-connections v0.5.0 github.com/docker/go-connections v0.5.0
github.com/satori/go.uuid v1.2.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/api v0.30.2
k8s.io/apimachinery v0.30.2 k8s.io/apimachinery v0.30.2
) )
+2 -2
View File
@@ -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/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 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shuffle/shuffle-shared v0.8.18 h1:7f7cV+P2pr/g44i+AI8P0UheEe8oG8O2V8loBfe9YSw= github.com/shuffle/shuffle-shared v0.8.35 h1:3awc0TrsLLZiQeWD2XGIkTnFbczAG0cMfy1+cB/P7zg=
github.com/shuffle/shuffle-shared v0.8.18/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI= 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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+183 -164
View File
@@ -1,13 +1,8 @@
package main 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 ( import (
"archive/zip" "archive/zip"
"bytes" "bytes"
@@ -60,7 +55,7 @@ import (
"k8s.io/apimachinery/pkg/util/intstr" "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 var sleepTime = 2
// Making it work on low-end machines even during busy times :) // 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 newWorkerImage = os.Getenv("SHUFFLE_WORKER_IMAGE")
var dockerSwarmBridgeMTU = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_MTU") var dockerSwarmBridgeMTU = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_MTU")
var dockerSwarmBridgeInterface = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_INTERFACE") var dockerSwarmBridgeInterface = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_INTERFACE")
var maxCPUPercent = 90
// Kubernetes settings
var isKubernetes = os.Getenv("IS_KUBERNETES") var isKubernetes = os.Getenv("IS_KUBERNETES")
var kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") 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 = "docker.pkg.github.com/shuffle/shuffle"
// var baseimagename = "ghcr.io/frikky" // var baseimagename = "ghcr.io/frikky"
@@ -217,8 +216,7 @@ func skipCheckInCleanup(name string) bool {
func cleanupExistingNodes(ctx context.Context) error { func cleanupExistingNodes(ctx context.Context) error {
if isKubernetes == "true" { if isKubernetes == "true" {
// of course, this doesn't clean up "nodes" but // Cleanup all workers created by orborus and all apps created by workers.
// rather pods, services, roles etc.
if kubernetesNamespace == "" { if kubernetesNamespace == "" {
kubernetesNamespace = "default" kubernetesNamespace = "default"
@@ -230,62 +228,38 @@ func cleanupExistingNodes(ctx context.Context) error {
return err 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 // 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 { if err != nil {
log.Printf("[ERROR] Failed listing services: %s", err) log.Printf("[ERROR] Failed listing services: %s", err)
return err return err
} }
for _, service := range services.Items { for _, service := range services.Items {
if skipCheckInCleanup(service.Name) {
continue
}
err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{}) err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{})
if err != nil { if err != nil {
log.Printf("[ERROR] Failed deleting service %s: %s", service.Name, err) 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 { if err != nil {
log.Printf("[ERROR] Failed listing deployments: %s", err) log.Printf("[ERROR] Failed listing deployments: %s", err)
return err return err
} }
for _, deployment := range deployments.Items { for _, deployment := range deployments.Items {
if skipCheckInCleanup(deployment.Name) {
continue
}
err := clientset.AppsV1().Deployments(kubernetesNamespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{}) err := clientset.AppsV1().Deployments(kubernetesNamespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{})
if err != nil { if err != nil {
log.Printf("[ERROR] Failed deleting deployment %s: %s", deployment.Name, err) 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) time.Sleep(10 * time.Second)
@@ -457,19 +431,19 @@ func deployServiceWorkers(image string) {
} }
/* /*
isMemcachedRunning, err := checkMemcached(ctx, dockercli) isMemcachedRunning, err := checkMemcached(ctx, dockercli)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed checking memcached: %s", err) log.Printf("[ERROR] Failed checking memcached: %s", err)
} }
if isMemcachedRunning == false { if isMemcachedRunning == false {
log.Printf("[ERROR] Memcached is not running. Will try to deploy it.") log.Printf("[ERROR] Memcached is not running. Will try to deploy it.")
deployMemcached(dockercli) deployMemcached(dockercli)
} }
ip := "shuffle-cache" ip := "shuffle-cache"
if len(os.Getenv("SHUFFLE_MEMCACHED")) == 0 { if len(os.Getenv("SHUFFLE_MEMCACHED")) == 0 {
os.Setenv("SHUFFLE_MEMCACHED", fmt.Sprintf("%s:11211", ip)) os.Setenv("SHUFFLE_MEMCACHED", fmt.Sprintf("%s:11211", ip))
} }
*/ */
defaultNetworkAttach := false defaultNetworkAttach := false
@@ -725,7 +699,6 @@ func deployServiceWorkers(image string) {
if err == nil { if err == nil {
log.Printf("[DEBUG] Successfully deployed workers with %d replica(s) on %d node(s)", replicas, cnt) 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) //log.Printf("[DEBUG] Servicecreate request: %#v %#v", service, err)
} else { } else {
if !strings.Contains(fmt.Sprintf("%s", err), "Already Exists") && !strings.Contains(fmt.Sprintf("%s", err), "is already in use by service") { 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 // Remove the image
handled := []string{} 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) //log.Printf("[DEBUG] Removing existing image (s): %s", images)
//time.Sleep(time.Duration(30) * time.Second)
newImages := []string{} newImages := []string{}
successful := []string{}
for _, curimage := range strings.Split(images, ",") { for _, curimage := range strings.Split(images, ",") {
curimage = strings.TrimSpace(curimage) curimage = strings.TrimSpace(curimage)
if shuffle.ArrayContains(handled, curimage) { if shuffle.ArrayContains(handled, curimage) {
@@ -787,25 +760,36 @@ func handleBackendImageDownload(ctx context.Context, images string) error {
newImages = append(newImages, curimage) newImages = append(newImages, curimage)
// Force remove the current image to avoid cached layers // Force remove the current image to avoid cached layers
_, err := dockercli.ImageRemove(ctx, curimage, image.RemoveOptions{ if swarmConfig == "run" || swarmConfig == "swarm" {
Force: true, _, err := dockercli.ImageRemove(ctx, curimage, image.RemoveOptions{
PruneChildren: true, Force: true,
}) PruneChildren: true,
})
if err != nil { if err != nil {
log.Printf("[ERROR] Failed removing image for re-download: %s", err) log.Printf("[ERROR] Failed removing image for re-download: %s", err)
} else {
log.Printf("[DEBUG] Removed image: %s", curimage)
}
} else { } 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 { if err != nil {
log.Printf("[ERROR] Failed downloading image: %s", err) //log.Printf("[ERROR] Failed downloading image: %s", err)
} else { } 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" { 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")) 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") { if !strings.Contains(fmt.Sprintf("%s", resp), "error") {
break break
} else { } 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) 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"))) 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() clientset, _, err := shuffle.GetKubernetesClient()
if err != nil { if err != nil {
log.Printf("[ERROR] Error getting kubernetes client:", err) 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())) //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 // Check if namespace exist as variable. If so, make it
if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 && !namespacemade { if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 && !namespacemade {
kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") 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", "container": "shuffle-worker",
} }
matchLabels := map[string]string{
"app.kubernetes.io/name": "shuffle-worker",
"app.kubernetes.io/instance": identifier,
}
containerAttachment := corev1.Container{ containerAttachment := corev1.Container{
Name: identifier, Name: identifier,
Image: kubernetesImage, Image: kubernetesImage,
@@ -1194,22 +1181,24 @@ func deployK8sWorker(image string, identifier string, env []string) error {
deployment := &appsv1.Deployment{ deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: identifier, Name: identifier,
Labels: labels,
}, },
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
Replicas: &replicaNumberInt32, Replicas: &replicaNumberInt32,
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: containerLabels, MatchLabels: matchLabels,
}, },
Template: corev1.PodTemplateSpec{ Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: containerLabels, Labels: labels,
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
containerAttachment, 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 // kubectl expose deployment shuffle-workers --type=NodePort --port=33333 --target-port=33333
service := &corev1.Service{ service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: identifier, Name: identifier,
Labels: labels,
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
Selector: containerLabels, Selector: matchLabels,
Ports: []corev1.ServicePort{ Ports: []corev1.ServicePort{
{ {
Protocol: "TCP", 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 { func deployWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) error {
if len(os.Getenv("REGISTRY_URL")) > 0 && os.Getenv("REGISTRY_URL") != "" { if len(os.Getenv("REGISTRY_URL")) > 0 && os.Getenv("REGISTRY_URL") != "" {
env = append(env, fmt.Sprintf("REGISTRY_URL=%s", 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{}, Resources: container.Resources{},
} }
certPath := "/certs"
// This is just to test the mounting locally so // This is just to test the mounting locally so
// I can control from what source I'm mounting // I can control from what source I'm mounting
// the certs to. Default behaviour is: // the certs to. Default behaviour is:
// /certs:/certs. // /certs:/certs.
certPath := "/certs"
if os.Getenv("SHUFFLE_CERT_PATH") != "" { if os.Getenv("SHUFFLE_CERT_PATH") != "" {
certPath = os.Getenv("SHUFFLE_CERT_PATH") certPath = os.Getenv("SHUFFLE_CERT_PATH")
} }
_, err := os.ReadDir(certPath) _, err := os.ReadDir(certPath)
if certPath != "" && err == nil { if certPath != "" && err == nil {
certVol := mount.Mount{ certVol := mount.Mount{
Type: mount.TypeBind, Type: mount.TypeBind,
@@ -1310,7 +1298,6 @@ func deployWorker(image string, identifier string, env []string, executionReques
} }
} }
//var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG") //var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG")
parsedUuid := uuid.NewV4() parsedUuid := uuid.NewV4()
@@ -1322,7 +1309,7 @@ func deployWorker(image string, identifier string, env []string, executionReques
if isKubernetes != "true" { if isKubernetes != "true" {
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
if strings.ToLower(cleanupEnv) != "false" { if strings.ToLower(cleanupEnv) == "true" {
hostConfig.AutoRemove = true hostConfig.AutoRemove = true
} }
} }
@@ -1351,11 +1338,11 @@ func deployWorker(image string, identifier string, env []string, executionReques
) )
if err != nil { 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 return err
} }
} else { } else {
log.Printf("[ERROR] Container create error: %s", err) log.Printf("[ERROR][%s] Container create error: %s", executionRequest.ExecutionId, err)
return err return err
} }
} }
@@ -1380,47 +1367,47 @@ func deployWorker(image string, identifier string, env []string, executionReques
identifier+"-2", identifier+"-2",
) )
if err != nil { 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) err = dockercli.ContainerStart(context.Background(), cont.ID, containerStartOptions)
if err != nil { 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 { } 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 { 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 return err
} else { } 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) stats, err := dockercli.ContainerInspect(ctx, cont.ID)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed checking worker '%s': %s", cont.ID, err) log.Printf("[WARNING][%s] Failed checking worker '%s': %s", executionRequest.ExecutionId, cont.ID, err)
return nil return nil
} }
containerStatus := stats.ContainerJSONBase.State.Status containerStatus := stats.ContainerJSONBase.State.Status
if containerStatus != "running" { 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) err = stopWorker(containerName)
if err != nil { if err != nil {
log.Printf("Failed stopping worker %s", execution.ExecutionId) log.Printf("Failed stopping worker %s", execution.ExecutionId)
return nil return nil
} }
err = deployWorker(dockercli, workerImage, containerName, env) err = deployWorker(dockercli, workerImage, containerName, env)
if err != nil { if err != nil {
log.Printf("Failed executing worker %s in state %s", execution.ExecutionId, containerStatus) log.Printf("Failed executing worker %s in state %s", execution.ExecutionId, containerStatus)
return nil return nil
}
} }
}
*/ */
} else { } else {
log.Printf("[INFO][%s] New Worker created. Environment %s: docker logs %s", executionRequest.ExecutionId, environment, cont.ID) 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 == "" { if baseimageregistry == "" {
//baseimageregistry = "ghcr.io" // Github
baseimageregistry = "docker.io" // Dockerhub 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 == "" { if baseimagename == "" {
// FIXME: This is probably the problem for image names tbh
//baseimagename = "shuffle" // Github (ghcr.io)
baseimagename = "frikky/shuffle" // Dockerhub 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) 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 { if len(newWorkerImage) > 0 {
newWorker = newWorkerImage newWorker = newWorkerImage
} }
// check whether they are the same first // Check whether they are the same first
if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") == "false" {
if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") != "true" { log.Printf("[DEBUG] Skipping image download as SHUFFLE_AUTO_IMAGE_DOWNLOAD is set to false")
} else {
images := []string{ images := []string{
fmt.Sprintf("frikky/shuffle:app_sdk"), fmt.Sprintf("frikky/shuffle:app_sdk"),
fmt.Sprintf("shuffle/shuffle:app_sdk"),
fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", baseimageregistry, baseimagename, appSdkVersion),
newWorker, newWorker,
} }
@@ -1513,8 +1512,6 @@ func initializeImages() {
log.Printf("[DEBUG] Successfully downloaded and built %s", image) 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 // 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" cleanupEnv = "true"
} }
@@ -2008,7 +2005,7 @@ func main() {
log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!") log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!")
initializeImages() 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 { if len(newWorkerImage) > 0 {
workerImage = newWorkerImage workerImage = newWorkerImage
} }
@@ -2119,6 +2116,7 @@ func main() {
// Marshal and set body // Marshal and set body
orborusStats := getOrborusStats(ctx) orborusStats := getOrborusStats(ctx)
pipelinePayload, pipelineerr := sendPipelineHealthStatus() pipelinePayload, pipelineerr := sendPipelineHealthStatus()
if pipelineerr != nil { 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" { if incRequest.Type == "PIPELINE_CREATE" || incRequest.Type == "PIPELINE_START" || incRequest.Type == "PIPELINE_STOP" || incRequest.Type == "PIPELINE_DELETE" {
os.Setenv("SHUFFLE_SKIP_PIPELINES", "false") os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
tenzirDisabled = false tenzirDisabled = false
// Running NEW or editing pipelines // Running NEW or editing pipelines
err := handlePipeline(incRequest) err := handlePipeline(incRequest)
@@ -2258,12 +2256,9 @@ func main() {
toBeRemoved.Data = append(toBeRemoved.Data, incRequest) toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
} else if incRequest.Type == "DOCKER_IMAGE_DOWNLOAD" { } 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 { 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) go handleBackendImageDownload(ctx, incRequest.ExecutionArgument)
} else { } else {
log.Printf("[ERROR] No image name provided for download. Removing job from queue.") 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" { } else if incRequest.Type == "CATEGORY_UPDATE" {
os.Setenv("SHUFFLE_SKIP_PIPELINES", "false") os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
tenzirDisabled = false tenzirDisabled = false
err = handleFileCategoryChange() err = handleFileCategoryChange()
if err != nil { if err != nil {
@@ -2318,7 +2313,7 @@ func main() {
// Manual command = overrides to allow starting of Tenzir from the frontend anyway. // Manual command = overrides to allow starting of Tenzir from the frontend anyway.
os.Setenv("SHUFFLE_SKIP_PIPELINES", "false") os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
tenzirDisabled = false tenzirDisabled = false
// Removed either way // Removed either way
toBeRemoved.Data = append(toBeRemoved.Data, incRequest) toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
@@ -2328,7 +2323,7 @@ func main() {
if strings.Contains(fmt.Sprintf("%s", err), "node available") { if strings.Contains(fmt.Sprintf("%s", err), "node available") {
// Disabling until UI is updated // Disabling until UI is updated
os.Setenv("SHUFFLE_SKIP_PIPELINES", "true") os.Setenv("SHUFFLE_SKIP_PIPELINES", "true")
tenzirDisabled = true tenzirDisabled = true
log.Printf("[ERROR] Failed to start tenzir, reason: %s", err) log.Printf("[ERROR] Failed to start tenzir, reason: %s", err)
err = shuffle.CreateOrgNotification( err = shuffle.CreateOrgNotification(
@@ -2426,11 +2421,11 @@ func main() {
} }
if execution.Status == "ABORT" || execution.Status == "FAILED" { 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) { 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) 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? // 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 // Look for volume binds
if len(os.Getenv("SHUFFLE_VOLUME_BINDS")) > 0 { 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"))) 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) toBeRemoved.Data = append(toBeRemoved.Data, execution)
executionIds = append(executionIds, execution.ExecutionId) executionIds = append(executionIds, execution.ExecutionId)
} else { } 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") { if strings.Contains(err.Error(), "already exists") {
toBeRemoved.Data = append(toBeRemoved.Data, execution) toBeRemoved.Data = append(toBeRemoved.Data, execution)
executionIds = append(executionIds, execution.ExecutionId) 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") // return errors.New("Pipelines are disabled by user with SHUFFLE_SKIP_PIPELINES")
//log.Printf("[INFO] Pipelines are enabled by user") //log.Printf("[INFO] Pipelines are enabled by user")
} else { } 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" { 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 // conflicting options: hostname and the network mode
/* /*
if isKubernetes != "true" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" { if isKubernetes != "true" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" {
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
} }
*/ */
resp, err := dockercli.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName) resp, err := dockercli.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName)
@@ -3060,7 +3069,6 @@ func createPipeline(command, identifier string) (string, error) {
return "", err return "", err
} }
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed reading response body: %s", err) log.Printf("[ERROR] Failed reading response body: %s", err)
@@ -3078,8 +3086,8 @@ func createPipeline(command, identifier string) (string, error) {
} }
type PipelineResponse struct { type PipelineResponse struct {
ID string `json:"id"` ID string `json:"id"`
Message string `json:"message"` Message string `json:"message"`
Severity string `json:"severity"` Severity string `json:"severity"`
} }
@@ -3215,10 +3223,28 @@ func deletePipeline(pipelineId string) error {
func listPipelines() ([]shuffle.PipelineInfo, error) { func listPipelines() ([]shuffle.PipelineInfo, error) {
responseData := shuffle.PipelineInfoWrapper{} responseData := shuffle.PipelineInfoWrapper{}
if tenzirDisabled {
return responseData.Pipelines, errors.New("Tenzir is disabled")
}
var reqBody []byte var reqBody []byte
url := fmt.Sprintf("%s/api/v0/pipeline/list", pipelineUrl) 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 { if err != nil {
return responseData.Pipelines, err return responseData.Pipelines, err
} }
@@ -3302,7 +3328,7 @@ func handleFileCategoryChange() error {
tenzirStorageFolder = "/tmp/" tenzirStorageFolder = "/tmp/"
} }
tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/") tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/")
sigmaPath := fmt.Sprintf("%s/sigma_rules", tenzirStorageFolder) sigmaPath := fmt.Sprintf("%s/sigma_rules", tenzirStorageFolder)
err = extractZIP("files.zip", sigmaPath) err = extractZIP("files.zip", sigmaPath)
if err != nil { if err != nil {
@@ -3310,7 +3336,6 @@ func handleFileCategoryChange() error {
return err return err
} }
log.Printf("[DEBUG] Detection files copied to '%s' successfully.", sigmaPath) log.Printf("[DEBUG] Detection files copied to '%s' successfully.", sigmaPath)
return nil return nil
@@ -3399,7 +3424,7 @@ func removeFileCategory() error {
tenzirStorageFolder = "/tmp/" tenzirStorageFolder = "/tmp/"
} }
tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/") tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/")
//sigmaPath := "/var/lib/tenzir/sigma_rules/*" //sigmaPath := "/var/lib/tenzir/sigma_rules/*"
sigmaPath := fmt.Sprintf("%s/sigma_rules", tenzirStorageFolder) sigmaPath := fmt.Sprintf("%s/sigma_rules", tenzirStorageFolder)
@@ -3446,6 +3471,10 @@ func sendPipelineHealthStatus() (shuffle.LakeConfig, error) {
Pipelines: []shuffle.PipelineInfoMini{}, Pipelines: []shuffle.PipelineInfoMini{},
} }
if tenzirDisabled {
return pipelinePayload, nil
}
// To not spam down the list API too much // To not spam down the list API too much
randint := rand.Intn(5) randint := rand.Intn(5)
if len(pipelines) == 0 || randint == 0 { if len(pipelines) == 0 || randint == 0 {
@@ -3468,10 +3497,6 @@ func sendPipelineHealthStatus() (shuffle.LakeConfig, error) {
pipelinePayload.Pipelines = pipelines pipelinePayload.Pipelines = pipelines
} }
if tenzirDisabled {
return pipelinePayload, nil
}
err := deployTenzirNode() err := deployTenzirNode()
if err != nil { 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") { 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 return 0
} }
labelSelector := "app=shuffle-worker"
pods, podErr := clientset.CoreV1().Pods(kubernetesNamespace).List(ctx, metav1.ListOptions{ pods, podErr := clientset.CoreV1().Pods(kubernetesNamespace).List(ctx, metav1.ListOptions{
LabelSelector: labelSelector, LabelSelector: "app.kubernetes.io/name=shuffle-worker",
}) })
if podErr != nil { if podErr != nil {
log.Printf("[ERROR] Failed getting running workers: %s", podErr) log.Printf("[ERROR] Failed getting running workers: %s", podErr)
@@ -3655,15 +3679,12 @@ func zombiecheck(ctx context.Context, workerTimeout int) error {
All: true, All: true,
}) })
//log.Printf("Len: %d", len(containers))
if err != nil { if err != nil {
log.Printf("[ERROR] Failed creating Containerlist: %s", err) log.Printf("[ERROR] Failed creating Containerlist: %s", err)
return err return err
} }
containerNames := map[string]string{} containerNames := map[string]string{}
stopContainers := []string{} stopContainers := []string{}
removeContainers := []string{} removeContainers := []string{}
log.Printf("[INFO] Baseimage: %s, Workertimeout: %d", baseimagename, int64(workerTimeout)) log.Printf("[INFO] Baseimage: %s, Workertimeout: %d", baseimagename, int64(workerTimeout))
@@ -3812,8 +3833,7 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest, image string,
if err != nil { if err != nil {
log.Printf("[ERROR] Failed creating worker request: %s", err) 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") { 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 { if len(newWorkerImage) > 0 {
workerImage = newWorkerImage 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) 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") { 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 { if len(newWorkerImage) > 0 {
workerImage = newWorkerImage workerImage = newWorkerImage
} }
+1 -1
View File
@@ -8,7 +8,7 @@ require (
github.com/docker/docker v27.5.0+incompatible github.com/docker/docker v27.5.0+incompatible
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/satori/go.uuid v1.2.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/api v0.30.2
k8s.io/apimachinery v0.30.2 k8s.io/apimachinery v0.30.2
k8s.io/client-go v0.30.2 k8s.io/client-go v0.30.2
+2 -2
View File
@@ -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/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 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shuffle/shuffle-shared v0.8.18 h1:7f7cV+P2pr/g44i+AI8P0UheEe8oG8O2V8loBfe9YSw= github.com/shuffle/shuffle-shared v0.8.35 h1:3awc0TrsLLZiQeWD2XGIkTnFbczAG0cMfy1+cB/P7zg=
github.com/shuffle/shuffle-shared v0.8.18/go.mod h1:NruHSAscDsW595wpK2r7MeHPGspUEKRNvBpcN1iGbHI= 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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+88 -44
View File
@@ -23,8 +23,8 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "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/image"
"github.com/docker/docker/api/types/mount"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
// This is for automatic removal of certain code :) // 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 cleanupEnv = strings.ToLower(os.Getenv("CLEANUP"))
var swarmNetworkName = os.Getenv("SHUFFLE_SWARM_NETWORK_NAME") var swarmNetworkName = os.Getenv("SHUFFLE_SWARM_NETWORK_NAME")
var dockerApiVersion = strings.ToLower(os.Getenv("DOCKER_API_VERSION")) 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 kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE")
var executionCount int64 var executionCount int64
@@ -104,9 +105,9 @@ var window = shuffle.NewTimeWindow(10 * time.Second)
// Images to be autodeployed in the latest version of Shuffle. // Images to be autodeployed in the latest version of Shuffle.
var autoDeploy = map[string]string{ var autoDeploy = map[string]string{
"http:1.4.0": "frikky/shuffle:http_1.4.0", "http:1.4.0": "frikky/shuffle:http_1.4.0",
"shuffle-tools:1.2.0": "frikky/shuffle:shuffle-tools_1.2.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-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", // "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 // 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() ctx := context.Background()
dockercli, err := dockerclient.NewEnvClient() dockercli, err := dockerclient.NewEnvClient()
@@ -482,9 +483,24 @@ func deployk8sApp(image string, identifier string, env []string) error {
//fix naming convention //fix naming convention
// podUuid := uuid.NewV4().String() // podUuid := uuid.NewV4().String()
// podName := fmt.Sprintf("%s-%s", value, podUuid) // name := fmt.Sprintf("%s-%s", value, podUuid)
// replace identifier "_" with "-" // 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{ // pod := &corev1.Pod{
// ObjectMeta: metav1.ObjectMeta{ // ObjectMeta: metav1.ObjectMeta{
@@ -564,20 +580,17 @@ func deployk8sApp(image string, identifier string, env []string) error {
deployment := &appsv1.Deployment{ deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: podName, Name: name,
Labels: labels,
}, },
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
Replicas: &replicaNumberInt32, Replicas: &replicaNumberInt32,
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{ MatchLabels: matchLabels,
"app": podName,
},
}, },
Template: corev1.PodTemplateSpec{ Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{ Labels: labels,
"app": podName,
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@@ -587,6 +600,8 @@ func deployk8sApp(image string, identifier string, env []string) error {
Env: buildEnvVars(envMap), 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 // kubectl expose deployment {podName} --type=NodePort --port=80 --target-port=80
service := &corev1.Service{ service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: podName, Name: name,
Labels: labels,
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
Selector: map[string]string{ Selector: matchLabels,
"app": podName,
},
Ports: []corev1.ServicePort{ Ports: []corev1.ServicePort{
{ {
Protocol: "TCP", Protocol: "TCP",
@@ -870,7 +884,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
} }
// Removing because log extraction should happen first // Removing because log extraction should happen first
if cleanupEnv == "true" { if strings.ToLower(cleanupEnv) == "true" {
hostConfig.AutoRemove = true hostConfig.AutoRemove = true
} }
@@ -880,19 +894,30 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
if len(volumeBindString) > 0 { if len(volumeBindString) > 0 {
volumeBindSplit := strings.Split(volumeBindString, ",") volumeBindSplit := strings.Split(volumeBindString, ",")
for _, volumeBind := range volumeBindSplit { for _, volumeBind := range volumeBindSplit {
if strings.Contains(volumeBind, ":") { if volumeBind == "srcfolder=dstfolder" || volumeBind == "srcfolder:dstfolder" || volumeBind == "/srcfolder:/dstfolder" {
volumeBinds = append(volumeBinds, volumeBind) log.Printf("[DEBUG] Volume bind '%s' is invalid.", volumeBind)
} else { continue
log.Printf("[ERROR] Volume bind '%s' is invalid.", volumeBind)
} }
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 // Add more volume binds if possible
if len(volumeBinds) > 0 { 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{} hostConfig.Mounts = []mount.Mount{}
for _, bind := range volumeBinds { for _, bind := range volumeBinds {
if !strings.Contains(bind, ":") || strings.Contains(bind, "..") || strings.HasPrefix(bind, "~") { 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 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, ":") bindSplit := strings.Split(bind, ":")
sourceFolder := bindSplit[0] sourceFolder := bindSplit[0]
destinationFolder := bindSplit[1] 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, Type: mount.TypeBind,
Source: sourceFolder, Source: sourceFolder,
Target: destinationFolder, 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 { func cleanupKubernetesExecution(clientset *kubernetes.Clientset, workflowExecution shuffle.WorkflowExecution, namespace string) error {
// workerName := fmt.Sprintf("worker-%s", workflowExecution.ExecutionId) // 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{ podList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: labelSelector, LabelSelector: labelSelector,
@@ -1035,17 +1073,24 @@ func DeployContainer(ctx context.Context, cli *dockerclient.Client, config *cont
err = cli.ContainerStart(ctx, cont.ID, container.StartOptions{}) err = cli.ContainerStart(ctx, cont.ID, container.StartOptions{})
if err != nil { if err != nil {
if strings.Contains(fmt.Sprintf("%s", err), "cannot join network") || strings.Contains(fmt.Sprintf("%s", err), "No such container") { 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() parsedUuid := uuid.NewV4()
identifier = fmt.Sprintf("%s-%s-nonetwork", identifier, parsedUuid) identifier = fmt.Sprintf("%s-%s-nonetwork", identifier, parsedUuid)
hostConfig = &container.HostConfig{
LogConfig: container.LogConfig{ hostConfig.NetworkMode = container.NetworkMode("")
Type: "json-file", hostConfig.LogConfig = container.LogConfig{
Config: map[string]string{ Type: "json-file",
"max-size": "10m", Config: map[string]string{
}, "max-size": "10m",
}, },
Resources: container.Resources{},
} }
hostConfig.Resources = container.Resources{}
cont, err = cli.ContainerCreate( cont, err = cli.ContainerCreate(
context.Background(), context.Background(),
@@ -1067,12 +1112,12 @@ func DeployContainer(ctx context.Context, cli *dockerclient.Client, config *cont
return err 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{}) err = cli.ContainerStart(ctx, cont.ID, container.StartOptions{})
} }
if err != nil { 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) cacheErr := shuffle.DeleteCache(ctx, actionExecId)
if cacheErr != nil { if cacheErr != nil {
@@ -1232,6 +1277,7 @@ func getWorkerURLs() ([]string, error) {
} }
func askOtherWorkersToDownloadImage(image string) { 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" { if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" {
return 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"))) 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 // FIXME: Ensure to NEVER do this anymore
// This potentially breaks too much stuff. Better to have the app poll the data. // This potentially breaks too much stuff. Better to have the app poll the data.
_ = executionData _ = executionData
@@ -1501,9 +1543,11 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
fmt.Sprintf("%s:%s_%s", baseimagename, parsedAppname, action.AppVersion), 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{} pullOptions := image.PullOptions{}
if cleanupEnv == "true" { if strings.ToLower(cleanupEnv) == "true" {
err = deployApp(dockercli, images[0], identifier, env, workflowExecution, action) err = deployApp(dockercli, images[0], identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {