diff --git a/.github/workflows/helm-release.yml b/.github/workflows/helm-release.yml
index 4c3c6b6f..153bb9ee 100644
--- a/.github/workflows/helm-release.yml
+++ b/.github/workflows/helm-release.yml
@@ -24,13 +24,13 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- - name: Install apt dependencies
- run: |
- curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
- sudo apt-get install apt-transport-https -y --no-install-recommends
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
- sudo apt-get update
- sudo apt-get install helm -y --no-install-recommends
+ - name: Setup Helm
+ uses: azure/setup-helm@v4
+ with:
+ version: v3.18.4 # I run it locally so I know it works :b
+
+ - name: Verify Helm
+ run: helm version
- name: Set versions
run: |
diff --git a/.github/workflows/helm-test.yml b/.github/workflows/helm-test.yml
index fb0e34aa..49c20240 100644
--- a/.github/workflows/helm-test.yml
+++ b/.github/workflows/helm-test.yml
@@ -18,9 +18,9 @@ jobs:
- name: Install apt dependencies
run: |
- curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https -y --no-install-recommends
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
+ curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
+ echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm -y --no-install-recommends
diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod
index 4896209c..1711153b 100644
--- a/backend/go-app/go.mod
+++ b/backend/go-app/go.mod
@@ -4,7 +4,9 @@ go 1.24.0
toolchain go1.24.3
+//replace github.com/shuffle/opensearch-go => ../../../opensearch-go
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
+
//replace github.com/frikky/schemaless => ../../../schemaless
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
@@ -24,8 +26,8 @@ require (
github.com/gorilla/mux v1.8.1
github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0
- github.com/shuffle/shuffle-shared v0.9.25
- github.com/shuffle/singul v0.0.16
+ github.com/shuffle/shuffle-shared v0.9.34
+ github.com/shuffle/singul v0.0.17
golang.org/x/crypto v0.40.0
google.golang.org/api v0.236.0
google.golang.org/grpc v1.72.2
@@ -73,7 +75,7 @@ require (
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/frikky/schemaless v0.0.20 // indirect
+ github.com/frikky/schemaless v0.0.22 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum
index dc8366b0..d7ba4a0e 100644
--- a/backend/go-app/go.sum
+++ b/backend/go-app/go.sum
@@ -152,8 +152,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0=
github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4=
-github.com/frikky/schemaless v0.0.20 h1:S/A2pQcRN9qa2RnufvxwCeM06trjG0JLTF3urt1tFQI=
-github.com/frikky/schemaless v0.0.20/go.mod h1:m9s+6gALXhA5ZERCrJw+jI2rRtTPNa8mkl4vav9sxnY=
+github.com/frikky/schemaless v0.0.22 h1:aMc7cc/lr1zpogjGWbY0j6J2f6QqyfPbbW6Y9JgTAqE=
+github.com/frikky/schemaless v0.0.22/go.mod h1:m9s+6gALXhA5ZERCrJw+jI2rRtTPNa8mkl4vav9sxnY=
github.com/fsouza/go-dockerclient v1.12.1 h1:FMoLq+Zhv9Oz/rFmu6JWkImfr6CBgZOPcL+bHW4gS0o=
github.com/fsouza/go-dockerclient v1.12.1/go.mod h1:OqsgJJcpCwqyM3JED7TdfM9QVWS5O7jSYwXxYKmOooY=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
@@ -363,10 +363,10 @@ github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1G
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
-github.com/shuffle/shuffle-shared v0.9.21 h1:ZxA/g0Lk93miHJpVYAhtTQmtWLJWcCtm6kFNF772Ynk=
-github.com/shuffle/shuffle-shared v0.9.21/go.mod h1:PhDEizuz4SmJaSmy0+yrFWwD1mXVUsy8/knKlrqF1qw=
-github.com/shuffle/singul v0.0.16 h1:dW+0Mln9R1aUJ0fjikpWcxbjoQWqJHxe4kSxh2tQN5E=
-github.com/shuffle/singul v0.0.16/go.mod h1:LYkp320A6gsoPlYbXUM+WvEPUVAuutlSsqnVKyRy4gs=
+github.com/shuffle/shuffle-shared v0.9.34 h1:4RtGRMoYtBQia2CLbuH7aPFM+EMFXbH0ccIQsBXs8n8=
+github.com/shuffle/shuffle-shared v0.9.34/go.mod h1:WbLPLyBrh86ieRBgDyjC43M2qBF8a7E8jO8Pv7t8SOk=
+github.com/shuffle/singul v0.0.17 h1:mxaPtj6z85Nf6tl7L2gwDliTfEZtRQqApuu9iKcP75o=
+github.com/shuffle/singul v0.0.17/go.mod h1:8c42n1NahhCIPxzLxwp9eYbWkvY4+ct0jfbhkRsRRsY=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
diff --git a/backend/go-app/main.go b/backend/go-app/main.go
index 38752409..633cd6f4 100755
--- a/backend/go-app/main.go
+++ b/backend/go-app/main.go
@@ -5,6 +5,8 @@ import (
"github.com/shuffle/shuffle-shared"
"github.com/shuffle/singul/pkg"
+ "net/http/pprof"
+
"archive/zip"
"bufio"
"bytes"
@@ -46,6 +48,7 @@ import (
newscheduler "github.com/carlescere/scheduler"
"golang.org/x/crypto/bcrypt"
"gopkg.in/yaml.v3"
+ "sort"
// Web
"github.com/gorilla/mux"
@@ -67,11 +70,13 @@ var debug = false
//var syncUrl = "http://localhost:5002"
type retStruct struct {
- Success bool `json:"success"`
- SyncFeatures shuffle.SyncFeatures `json:"sync_features"`
- SessionKey string `json:"session_key"`
- IntervalSeconds int64 `json:"interval_seconds"`
- Reason string `json:"reason"`
+ Success bool `json:"success"`
+ SyncFeatures shuffle.SyncFeatures `json:"sync_features"`
+ SessionKey string `json:"session_key"`
+ IntervalSeconds int64 `json:"interval_seconds"`
+ Reason string `json:"reason"`
+ Subscriptions []shuffle.PaymentSubscription `json:"subscriptions"`
+ Licensed bool `json:"licensed"`
}
type Contact struct {
@@ -933,6 +938,78 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
log.Printf("[DEBUG] Failed to get org during getinfo: %s", err)
}
+ childOrgs := []shuffle.Org{}
+ if len(org.CreatorOrg) > 0 {
+ childOrgs, err = shuffle.GetAllChildOrgs(ctx, org.CreatorOrg)
+ if err != nil {
+ log.Printf("[ERROR] Failed to get child orgs during getinfo: %s", err)
+ childOrgs = []shuffle.Org{}
+ }
+ }
+
+ failToLoadOrgs := []string{}
+ sort.Slice(childOrgs, func(i, j int) bool {
+ return childOrgs[i].Created < childOrgs[j].Created
+ })
+
+ parentOrg := &shuffle.Org{}
+ if len(org.CreatorOrg) > 0 {
+ parentOrg, err = shuffle.GetOrg(ctx, org.CreatorOrg)
+ if err != nil {
+ log.Printf("[ERROR] Failed to get parent org during getinfo: %s", err)
+ parentOrg = &shuffle.Org{}
+ }
+ licenseOrg := shuffle.HandleCheckLicense(ctx, *parentOrg)
+ parentOrg = &licenseOrg
+ }
+
+ limit := 3
+
+ if parentOrg.Licensed && parentOrg.SyncFeatures.MultiTenant.Active {
+ limit = int(parentOrg.SyncFeatures.MultiTenant.Limit)
+ }
+
+ for index, org := range childOrgs {
+
+ if index < limit {
+ continue
+ }
+
+ failToLoadOrgs = append(failToLoadOrgs, org.Id)
+ }
+
+ if len(org.CreatorOrg) > 0 && len(childOrgs) > limit && shuffle.ArrayContains(failToLoadOrgs, userInfo.ActiveOrg.Id) {
+
+ userInfo.ActiveOrg = shuffle.OrgMini{
+ Id: parentOrg.Id,
+ Name: parentOrg.Name,
+ Role: userInfo.Role,
+ Branding: parentOrg.Branding,
+ Image: parentOrg.Image,
+ }
+
+ if parentOrg.Licensed {
+ log.Printf("[INFO] Parent org %s is licensed. But Multi-tenant feature is not active. Moving user %s to parent org %s", parentOrg.Name, userInfo.Username)
+ } else {
+ log.Printf("[INFO] Parent org %s has more than 3 child orgs and is not licensed. Moving user %s to parent org %s", parentOrg.Name, userInfo.Username)
+ }
+
+ err = shuffle.SetUser(ctx, &userInfo, false)
+ if err != nil {
+ log.Printf("[WARNING] Failed setting user to parent org: %s", err)
+ }
+
+ reason := "Parent org has more than 3 child orgs and is not licensed. Moving user to parent org. Contact support@shuffler.io for more information"
+
+ if parentOrg.Licensed {
+ reason = fmt.Sprintf("Parent organization is licensed, but the maximum number of sub-organizations (%d) has been reached. You have been moved to the parent organization. Please contact support@shuffler.io for further assistance.", parentOrg.SyncFeatures.MultiTenant.Limit)
+ }
+
+ resp.WriteHeader(200)
+ resp.Write([]byte(`{"success": true, "reason": "` + reason + `", "switch_parent": true}`))
+ return
+ }
+
//if err == nil {
if len(org.Id) > 0 {
if userInfo.Role == "" {
@@ -1082,7 +1159,8 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
// Check for licensing/branding of parent and override
parentOrg, err := shuffle.GetOrg(ctx, parentOrgId)
if err == nil {
- if parentOrg.LeadInfo.IntegrationPartner {
+ parent := shuffle.HandleCheckLicense(ctx, *parentOrg)
+ if parent.SyncFeatures.Branding.Active {
parsedStatus = append(parsedStatus, "integration_partner")
// except theme take from parent org
@@ -1120,11 +1198,15 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
break
}
}
+ } else {
+ userInfo.ActiveOrg.Branding = shuffle.OrgBranding{}
}
}
} else {
// for parent org branding
- if org.LeadInfo.IntegrationPartner {
+ licenseOrg := shuffle.HandleCheckLicense(ctx, *org)
+ org = &licenseOrg
+ if org.SyncFeatures.Branding.Active {
userInfo.ActiveOrg.Branding.Theme = org.Branding.Theme
userInfo.ActiveOrg.Branding.DocumentationLink = org.Defaults.DocumentationReference
userInfo.ActiveOrg.Branding.SupportEmail = org.Branding.SupportEmail
@@ -1133,6 +1215,8 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
userInfo.ActiveOrg.Branding.BrandName = org.Branding.BrandName
parsedStatus = append(parsedStatus, "integration_partner")
+ } else {
+ userInfo.ActiveOrg.Branding = shuffle.OrgBranding{}
}
}
@@ -1198,7 +1282,6 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
}
count := len(users)
-
if count == 0 {
log.Printf("[WARNING] No users - redirecting for management user")
resp.WriteHeader(200)
@@ -2061,7 +2144,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
}
if len(hook.Workflows) == 1 {
- workflow, err := shuffle.GetWorkflow(ctx, hook.Workflows[0])
+ workflow, err := shuffle.GetWorkflow(ctx, hook.Workflows[0], true)
if err == nil {
for _, branch := range workflow.Branches {
if branch.SourceID == hook.Id {
@@ -3816,9 +3899,12 @@ func handleCloudJob(job shuffle.CloudSyncJob) error {
// Handles jobs from remote (cloud)
func remoteOrgJobController(org shuffle.Org, body []byte) error {
type retStruct struct {
- Success bool `json:"success"`
- Reason string `json:"reason"`
- Jobs []shuffle.CloudSyncJob `json:"jobs"`
+ Success bool `json:"success"`
+ Reason string `json:"reason"`
+ Jobs []shuffle.CloudSyncJob `json:"jobs"`
+ SyncFeatures shuffle.SyncFeatures `json:"sync_features"`
+ Subscriptions []shuffle.PaymentSubscription `json:"subscriptions"`
+ Licensed bool `json:"licensed"`
}
responseData := retStruct{}
@@ -3885,6 +3971,31 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error {
log.Printf("Got job with reason %s and %d job(s)", responseData.Reason, len(responseData.Jobs))
}
+ cacheKey := fmt.Sprintf("org_sync_features_%s", org.Id)
+ featuresBytes, err := json.Marshal(responseData.SyncFeatures)
+ if err != nil {
+ log.Printf("[ERROR] Failed to marshal SyncFeatures for cache: %s", err)
+ } else {
+ shuffle.SetCache(ctx, cacheKey, featuresBytes, 1800)
+ }
+
+ subscriptionCacheKey := fmt.Sprintf("org_subscriptions_%s", org.Id)
+ subscriptionsBytes, err := json.Marshal(responseData.Subscriptions)
+ if err != nil {
+ log.Printf("[ERROR] Failed to marshal Subscriptions for cache: %s", err)
+ } else {
+ shuffle.SetCache(ctx, subscriptionCacheKey, subscriptionsBytes, 1800)
+ }
+
+ licenseCacheKey := fmt.Sprintf("org_licensed_%s", org.Id)
+ licensedBytes, err := json.Marshal(responseData.Licensed)
+ if err != nil {
+ log.Printf("[ERROR] Failed to marshal Licensed for cache: %s", err)
+ } else {
+
+ shuffle.SetCache(ctx, licenseCacheKey, licensedBytes, 1800)
+ }
+
for _, job := range responseData.Jobs {
err = handleCloudJob(job)
if err != nil {
@@ -4550,6 +4661,45 @@ func runInitEs(ctx context.Context) {
}
}
+ // Self-cleaning
+ go func() {
+ cursor := ""
+ cnt := 0
+ newCtx := context.Background()
+ for _, org := range activeOrgs {
+ if len(org.Id) == 0 {
+ log.Printf("[DEBUG] No ID found for org with name '%s'. Why was it made?", org.Name)
+ continue
+ }
+
+ log.Printf("[INFO] Starting self-cleanup of cache keys for org %s", org.Id)
+
+ for {
+ keys, newCursor, err := shuffle.GetAllCacheKeys(newCtx, org.Id, "", 1000, cursor)
+ if err != nil {
+ //log.Printf("[ERROR] Failed getting all cache keys for cleanup: %s", err)
+ break
+ }
+
+ if newCursor == cursor || len(newCursor) == 0 {
+ break
+ }
+
+ if len(keys) == 0 {
+ break
+ }
+
+ cursor = newCursor
+ cnt += 1
+ if cnt > 10 {
+ break
+ }
+ }
+
+ log.Printf("[INFO] Finished self-cleanup of cache keys for org %s", org.Id)
+ }
+ }()
+
log.Printf("[INFO] Finished INIT (ES)")
}
@@ -4648,6 +4798,7 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error)
org.CloudSync = false
org.SyncFeatures = shuffle.SyncFeatures{}
org.SyncConfig = shuffle.SyncConfig{}
+ org.Subscriptions = []shuffle.PaymentSubscription{}
err = shuffle.SetOrg(ctx, org, org.Id)
if err != nil {
@@ -4863,8 +5014,6 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
return
}
- log.Printf("[DEBUG] Respbody from sync: %s", string(respBody))
-
responseData := retStruct{}
err = json.Unmarshal(respBody, &responseData)
if err != nil {
@@ -4890,7 +5039,15 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
// 2. Add iterative sync schedule for interval seconds
// 3. Add another environment for the org's users
org.CloudSync = true
- org.SyncFeatures = responseData.SyncFeatures
+
+ // set cache here for 30 min
+ cacheKey := fmt.Sprintf("org_sync_features_%s", org.Id)
+ featuresBytes, err := json.Marshal(responseData.SyncFeatures)
+ if err != nil {
+ log.Printf("[ERROR] Failed to marshal SyncFeatures for cache: %s", err)
+ } else {
+ shuffle.SetCache(ctx, cacheKey, featuresBytes, 1800)
+ }
org.SyncConfig = shuffle.SyncConfig{
Apikey: responseData.SessionKey,
@@ -5362,6 +5519,11 @@ func initHandlers() {
r.HandleFunc("/api/v2/workflows/{key}/executions", shuffle.GetWorkflowExecutionsV2).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v2/workflows/generate/llm", shuffle.HandleWorkflowGenerationResponse).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v2/workflows/edit/llm", shuffle.HandleEditWorkflowWithLLM).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v2/workflows/generate", shuffle.GenerateSingulWorkflows).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v2/datastore", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v2/datastore", shuffle.HandleSetDatastoreKey).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v2/datastore/category/{category_key}", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v2/datastore/automate", shuffle.HandleDatastoreCategoryConfig).Methods("POST", "OPTIONS")
// New for recommendations in Shuffle
r.HandleFunc("/api/v1/recommendations/get_actions", shuffle.HandleActionRecommendation).Methods("POST", "OPTIONS")
@@ -5456,6 +5618,10 @@ func initHandlers() {
r.HandleFunc("/api/v1/orgs/{orgId}/stats/{key}", shuffle.GetSpecificStats).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/statistics", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v1/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v1/stats", shuffle.HandleAppendStatistics).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/stats/{key}", shuffle.GetSpecificStats).Methods("GET", "OPTIONS")
+
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
@@ -5511,6 +5677,17 @@ func initHandlers() {
r.HandleFunc("/api/v1/dashboards/{key}/widgets", shuffle.HandleNewWidget).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/dashboards/{key}/widgets/{widget_id}", shuffle.HandleGetWidget).Methods("GET", "OPTIONS")
+ if (strings.ToLower(os.Getenv("SHUFFLE_DEBUG_MEMORY")) == "true" || strings.ToLower(os.Getenv("DEBUG_MEMORY")) == "true") {
+ log.Printf("[DEBUG] Memory debugging is enabled on /debug/pprof")
+ r.HandleFunc("/debug/pprof/", pprof.Index)
+ r.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
+ r.HandleFunc("/debug/pprof/profile", pprof.Profile)
+ r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
+ r.HandleFunc("/debug/pprof/trace", pprof.Trace)
+ } else {
+ log.Printf("[DEBUG] Memory debugging is disabled. To enable, set SHUFFLE_DEBUG_MEMORY or DEBUG_MEMORY to true")
+ }
+
r.Use(shuffle.RequestMiddleware)
http.Handle("/", r)
}
diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go
index 3cf355ab..54635430 100755
--- a/backend/go-app/walkoff.go
+++ b/backend/go-app/walkoff.go
@@ -249,7 +249,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
resp.Write([]byte(`{"success": true}`))
}
-// FIXME: Authenticate this one? Can org ID be auth enough?
+// FIXME: Authenticate this one. Can org ID be auth enough?
// (especially since we have a default: shuffle)
func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
cors := shuffle.HandleCors(resp, request)
@@ -325,7 +325,7 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
orgId = env.OrgId
}
- executionRequests, err := shuffle.GetWorkflowQueue(ctx, environment, 100)
+ executionRequests, err := shuffle.GetWorkflowQueue(ctx, environment, 100, *env)
if err != nil {
resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
@@ -336,47 +336,11 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
if len(executionRequests.Data) == 0 {
executionRequests.Data = []shuffle.ExecutionRequest{}
} else {
-
// Try again? I don't think this is necessary, and shouldn't really ever occur.
- /*
- if len(env.Id) == 0 && len(env.Name) == 0 {
- timeNow := int64(time.Now().Unix())
- foundId := ""
- for _, requestData := range executionRequests.Data {
- execution, err := shuffle.GetWorkflowExecution(ctx, requestData.ExecutionId)
- if err == nil {
- if len(execution.ExecutionOrg) > 0 {
- foundId = execution.ExecutionOrg
- break
- }
- }
- }
-
- if len(environment) > 0 {
-
- env, err := shuffle.GetEnvironment(ctx, environment, foundId)
- if err != nil {
- log.Printf("[WARNING] No env found matching %s - continuing without updating orborus anyway: %s", environment, err)
- //resp.WriteHeader(401)
- //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No env found matching %s"}`, id)))
- //return
- } else {
- if timeNow > env.Edited+60 {
- env.RunningIp = request.RemoteAddr
- env.Checkin = timeNow
- err = shuffle.SetEnvironment(ctx, env)
- if err != nil {
- log.Printf("[WARNING] Failed updating environment: %s", err)
- }
- }
- }
- }
- }
- */
-
if len(executionRequests.Data) > 50 {
executionRequests.Data = executionRequests.Data[0:49]
}
+
}
newjson, err := json.Marshal(executionRequests)
@@ -1847,13 +1811,12 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000, 0)
if err != nil {
log.Printf("{WARNING] Failed getting apps (getworkflowapps): %s", err)
- resp.WriteHeader(401)
+ resp.WriteHeader(400)
resp.Write([]byte(`{"success": false}`))
return
}
newapps := workflowapps
-
if len(user.PrivateApps) > 0 {
found := false
for _, item := range user.PrivateApps {
diff --git a/docker-compose.yml b/docker-compose.yml
index c1c27efb..8ab7e3b9 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -91,6 +91,27 @@ services:
- shuffle
restart: unless-stopped
+ # OPTIONAL: advanced monitoring with cAdvisor
+ # If you have a seperate orborus config YAML on another server,
+ # Please add it there as well if you want to monitor that server too.
+ # FYI: both servers will show up on different dashboards.
+ # cadvisor:
+ # image: gcr.io/cadvisor/cadvisor:latest
+ # volumes:
+ # - /:/rootfs:ro
+ # - /var/run:/var/run:ro
+ # - /sys:/sys:ro
+ # - /var/lib/docker/:/var/lib/docker:ro
+ # - /dev/disk/:/dev/disk:ro
+ # - /var/run/docker.sock:/var/run/docker.sock:ro
+ # ports:
+ # - "8080:8080"
+ # privileged: true
+ # devices:
+ # - /dev/kmsg:/dev/kmsg
+ # networks:
+ # - shuffle
+
#memcached:
# image: memcached:latest
# container_name: shuffle-cache
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 9425a1c6..18083aea 100755
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -25,6 +25,7 @@ import AgentUI from "./views/AgentUI.jsx";
import Welcome from "./views/Welcome.jsx";
import Dashboard from "./views/Dashboard.jsx";
import DashboardView from "./views/DashboardViews.jsx";
+import NewDashboard from "./views/NewDashboard.jsx";
import AdminSetup from "./views/AdminSetup.jsx";
import Admin from "./views/Admin.jsx";
import Docs from "./views/Docs.jsx";
@@ -167,6 +168,47 @@ const App = (message, props) => {
});
};
+ useEffect(() => {
+ if (userdata &&
+ userdata?.org_status?.includes("integration_partner") &&
+ userdata?.active_org?.image) {
+
+ const existingLinks = document.querySelectorAll("link[rel*='icon']");
+ existingLinks.forEach(link => link.parentNode.removeChild(link));
+ const newLink = document.createElement('link');
+ newLink.rel = 'icon';
+ newLink.type = 'image/x-icon';
+ try {
+ if (typeof userdata.active_org.image === 'string') {
+ if (userdata.active_org.image.startsWith('data:')) {
+ newLink.href = userdata.active_org.image;
+ } else if (userdata.active_org.image.startsWith('/')) {
+ newLink.href = userdata.active_org.image;
+ } else {
+ let mimeType = 'image/png';
+ newLink.href = `data:${mimeType};base64,${userdata.active_org.image}`;
+ }
+ } else {
+ console.error("Image data is not in expected format:", userdata.active_org.image);
+ return;
+ }
+
+ // Add the new favicon link to head
+ document.head.appendChild(newLink);
+ const iframe = document.createElement('iframe');
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+ iframe.contentDocument.write('');
+ setTimeout(() => {
+ document.body.removeChild(iframe);
+ }, 100);
+
+ } catch (error) {
+ console.error("Error updating favicon:", error);
+ }
+ }
+ }, [userdata]);
+
const checkLogin = () => {
var baseurl = globalUrl;
fetch(`${globalUrl}/api/v1/getinfo`, {
@@ -180,6 +222,13 @@ const App = (message, props) => {
var userInfo = {};
if (responseJson.success === true) {
//console.log("USER: ", responseJson);
+ if (responseJson?.switch_parent === true) {
+ toast.info(responseJson.reason)
+ setTimeout(() => {
+ window.location.reload();
+ }, 3000);
+ return
+ }
userInfo = responseJson;
setIsLoggedIn(true);
@@ -824,6 +873,21 @@ const App = (message, props) => {
/>
}
/>
+
+
+ }
+ />
+
{
App Authentication
- Control the authentication options for individual apps.
+ Control the authentication options for individual apps. These keys are write-only, and cannot be viewed after creation. If you want editable secrets (e.g. for use in code), use Protected Keys.
- Learn more about App Authentication
+ Learn more
@@ -1787,7 +1787,7 @@ const Hits = ({
if (selectedAppData.authentication === undefined || selectedAppData.authentication === null) {
setAuthenticationType({
- type: "",
+ type: "",
})
selectedAppData.authentication = {
@@ -1955,6 +1955,7 @@ const Hits = ({
if (data === undefined || data === null) {
return;
}
+
const filteredData = data.filter((appAuth) => appAuth?.app?.id === appid);
if (filteredData.length === 0) {
setAppAuthentication([]);
@@ -1965,7 +1966,7 @@ const Hits = ({
}
};
- const HandleAppAuthentication = ()=>{
+ const HandleAppAuthentication = () => {
const url = `${globalUrl}/api/v1/apps/authentication`;
diff --git a/frontend/src/components/AppCreationModal.jsx b/frontend/src/components/AppCreationModal.jsx
index 28dea461..6a66f0e0 100644
--- a/frontend/src/components/AppCreationModal.jsx
+++ b/frontend/src/components/AppCreationModal.jsx
@@ -21,8 +21,9 @@ import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'
import CreateIcon from '@mui/icons-material/Create'
import { toast } from 'react-toastify'
import YAML from "yaml";
+import Dropzone from "./Dropzone.jsx";
-const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
+const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud, startOpenApi = false, prefillOpenApiData = "" }) => {
const [openApiModal, setOpenApiModal] = useState(false)
const [generateAppModal, setGenerateAppModal] = useState(false)
const [openApi, setOpenApi] = useState("")
@@ -35,6 +36,16 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
const navigate = useNavigate()
const upload = useRef()
+ useEffect(() => {
+ if (open && (startOpenApi || (prefillOpenApiData && prefillOpenApiData.length > 0))) {
+ if (prefillOpenApiData && prefillOpenApiData.length > 0) {
+ setOpenApiData(prefillOpenApiData)
+ setIsDropzone(true)
+ }
+ setOpenApiModal(true)
+ }
+ }, [open, startOpenApi, prefillOpenApiData])
+
// Style for the create options
const AppCreateButton = ({ text, func, icon }) => {
const [hover, setHover] = React.useState(false)
@@ -467,6 +478,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
}
}}
>
+ {
Paste in the URI for the OpenAPI or find out
-
How to find URI for openAPI?
@@ -568,31 +583,54 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
Must point to a version 2 or 3 OpenAPI specification.
-
+
Or upload a YAML or JSON specification
-
-
+
+
+ Drag & drop your OpenAPI (YAML/JSON) anywhere
+
+
+ or click to browse files
+
+