Merge branch 'nightly' of https://github.com/Shuffle/Shuffle into release-changes

This commit is contained in:
lalitdeore12@gmail.com
2025-06-03 13:54:30 +05:30
44 changed files with 2818 additions and 1209 deletions
@@ -1,3 +1,11 @@
# No extra requirements needed # No extra requirements needed
requests requests==2.32.3
urllib3 urllib3==2.3.0
liquidpy==0.8.2
MarkupSafe==3.0.2
flask[async]==3.1.0
python-dateutil==2.9.0.post0
PyJWT==2.10.1
cryptography==44.0.2
shufflepy==0.1.0
shuffle-sdk==0.0.25
+78 -67
View File
@@ -211,7 +211,7 @@ func fixTags(tags []string) []string {
func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error { func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error {
ctx := context.Background() ctx := context.Background()
client, err := client.NewEnvClient() client, err := client.NewEnvClient()
defer client.Close() defer client.Close()
if err != nil { if err != nil {
log.Printf("Unable to create docker client: %s", err) log.Printf("Unable to create docker client: %s", err)
return err return err
@@ -473,73 +473,84 @@ func buildImage(tags []string, dockerfileLocation string) error {
} }
} }
} }
} else {
ctx := context.Background()
client, err := client.NewEnvClient()
defer client.Close()
if err != nil {
log.Printf("Unable to create docker client: %s", err)
return err
}
log.Printf("[INFO] Docker Tags: %s", tags)
dockerfileSplit := strings.Split(dockerfileLocation, "/")
// Create a buffer
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
defer tw.Close()
baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/")
// Builds the entire folder into buf
err = getParsedTar(tw, baseDir, "")
if err != nil {
log.Printf("Tar issue: %s", err)
}
dockerFileTarReader := bytes.NewReader(buf.Bytes())
buildOptions := types.ImageBuildOptions{
Remove: true,
Tags: tags,
BuildArgs: map[string]*string{},
}
//NetworkMode: "host",
httpProxy := os.Getenv("HTTP_PROXY")
if len(httpProxy) > 0 {
buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy
}
httpsProxy := os.Getenv("HTTPS_PROXY")
if len(httpProxy) > 0 {
buildOptions.BuildArgs["https_proxy"] = &httpsProxy
}
// Build the actual image
imageBuildResponse, err := client.ImageBuild(
ctx,
dockerFileTarReader,
buildOptions,
)
if err != nil {
return err
}
// Read the STDOUT from the build process
defer imageBuildResponse.Body.Close()
buildBuf := new(strings.Builder)
_, err = io.Copy(buildBuf, imageBuildResponse.Body)
if err != nil {
return err
} else {
if strings.Contains(buildBuf.String(), "errorDetail") {
log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n"))
return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ",")))
}
}
return nil
} }
ctx := context.Background()
client, err := client.NewEnvClient()
defer client.Close()
if err != nil {
log.Printf("Unable to create docker client: %s", err)
return err
}
log.Printf("[INFO] Docker Tags: %s", tags)
dockerfileSplit := strings.Split(dockerfileLocation, "/")
// Create a buffer
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
defer tw.Close()
baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/")
// Builds the entire folder into buf
err = getParsedTar(tw, baseDir, "")
if err != nil {
log.Printf("[ERROR] Tar issue during app build: %s", err)
}
dockerFileTarReader := bytes.NewReader(buf.Bytes())
buildOptions := types.ImageBuildOptions{
Remove: true,
Tags: tags,
BuildArgs: map[string]*string{},
}
//NetworkMode: "host",
httpProxy := os.Getenv("HTTP_PROXY")
if len(httpProxy) > 0 {
buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy
}
httpsProxy := os.Getenv("HTTPS_PROXY")
if len(httpProxy) > 0 {
buildOptions.BuildArgs["https_proxy"] = &httpsProxy
}
// Print the actual file content from dockerFileTarReader
/*
data, err := ioutil.ReadAll(dockerFileTarReader)
if err != nil {
log.Printf("[ERROR] Failed reading Dockerfile TAR reader: %s", err)
} else {
log.Printf("[DEBUG] Dockerfile TAR reader content: %s", string(data))
}
*/
// Build the actual image
imageBuildResponse, err := client.ImageBuild(
ctx,
dockerFileTarReader,
buildOptions,
)
if err != nil {
return err
}
// Read the STDOUT from the build process
defer imageBuildResponse.Body.Close()
buildBuf := new(strings.Builder)
_, err = io.Copy(buildBuf, imageBuildResponse.Body)
if err != nil {
return err
} else {
if strings.Contains(buildBuf.String(), "errorDetail") {
log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n"))
return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ",")))
}
}
return nil return nil
} }
@@ -870,7 +881,7 @@ func handleRemoteDownloadApp(resp http.ResponseWriter, ctx context.Context, user
type tmpapp struct { type tmpapp struct {
Success bool `json:"success"` Success bool `json:"success"`
OpenAPI string `json:"openapi"` OpenAPI string `json:"openapi"`
App string `json:"app"` App string `json:"app"`
} }
app := tmpapp{} app := tmpapp{}
+1 -1
View File
@@ -22,7 +22,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.58 github.com/shuffle/shuffle-shared v0.8.72
golang.org/x/crypto v0.37.0 golang.org/x/crypto v0.37.0
google.golang.org/api v0.228.0 google.golang.org/api v0.228.0
google.golang.org/grpc v1.71.1 google.golang.org/grpc v1.71.1
+2 -2
View File
@@ -341,8 +341,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.58 h1:QzCKtQjuaozb+xyLkw6IjwqCNVWzpuJJnxkpXkLCP9M= github.com/shuffle/shuffle-shared v0.8.72 h1:HVOsRt83/1k9P+8q1FAxXnDKyROoDAFa1A3MnoRJYb0=
github.com/shuffle/shuffle-shared v0.8.58/go.mod h1:OLAwH/Ym4941Jn5DF1oZaq6iBpmjG2SNrTZ9Xqck5So= github.com/shuffle/shuffle-shared v0.8.72/go.mod h1:OLAwH/Ym4941Jn5DF1oZaq6iBpmjG2SNrTZ9Xqck5So=
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=
+100 -58
View File
@@ -11,17 +11,18 @@ import (
"crypto/md5" "crypto/md5"
"strconv" "strconv"
"os"
"io"
"log"
"fmt"
"errors"
"net/url"
"os/exec"
"net/http"
"io/ioutil"
"math/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
@@ -35,9 +36,9 @@ import (
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
gitProxy "github.com/go-git/go-git/v5/plumbing/transport" gitProxy "github.com/go-git/go-git/v5/plumbing/transport"
http2 "github.com/go-git/go-git/v5/plumbing/transport/http" http2 "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage/memory"
// Random // Random
xj "github.com/basgys/goxml2json" xj "github.com/basgys/goxml2json"
@@ -61,6 +62,7 @@ var registryName = "registry.hub.docker.com"
var runningEnvironment = "onprem" var runningEnvironment = "onprem"
var syncUrl = "https://shuffler.io" var syncUrl = "https://shuffler.io"
//var syncUrl = "http://localhost:5002"
type retStruct struct { type retStruct struct {
Success bool `json:"success"` Success bool `json:"success"`
@@ -447,7 +449,7 @@ func checkGitProxy(cloneOptions *git.CloneOptions) *git.CloneOptions {
func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) error { func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) error {
// Returns false if there is an issue // Returns false if there is an issue
// Use this for register // Use this for register
err := shuffle.CheckPasswordStrength(password) err := shuffle.CheckPasswordStrength(username, password)
if err != nil { if err != nil {
log.Printf("[WARNING] Bad password strength: %s", err) log.Printf("[WARNING] Bad password strength: %s", err)
return err return err
@@ -460,8 +462,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
} }
ctx := context.Background() ctx := context.Background()
//users, err := FindUser(ctx context.Context, username string) ([]User, error) {
users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(username))) users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(username)))
if err != nil && len(users) == 0 { if err != nil && len(users) == 0 {
log.Printf("[WARNING] Failed getting user %s: %s", username, err) log.Printf("[WARNING] Failed getting user %s: %s", username, err)
@@ -486,7 +486,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
newUser.Active = true newUser.Active = true
newUser.Orgs = []string{org.Id} newUser.Orgs = []string{org.Id}
// FIXME - Remove this later
if role == "admin" { if role == "admin" {
newUser.Role = "admin" newUser.Role = "admin"
newUser.Roles = []string{"admin"} newUser.Roles = []string{"admin"}
@@ -1852,6 +1851,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
// return // return
//} //}
log.Printf("[DEBUG] HOOKS: webhook callback: %s", request.URL.String())
if request.Method != "POST" { if request.Method != "POST" {
request.Method = "POST" request.Method = "POST"
} }
@@ -1863,6 +1864,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
path := strings.Split(request.URL.String(), "/") path := strings.Split(request.URL.String(), "/")
if len(path) < 4 { if len(path) < 4 {
log.Printf("[DEBUG] HOOKS: Invalid webhook path: %s", request.URL.String())
resp.WriteHeader(403) resp.WriteHeader(403)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
@@ -1878,7 +1880,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
if location[1] == "api" { if location[1] == "api" {
if len(location) <= 4 { if len(location) <= 4 {
log.Printf("[INFO] Couldn't handle location. Too short in webhook: %d", len(location)) log.Printf("[INFO] Couldn't handle location. Too short in webhook: %d", len(location))
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
} }
@@ -1895,6 +1897,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
} }
} }
log.Printf("[DEBUG] HOOKS: Pre user agent check")
// Find user agent header // Find user agent header
userAgent := request.Header.Get("User-Agent") userAgent := request.Header.Get("User-Agent")
if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") { if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") {
@@ -1917,8 +1921,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
//log.Printf("HookID: %s", hookId) //log.Printf("HookID: %s", hookId)
hook, err := shuffle.GetHook(ctx, hookId) hook, err := shuffle.GetHook(ctx, hookId)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed getting hook %s (callback): %s", hookId, err) log.Printf("[WARNING] HOOKS: Failed getting hook %s (callback): %s", hookId, err)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
} }
@@ -1930,21 +1934,21 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
//resp.WriteHeader(200) //resp.WriteHeader(200)
//resp.Write([]byte(`{"success": true}`)) //resp.Write([]byte(`{"success": true}`))
if hook.Status == "stopped" { if hook.Status == "stopped" {
log.Printf("[WARNING] Not running %s because hook status is stopped", hook.Id) log.Printf("[WARNING] HOOKS: Not running %s because hook status is stopped", hook.Id)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Is it running?"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Is it running?"}`)))
return return
} }
if len(hook.Workflows) == 0 { if len(hook.Workflows) == 0 {
log.Printf("[DEBUG] Not running because hook isn't connected to any workflows") log.Printf("[DEBUG] HOOKS: Not running because hook isn't connected to any workflows")
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`)))
return return
} }
if hook.Environment == "cloud" { if hook.Environment == "cloud" {
log.Printf("[DEBUG] This should trigger in the cloud. Duplicate action allowed onprem.") log.Printf("[DEBUG] HOOKS: This should trigger in the cloud. Duplicate action allowed onprem.")
} }
// Check auth // Check auth
@@ -1960,7 +1964,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
body, err := ioutil.ReadAll(request.Body) body, err := ioutil.ReadAll(request.Body)
if err != nil { if err != nil {
log.Printf("[DEBUG] Body data error: %s", err) log.Printf("[DEBUG] HOOKS: data read error: %s", err)
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
@@ -2001,7 +2005,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
b, err := json.Marshal(newBody) b, err := json.Marshal(newBody)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed newBody marshaling for webhook: %s", err) log.Printf("[ERROR] HOOKS: Failed newBody marshaling for webhook: %s", err)
resp.WriteHeader(500) resp.WriteHeader(500)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
@@ -2017,7 +2021,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
} }
if len(hook.Start) == 0 { if len(hook.Start) == 0 {
log.Printf("[WARNING] No start node for hook %s - running with workflow default.", hook.Id) log.Printf("[ERROR] HOOKS: No start node for hook %s - running with workflow default.", hook.Id)
//bodyWrapper = string(parsedBody) //bodyWrapper = string(parsedBody)
} }
@@ -2029,7 +2033,6 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
// OrgId: activeOrgs[0].Id, // OrgId: activeOrgs[0].Id,
workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest, hook.OrgId) workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest, hook.OrgId)
if err == nil { if err == nil {
if hook.Version == "v2" { if hook.Version == "v2" {
timeout := 15 timeout := 15
@@ -2064,6 +2067,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
} else { } else {
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId))) resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId)))
} }
return return
} }
@@ -2071,6 +2075,10 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
} }
log.Printf("[ERROR] HOOKS: END OF FUNCTION FOR '%s'. IF this is reached, something went wrong.", hook.Id)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Failed to run workflow. Check logs."}`))
} }
func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
@@ -3087,7 +3095,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
return return
} }
if user.Id == app.Owner || (user.Role == "admin" && user.ActiveOrg.Id == app.ReferenceOrg) || shuffle.ArrayContains(app.Contributors, user.Id) { if user.Id == app.Owner || (user.Role == "admin" && user.ActiveOrg.Id == app.ReferenceOrg) || shuffle.ArrayContains(app.Contributors, user.Id) {
log.Printf("[DEBUG] Editing app %s with user %s (%s) in org %s", test.Id, user.Username, user.Id, user.ActiveOrg.Id) log.Printf("[DEBUG] Editing app %s with user %s (%s) in org %s", test.Id, user.Username, user.Id, user.ActiveOrg.Id)
} else { } else {
log.Printf("[WARNING] Wrong user (%s) for app %s when verifying swagger", user.Username, app.Name) log.Printf("[WARNING] Wrong user (%s) for app %s when verifying swagger", user.Username, app.Name)
@@ -3375,7 +3383,6 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
} }
} }
log.Printf("[DEBUG] Successfully built app %s (%s)", api.Name, api.ID) log.Printf("[DEBUG] Successfully built app %s (%s)", api.Name, api.ID)
if len(user.Id) > 0 { if len(user.Id) > 0 {
resp.WriteHeader(200) resp.WriteHeader(200)
@@ -3799,30 +3806,56 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error {
} }
} }
if org.SyncConfig.WorkflowBackup { // Check if it's 1/20 times (600 seconds - 10 min on average)
workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "") // Only problem: May take time to sync the first time, which is annoying
if err != nil { shouldBackupData := false
log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err) randomNumber := rand.Intn(20)
} else { if randomNumber == 0 {
backupJob.Workflows = workflows shouldBackupData = true
}
} }
if org.SyncConfig.AppBackup && len(org.Users) > 0 { // Just to prevent it from spamming large outbound requests
if shouldBackupData {
apps, err := shuffle.GetPrioritizedApps(ctx, foundUser) if org.SyncConfig.WorkflowBackup {
if err != nil { workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "")
log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err) if err != nil {
} else { log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err)
backupJob.Apps = apps } else {
backupJob.Workflows = workflows
}
} }
}
info, err := shuffle.GetOrgStatistics(ctx, org.Id) if org.SyncConfig.AppBackup && len(org.Users) > 0 {
if err != nil { foundUser.ActiveOrg.Id = org.Id
log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err) apps, err := shuffle.GetPrioritizedApps(ctx, foundUser)
} else { if err != nil {
backupJob.Stats = *info log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err)
} else {
parsedApps := []shuffle.WorkflowApp{}
for _, app := range apps {
if len(app.Actions) == 0 {
continue
}
if !app.Generated {
continue
}
parsedApps = append(parsedApps, app)
}
backupJob.Apps = parsedApps
}
}
// Send stats once every 10 times or so..?
// For now, just send every time
info, err := shuffle.GetOrgStatistics(ctx, org.Id)
if err != nil {
log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err)
} else {
backupJob.Stats = *info
}
} }
backupJobData, err := json.Marshal(backupJob) backupJobData, err := json.Marshal(backupJob)
@@ -3859,6 +3892,7 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error {
//log.Printf("[ERROR] Failed cloud sync job controller run for '%s': %s", respBody, err) //log.Printf("[ERROR] Failed cloud sync job controller run for '%s': %s", respBody, err)
return err return err
} }
return nil return nil
} }
@@ -3996,6 +4030,8 @@ func runInitEs(ctx context.Context) {
time.Sleep(30 * time.Second) time.Sleep(30 * time.Second)
} }
// FIXME: This should ONLY run on one backend instance
schedules, err := shuffle.GetAllSchedules(ctx, "ALL") schedules, err := shuffle.GetAllSchedules(ctx, "ALL")
if err != nil { if err != nil {
log.Printf("[WARNING] Failed getting schedules during service init: %s", err) log.Printf("[WARNING] Failed getting schedules during service init: %s", err)
@@ -4139,7 +4175,7 @@ func runInitEs(ctx context.Context) {
} }
//interval := int(org.SyncConfig.Interval) //interval := int(org.SyncConfig.Interval)
interval := 15 interval := 30
if interval == 0 { if interval == 0 {
log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id) log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id)
continue continue
@@ -4241,17 +4277,20 @@ func runInitEs(ctx context.Context) {
continue continue
} }
if newresp.StatusCode != 200 {
log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d", environment, newresp.StatusCode) respBody, err := ioutil.ReadAll(newresp.Body)
if err != nil {
log.Printf("[ERROR] Failed setting respbody %s for execution stop. Status: %d", err, newresp.StatusCode)
continue continue
} }
//respBody, err := ioutil.ReadAll(newresp.Body) if newresp.StatusCode != 200 {
//if err != nil { if !strings.Contains(string(respBody), "is active") {
// log.Printf("[ERROR] Failed setting respbody %s", err) log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d. Body: %s", environment, newresp.StatusCode, string(respBody))
// continue }
//}
//log.Printf("[DEBUG] Successfully ran workflow cleanup request for %s. Body: %s", environment, string(respBody)) continue
}
url = fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/rerun", backendPort, environment) url = fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/rerun", backendPort, environment)
req, err = http.NewRequest( req, err = http.NewRequest(
@@ -4669,7 +4708,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
// If you want to disable cloud sync, see previous section. // If you want to disable cloud sync, see previous section.
if org.CloudSync { if org.CloudSync {
log.Printf("[WARNING] Org %s is already syncing. Skip", org.Id) log.Printf("[WARNING] Org %s is already syncing. Skip", org.Id)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Your org is already syncing. Nothing to set up."}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Your org is already syncing. Nothing to set up."}`)))
return return
} }
@@ -4746,6 +4785,9 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
org.SyncConfig = shuffle.SyncConfig{ org.SyncConfig = shuffle.SyncConfig{
Apikey: responseData.SessionKey, Apikey: responseData.SessionKey,
Interval: responseData.IntervalSeconds, Interval: responseData.IntervalSeconds,
WorkflowBackup: true,
AppBackup: true,
} }
interval := int(responseData.IntervalSeconds) interval := int(responseData.IntervalSeconds)
+12 -7
View File
@@ -1905,8 +1905,8 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
err = shuffle.SetSchedule(ctx, newSchedule) err = shuffle.SetSchedule(ctx, newSchedule)
if err != nil { if err != nil {
log.Printf("Failed setting cloud schedule: %s", err) log.Printf("[ERROR] Failed setting cloud schedule: %s", err)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return return
} }
@@ -1941,17 +1941,22 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
// FIXME - real error message lol // FIXME - real error message lol
if err != nil { if err != nil {
log.Printf("Failed creating schedule: %s", err) log.Printf("[ERROR] Failed creating schedule: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid argument. Try cron */15 * * * *"}`))) resp.WriteHeader(400)
if schedule.Environment == "cloud" {
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid argument. For cloud schedules, try cron */15 * * * *"}`)))
} else {
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid argument. For onprem schedules, try 60 for 60 seconds"}`)))
}
return return
} }
//workflow.Schedules = append(workflow.Schedules, schedule) //workflow.Schedules = append(workflow.Schedules, schedule)
err = shuffle.SetWorkflow(ctx, *workflow, workflow.ID) err = shuffle.SetWorkflow(ctx, *workflow, workflow.ID)
if err != nil { if err != nil {
log.Printf("Failed setting workflow for schedule: %s", err) log.Printf("[ERROR] Failed setting workflow for schedule: %s", err)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
} }
+1 -1
View File
@@ -468,7 +468,7 @@ const App = (message, props) => {
<Route <Route
exact exact
path="/AdminSetup" path="/adminsetup"
element={ element={
<AdminSetup <AdminSetup
isLoaded={isLoaded} isLoaded={isLoaded}
+9 -1
View File
@@ -208,9 +208,17 @@ const AdminNavBar = (props) => {
const ComponentToRender = selectedItemData.component; const ComponentToRender = selectedItemData.component;
const componentProps = selectedItemData.props; const componentProps = selectedItemData.props;
return <ComponentToRender {...componentProps} />; const updatedProps = {
...componentProps,
notifications: notifications,
setNotifications: setNotifications,
userdata: userdata,
selectedOrganization: selectedOrganization
}; };
return <ComponentToRender {...updatedProps} />;
};
const defaultImage = "/images/logos/orange_logo.svg" const defaultImage = "/images/logos/orange_logo.svg"
const imageData = const imageData =
selectedOrganization?.image === undefined || selectedOrganization?.image.length === 0 selectedOrganization?.image === undefined || selectedOrganization?.image.length === 0
+42 -1
View File
@@ -66,7 +66,7 @@ import { Context } from '../context/ContextApi.jsx';
const searchClient = algoliasearch( const searchClient = algoliasearch(
"JNSS5CFDZZ", "JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240" "c8f882473ff42d41158430be09ec2b4e"
) )
const AppAuthTab = memo((props) => { const AppAuthTab = memo((props) => {
@@ -1181,6 +1181,47 @@ const AppAuthTab = memo((props) => {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}
<Tooltip
title={"Copy Auth ID"}
style={{}}
aria-label={"copy"}
>
<IconButton
style = {{padding: "6px"}}
onClick={() => {
navigator.clipboard.writeText(data.id);
document.execCommand("copy");
toast(data.id + " copied to clipboard");
}}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="24"
height="24"
fillOpacity="1"
/>
<path
d="M14 4H7.6C7.17565 4 6.76869 4.16857 6.46863 4.46863C6.16857 4.76869 6 5.17565 6 5.6V18.4C6 18.8243 6.16857 19.2313 6.46863 19.5314C6.76869 19.8314 7.17565 20 7.6 20H17.2C17.6243 20 18.0313 19.8314 18.3314 19.5314C18.6314 19.2313 18.8 18.8243 18.8 18.4V8.8L14 4Z"
stroke={themeMode === "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14 4V8.8H18.8"
stroke={themeMode === "dark" ? "#F1F1F1" : "#333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</IconButton>
</Tooltip>
<IconButton <IconButton
style={{ }} style={{ }}
disabled={data.org_id !== selectedOrganization.id} disabled={data.org_id !== selectedOrganization.id}
+1 -1
View File
@@ -53,7 +53,7 @@ import {
const searchClient = algoliasearch( const searchClient = algoliasearch(
"JNSS5CFDZZ", "JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240" "c8f882473ff42d41158430be09ec2b4e"
); );
//const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6")
+1 -1
View File
@@ -35,7 +35,7 @@ import { Context } from '../context/ContextApi.jsx';
const searchClient = algoliasearch( const searchClient = algoliasearch(
"JNSS5CFDZZ", "JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240" "c8f882473ff42d41158430be09ec2b4e"
);; );;
const AppModal = ({ open, onClose, app, globalUrl, getApps}) => { const AppModal = ({ open, onClose, app, globalUrl, getApps}) => {
+1 -1
View File
@@ -13,7 +13,7 @@ import {
InputAdornment, InputAdornment,
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const Appsearch = props => { const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp, placeholder, const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp, placeholder,
+1 -1
View File
@@ -20,7 +20,7 @@ import {
} from '@mui/material'; } from '@mui/material';
import aa from 'search-insights' import aa from 'search-insights'
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const Appsearch = props => { const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props
const { themeMode } = useContext(Context) const { themeMode } = useContext(Context)
+76 -51
View File
@@ -52,11 +52,13 @@ import {
//import { useAlert //import { useAlert
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
import BillingStats from "./BillingStats.jsx"; import BillingStats from "../components/BillingStats.jsx";
import LicencePopup from "../components/LicencePopup.jsx";
import { handlePayasyougo } from "../views/HandlePaymentNew.jsx" import { handlePayasyougo } from "../views/HandlePaymentNew.jsx"
import DeleteIcon from '@mui/icons-material/Delete';
import { Context } from "../context/ContextApi.jsx"; import { Context } from "../context/ContextApi.jsx";
import LicencePopup from "./LicencePopup.jsx";
import DeleteIcon from '@mui/icons-material/Delete';
import { DataGrid } from "@mui/x-data-grid"; import { DataGrid } from "@mui/x-data-grid";
const Billing = memo((props) => { const Billing = memo((props) => {
@@ -2597,64 +2599,87 @@ const Billing = memo((props) => {
Utilization & Stats Utilization & Stats
</Typography> </Typography>
</div> </div>
{isChildOrg ? ( <span>
<BillingStats
isCloud={isCloud}
clickedFromOrgTab={clickedFromOrgTab}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
userdata={userdata}
/>
): (
<>
<Tabs <Tabs
value={currentTab} value={currentTab}
onChange={(event, newValue) => setCurrentTab(newValue)} onChange={(event, newValue) => {
setCurrentTab(-1)
// Force re-render
setTimeout(() => {
setCurrentTab(newValue)
}, 100);
}}
style={{ marginTop: 20 }} style={{ marginTop: 20 }}
TabIndicatorProps={{ TabIndicatorProps={{
style: { style: {
height: 3, height: 3,
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.main,
marginLeft: 12, marginLeft: 12,
marginRight: 12, marginRight: 12,
} }
}} }}
> >
<Tab <Tab
label="Parent Organization" label="Parent Organization"
style={{ textTransform: 'none', fontSize: 16, minWidth: 'auto', paddingLeft: 12, paddingRight: 12 }} style={{ textTransform: 'none',}}
/> value={0}
<Tab />
label="Child Organization"
style={{ textTransform: 'none', fontSize: 16, minWidth: 'auto', paddingLeft: 12, paddingRight: 12 }} {isCloud ?
/> <Tab
label="Cloud-Synced Stats"
style={{ textTransform: 'none', }}
value={1}
/>
: null}
<Tab
label="Child Organization Stats"
disabled={isChildOrg}
style={{ textTransform: 'none', }}
value={2}
/>
</Tabs> </Tabs>
{currentTab === 0 ? ( <div style={{paddingBottom: 200, minHeight: 750, }}>
<div style={{ marginTop: 30,}}> {currentTab === 0 ?
<BillingStats <div style={{ marginTop: 30,}}>
isCloud={isCloud} <BillingStats
clickedFromOrgTab={clickedFromOrgTab} isCloud={isCloud}
globalUrl={globalUrl} clickedFromOrgTab={clickedFromOrgTab}
selectedOrganization={selectedOrganization} globalUrl={globalUrl}
userdata={userdata} selectedOrganization={selectedOrganization}
/> userdata={userdata}
</div> />
): ( </div>
<BillingStatsChildOrg : currentTab === 1 ?
isCloud={isCloud} <div style={{ marginTop: 30,}}>
clickedFromOrgTab={clickedFromOrgTab} <BillingStats
globalUrl={globalUrl} isCloud={isCloud}
selectedOrganization={selectedOrganization} clickedFromOrgTab={clickedFromOrgTab}
userdata={userdata} globalUrl={globalUrl}
allChildOrgs={allChildOrgs} selectedOrganization={selectedOrganization}
setAllChildOrgs={setAllChildOrgs} userdata={userdata}
allChildOrgsStats={allChildOrgsStats}
setAllChildOrgsStats={setAllChildOrgsStats} syncStats={true}
/> />
)} </div>
</> :
)} <BillingStatsChildOrg
isCloud={isCloud}
clickedFromOrgTab={clickedFromOrgTab}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
userdata={userdata}
allChildOrgs={allChildOrgs}
setAllChildOrgs={setAllChildOrgs}
allChildOrgsStats={allChildOrgsStats}
setAllChildOrgsStats={setAllChildOrgsStats}
/>
}
</div>
</span>
</div> </div>
</Wrapper> </Wrapper>
) )
+179 -102
View File
@@ -33,8 +33,15 @@ import {
import { import {
BarChart, BarChart,
BarSeries,
Bar,
BarLabel,
GridlineSeries, GridlineSeries,
Gridline, Gridline,
TooltipArea,
ChartTooltip,
TooltipTemplate,
} from 'reaviz'; } from 'reaviz';
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
@@ -46,30 +53,51 @@ const LineChartWrapper = ({keys, inputname, height, width}) => {
const {themeMode} = useContext(Context) const {themeMode} = useContext(Context)
const theme = getTheme(themeMode) const theme = getTheme(themeMode)
return ( return (
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, padding: 30, marginTop: 15, backgroundColor: theme.palette.platformColor, overflow: "hidden", }}> <div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, padding: 30, marginTop: 15, backgroundColor: theme.palette.platformColor, overflow: "hidden", }}>
<Typography variant="h6" style={{marginBotton: 15, }}> <Typography variant="h6" style={{marginBotton: 30, }}>
{inputname} {inputname}
</Typography> </Typography>
<BarChart <BarChart
style={{marginTop: 100, }}
width={"100%"} width={"100%"}
height={height} height={height}
data={inputdata} data={inputdata}
series={
<BarSeries
bar={
<Bar />
}
/>
}
gridlines={ gridlines={
<GridlineSeries line={<Gridline direction="all" />} /> <GridlineSeries line={<Gridline direction="all" />} />
} }
/> />
</div> </div>
) )
} }
const AppStats = (defaultprops) => { const AppStats = (defaultprops) => {
const { globalUrl, selectedOrganization, userdata, isCloud, inputWorkflows,clickedFromOrgTab } = defaultprops; const {
globalUrl,
selectedOrganization,
userdata,
isCloud,
inputWorkflows,
clickedFromOrgTab,
syncStats,
} = defaultprops;
const [keys, setKeys] = useState([]) const [keys, setKeys] = useState([])
const [searches, setSearches] = useState([]); const [searches, setSearches] = useState([]);
const [appRuns, setAppruns] = useState(undefined); const [appRuns, setAppruns] = useState(undefined);
const [childOrgsAppRuns, setChildOrgsAppRuns] = useState(undefined);
const [appRunCosts, setApprunCosts] = useState(undefined); const [appRunCosts, setApprunCosts] = useState(undefined);
const [workflowRuns, setWorkflowRuns] = useState(undefined); const [workflowRuns, setWorkflowRuns] = useState(undefined);
const [subflowRuns, setSubflowRuns] = useState(undefined); const [subflowRuns, setSubflowRuns] = useState(undefined);
@@ -99,9 +127,6 @@ const AppStats = (defaultprops) => {
const getWorkflowStats = async (workflow, startTime, endTime) => { const getWorkflowStats = async (workflow, startTime, endTime) => {
if (!userdata.support) {
return workflow
}
if (workflow.id === undefined || workflow.id === null || workflow.id === "") { if (workflow.id === undefined || workflow.id === null || workflow.id === "") {
return workflow return workflow
@@ -166,12 +191,8 @@ const AppStats = (defaultprops) => {
} }
const loadWorkflowStats = (foundWorkflows, startTime, endTime) => { const loadWorkflowStats = (foundWorkflows, startTime, endTime) => {
if (!userdata.support) {
return
}
if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) { if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) {
console.log("Not workflows") setResultLoading(false)
return return
} }
@@ -180,6 +201,9 @@ const AppStats = (defaultprops) => {
const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime)); const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime));
const allData = Promise.all(promises); const allData = Promise.all(promises);
if (allData === undefined || allData === null) {
setResultLoading(false)
}
allData.then((data) => { allData.then((data) => {
var total = 0 var total = 0
@@ -239,15 +263,16 @@ const AppStats = (defaultprops) => {
return return
} }
if (statistics["daily_statistics"] === undefined || statistics["daily_statistics"] === null) { const statKey = syncStats === true ? "onprem_stats" : "daily_statistics"
if (statistics[statKey] === undefined || statistics[statKey] === null) {
setFilteredStatistics(statistics) setFilteredStatistics(statistics)
return return
} }
// Calculate month to date cost // Calculate month to date cost
var mtd_cost = 0 var mtd_cost = 0
for (let key in statistics["daily_statistics"]) { for (let key in statistics[statKey]) {
const item = statistics["daily_statistics"][key] const item = statistics[statKey][key]
if (item["date"] === undefined) { if (item["date"] === undefined) {
continue continue
} }
@@ -305,8 +330,8 @@ const AppStats = (defaultprops) => {
// Check if start time is before the daily statistics["date"] string // Check if start time is before the daily statistics["date"] string
var newlist = [] var newlist = []
for (let key in statistics["daily_statistics"]) { for (let key in statistics[statKey]) {
const item = statistics["daily_statistics"][key] const item = statistics[statKey][key]
if (item["date"] === undefined) { if (item["date"] === undefined) {
continue continue
} }
@@ -337,7 +362,7 @@ const AppStats = (defaultprops) => {
var appexecutions = 0 var appexecutions = 0
var estimatedcost = 0 var estimatedcost = 0
if (newlist.length > 0) { if (newlist.length > 0) {
tmpstats["daily_statistics"] = newlist tmpstats[statKey] = newlist
for (let key in newlist) { for (let key in newlist) {
const item = newlist[key] const item = newlist[key]
@@ -391,7 +416,8 @@ const AppStats = (defaultprops) => {
return return
} }
const dailyStats = inputdata.daily_statistics const statKey = syncStats === true ? "onprem_stats" : "daily_statistics"
const dailyStats = inputdata[statKey]
if (dailyStats === undefined || dailyStats === null) { if (dailyStats === undefined || dailyStats === null) {
return return
} }
@@ -401,6 +427,11 @@ const AppStats = (defaultprops) => {
"data": [] "data": []
} }
var childorgappRuns = {
"key": "Child Org App Runs",
"data": []
}
var workflowRuns = { var workflowRuns = {
"key": "Workflow Runs (includes subflows)", "key": "Workflow Runs (includes subflows)",
"data": [] "data": []
@@ -442,6 +473,13 @@ const AppStats = (defaultprops) => {
}) })
} }
if (item["child_app_executions"] !== undefined && item["child_app_executions"] !== null) {
childorgappRuns["data"].push({
key: new Date(item["date"]),
data: inputdata["child_app_executions"]
})
}
// Check if workflow_executions key in item // Check if workflow_executions key in item
if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) { if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) {
workflowRuns["data"].push({ workflowRuns["data"].push({
@@ -471,6 +509,15 @@ const AppStats = (defaultprops) => {
}) })
} }
if (inputdata["daily_child_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) {
childorgappRuns["data"].push({
key: new Date(),
data: inputdata["daily_child_app_executions"]
})
//setApprunCosts(appcostRuns)
}
if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) { if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) {
workflowRuns["data"].push({ workflowRuns["data"].push({
key: new Date(), key: new Date(),
@@ -485,6 +532,11 @@ const AppStats = (defaultprops) => {
}) })
} }
// Only for parent orgs
if (childorgappRuns["data"].length > 0) {
setChildOrgsAppRuns(childorgappRuns)
}
setSubflowRuns(subflowRuns) setSubflowRuns(subflowRuns)
setWorkflowRuns(workflowRuns) setWorkflowRuns(workflowRuns)
setAppruns(appRuns) setAppruns(appRuns)
@@ -659,44 +711,57 @@ const AppStats = (defaultprops) => {
style={{ textDecoration: "none", color: theme.palette.linkColor,}} style={{ textDecoration: "none", color: theme.palette.linkColor,}}
>Your Organisation Statistics. </a> >Your Organisation Statistics. </a>
It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. <b>The billing tracker is in Beta, and is always calculated manually before being invoiced.</b> It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. <b>The billing tracker is in Beta, and is always calculated manually before being invoiced.</b>
<br style={{}}/>
{syncStats !== true ? null :
"PS: You are currently looking at data from your onprem synced org"}
</Typography> </Typography>
<div style={{display: "flex", flexDirection: "column", textAlign: "center",}}> <div style={{display: "flex", flexDirection: "column", textAlign: "center",}}>
<div style={{flexDirection: "row", }}> <div style={{flexDirection: "row", }}>
{filteredStatistics !== undefined ? {filteredStatistics !== undefined ?
<div style={{flex: 1, display: "flex", textAlign: "center",}}> <div style={{flex: 1, display: "flex", textAlign: "center",}}>
<Tooltip title={
<Typography variant="body1" style={{padding: 10, }}> {syncStats == true ? null :
The cost of app runs in the selected period based on {filteredStatistics.monthly_app_executions} App Runs. These numbers do not exclude your included 10.000/month or {includedExecutions} App Runs per month. App Run cost: ${invocationCost}. <Tooltip title={
</Typography> <Typography variant="body1" style={{padding: 10, }}>
}> The cost of app runs in the selected period based on {filteredStatistics.monthly_app_executions} App Runs. These numbers do not exclude your included 10.000/month or {includedExecutions} App Runs per month. App Run cost: ${invocationCost}.
<Box sx={paperStyle}>
<Typography variant="h4">
${selectedOrganization?.lead_info?.customer === false && selectedOrganization?.lead_info?.pov === false ?
0
:
apprunCost
}
</Typography> </Typography>
<Typography variant="h6"> }>
Period Cost <Box sx={paperStyle}>
</Typography> <Typography variant="h4">
</Box> ${selectedOrganization?.lead_info?.customer === false && selectedOrganization?.lead_info?.pov === false ?
</Tooltip> 0
:
apprunCost
}
</Typography>
<Typography variant="h6">
Period Cost
</Typography>
</Box>
</Tooltip>
}
{syncStats === true ? null :
<Tooltip title={ <Tooltip title={
<Typography variant="body1" style={{padding: 10, }}> <Typography variant="body1" style={{padding: 10, }}>
App runs in the selected period App runs in the selected period
</Typography> </Typography>
}> }>
<Box sx={paperStyle}> <Box sx={paperStyle}>
<Typography variant="h4"> <Typography variant="h4">
{filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions} {filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions}
</Typography> </Typography>
<Typography variant="h6"> <Typography variant="h6">
App Runs App Runs
</Typography> </Typography>
</Box> </Box>
</Tooltip> </Tooltip>
}
{syncStats === true ? null :
<Tooltip title={ <Tooltip title={
<Typography variant="body1" style={{padding: 10, }}> <Typography variant="body1" style={{padding: 10, }}>
Workflow runs in the selected period Workflow runs in the selected period
@@ -711,20 +776,24 @@ const AppStats = (defaultprops) => {
</Typography> </Typography>
</Box> </Box>
</Tooltip> </Tooltip>
<Tooltip title={ }
<Typography variant="body1" style={{padding: 10, }}>
Estimated cost to be billed at the end of the current month. Subtracted contractually included app runs. Actual cost month to date: ${monthToDateCost}. App Run cost: ${invocationCost}. {syncStats === true ? null :
</Typography> <Tooltip title={
}> <Typography variant="body1" style={{padding: 10, }}>
<Box sx={paperStyle}> Estimated cost to be billed at the end of the current month. Subtracted contractually included app runs. Actual cost month to date: ${monthToDateCost}. App Run cost: ${invocationCost}.
<Typography variant="h4">
${monthTotalCost}
</Typography> </Typography>
<Typography variant="h6"> }>
Estimated cost <Box sx={paperStyle}>
</Typography> <Typography variant="h4">
</Box> ${monthTotalCost}
</Tooltip> </Typography>
<Typography variant="h6">
Estimated cost
</Typography>
</Box>
</Tooltip>
}
</div> </div>
: null} : null}
</div> </div>
@@ -895,7 +964,13 @@ const AppStats = (defaultprops) => {
{appRuns === undefined ? {appRuns === undefined ?
null null
: :
<LineChartWrapper keys={appRuns} height={300} width={"100%"} inputname={"Daily App Runs"}/> <LineChartWrapper keys={appRuns} height={300} width={"100%"} inputname={"App Runs - Current Org"}/>
}
{childOrgsAppRuns === undefined ?
null
:
<LineChartWrapper keys={childOrgsAppRuns} height={300} width={"100%"} inputname={"Child Org App Runs"}/>
} }
{workflowRuns === undefined ? {workflowRuns === undefined ?
@@ -916,56 +991,58 @@ const AppStats = (defaultprops) => {
<LineChartWrapper keys={appRunCosts} height={300} width={"100%"} inputname={"Apprun cost - Cost per day"}/> <LineChartWrapper keys={appRunCosts} height={300} width={"100%"} inputname={"Apprun cost - Cost per day"}/>
*/} */}
{syncStats === true ? null :
<div style={{height: 150+resultRows.length * 25, padding: "10px 0px 10px 0px", }}>
{resultLoading ?
<div style={{margin: "auto", alignItems: "center", width: 350, height: "100%", }}>
<Typography variant="body2" color="textSecondary" component="p" style={{textAlign: "center", marginTop: 50, marginBottom: 15, }}>
Loading usage for selected period (may take a while)
<div style={{height: 150+resultRows.length * 25, padding: "10px 0px 10px 0px", }}> <CircularProgress style={{marginTop: 15, }} />
{resultLoading ? </Typography>
<div style={{margin: "auto", alignItems: "center", width: 350, height: "100%", }}> </div>
<Typography variant="body2" color="textSecondary" component="p" style={{textAlign: "center", marginTop: 50, marginBottom: 15, }}> :
Loading usage for selected period (may take a while) <DataGrid
</Typography> rows={resultRows}
<CircularProgress style={{}} /> columns={columns}
</div> pageSize={100}
: rowsPerPageOptions={[10, 20, 50, 100]}
<DataGrid checkboxSelection
rows={resultRows} disableSelectionOnClick
columns={columns} onPageSizeChange={(newPageSize) => {
pageSize={100} //setRowsPerPage(newPageSize)
rowsPerPageOptions={[10, 20, 50, 100]} //submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize)
checkboxSelection }}
disableSelectionOnClick // event for when clicking next page
onPageSizeChange={(newPageSize) => { // Hide page changer
//setRowsPerPage(newPageSize) onPageChange={(params) => {
//submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize) console.log("page params: ", params)
}} }}
// event for when clicking next page onSelectionModelChange={(newSelection) => {
// Hide page changer console.log("newSelection: ", newSelection)
onPageChange={(params) => { //console.log("newSelection: ", newSelection)
console.log("page params: ", params) //setSelectedWorkflowExecutionsIndexes(newSelection)
}} //var found = []
onSelectionModelChange={(newSelection) => { //for (var i = 0; i < newSelection.length; i++) {
console.log("newSelection: ", newSelection) // // Find the workflow in the resultRows
//console.log("newSelection: ", newSelection) // var selected = resultRows.find((workflow) => {
//setSelectedWorkflowExecutionsIndexes(newSelection) // return workflow.id === newSelection[i]
//var found = [] // })
//for (var i = 0; i < newSelection.length; i++) {
// // Find the workflow in the resultRows
// var selected = resultRows.find((workflow) => {
// return workflow.id === newSelection[i]
// })
// if (selected === undefined || selected === null) { // if (selected === undefined || selected === null) {
// continue // continue
// } // }
// found.push(selected) // found.push(selected)
//} //}
//setSelectedWorkflowExecutions(found) //setSelectedWorkflowExecutions(found)
}} }}
// Track which items are selected // Track which items are selected
/> />
} }
</div> </div>
}
</div> </div>
) )
+2 -2
View File
@@ -39,7 +39,7 @@ const Branding = (props) => {
const theme = getTheme(themeMode, brandColor) const theme = getTheme(themeMode, brandColor)
const [selectedBrandColor, setSelectedBrandColor] = useState(theme?.palette?.main || "#FF8544") const [selectedBrandColor, setSelectedBrandColor] = useState(theme?.palette?.main || "#FF8544")
const [selectedBrandName, setSelectedBrandName] = useState(selectedOrganization?.branding?.brand_name || "") const [selectedBrandName, setSelectedBrandName] = useState(selectedOrganization?.branding?.brand_name || "")
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const [isLoading,setIsLoading] = useState(false); const [isLoading,setIsLoading] = useState(false);
const handleEditOrg = (joinStatus) => { const handleEditOrg = (joinStatus) => {
@@ -391,7 +391,7 @@ const Branding = (props) => {
</div> </div>
{ integrationPartner ? ( {integrationPartner ? (
<> <>
<Divider style={{marginTop: 50, marginBottom: 50, color: theme.palette.defaultBorder}} /> <Divider style={{marginTop: 50, marginBottom: 50, color: theme.palette.defaultBorder}} />
<Typography style={{fontSize: 24, fontWeight: "bold"}}> <Typography style={{fontSize: 24, fontWeight: "bold"}}>
+62 -2
View File
@@ -867,7 +867,7 @@ const CacheView = memo((props) => {
overflowX: "auto", overflowX: "auto",
}}> }}>
<ListItem style={{width: isSelectedDataStore?"100%":null, borderBottom:isSelectedDataStore? theme.palette.defaultBorder :null, display: "table-row"}}> <ListItem style={{width: isSelectedDataStore?"100%":null, borderBottom:isSelectedDataStore? theme.palette.defaultBorder :null, display: "table-row"}}>
{["Key", "Value", "Actions", "Updated", "Distribution"].map((header, index) => ( {["Key", "Value", "workflow", "Actions", "Updated", "Distribution"].map((header, index) => (
<ListItemText <ListItemText
key={index} key={index}
primary={header} primary={header}
@@ -891,7 +891,7 @@ const CacheView = memo((props) => {
backgroundColor: theme.palette.platformColor, backgroundColor: theme.palette.platformColor,
}} }}
> >
{Array(5) {Array(6)
.fill() .fill()
.map((_, colIndex) => ( .map((_, colIndex) => (
<ListItemText <ListItemText
@@ -1001,6 +1001,66 @@ const CacheView = memo((props) => {
data.value data.value
} }
/> />
<ListItemText
primary={
data.workflow_id === "" || data.workflow_id === null || data.workflow_id === undefined ?
<IconButton
disabled={data.workflow_id?.length === 0}
style={{marginLeft: 10}}
>
<OpenInNewIcon
style={{
color:
data.workflow_id?.length !== 0
? "#FF8444"
: "grey",
}}
/>
</IconButton>
: (
<Tooltip
title={"Go to workflow"}
style={{}}
aria-label={"Download"}
>
<span>
<a
rel="noopener noreferrer"
style={{
textDecoration: "none",
color: "#f85a3e",
}}
href={`/workflows/${data.workflow_id}`}
target="_blank"
>
<IconButton
disabled={data.workflow_id?.length ===0}
style={{marginLeft: 10}}
>
<OpenInNewIcon
style={{
width: 24, height: 24,
color:
data.workflow_id?.length !== 0
? "#FF8444"
: "grey",
}}
/>
</IconButton>
</a>
</span>
</Tooltip>
)
}
style={{
display: "table-cell",
overflow: "hidden",
verticalAlign: "middle",
padding: "8px 8px 8px 15px",
maxWidth: 200,
overflowX: "auto",
}}
/>
<ListItemText <ListItemText
style={{ style={{
display: "table-cell", display: "table-cell",
+23 -6
View File
@@ -48,15 +48,21 @@ const CloudSyncTab = (props) => {
const [, forceUpdate] = React.useState(); const [, forceUpdate] = React.useState();
const itemColor = "white"; const itemColor = "white";
const isCloud = window?.location?.host === "localhost:3002" || window?.location?.host === "shuffler.io"; const isCloud = window?.location?.host === "localhost:3002" || window?.location?.host === "shuffler.io";
const { themeMode, brandColor } = useContext(Context); const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor); const theme = getTheme(themeMode, brandColor);
useEffect(() => { getSettings(); }, []); useEffect(() => { getSettings(); }, []);
const GridItem = (props) => { const GridItem = (props) => {
const [expanded, setExpanded] = React.useState(false); const [expanded, setExpanded] = React.useState(false);
const [showEdit, setShowEdit] = React.useState(false); const [showEdit, setShowEdit] = React.useState(false);
const [newValue, setNewValue] = React.useState(-100); const [newValue, setNewValue] = React.useState(-100);
const primary = props.data.primary; var primary = props.data.primary
const shownName = props.data.newname !== undefined && props.data.newname !== null && props.data.newname !== primary ? props.data.newname : primary
const secondary = props.data.secondary; const secondary = props.data.secondary;
const primaryIcon = props.data.icon; const primaryIcon = props.data.icon;
const secondaryIcon = props.data.active ? const secondaryIcon = props.data.active ?
@@ -191,7 +197,7 @@ const CloudSyncTab = (props) => {
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
style={{ textTransform: "capitalize", color: theme.palette.text.primary, fontSize: 14, fontWeight: 400, }} style={{ textTransform: "capitalize", color: theme.palette.text.primary, fontSize: 14, fontWeight: 400, }}
primary={primary} primary={shownName}
/> />
{isCloud && userdata.support === true ? {isCloud && userdata.support === true ?
<Tooltip title="Edit features (support users only)"> <Tooltip title="Edit features (support users only)">
@@ -477,7 +483,7 @@ const CloudSyncTab = (props) => {
} else { } else {
toast("Cloud Syncronization successfully set up!"); toast("Cloud Syncronization successfully set up!");
setOrgSyncResponse( setOrgSyncResponse(
"Successfully started syncronization. Cloud features you now have access to can be seen below." "Successfully started syncronization. Cloud/Hybrid features are available below."
); );
} }
@@ -535,8 +541,8 @@ const CloudSyncTab = (props) => {
Cloud syncronization Cloud syncronization
</Typography> </Typography>
<Typography variant="body2" style={{ color: theme.palette.text.secondary, fontSize: 16, fontWeight: 400, }}> <Typography variant="body2" style={{ color: theme.palette.text.secondary, fontSize: 16, fontWeight: 400, }}>
What does <a href="/docs/organizations#cloud_sync" target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.linkColor, fontSize: 16, textDecoration: 'none', }}>cloud sync</a> do? Cloud synchronization is a way of getting more out of Shuffle. Shuffle will ALWAYS make every option open source, but features relying on other users can't be done without a collaborative approach. What does <a href="/docs/organizations#cloud_sync" target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.linkColor, fontSize: 16, textDecoration: 'none', }}>cloud sync</a> do? Cloud synchronization is a way of getting more out of Shuffle. Shuffle will ALWAYS make every option open source, but features relying on other users can't be done without a collaborative approach. This will by default back up apps and workflows.
</Typography> </Typography>
</div> </div>
{isCloud ? ( {isCloud ? (
@@ -708,7 +714,7 @@ const CloudSyncTab = (props) => {
)} )}
<Typography variant="h5" style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}> <Typography variant="h5" style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}>
Features {isCloud ? "Cloud" : "Hybrid"} Features
</Typography> </Typography>
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 24, fontSize: 16, fontWeight: 400, marginLeft: 5, color: theme.palette.text.secondary }}> <Typography variant="body2" color="textSecondary" style={{ marginBottom: 24, fontSize: 16, fontWeight: 400, marginLeft: 5, color: theme.palette.text.secondary }}>
Features and Limitations that are currently available to you in your Cloud or Hybrid Organization. App Executions (App Runs) reset monthly. If the organization is a customer or in a trial, these features limitations are not always enforced. </Typography> Features and Limitations that are currently available to you in your Cloud or Hybrid Organization. App Executions (App Runs) reset monthly. If the organization is a customer or in a trial, these features limitations are not always enforced. </Typography>
@@ -717,7 +723,9 @@ const CloudSyncTab = (props) => {
{selectedOrganization.sync_features === undefined || {selectedOrganization.sync_features === undefined ||
selectedOrganization.sync_features === null selectedOrganization.sync_features === null
? <Grid container spacing={2} justifyContent="center"> ? <Grid container spacing={2} justifyContent="center">
{[...Array(18)].map((_, i) => ( {[...Array(18)].map((_, i) => (
<Grid item xs={12} sm={6} md={4} key={i}> <Grid item xs={12} sm={6} md={4} key={i}>
<div <div
style={{ style={{
@@ -756,6 +764,13 @@ const CloudSyncTab = (props) => {
} }
const newkey = key.replaceAll("_", " "); const newkey = key.replaceAll("_", " ");
// Rewrites to frontend names
var newname = newkey
if (newkey === "app executions") {
newname = "app runs"
}
const griditem = { const griditem = {
primary: newkey, primary: newkey,
secondary: secondary:
@@ -770,6 +785,8 @@ const CloudSyncTab = (props) => {
data_collection: "None", data_collection: "None",
active: item.active, active: item.active,
icon: <PolylineIcon style={{ color: "#1a1a1a" }} />, icon: <PolylineIcon style={{ color: "#1a1a1a" }} />,
newname: newname,
}; };
return ( return (
@@ -633,7 +633,7 @@ const ConfigureWorkflow = (props) => {
if (aa !== undefined) { if (aa !== undefined) {
aa('init', { aa('init', {
appId: "JNSS5CFDZZ", appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240", apiKey: "c8f882473ff42d41158430be09ec2b4e",
}) })
const timestamp = new Date().getTime() const timestamp = new Date().getTime()
+1 -1
View File
@@ -37,7 +37,7 @@ import {
AvatarGroup, AvatarGroup,
} from "@mui/material" } from "@mui/material"
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const CreatorGrid = props => { const CreatorGrid = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, isHeader } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, isHeader } = props
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
+1 -1
View File
@@ -29,7 +29,7 @@ import {
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const DocsGrid = props => { const DocsGrid = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, } = props
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
+4 -4
View File
@@ -188,7 +188,7 @@ const EditWorkflow = (props) => {
} }
const newWorkflow = isEditing === true ? false : true const newWorkflow = isEditing === true ? false : true
const priority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) const priority = userdata === undefined || userdata === null || userdata.priorities === null || userdata.priorities === undefined ? null : userdata?.priorities?.find(prio => prio.type === "usecase" && prio.active === true)
var upload = ""; var upload = "";
var total_count = 0 var total_count = 0
@@ -226,8 +226,8 @@ const EditWorkflow = (props) => {
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<div style={{ flex: 1, color: theme.palette.textColor }}> <div style={{ flex: 1, color: theme.palette.textColor }}>
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<Typography variant="h4" style={{ flex: 9, }}> <Typography variant="h4" style={{ flex: 9, marginTop: 25, }}>
{newWorkflow ? "New" : "Editing"} workflow {newWorkflow ? "New" : "Editing"} Workflow
</Typography> </Typography>
{newWorkflow === true ? null : {newWorkflow === true ? null :
@@ -393,7 +393,7 @@ const EditWorkflow = (props) => {
</Button> </Button>
</div> </div>
<DialogContent style={{ paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, paddingBottom: 200, paddingLeft: "50px" }}> <DialogContent style={{ paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, paddingBottom: 400, paddingLeft: 50, }}>
<div style={{ minWidth: newWorkflow ? 500 : 550, maxWidth: newWorkflow ? 450 : 500, }}> <div style={{ minWidth: newWorkflow ? 500 : 550, maxWidth: newWorkflow ? 450 : 500, }}>
<TextField <TextField
onChange={(event) => { onChange={(event) => {
+13 -11
View File
@@ -403,7 +403,6 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
const handleChangeTheme = (newTheme) => { const handleChangeTheme = (newTheme) => {
toast.info("Changing theme to " + newTheme + " - please wait!");
const data = { const data = {
"org_id": userdata?.active_org?.id, "org_id": userdata?.active_org?.id,
@@ -449,7 +448,10 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
}); });
}; };
const handleUpdateTheme = (newTheme) => { const handleUpdateTheme = (newTheme) => {
handleThemeChange(newTheme);
setCurrentSelectedTheme(newTheme)
const data = { const data = {
"user_id": userdata?.id, "user_id": userdata?.id,
@@ -470,14 +472,11 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
}).then((response) => }).then((response) =>
response.json().then((responseJson) => { response.json().then((responseJson) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
toast("Failed updating theme: ", responseJson.reason); toast("Failed saving your theme: ", responseJson.reason);
} else {
handleThemeChange(newTheme);
setCurrentSelectedTheme(newTheme);
} }
}) })
).catch((error) => { ).catch((error) => {
console.log("Error changing theme: ", error); console.log("Error saving your theme: ", error);
}); });
}; };
@@ -518,6 +517,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
console.error("Logout error:", error); console.error("Logout error:", error);
}); });
}; };
const avatarMenu = ( const avatarMenu = (
<span> <span>
<IconButton <IconButton
@@ -588,7 +588,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
} }
if (userdata?.org_status?.includes("integration_partner")){ if (userdata?.org_status?.includes("integration_partner")){
handleChangeTheme(newTheme); handleChangeTheme(newTheme);
}else { } else {
handleUpdateTheme(newTheme); handleUpdateTheme(newTheme);
} }
}} }}
@@ -646,7 +647,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
<Divider style={{ marginTop: 10, marginBottom: 10, }} /> <Divider style={{ marginTop: 10, marginBottom: 10, }} />
<Link to="/docs" style={hrefStyle}> <Link to={userdata && userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.branding?.documentation_link?.length > 0 ? userdata?.active_org?.branding?.documentation_link : "/docs" } target={userdata?.active_org?.branding?.documentation_link?.length > 0 && userdata?.org_status?.includes("integration_partner") ? "_blank" : "_self" } style={hrefStyle}>
<MenuItem <MenuItem
onClick={(event) => { onClick={(event) => {
handleClose(); handleClose();
@@ -670,7 +671,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
<Divider style={{ marginBottom: 10, }} /> <Divider style={{ marginBottom: 10, }} />
<Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}> <Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}>
Version: 2.0.2 Version: 2.1.0-rc1
</Typography> </Typography>
</Menu> </Menu>
</span> </span>
@@ -1632,7 +1633,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
<Button <Button
component={Link} component={Link}
to="/docs" to={userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.branding?.documentation_link?.length > 0 ? userdata?.active_org?.branding?.documentation_link : "/docs"}
target={userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.branding?.documentation_link?.length > 0 ? "_blank" : "_self"}
onClick={(event) => { onClick={(event) => {
setCurrentOpenTab("docs"); setCurrentOpenTab("docs");
localStorage.setItem("lastTabOpenByUser", "docs"); localStorage.setItem("lastTabOpenByUser", "docs");
@@ -1683,7 +1685,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
<Button <Button
component={Link} component={Link}
to={isCloud ? "/admin?admin_tab=billingstats" : "/admin?admin_tab=locations"} to={isCloud ? "/admin" : "/admin"}
onClick={(event) => { onClick={(event) => {
setCurrentOpenTab("admin"); setCurrentOpenTab("admin");
localStorage.setItem("lastTabOpenByUser", "admin"); localStorage.setItem("lastTabOpenByUser", "admin");
+1 -3
View File
@@ -1127,7 +1127,6 @@ const LicencePopup = (props) => {
color: "white", color: "white",
} }
console.log("Priceitem: ", shuffleVariant)
// const isLoggedInHandler = () => { // const isLoggedInHandler = () => {
// if (calculatedCost === payasyougo) { // if (calculatedCost === payasyougo) {
// handlePayasyougo(props.userdata) // handlePayasyougo(props.userdata)
@@ -1237,12 +1236,11 @@ const LicencePopup = (props) => {
}); });
}; };
console.log("Selected Organization: ", selectedOrganization.subscriptions)
return ( return (
<div> <div>
<Grid container spacing={2} style={{ flexDirection: "row", flexWrap: "nowrap", borderRadius: '16px', display: "flex"}}> <Grid container spacing={2} style={{ flexDirection: "row", flexWrap: "nowrap", borderRadius: '16px', display: "flex"}}>
<Grid item maxWidth={licensePopup ? 400 : 450}> <Grid item maxWidth={licensePopup ? 400 : 450}>
{selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0 ? {(selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0) && isCloud ?
<SubscriptionObject <SubscriptionObject
index={0} index={0}
globalUrl={globalUrl} globalUrl={globalUrl}
+28 -24
View File
@@ -1216,7 +1216,7 @@ const ParsedAction = (props) => {
selectedActionParameters[1].value = splitparsed[1] selectedActionParameters[1].value = splitparsed[1]
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 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.", { 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, autoClose: 10000,
}) })
@@ -1494,7 +1494,7 @@ const ParsedAction = (props) => {
// Helpertext for openapi fields // Helpertext for openapi fields
//if (helperText === "" && name === "body" && selectedApp.generated && selectedApp.activated) { //if (helperText === "" && name === "body" && selectedApp.generated && selectedApp.activated) {
// helperText = <span style={{color: "white", marginBottom: 5, marginleft: 5}}> // helperText = <span style={{color: theme.palette.text.primary, marginBottom: 5, marginleft: 5}}>
//} //}
return helperText return helperText
@@ -1563,7 +1563,7 @@ const ParsedAction = (props) => {
} }
return <Paper style={{ padding: 10, backgroundColor: theme.palette.surfaceColor, border: "1px solid red", }}> return <Paper style={{ padding: 10, backgroundColor: theme.palette.surfaceColor, border: "1px solid red", }}>
<Typography variant="body" style={{ color: "white", }}> <Typography variant="body" style={{ color: theme.palette.text.primary, }}>
<b>Tip:</b> {suggestionText} <b>Tip:</b> {suggestionText}
</Typography> </Typography>
</Paper> </Paper>
@@ -2606,7 +2606,7 @@ const ParsedAction = (props) => {
}} }}
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.text.primary,
height: "50px", height: "50px",
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
}} }}
@@ -2623,7 +2623,7 @@ const ParsedAction = (props) => {
key={data.Name} key={data.Name}
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.text.primary,
}} }}
value={data.Name} value={data.Name}
> >
@@ -2702,7 +2702,7 @@ const ParsedAction = (props) => {
}} }}
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.text.primary,
height: "50px", height: "50px",
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
}} }}
@@ -2710,7 +2710,7 @@ const ParsedAction = (props) => {
<MenuItem <MenuItem
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.text.primary,
}} }}
value="No selection" value="No selection"
> >
@@ -2721,7 +2721,7 @@ const ParsedAction = (props) => {
<MenuItem <MenuItem
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.text.primary,
}} }}
value={data.name} value={data.name}
> >
@@ -2986,7 +2986,7 @@ const ParsedAction = (props) => {
<div <div
style={{ style={{
marginTop: "10px", marginTop: "10px",
borderColor: "white", borderColor: theme.palette.text.primary,
borderWidth: "2px", borderWidth: "2px",
marginBottom: hideExtraTypes ? 50 : 200, marginBottom: hideExtraTypes ? 50 : 200,
}} }}
@@ -3182,7 +3182,7 @@ const ParsedAction = (props) => {
ListboxProps={{ ListboxProps={{
style: { style: {
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.text.primary,
}, },
}} }}
getOptionLabel={(option) => { getOptionLabel={(option) => {
@@ -4422,7 +4422,7 @@ const ParsedAction = (props) => {
}} }}
open={!!menuPosition} open={!!menuPosition}
style={{ style={{
color: "white", color: theme.palette.text.primary,
marginTop: 2, marginTop: 2,
maxHeight: 650, maxHeight: 650,
}} }}
@@ -4550,7 +4550,7 @@ const ParsedAction = (props) => {
} }
parentMenuOpen={!!menuPosition} parentMenuOpen={!!menuPosition}
style={{ style={{
color: "white", color: theme.palette.text.primary,
minWidth: 250, minWidth: 250,
maxWidth: 250, maxWidth: 250,
maxHeight: 50, maxHeight: 50,
@@ -4566,7 +4566,7 @@ const ParsedAction = (props) => {
key={innerdata.name} key={innerdata.name}
style={{ style={{
marginLeft: 15, marginLeft: 15,
color: "white", color: theme.palette.text.primary,
minWidth: 250, minWidth: 250,
maxWidth: 250, maxWidth: 250,
padding: 0, padding: 0,
@@ -4607,7 +4607,7 @@ const ParsedAction = (props) => {
<MenuItem <MenuItem
key={pathdata.name} key={pathdata.name}
style={{ style={{
color: "white", color: theme.palette.text.primary,
minWidth: 250, minWidth: 250,
maxWidth: 250, maxWidth: 250,
padding: boxPadding, padding: boxPadding,
@@ -4664,7 +4664,7 @@ const ParsedAction = (props) => {
key={innerdata.name} key={innerdata.name}
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.text.primary,
minWidth: 250, minWidth: 250,
maxWidth: 250, maxWidth: 250,
marginRight: 0, marginRight: 0,
@@ -4835,17 +4835,21 @@ const ParsedAction = (props) => {
<OpenInFullIcon <OpenInFullIcon
style={{ color: theme.palette.textPrimary, cursor: "pointer", margin: multiline ? 5 : 0, height: 20, width: 20, }} style={{ color: theme.palette.textPrimary, cursor: "pointer", margin: multiline ? 5 : 0, height: 20, width: 20, }}
onMouseOver={(event) => { onMouseOver={(event) => {
const clickedField = document.getElementById(clickedFieldId) if(selectedAction?.name !== "filter_list"){
if (clickedField !== null) { const clickedField = document.getElementById(clickedFieldId)
clickedField.focus() if (clickedField !== null) {
clickedField.focus()
}
} }
}} }}
onClick={(event) => { onClick={(event) => {
// Set focus to the Textfield we just clicked // Set focus to the Textfield we just clicked
// This is to ensure focus is set correctly at all times with blur // This is to ensure focus is set correctly at all times with blur
const clickedField = document.getElementById(clickedFieldId) if(selectedAction?.name !== "filter_list"){
if (clickedField !== null) { const clickedField = document.getElementById(clickedFieldId)
clickedField.focus() if (clickedField !== null) {
clickedField.focus()
}
} }
event.preventDefault() event.preventDefault()
@@ -4887,7 +4891,7 @@ const ParsedAction = (props) => {
<FormControl fullWidth style={{ marginTop: 0 }}> <FormControl fullWidth style={{ marginTop: 0 }}>
<InputLabel <InputLabel
id="action-autocompleter" id="action-autocompleter"
style={{ marginLeft: 10, color: "white" }} style={{ marginLeft: 10, color: theme.palette.text.primary }}
> >
Autocomplete Autocomplete
</InputLabel> </InputLabel>
@@ -4915,7 +4919,7 @@ const ParsedAction = (props) => {
fullWidth fullWidth
open={showAutocomplete} open={showAutocomplete}
style={{ style={{
color: "white", color: theme.palette.text.primary,
height: 35, height: 35,
marginTop: 2, marginTop: 2,
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
@@ -4954,7 +4958,7 @@ const ParsedAction = (props) => {
key={data.name} key={data.name}
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
color: "white", color: theme.palette.text.primary,
}} }}
value={data} value={data}
onMouseOver={() => { }} onMouseOver={() => { }}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -47,7 +47,7 @@ const chipStyle = {
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
} }
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const SearchData = props => { const SearchData = props => {
const { serverside, globalUrl, userdata } = props const { serverside, globalUrl, userdata } = props
let navigate = useNavigate(); let navigate = useNavigate();
@@ -132,7 +132,7 @@ const CodeEditor = (props) => {
fieldname, fieldname,
contentLoading, contentLoading,
editorData, editorData,
handleSubflowParamChange, handleTriggerParamChange,
setAiQueryModalOpen, setAiQueryModalOpen,
fullScreenMode, fullScreenMode,
environment, environment,
@@ -212,7 +212,7 @@ const CodeEditor = (props) => {
const triggerField = searchParams.get('trigger_field'); const triggerField = searchParams.get('trigger_field');
const triggerName = searchParams.get('trigger_name'); const triggerName = searchParams.get('trigger_name');
const conditionId = searchParams.get('condition_id'); const conditionId = searchParams.get('condition_id');
const conditionField = searchParams.get('field'); const conditionField = searchParams.get('condition_field');
useEffect(() => { useEffect(() => {
if (actionId === undefined || actionId === null) { if (actionId === undefined || actionId === null) {
@@ -251,7 +251,7 @@ const CodeEditor = (props) => {
setSelectedCondition(condition); setSelectedCondition(condition);
// Update available variables when condition changes // Update available variables when condition changes
updateAvailableVariables(actionlist); updateAvailableVariables(actionlist);
}, [conditionId, fieldName]) }, [conditionId, conditionField])
// Extract variable updating logic into a separate function // Extract variable updating logic into a separate function
const updateAvailableVariables = (actionlist) => { const updateAvailableVariables = (actionlist) => {
@@ -2588,7 +2588,7 @@ const CodeEditor = (props) => {
// Handle condition fields // Handle condition fields
if (conditionField !== null && handleConditionFieldChange !== undefined) { if (conditionField !== null && handleConditionFieldChange !== undefined) {
handleConditionFieldChange(conditionField, fieldName, fixedcodedata); handleConditionFieldChange(conditionField, fixedcodedata);
} }
// Handle action fields // Handle action fields
else if (actionId !== undefined && actionId !== null && actionId.length > 0) { else if (actionId !== undefined && actionId !== null && actionId.length > 0) {
@@ -2596,7 +2596,7 @@ const CodeEditor = (props) => {
} }
// Handle trigger fields // Handle trigger fields
else if (triggerId !== undefined && triggerId !== null && triggerId.length > 0) { else if (triggerId !== undefined && triggerId !== null && triggerId.length > 0) {
handleSubflowParamChange(triggerId, triggerField, fixedcodedata) handleTriggerParamChange(triggerId, triggerField, fixedcodedata)
} }
setExpansionModalOpen(false) setExpansionModalOpen(false)
+33 -1
View File
@@ -1359,7 +1359,7 @@ const UserManagmentTab = memo((props) => {
}} }}
> >
<ListItem style={{ width: "100%", padding: "10px 10px 10px 0px", verticalAlign: 'middle', borderBottom: theme.palette.defaultBorder, display: "table-row" }}> <ListItem style={{ width: "100%", padding: "10px 10px 10px 0px", verticalAlign: 'middle', borderBottom: theme.palette.defaultBorder, display: "table-row" }}>
{["Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => ( {["Region", "Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => (
<ListItemText <ListItemText
key={index} key={index}
primary={header} primary={header}
@@ -1459,8 +1459,40 @@ const UserManagmentTab = memo((props) => {
); );
} }
const getRegionFlag = (region_url) => {
let regiontag = "UK";
let regionCode = "gb";
const regionsplit = region_url?.split(".");
if (regionsplit?.length > 2 && !regionsplit[0]?.includes("shuffler")) {
const namesplit = regionsplit[0]?.split("/");
regiontag = namesplit[namesplit?.length - 1];
if (regiontag === "california") {
regiontag = "US";
regionCode = "us";
} else if (regiontag === "frankfurt") {
regiontag = "EU-2";
regionCode = "eu";
} else if (regiontag === "ca") {
regiontag = "CA";
regionCode = "ca";
}else if (regiontag === "au") {
regiontag = "AUS";
regionCode = "au"
}
}
return regionCode;
};
const userRegion = data?.user_geo_info?.country?.iso_code?.length > 0 ? data?.user_geo_info?.country?.iso_code : data?.active_org?.region_url?.length > 0 ? getRegionFlag(data?.active_org?.region_url) : "eu";
return ( return (
<ListItem key={index} style={{ backgroundColor: bgColor, display: 'table-row', borderBottomLeftRadius: users?.length - 1 === index ? 8 : 0, borderBottomRightRadius: users?.length - 1 === index ? 8 : 0 }}> <ListItem key={index} style={{ backgroundColor: bgColor, display: 'table-row', borderBottomLeftRadius: users?.length - 1 === index ? 8 : 0, borderBottomRightRadius: users?.length - 1 === index ? 8 : 0 }}>
<ListItemText
primary={(<img src={`https://flagcdn.com/48x36/${userRegion.toLowerCase()}.png`} alt={data?.user_geo_info?.country?.iso_code} style={{ marginRight: 30, width: 25, height: 23, }} />)}
style={{ display: 'table-cell', verticalAlign: 'middle', textAlign: 'center' }}
/>
<ListItemText <ListItemText
primary={( primary={(
<Tooltip title={data.username || 'No username available'}> <Tooltip title={data.username || 'No username available'}>
+1 -1
View File
@@ -24,7 +24,7 @@ import {
import WorkflowPaper from "../components/WorkflowPaper.jsx" import WorkflowPaper from "../components/WorkflowPaper.jsx"
import WorkflowPaperNew from "../components/WorkflowPaperNew.jsx" import WorkflowPaperNew from "../components/WorkflowPaperNew.jsx"
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const AppGrid = props => { const AppGrid = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props
+1 -1
View File
@@ -10,7 +10,7 @@ import algoliasearch from 'algoliasearch';
import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom';
import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@mui/material'; import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@mui/material';
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const WorkflowSearch = props => { const WorkflowSearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, selectAble, } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, selectAble, } = props
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
+2 -1
View File
@@ -334,7 +334,8 @@ const Admin2 = (props) => {
} }
return ( return (
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 29, zoom: 0.9}}> //<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 29, zoom: 0.9}}>
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 29, zoom: 1, }}>
<AdminNavBar userdata={userdata} isLoaded={isLoaded} isOrgLoaded={isOrgLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud}/> <AdminNavBar userdata={userdata} isLoaded={isLoaded} isOrgLoaded={isOrgLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud}/>
</div> </div>
); );
+9 -3
View File
@@ -68,13 +68,19 @@ const AdminAccount = (props) => {
.then((response) => .then((response) =>
response.json().then((responseJson) => { response.json().then((responseJson) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]); setLoginInfo(responseJson["reason"])
if (responseJson?.reason?.toLowerCase().includes("connection refused")) {
navigate("/loginsetup")
}
} else { } else {
if (responseJson.reason === "redirect") { if (responseJson.reason === "redirect") {
setTimeout(() => { setTimeout(() => {
window.location.pathname = "/login"; window.location.pathname = "/login"
}, 2500) }, 2500)
} }
} }
}) })
) )
@@ -111,7 +117,7 @@ const AdminAccount = (props) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]); setLoginInfo(responseJson["reason"]);
} else { } else {
setLoginInfo("Successful register :)"); setLoginInfo("Successful register! Redirecting in a moment...");
setTimeout(() => { setTimeout(() => {
window.location.pathname = "/login"; window.location.pathname = "/login";
+54 -26
View File
@@ -438,7 +438,7 @@ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
//const referenceUrl = "https://shuffler.io/functions/webhooks/" //const referenceUrl = "https://shuffler.io/functions/webhooks/"
//const referenceUrl = window.location.origin+"/api/v1/hooks/" //const referenceUrl = window.location.origin+"/api/v1/hooks/"
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const AngularWorkflow = (defaultprops) => { const AngularWorkflow = (defaultprops) => {
const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id, ReactGA, } = defaultprops; const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id, ReactGA, } = defaultprops;
const {themeMode, supportEmail, brandColor} = useContext(Context) const {themeMode, supportEmail, brandColor} = useContext(Context)
@@ -790,7 +790,7 @@ const AngularWorkflow = (defaultprops) => {
}, },
{ {
"name": "fields", "name": "fields",
"value": "", "value": '{\n "ticket_id": "123456",\n "comment": "This is a comment"\n}',
"required": false, "required": false,
"multiline": true, "multiline": true,
}, },
@@ -3573,6 +3573,12 @@ const AngularWorkflow = (defaultprops) => {
if (curapp?.actions === undefined || curapp?.actions === null || curapp?.actions?.length === 0 || curapp?.actions?.length === 1) { if (curapp?.actions === undefined || curapp?.actions === null || curapp?.actions?.length === 0 || curapp?.actions?.length === 1) {
loadAppConfig(curapp?.id, false, true) loadAppConfig(curapp?.id, false, true)
} }
if (key > 10) {
console.log("Breaking on 10 sideloads of total", responseJson.length)
break
}
} }
// Find app with ID "794e51c3c1a8b24b89ccc573a3defc47" (gmail) to force-break it, // Find app with ID "794e51c3c1a8b24b89ccc573a3defc47" (gmail) to force-break it,
@@ -7000,7 +7006,11 @@ const AngularWorkflow = (defaultprops) => {
}) })
.then((responseJson) => { .then((responseJson) => {
if (responseJson.success === false) { if (responseJson.success === false) {
toast("Failed to auto-activate the app. Go to /apps and activate it.") if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
toast.error("Failed to auto-activate the app: " + responseJson.reason)
} else {
toast.error("Failed to auto-activate the app. Go to /apps and activate it.")
}
} else { } else {
if (refresh === true) { if (refresh === true) {
setHighlightedApp(appid) setHighlightedApp(appid)
@@ -10312,7 +10322,7 @@ const AngularWorkflow = (defaultprops) => {
} }
} }
toast("Creating schedule") toast.info("Creating schedule")
var data = { var data = {
name: trigger.name, name: trigger.name,
frequency: workflow.triggers[triggerindex].parameters[0].value, frequency: workflow.triggers[triggerindex].parameters[0].value,
@@ -10361,9 +10371,9 @@ const AngularWorkflow = (defaultprops) => {
}) })
.then((responseJson) => { .then((responseJson) => {
if (!responseJson.success) { if (!responseJson.success) {
toast("Failed to set schedule: " + responseJson.reason); toast.error("Failed to set schedule: " + responseJson.reason);
} else { } else {
toast("Successfully created schedule"); toast.success("Successfully created schedule");
workflow.triggers[triggerindex].status = "running"; workflow.triggers[triggerindex].status = "running";
trigger.status = "running"; trigger.status = "running";
setSelectedTrigger(trigger); setSelectedTrigger(trigger);
@@ -11852,7 +11862,7 @@ const AngularWorkflow = (defaultprops) => {
if (queryID !== undefined && queryID !== null) { if (queryID !== undefined && queryID !== null) {
aa('init', { aa('init', {
appId: "JNSS5CFDZZ", appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240", apiKey: "c8f882473ff42d41158430be09ec2b4e",
}) })
const timestamp = new Date().getTime() const timestamp = new Date().getTime()
@@ -11876,8 +11886,9 @@ const AngularWorkflow = (defaultprops) => {
var type = "app" var type = "app"
const baseImage = <LibraryBooksIcon /> const baseImage = <LibraryBooksIcon />
const width = 230
return ( return (
<div style={{ position: "relative", marginTop: 15, marginLeft: 0, marginRight: 10, position: "absolute", color: theme.palette.textColor, zIndex: 1001, backgroundColor: theme.palette.textFieldStyle.backgroundColor, minWidth: leftBarSize+30, maxWidth: 340, boxShadows: "none", overflowX: "hidden", }}> <div style={{ position: "relative", marginTop: 15, marginLeft: 0, marginRight: 10, position: "absolute", color: theme.palette.textColor, zIndex: 1001, backgroundColor: theme.palette.textFieldStyle.backgroundColor, /*minWidth: leftBarSize+30,*/ minWidth: width, maxWidth: width, boxShadows: "none", overflowX: "hidden", }}>
<List style={{ backgroundColor: theme.palette.inputColor, }}> <List style={{ backgroundColor: theme.palette.inputColor, }}>
{hits.length === 0 ? {hits.length === 0 ?
<ListItem style={outerlistitemStyle}> <ListItem style={outerlistitemStyle}>
@@ -11971,7 +11982,7 @@ const AngularWorkflow = (defaultprops) => {
}} }}
defaultPosition={{ x: 0, y: 0 }} defaultPosition={{ x: 0, y: 0 }}
> >
<div style={{ textDecoration: "none", color: theme.palette.text.primary, }} onClick={(event) => { <div style={{ overflow: "hidden", textDecoration: "none", color: theme.palette.text.primary, }} onClick={(event) => {
clickedApp(hit) clickedApp(hit)
}}> }}>
@@ -12125,7 +12136,12 @@ const AngularWorkflow = (defaultprops) => {
} }
if ((app.id === "integration" || app.id === "shuffle_agent") && userdata.support !== true) { if ((app.id === "integration" || app.id === "shuffle_agent") && userdata.support !== true) {
return null console.log("APPID: ", app.id, isCloud)
if (isCloud === false && app.id === "integration") {
} else {
console.log("RETURNING", app.id)
return null
}
} }
if (viewedApps.includes(app.id)) { if (viewedApps.includes(app.id)) {
@@ -12190,7 +12206,7 @@ const AngularWorkflow = (defaultprops) => {
</div> </div>
) : apps.length > 0 ? ( ) : apps.length > 0 ? (
<div <div
style={{ textAlign: "center", width: leftBarSize, marginTop: 10, marginLeft: 5, marginRight: 5, }} style={{ textAlign: "center", width: leftBarSize, marginTop: 25, marginLeft: 10 , marginRight: 10, }}
onLoad={() => { onLoad={() => {
console.log("Should load in extra apps?") console.log("Should load in extra apps?")
}} }}
@@ -12198,6 +12214,7 @@ const AngularWorkflow = (defaultprops) => {
<Typography variant="body1" color="textSecondary"> <Typography variant="body1" color="textSecondary">
Couldn't find the apps you were looking for? Searching unactivated apps. Click one of these apps to Activate it for your organisation. Couldn't find the apps you were looking for? Searching unactivated apps. Click one of these apps to Activate it for your organisation.
</Typography> </Typography>
<InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => { <InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
console.log("CLICKED") console.log("CLICKED")
}}> }}>
@@ -13021,7 +13038,7 @@ const AngularWorkflow = (defaultprops) => {
event.preventDefault() event.preventDefault()
setExpansionModalOpen(true) setExpansionModalOpen(true)
setActiveDialog("codeeditor") setActiveDialog("codeeditor")
navigate(`?condition_id=${data.id}&field=${data.name}`) navigate(`?condition_id=${data.id}&condition_field=${data.name}`)
setEditorData({ setEditorData({
"name": data.name, "name": data.name,
"value": data.value || "", "value": data.value || "",
@@ -13106,9 +13123,9 @@ const AngularWorkflow = (defaultprops) => {
// Update the field value based on type // Update the field value based on type
if (type === "source") { if (type === "source") {
handleConditionFieldChange("source", "value", toComplete); handleConditionFieldChange("source", toComplete);
} else if (type === "destination") { } else if (type === "destination") {
handleConditionFieldChange("destination", "value", toComplete); handleConditionFieldChange("destination", toComplete);
} }
handleMenuClose(); handleMenuClose();
@@ -13985,7 +14002,7 @@ const AngularWorkflow = (defaultprops) => {
/> />
</Dialog> </Dialog>
const handleConditionFieldChange = (fieldType, fieldName, value) => { const handleConditionFieldChange = (fieldType, value) => {
if (fieldType === "source") { if (fieldType === "source") {
setSourceValue({ setSourceValue({
...sourceValue, ...sourceValue,
@@ -15186,7 +15203,7 @@ const AngularWorkflow = (defaultprops) => {
} }
] ]
const handleSubflowParamChange = (triggerId, triggerField, newData) => { const handleTriggerParamChange = (triggerId, triggerField, newData) => {
var updateFail = "" var updateFail = ""
if (workflow !== undefined && workflow !== null) { if (workflow !== undefined && workflow !== null) {
@@ -15303,7 +15320,8 @@ const AngularWorkflow = (defaultprops) => {
<Typography>Name</Typography> <Typography>Name</Typography>
<TextField <TextField
style={{ style={{
backgroundColor: "#212121", backgroundColor: theme.palette.inputColor,
color: theme.palette.text.primary,
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
marginTop: 3, marginTop: 3,
}} }}
@@ -15332,7 +15350,8 @@ const AngularWorkflow = (defaultprops) => {
<Typography>Delay</Typography> <Typography>Delay</Typography>
<TextField <TextField
style={{ style={{
backgroundColor: "#212121", backgroundColor: theme.palette.inputColor,
color: theme.palette.text.primary,
marginTop: 3, marginTop: 3,
maxWidth: 50, maxWidth: 50,
}} }}
@@ -17323,9 +17342,13 @@ const AngularWorkflow = (defaultprops) => {
fullWidth fullWidth
rows="4" rows="4"
multiline multiline
defaultValue={selectedTrigger.parameters[0]?.value} value={selectedTriggerValue || ""}
color="primary" color="primary"
placeholder="" placeholder=""
onChange={(e) => {
setLastSaved(false)
setSelectedTriggerValue(e.target.value)
}}
onBlur={(e) => { onBlur={(e) => {
setLastSaved(false) setLastSaved(false)
setTriggerTextInformationWrapper(e.target.value); setTriggerTextInformationWrapper(e.target.value);
@@ -18681,7 +18704,9 @@ const AngularWorkflow = (defaultprops) => {
{originalWorkflow?.suborg_distribution === undefined || originalWorkflow?.suborg_distribution === null || originalWorkflow?.suborg_distribution?.length === 0 || originalWorkflow?.suborg_distribution.includes("none") ? {originalWorkflow?.suborg_distribution === undefined || originalWorkflow?.suborg_distribution === null || originalWorkflow?.suborg_distribution?.length === 0 || originalWorkflow?.suborg_distribution.includes("none") ?
originalWorkflow?.parentorg_workflow !== undefined && originalWorkflow?.parentorg_workflow !== null && originalWorkflow?.parentorg_workflow.length > 0 || workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow.length > 0 ? <div>
{originalWorkflow?.parentorg_workflow !== undefined && originalWorkflow?.parentorg_workflow !== null && originalWorkflow?.parentorg_workflow.length > 0 || workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow.length > 0 ?
<Button <Button
color="secondary" color="secondary"
variant="outlined" variant="outlined"
@@ -18691,7 +18716,7 @@ const AngularWorkflow = (defaultprops) => {
textTransform: "none", textTransform: "none",
marginLeft: 10, marginLeft: 10,
}} }}
onClick={() => { onClick={() => {
navigate(`/workflows/${workflow.parentorg_workflow}`) navigate(`/workflows/${workflow.parentorg_workflow}`)
// Reload the page // Reload the page
window.location.reload() window.location.reload()
@@ -18699,7 +18724,10 @@ const AngularWorkflow = (defaultprops) => {
> >
Go to parent org workflow Go to parent org workflow
</Button> </Button>
: userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 1 && workflow?.id !== undefined && workflow?.id && workflow?.id?.length > 0 ?
: null}
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 1 && workflow?.id !== undefined && workflow?.id && workflow?.id?.length > 0 ?
<Button <Button
color="secondary" color="secondary"
variant="outlined" variant="outlined"
@@ -18715,8 +18743,8 @@ const AngularWorkflow = (defaultprops) => {
> >
Enable Suborg Distribution Enable Suborg Distribution
</Button> </Button>
: null : null}
</div>
: :
<Tooltip title={lastSaved === false && originalWorkflow.id === workflow.id ? <Tooltip title={lastSaved === false && originalWorkflow.id === workflow.id ?
@@ -20063,7 +20091,7 @@ const AngularWorkflow = (defaultprops) => {
if (queryID !== undefined && queryID !== null) { if (queryID !== undefined && queryID !== null) {
aa('init', { aa('init', {
appId: "JNSS5CFDZZ", appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240", apiKey: "c8f882473ff42d41158430be09ec2b4e",
}) })
const timestamp = new Date().getTime() const timestamp = new Date().getTime()
@@ -25459,7 +25487,7 @@ const AngularWorkflow = (defaultprops) => {
// selectedTrigger={selectedTrigger} // selectedTrigger={selectedTrigger}
aiSubmit={aiSubmit} aiSubmit={aiSubmit}
toolsAppId={toolsApp.id} toolsAppId={toolsApp.id}
handleSubflowParamChange={handleSubflowParamChange} handleTriggerParamChange={handleTriggerParamChange}
codedata={editorData.value} codedata={editorData.value}
setcodedata={setcodedata} setcodedata={setcodedata}
selectedEdge={selectedEdge} selectedEdge={selectedEdge}
+1 -1
View File
@@ -50,7 +50,7 @@ import { green } from "../views/AngularWorkflow.jsx"
const searchClient = algoliasearch( const searchClient = algoliasearch(
"JNSS5CFDZZ", "JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240" "c8f882473ff42d41158430be09ec2b4e"
) )
// Lazy loading of ApiExplorer component to reduce initial load time // Lazy loading of ApiExplorer component to reduce initial load time
+3 -3
View File
@@ -93,7 +93,7 @@ import aa from "search-insights";
// 2 = OpenAPI (Invalid) // 2 = OpenAPI (Invalid)
const searchClient = algoliasearch( const searchClient = algoliasearch(
"JNSS5CFDZZ", "JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240" "c8f882473ff42d41158430be09ec2b4e"
) )
const AppExplorer = (props) => { const AppExplorer = (props) => {
@@ -3996,7 +3996,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)";
if (queryID !== undefined && queryID !== null) { if (queryID !== undefined && queryID !== null) {
aa("init", { aa("init", {
appId: "JNSS5CFDZZ", appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240", apiKey: "c8f882473ff42d41158430be09ec2b4e",
}); });
const timestamp = new Date().getTime(); const timestamp = new Date().getTime();
@@ -4085,7 +4085,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)";
if (queryID !== undefined && queryID !== null) { if (queryID !== undefined && queryID !== null) {
aa("init", { aa("init", {
appId: "JNSS5CFDZZ", appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240", apiKey: "c8f882473ff42d41158430be09ec2b4e",
}); });
const timestamp = new Date().getTime(); const timestamp = new Date().getTime();
+3 -3
View File
@@ -280,7 +280,7 @@ export const GetParsedPaths = (inputdata, basekey) => {
return parsedValues; return parsedValues;
}; };
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const Apps = (props) => { const Apps = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata, serverside, } = props; const { globalUrl, isLoggedIn, isLoaded, userdata, serverside, } = props;
@@ -1305,7 +1305,7 @@ const Apps = (props) => {
if (queryID !== undefined && queryID !== null) { if (queryID !== undefined && queryID !== null) {
aa("init", { aa("init", {
appId: "JNSS5CFDZZ", appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240", apiKey: "c8f882473ff42d41158430be09ec2b4e",
}); });
const timestamp = new Date().getTime(); const timestamp = new Date().getTime();
@@ -2036,7 +2036,7 @@ const Apps = (props) => {
if (queryID !== undefined && queryID !== null) { if (queryID !== undefined && queryID !== null) {
aa('init', { aa('init', {
appId: "JNSS5CFDZZ", appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240", apiKey: "c8f882473ff42d41158430be09ec2b4e",
}) })
const timestamp = new Date().getTime() const timestamp = new Date().getTime()
+68 -102
View File
@@ -46,7 +46,7 @@ import AppCreationModal from "../components/AppCreationModal.jsx";
const searchClient = algoliasearch( const searchClient = algoliasearch(
"JNSS5CFDZZ", "JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240" "c8f882473ff42d41158430be09ec2b4e"
); );
// AppCard Component // AppCard Component
@@ -1122,6 +1122,7 @@ const Apps2 = (props) => {
const [defaultSearch, setDefaultSearch] = useState(""); const [defaultSearch, setDefaultSearch] = useState("");
const [apps, setApps] = useState([]); const [apps, setApps] = useState([]);
const [backupApps, setBackupApps] = useState([]);
const [filteredApps, setFilteredApps] = useState([]); const [filteredApps, setFilteredApps] = useState([]);
const [appSearchLoading, setAppSearchLoading] = useState(false); const [appSearchLoading, setAppSearchLoading] = useState(false);
const [creatorProfile, setCreatorProfile] = useState({}); const [creatorProfile, setCreatorProfile] = useState({});
@@ -1198,57 +1199,9 @@ const Apps2 = (props) => {
getFramework(); getFramework();
}, []); }, []);
// Fetch apps based on the current tab : 0 -> org_apps, 1 -> my_apps, 2 -> all_apps
const fetchApps = async () => {
const baseUrl = globalUrl;
let url;
setIsLoading(true);
const userId = userdata?.id;
if (currTab === 1 && userId) {
url = `${baseUrl}/api/v1/users/${userId}/apps`;
} else if (currTab === 0) {
url = `${baseUrl}/api/v1/apps`;
}
try {
const response = await fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (currTab === 1) {
setAppsToShow(data);
setUserApps(data);
} else if (currTab === 0) {
setAppsToShow(data);
setOrgApps(data);
// For testing the empty state
// setAppsToShow([]);
// setOrgApps([]);
}
setIsLoading(false);
} catch (err) {
console.error("Error fetching apps:", err);
setIsLoading(false);
}
};
useEffect(() => { useEffect(() => {
getApps()
// Only fetch if we have required data }, [])
if (globalUrl && (currTab === 0 || (currTab === 1 && userdata?.id))) {
fetchApps();
}
}, [currTab, globalUrl, userdata?.id]); // Remove location.search dependency
// useEffect(() => {
// // setSearchQuery("");
// setSelectedCategory([]);
// setSelectedLabel([]);
// }, [currTab])
// Find top categories and tags based on the current tab // Find top categories and tags based on the current tab
useEffect(() => { useEffect(() => {
@@ -1293,11 +1246,13 @@ const Apps2 = (props) => {
}); });
}; };
/*
useEffect(() => { useEffect(() => {
if (serverside) { if (serverside) {
return null; return null;
} }
}, [serverside]); }, [serverside]);
*/
const getApps = () => { const getApps = () => {
// Get apps from localstorage // Get apps from localstorage
@@ -1308,7 +1263,7 @@ const Apps2 = (props) => {
if (storageApps === null || storageApps === undefined || storageApps.length === 0) { if (storageApps === null || storageApps === undefined || storageApps.length === 0) {
storageApps = [] storageApps = []
} else { } else {
setAppsToShow(storageApps) //setAppsToShow(storageApps)
setOrgApps(storageApps) setOrgApps(storageApps)
setApps(storageApps) setApps(storageApps)
// setFilteredApps(storageApps) // setFilteredApps(storageApps)
@@ -1344,18 +1299,25 @@ const Apps2 = (props) => {
var privateapps = []; var privateapps = [];
var valid = []; var valid = [];
var invalid = []; var invalid = [];
var backups = []
for (var key in responseJson) { for (var key in responseJson) {
const app = responseJson[key]; const app = responseJson[key];
if (app.categories !== undefined && app.categories !== null && app?.categories.includes("Eradication")) { if (app?.reference_info?.onprem_backup === true) {
backups.push(app)
continue
}
if (app?.categories !== undefined && app?.categories !== null && app?.categories?.includes("Eradication")) {
app.categories = ["EDR"] app.categories = ["EDR"]
} }
if (app.is_valid && !(!app.activated && app.generated)) { if (app?.is_valid && !(!app?.activated && app?.generated)) {
privateapps.push(app); privateapps.push(app);
} else if ( } else if (
app.private_id !== undefined && app?.private_id !== undefined &&
app.private_id.length > 0 app?.private_id.length > 0
) { ) {
valid.push(app); valid.push(app);
} else { } else {
@@ -1363,6 +1325,11 @@ const Apps2 = (props) => {
} }
} }
console.log("BACKUPAPPS: ", backups)
if (backups.length > 0) {
setBackupApps(backups)
}
privateapps.push(...valid); privateapps.push(...valid);
privateapps.push(...invalid); privateapps.push(...invalid);
console.log("privateapps: setting apps ", privateapps) console.log("privateapps: setting apps ", privateapps)
@@ -1372,39 +1339,22 @@ const Apps2 = (props) => {
// setFilteredApps(privateapps); // setFilteredApps(privateapps);
if (privateapps.length > 0) { if (privateapps.length > 0) {
if (selectedApp.id === undefined || selectedApp.id === null) { if (selectedApp?.id === undefined || selectedApp?.id === null) {
if (privateapps[0].owner !== undefined && privateapps[0].owner !== null) { if (privateapps[0]?.owner !== undefined && privateapps[0]?.owner !== null) {
getUserProfile(privateapps[0].owner); getUserProfile(privateapps[0]?.owner);
} }
// setContact(privateapps[0].contact_info)
// setSelectedApp(privateapps[0]);
// setSharingConfiguration(privateapps[0].sharing === true ? "public" : "you")
} }
// if (
// privateapps[0].actions !== null &&
// privateapps[0].actions.length > 0
// ) {
// setSelectedAction(privateapps[0].actions[0]);
// } else {
// setSelectedAction({});
// }
} }
if (privateapps.length > 0 && storageApps.length === 0) { if (privateapps?.length > 0 && storageApps?.length === 0) {
try { try {
localStorage.setItem("apps", JSON.stringify(privateapps)) localStorage.setItem("apps", JSON.stringify(privateapps))
} catch (e) { } catch (e) {
console.log("Failed to set apps in localstorage: ", e) console.log("Failed to set apps in localstorage: ", e)
} }
} }
//setTimeout(() => {
// setFirstLoad(false)
//}, 5000)
}) })
.catch((error) => { .catch((error) => {
console.log("Failed to get apps: ", error.toString());
toast(error.toString()); toast(error.toString());
setIsLoading(false); setIsLoading(false);
}); });
@@ -1780,7 +1730,6 @@ const Apps2 = (props) => {
// setOpenModal(true); // setOpenModal(true);
}; };
useEffect(() => { useEffect(() => {
const apps = currTab === 1 ? userApps : orgApps; const apps = currTab === 1 ? userApps : orgApps;
const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel); const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel);
@@ -1798,33 +1747,38 @@ const Apps2 = (props) => {
} else if (newTab === 1) { } else if (newTab === 1) {
const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel); const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel);
setAppsToShow(filteredUserApps); setAppsToShow(filteredUserApps);
} } else if (newTab === 3) {
const filteredUserApps = filterApps(backupApps, searchQuery, selectedCategory, selectedLabel);
setAppsToShow(filteredUserApps);
return
}
// Update URL query params based on tab index // Update URL query params based on tab index
const tabMapping = { const tabMapping = {
0: 'org_apps', 0: 'org_apps',
1: 'my_apps', 1: 'my_apps',
2: 'all_apps' 2: 'all_apps',
3: 'backup_apps',
}; };
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
queryParams.set('tab', tabMapping[newTab]); queryParams.set('tab', tabMapping[newTab]);
// Maintain search query in URL regardless of tab // Maintain search query in URL regardless of tab
if (searchQuery) { if (searchQuery) {
queryParams.set('q', searchQuery); queryParams.set('q', searchQuery);
} else { } else {
queryParams.delete('q'); queryParams.delete('q');
} }
navigate(`${location.pathname}?${queryParams.toString()}`); navigate(`${location.pathname}?${queryParams.toString()}`);
}; };
// Update useEffect for filtering without URL manipulation // Update useEffect for filtering without URL manipulation
useEffect(() => { useEffect(() => {
if (currTab === 2) return; // Skip for "Discover Apps" tab as it uses Algolia if (currTab === 2) return; // Skip for "Discover Apps" tab as it uses Algolia
const apps = currTab === 1 ? userApps : orgApps; const apps = currTab === 1 ? userApps : currTab === 3 ? backupApps : orgApps;
const filteredApps = filterApps(apps, searchQuery, selectedCategory, selectedLabel); const filteredApps = filterApps(apps, searchQuery, selectedCategory, selectedLabel);
setAppsToShow(filteredApps); setAppsToShow(filteredApps);
}, [searchQuery, selectedCategory, selectedLabel, currTab, userApps, orgApps]); }, [searchQuery, selectedCategory, selectedLabel, currTab, userApps, orgApps]);
@@ -1914,7 +1868,7 @@ const Apps2 = (props) => {
<div style={boxStyle}> <div style={boxStyle}>
<div style={{ display: "flex", flexDirection: "row", width: "100%", justifyContent: "space-between" }}> <div style={{ display: "flex", flexDirection: "row", width: "100%", justifyContent: "space-between" }}>
<Typography variant="h4" style={{ marginBottom: 20, paddingLeft: 15, textTransform: 'none', fontFamily: theme?.typography?.fontFamily }}> <Typography variant="h4" style={{ marginBottom: 20, paddingLeft: 15, textTransform: 'none', fontFamily: theme?.typography?.fontFamily }}>
{currTab === 0 ? "Org" : currTab === 1 ? "Your" : "Discover"} Apps {currTab === 0 ? "Org" : currTab === 1 ? "Your" : currTab === 3 ? "Backup" : "Discover"} Apps
</Typography> </Typography>
{isCloud ? null : ( {isCloud ? null : (
<span style={{ display: "flex", gap: 15 }}> <span style={{ display: "flex", gap: 15 }}>
@@ -1940,7 +1894,7 @@ const Apps2 = (props) => {
style={{ style={{
height: 45, height: 45,
minWidth: 45, minWidth: 45,
backgroundColor: "#2F2F2F", backgroundColor: theme.palette.platformColor,
borderRadius: 4, borderRadius: 4,
padding: "8px 16px", padding: "8px 16px",
}} }}
@@ -1950,9 +1904,9 @@ const Apps2 = (props) => {
}} }}
> >
{isLoading ? ( {isLoading ? (
<CircularProgress size={20} style={{ color: "#FF8544" }} /> <CircularProgress size={20} style={{ color: theme.palette.primary.main }} />
) : ( ) : (
<CachedIcon style={{ color: "#F1F1F1" }} /> <CachedIcon style={{ color: theme.palette.text.primary }} />
)} )}
</Button> </Button>
</Tooltip> </Tooltip>
@@ -1980,7 +1934,7 @@ const Apps2 = (props) => {
style={{ style={{
height: 45, height: 45,
minWidth: 45, minWidth: 45,
backgroundColor: "#2F2F2F", backgroundColor: theme.palette.platformColor,
borderRadius: 4, borderRadius: 4,
padding: "8px 16px", padding: "8px 16px",
}} }}
@@ -1992,9 +1946,9 @@ const Apps2 = (props) => {
}} }}
> >
{isLoading ? ( {isLoading ? (
<CircularProgress size={20} style={{ color: "#FF8544" }} /> <CircularProgress size={20} style={{ color: theme.palette.primary.main }} />
) : ( ) : (
<CloudDownloadIcon style={{ color: "#F1F1F1" }} /> <CloudDownloadIcon style={{ color: theme.palette.text.primary }} />
)} )}
</Button> </Button>
</Tooltip> </Tooltip>
@@ -2027,6 +1981,7 @@ const Apps2 = (props) => {
...(currTab === 1 ? tabActive : {}) ...(currTab === 1 ? tabActive : {})
}} }}
/> />
<Tab <Tab
label="Discover Public Apps" label="Discover Public Apps"
style={{ style={{
@@ -2035,6 +1990,17 @@ const Apps2 = (props) => {
...(currTab === 2 ? tabActive : {}) ...(currTab === 2 ? tabActive : {})
}} }}
/> />
{backupApps.length > 0 &&
<Tab
label={`Onprem Backup (${backupApps.length})`}
style={{
...tabStyle,
marginLeft: 25,
...(currTab === 3 ? tabActive : {})
}}
/>
}
</Tabs> </Tabs>
</div> </div>
<div style={{ display: "flex", justifyContent: "space-between", gap: 10, marginBottom: 20, height: 45, paddingRight: 25 }}> <div style={{ display: "flex", justifyContent: "space-between", gap: 10, marginBottom: 20, height: 45, paddingRight: 25 }}>
@@ -2043,7 +2009,7 @@ const Apps2 = (props) => {
minWidth: "25%", minWidth: "25%",
maxWidth: "25%" maxWidth: "25%"
}}> }}>
{(currTab === 0 || currTab === 1) ? ( {(currTab === 0 || currTab === 1 || currTab === 3) ? (
<TextField <TextField
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -2253,7 +2219,7 @@ const Apps2 = (props) => {
<div> <div>
{ {
currTab === 0 && ( currTab === 0 || currTab === 3 && (
<div style={{ minHeight: 570 }}> <div style={{ minHeight: 570 }}>
{isLoading ? ( {isLoading ? (
<LoadingGrid /> <LoadingGrid />
@@ -2285,7 +2251,7 @@ const Apps2 = (props) => {
handleAppClick={handleAppClick} handleAppClick={handleAppClick}
leftSideBarOpenByClick={leftSideBarOpenByClick} leftSideBarOpenByClick={leftSideBarOpenByClick}
userdata={userdata} userdata={userdata}
fetchApps={fetchApps} fetchApps={getApps}
setUserApps={setUserApps} setUserApps={setUserApps}
appsToShow={appsToShow} appsToShow={appsToShow}
@@ -2338,7 +2304,7 @@ const Apps2 = (props) => {
{appsToShow.map((data, index) => ( {appsToShow.map((data, index) => (
<AppCard key={index} data={data} index={index} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} globalUrl={globalUrl} deactivatedIndexes={deactivatedIndexes} currTab={currTab} userdata={userdata} <AppCard key={index} data={data} index={index} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} globalUrl={globalUrl} deactivatedIndexes={deactivatedIndexes} currTab={currTab} userdata={userdata}
handleAppClick={handleAppClick} leftSideBarOpenByClick={leftSideBarOpenByClick} handleAppClick={handleAppClick} leftSideBarOpenByClick={leftSideBarOpenByClick}
fetchApps={fetchApps} fetchApps={getApps}
setUserApps={setUserApps} setUserApps={setUserApps}
appsToShow={appsToShow} appsToShow={appsToShow}
setAppsToShow={setAppsToShow} setAppsToShow={setAppsToShow}
+41 -27
View File
@@ -314,12 +314,15 @@ const LoginPage = props => {
}, },
}) })
if (serverside !== true) { useEffect(() => {
const tmpMessage = new URLSearchParams(window.location.search).get("message") if (serverside !== true) {
if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) { const tmpMessage = new URLSearchParams(window.location.search).get("message")
setMessage(tmpMessage) if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) {
setMessage(tmpMessage)
toast(tmpMessage)
}
} }
} }, [])
if (document !== undefined) { if (document !== undefined) {
if (register) { if (register) {
@@ -361,7 +364,7 @@ const LoginPage = props => {
console.log("Should login instead of register!") console.log("Should login instead of register!")
setRegister(!register) setRegister(!register)
} else { } else {
console.log("Path: " + path, "Register: " + register) //console.log("Path: " + path, "Register: " + register)
} }
} }
@@ -444,13 +447,17 @@ const LoginPage = props => {
} }
if (isLoggedIn === true && serverside !== true) { if (isLoggedIn === true && serverside !== true) {
const tmpView = new URLSearchParams(window.location.search).get("view") const tmpView = new URLSearchParams(window.location.search).get("view");
if (tmpView !== undefined && tmpView !== null && tmpView === "pricing") { if (tmpView !== undefined && tmpView !== null) {
window.location.pathname = "/pricing" let pathOnly = tmpView.split("?")[0];
return if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly;
} else if (tmpView !== undefined && tmpView !== null) {
window.location.pathname = tmpView if (pathOnly === "/pricing" || pathOnly === "admin") {
return window.location.replace(pathOnly + window.location.search);
} else {
window.location.replace(pathOnly);
}
return;
} }
window.location.pathname = "/workflows" window.location.pathname = "/workflows"
@@ -468,6 +475,11 @@ const LoginPage = props => {
response.json().then((responseJson) => { response.json().then((responseJson) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]); setLoginInfo(responseJson["reason"]);
if (responseJson?.reason?.toLowerCase().includes("connection refused")) {
navigate("/loginsetup")
}
} else { } else {
if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) { if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) {
setSSOUrl(responseJson.sso_url); setSSOUrl(responseJson.sso_url);
@@ -570,20 +582,17 @@ const LoginPage = props => {
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }) setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
} }
const tmpView = new URLSearchParams(window.location.search).get("view") const tmpView = new URLSearchParams(window.location.search).get("view");
if (tmpView !== undefined && tmpView !== null) { if (tmpView !== undefined && tmpView !== null) {
//const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}` let pathOnly = tmpView.split("?")[0];
// Check if slash in the url if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly;
var newUrl = `/${tmpView}` if (pathOnly === "/pricing" || pathOnly === "admin") {
if (tmpView.startsWith("/")) { window.location.replace(pathOnly + window.location.search);
newUrl = `${tmpView}` } else {
window.location.replace(pathOnly);
} }
return;
console.log("Found url: ", newUrl)
window.location.pathname = newUrl
return
} }
console.log("LOGIN DATA: ", responseJson) console.log("LOGIN DATA: ", responseJson)
@@ -642,9 +651,14 @@ const LoginPage = props => {
const tmpView = new URLSearchParams(window.location.search).get("view") const tmpView = new URLSearchParams(window.location.search).get("view")
if (tmpView !== undefined && tmpView !== null) { if (tmpView !== undefined && tmpView !== null) {
//const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}` let pathOnly = tmpView.split("?")[0];
const newUrl = `/${tmpView}` if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly;
window.location.pathname = newUrl
if (pathOnly === "/pricing" || pathOnly === "admin") {
window.location.replace(pathOnly + window.location.search);
} else {
window.location.replace(pathOnly);
}
return return
} }
+7 -1
View File
@@ -85,7 +85,13 @@ const LoginDialog = (props) => {
.then((response) => .then((response) =>
response.json().then((responseJson) => { response.json().then((responseJson) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]); setLoginInfo(responseJson["reason"])
if (responseJson?.reason?.toLowerCase().includes("connection refused")) {
setLoginViewLoading(true)
start()
}
} else { } else {
if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) { if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) {
+6 -1
View File
@@ -458,7 +458,12 @@ const Settings = (props) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
setPasswordFormMessage(responseJson["reason"]); setPasswordFormMessage(responseJson["reason"]);
} else { } else {
toast("Changed password!"); var reason = ""
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
reason += responseJson.reason
}
toast.success("Changed password! " + reason);
setPasswordFormMessage(""); setPasswordFormMessage("");
} }
}) })
+8 -1
View File
@@ -126,6 +126,13 @@ const UsecaseListComponent = (props) => {
const { themeMode, brandName } = useContext(Context) const { themeMode, brandName } = useContext(Context)
const theme = getTheme(themeMode) const theme = getTheme(themeMode)
const usecaseLightThemeColor = {
"collect": "#FB47A0",
"enrich": "#F38B14",
"detect": "#0AAD65",
"respond": "#289BDB",
"verify": "#624CE9",
}
const [expandedIndex, setExpandedIndex] = useState(-1); const [expandedIndex, setExpandedIndex] = useState(-1);
const [expandedItem, setExpandedItem] = useState(-1); const [expandedItem, setExpandedItem] = useState(-1);
const [inputUsecase, setInputUsecase] = useState({}); const [inputUsecase, setInputUsecase] = useState({});
@@ -743,7 +750,7 @@ const UsecaseListComponent = (props) => {
{keys.map((usecase, index) => { {keys.map((usecase, index) => {
return ( return (
<div key={index} style={{marginTop: 75, }}> <div key={index} style={{marginTop: 75, }}>
<Typography variant="body1" style={{color: usecase.color, textAlign: "left", marginBottom: 10, }}> <Typography variant="body1" style={{color: themeMode === "dark" ? usecase.color : usecaseLightThemeColor[usecase.name.slice(3, 100).toLowerCase()], textAlign: "left", marginBottom: 10, }}>
<b>{index+1}. {usecase.name.slice(3, 100)}</b> <b>{index+1}. {usecase.name.slice(3, 100)}</b>
</Typography> </Typography>
<Grid container spacing={1}> <Grid container spacing={1}>
+74 -20
View File
@@ -111,7 +111,7 @@ import { removeQuery } from "../components/ScrollToTop.jsx";
import {green, yellow, red, grey } from "../views/AngularWorkflow.jsx" import {green, yellow, red, grey } from "../views/AngularWorkflow.jsx"
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240"); const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e");
const svgSize = 24; const svgSize = 24;
const imagesize = 22; const imagesize = 22;
@@ -678,6 +678,7 @@ const Workflows2 = (props) => {
var upload = ""; var upload = "";
const [workflows, setWorkflows] = React.useState([]); const [workflows, setWorkflows] = React.useState([]);
const [backupWorkflows, setBackupWorkflows] = React.useState([]);
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
const [selectedUsecases, setSelectedUsecases] = React.useState([]); const [selectedUsecases, setSelectedUsecases] = React.useState([]);
const [filteredWorkflows, setFilteredWorkflows] = React.useState([]); const [filteredWorkflows, setFilteredWorkflows] = React.useState([]);
@@ -756,7 +757,8 @@ const Workflows2 = (props) => {
const tabMapping = { const tabMapping = {
0: 'org_workflows', 0: 'org_workflows',
1: 'my_workflows', 1: 'my_workflows',
2: 'all_workflows' 2: 'all_workflows',
3: 'backup_apps',
}; };
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
queryParams.set('tab', tabMapping[newValue]); queryParams.set('tab', tabMapping[newValue]);
@@ -1107,23 +1109,31 @@ const Workflows2 = (props) => {
setSelectedWorkflowId(""); setSelectedWorkflowId("");
}} }}
PaperProps={{ PaperProps={{
style: { sx: {
backgroundColor: theme.palette.surfaceColor, borderRadius: theme?.palette?.DialogStyle?.borderRadius,
color: "white", border: theme?.palette?.DialogStyle?.border,
minWidth: 500, minWidth: '440px',
padding: 50, fontFamily: theme?.typography?.fontFamily,
}, backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
}} zIndex: 1000,
'& .MuiDialogContent-root': {
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
},
'& .MuiDialogTitle-root': {
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
},
}
}}
> >
<DialogTitle> <DialogTitle>
<div style={{ textAlign: "center", color: "rgba(255,255,255,0.9)" }}> <div style={{ textAlign: "center", color: theme.palette.DialogStyle?.color }}>
Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId)?.name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}? <div /> Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId)?.name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}? <div />
Other workflows relying on {selectedWorkflowIndexes.length > 0 ? "them" : "it"} one will stop working Other workflows relying on {selectedWorkflowIndexes.length > 0 ? "them" : "it"} one will stop working
</div> </div>
</DialogTitle> </DialogTitle>
<DialogContent <DialogContent
style={{ color: "rgba(255,255,255,0.65)", textAlign: "center" }} style={{ color: theme.palette.DialogStyle.color, textAlign: "center" }}
> >
<Button <Button
style={{}} style={{}}
@@ -1216,7 +1226,7 @@ const Workflows2 = (props) => {
data.status data.status
).then((response) => { ).then((response) => {
if (response !== undefined) { if (response !== undefined) {
toast(`Successfully imported ${data.name}`); toast.success(`Successfully imported ${data.name}`);
} }
}); });
} }
@@ -1341,21 +1351,33 @@ const Workflows2 = (props) => {
// When there are no workflows, we can set the loading to false // When there are no workflows, we can set the loading to false
setIsLoadingWorkflow(false) setIsLoadingWorkflow(false)
if (currTab !== 2) { if (currTab !== 2) {
toast("No workflows found. Showing workflow discovery") toast.info("No workflows found in this org. Feel free to look into our public workflows!" , {
timeout: 7500,
})
setCurrTab(2) setCurrTab(2)
} }
} }
var newarray = [] var newarray = []
var backupWf = []
for (var wfkey in responseJson) { for (var wfkey in responseJson) {
const wf = responseJson[wfkey] const wf = responseJson[wfkey]
if (wf.public === true || wf.hidden === true) { if (wf.public === true || wf.hidden === true) {
continue continue
} }
if (wf?.backup_config?.onprem_backup === true) {
backupWf.push(wf)
continue
}
newarray.push(wf) newarray.push(wf)
} }
if (backupWf.length > 0) {
setBackupWorkflows(backupWf)
}
var setProdFilter = false var setProdFilter = false
var actionnamelist = []; var actionnamelist = [];
@@ -2073,7 +2095,7 @@ const Workflows2 = (props) => {
} }
} else { } else {
if (bulk !== true) { if (bulk !== true) {
toast(`Deleted workflow ${id}. Child Workflows in Suborgs were also removed.`) toast.success(`Deleted workflow ${id}. Child Workflows in Suborgs were also removed.`)
} }
} }
@@ -2087,7 +2109,7 @@ const Workflows2 = (props) => {
} }
}) })
.catch((error) => { .catch((error) => {
toast(error.toString()); toast.error(error.toString());
}) })
} }
@@ -3040,7 +3062,7 @@ const Workflows2 = (props) => {
data.status, data.status,
).then((response) => { ).then((response) => {
if (response !== undefined) { if (response !== undefined) {
toast("Successfully imported " + data.name); toast.success("Imported " + data.name);
} }
}); });
} }
@@ -4322,6 +4344,17 @@ const Workflows2 = (props) => {
}} }}
/> />
{backupWorkflows.length > 0 &&
<Tab
label={`Onprem Backup (${backupWorkflows.length})`}
style={{
...tabStyle,
marginLeft: 25,
...(currTab === 3 ? tabActive : {})
}}
/>
}
<Tab <Tab
label="Org Forms" label="Org Forms"
onClick={() => { onClick={() => {
@@ -4331,7 +4364,7 @@ const Workflows2 = (props) => {
...tabStyle, ...tabStyle,
marginRight: 0, marginRight: 0,
marginLeft: 25, marginLeft: 25,
...(currTab === 3 ? tabActive : {}) ...(currTab === 4 ? tabActive : {})
}} }}
/> />
</Tabs> </Tabs>
@@ -4666,8 +4699,6 @@ const Workflows2 = (props) => {
paddingBottom: 40 paddingBottom: 40
}}> }}>
{currTab === 0 && orgWorkflows.map((data, index) => { {currTab === 0 && orgWorkflows.map((data, index) => {
// Shouldn't be a part of this list // Shouldn't be a part of this list
if (data.public === true) { if (data.public === true) {
@@ -4689,6 +4720,27 @@ const Workflows2 = (props) => {
) )
})} })}
{currTab === 3 && backupWorkflows.map((data, index) => {
// Shouldn't be a part of this list
if (data.public === true) {
return null
}
// if (firstLoad) {
// workflowDelay += 75
// } else {
// return <WorkflowPaper key={index} data={data} />
// }
return (
<span key={index}>
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
<WorkflowPaper data={data} />
{/*</Zoom>*/}
</span>
)
})}
{ {
currTab === 1 && myWorkflows.map((data, index) => { currTab === 1 && myWorkflows.map((data, index) => {
if (data.public === true) { if (data.public === true) {
@@ -5134,7 +5186,9 @@ const Workflows2 = (props) => {
}} }}
> >
<CircularProgress /> <CircularProgress />
<Typography>Loading Workflows</Typography> <Typography style={{marginTop: 5, }}>
Loading Workflows and Apps
</Typography>
</div> </div>
); );
+52 -6
View File
@@ -610,13 +610,31 @@ func deployServiceWorkers(image string) {
if defaultNetworkAttach == true || strings.ToLower(os.Getenv("SHUFFLE_DEFAULT_NETWORK_ATTACH")) == "true" { if defaultNetworkAttach == true || strings.ToLower(os.Getenv("SHUFFLE_DEFAULT_NETWORK_ATTACH")) == "true" {
targetName := "shuffle_shuffle" targetName := "shuffle_shuffle"
log.Printf("[DEBUG] Adding network attach for network %s to worker in swarm", targetName) isAttachable := false
serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{ networks, err := dockercli.NetworkList(ctx, network.ListOptions{})
Target: targetName, if err == nil {
}) for _, net := range networks {
if net.Name == targetName {
if net.Scope == "swarm" {
log.Printf("[DEBUG] Found swarm-scoped network: %s", targetName)
isAttachable = true
} else {
log.Printf("[WARNING] Network %s exist but is not swarm scoped (scope=%s)", targetName, net.Scope)
}
break
}
}
}
// FIXM: Remove this if deployment fails? if isAttachable {
serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=%s", targetName)) log.Printf("[DEBUG] Adding network attach for network %s to worker in swarm", targetName)
serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{
Target: targetName,
})
// FIXM: Remove this if deployment fails?
serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=%s", targetName))
}
} }
if dockerApiVersion != "" { if dockerApiVersion != "" {
@@ -709,6 +727,34 @@ func deployServiceWorkers(image string) {
} 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") {
log.Printf("[ERROR] Failed making service: %s", err) log.Printf("[ERROR] Failed making service: %s", err)
if strings.Contains(fmt.Sprintf("%s", err), "networks scoped to the swarm can be used") {
log.Printf("[WARNING] Swarm network attachment failed, retrying without shuffle_shuffle")
var updatedNetworks []swarm.NetworkAttachmentConfig
for _, net := range serviceSpec.Networks {
if net.Target != "shuffle_shuffle" {
updatedNetworks = append(updatedNetworks, net)
}
}
serviceSpec.Networks = updatedNetworks
var updatedEnv []string
for _, env := range serviceSpec.TaskTemplate.ContainerSpec.Env {
if !strings.HasPrefix(env, "SHUFFLE_SWARM_OTHER_NETWORK=") {
updatedEnv = append(updatedEnv, env)
}
}
serviceSpec.TaskTemplate.ContainerSpec.Env = updatedEnv
serviceOptions := types.ServiceCreateOptions{}
_, err = dockercli.ServiceCreate(
ctx,
serviceSpec,
serviceOptions,
)
if err != nil {
log.Printf("[ERROR] Failed to deploy service even without shuffle_shuffle network: %s", err)
}
}
} else { } else {
log.Printf("[WARNING] Failed deploying workers: %s", err) log.Printf("[WARNING] Failed deploying workers: %s", err)
if len(serviceSpec.Networks) > 1 { if len(serviceSpec.Networks) > 1 {