diff --git a/backend/Dockerfile b/backend/Dockerfile index 5919a05e..9e27ffbe 100755 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23 as builder +FROM golang:1.24 as builder # Add files RUN mkdir /app diff --git a/backend/app_gen/openapi/baseline/requirements.txt b/backend/app_gen/openapi/baseline/requirements.txt index dfad3eb9..c0d2f96f 100755 --- a/backend/app_gen/openapi/baseline/requirements.txt +++ b/backend/app_gen/openapi/baseline/requirements.txt @@ -1,3 +1,11 @@ # No extra requirements needed -requests -urllib3 +requests==2.32.3 +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 diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go index 9f0c72e2..b7ee7e6d 100755 --- a/backend/go-app/docker.go +++ b/backend/go-app/docker.go @@ -211,7 +211,7 @@ func fixTags(tags []string) []string { func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error { ctx := context.Background() client, err := client.NewEnvClient() - defer client.Close() + defer client.Close() if err != nil { log.Printf("Unable to create docker client: %s", 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 } @@ -671,7 +682,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) { resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "No image name"}`))) return - + } log.Printf("[INFO] Trying to download image: '%s'. Appname: '%s'. BaseAppname: '%s', Split2: %s", version.Name, appname, baseAppname, appnameSplit2) @@ -870,7 +881,7 @@ func handleRemoteDownloadApp(resp http.ResponseWriter, ctx context.Context, user type tmpapp struct { Success bool `json:"success"` OpenAPI string `json:"openapi"` - App string `json:"app"` + App string `json:"app"` } app := tmpapp{} diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 46a339f1..aeb3e7e0 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -1,63 +1,66 @@ module shuffle -go 1.23.0 +go 1.24.0 -toolchain go1.23.8 +toolchain go1.24.3 //replace github.com/frikky/schemaless => ../../../schemaless -//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi //replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi + require ( cloud.google.com/go/datastore v1.20.0 - cloud.google.com/go/storage v1.51.0 + cloud.google.com/go/storage v1.55.0 github.com/basgys/goxml2json v1.1.0 github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 - github.com/docker/docker v28.0.4+incompatible + github.com/docker/docker v28.2.2+incompatible github.com/frikky/kin-openapi v0.42.0 github.com/fsouza/go-dockerclient v1.12.1 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-billy/v5 v5.6.2 - github.com/go-git/go-git/v5 v5.14.0 + github.com/go-git/go-git/v5 v5.16.1 github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.8.56 - golang.org/x/crypto v0.36.0 - google.golang.org/api v0.228.0 - google.golang.org/grpc v1.71.1 + github.com/shuffle/shuffle-shared v0.8.84 + golang.org/x/crypto v0.38.0 + google.golang.org/api v0.236.0 + google.golang.org/grpc v1.72.2 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/client-go v0.32.3 + k8s.io/api v0.33.1 + k8s.io/apimachinery v0.33.1 + k8s.io/client-go v0.33.1 ) require ( - cel.dev/expr v0.19.2 // indirect - cloud.google.com/go v0.118.3 // indirect - cloud.google.com/go/auth v0.15.0 // indirect + cel.dev/expr v0.20.0 // indirect + cloud.google.com/go v0.121.1 // indirect + cloud.google.com/go/auth v0.16.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/iam v1.4.1 // indirect - cloud.google.com/go/monitoring v1.24.0 // indirect - cloud.google.com/go/scheduler v1.11.4 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect + cloud.google.com/go/scheduler v1.11.7 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.5 // indirect - github.com/adrg/strutil v0.2.3 // indirect - github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/strutil v0.3.1 // indirect + github.com/algolia/algoliasearch-client-go/v3 v3.31.4 // indirect github.com/bitly/go-simplejson v0.5.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.6.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -69,9 +72,10 @@ 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.13 // indirect + github.com/frikky/schemaless v0.0.16 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -80,26 +84,26 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-github/v28 v28.1.1 // indirect - github.com/google/go-querystring v1.0.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/googleapis/gax-go/v2 v2.14.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.15.9 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -107,55 +111,58 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opensearch-project/opensearch-go v1.1.0 // indirect github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/sashabaranov/go-openai v1.40.0 // indirect + github.com/sashabaranov/go-openai v1.40.1 // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect - github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect + github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect - go4.org v0.0.0-20201209231011-d4a079459e60 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect + google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index a3382d6a..098db5c0 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4= -cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= +cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -9,48 +9,48 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME= -cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= -cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= -cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw= +cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= +cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM= cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew= -cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM= -cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q= -cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY= -cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= -cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/scheduler v1.11.4 h1:ewVvigBnEnrr9Ih8CKnLVoB5IiULaWfYU5nEnnfVAto= -cloud.google.com/go/scheduler v1.11.4/go.mod h1:0ylvH3syJnRi8EDVo9ETHW/vzpITR/b+XNnoF+GPSz4= +cloud.google.com/go/scheduler v1.11.7 h1:zkMEJ0UbEJ3O7NwEUlKLIp6eXYv1L7wHjbxyxznajKM= +cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw= -cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc= -cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= -cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= +cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= +cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= +cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= +cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krnnfGj4pV65YLUm3C0/8bl7V5Nry2Pwvy3ru/wLc= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= @@ -62,12 +62,12 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= -github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= -github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= -github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ= -github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4= +github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA= +github.com/algolia/algoliasearch-client-go/v3 v3.31.4 h1:UJhx6AhZCYf0qZygDz2c1x1+1q2q2sfzsRaQM6yswWk= +github.com/algolia/algoliasearch-client-go/v3 v3.31.4/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -90,14 +90,14 @@ github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUK github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w= github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 h1:9bAydALqAjBfPHd/eAiJBHnMZUYov8m2PkXVr+YGQeI= github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82/go.mod h1:tyA14J0sA3Hph4dt+AfCjPrYR13+vVodshQSM7km9qw= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -105,10 +105,14 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -122,8 +126,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= -github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= +github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -148,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.13 h1:ARiN9V7wr2VZXAr9JK5wvTbyPgpGrgeiL1VhR5MlgaQ= -github.com/frikky/schemaless v0.0.13/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= +github.com/frikky/schemaless v0.0.16 h1:4d2ZktB9xGsAusbbKliOI8TuriSrdIMzD/6ToY3wkz8= +github.com/frikky/schemaless v0.0.16/go.mod h1:jT48kTcmr1q3o8i+8qe7g+eCsbwaz2Q9CjOJevQQzQs= 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= @@ -164,10 +168,12 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= -github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= +github.com/go-git/go-git/v5 v5.16.1 h1:TuxMBWNL7R05tXsUGi0kh1vi4tq0WfXNLlIrAkXG1k8= +github.com/go-git/go-git/v5 v5.16.1/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -207,12 +213,13 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -220,11 +227,10 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= @@ -243,8 +249,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= @@ -268,8 +274,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -284,12 +290,16 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= @@ -309,8 +319,8 @@ github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M= github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ= @@ -323,26 +333,25 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/sashabaranov/go-openai v1.19.2 h1:+dkuCADSnwXV02YVJkdphY8XD9AyHLUWwk6V7LB6EL8= -github.com/sashabaranov/go-openai v1.19.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sashabaranov/go-openai v1.40.1 h1:bJ08Iwct5mHBVkuvG6FEcb9MDTfsXdTYPGjYLRdeTEU= +github.com/sashabaranov/go-openai v1.40.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= -github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA= -github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= +github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs= +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.8.39 h1:ylRj+2xGIOPQfpawf45udnQzKLtTP9JFEFyREc2GJXM= -github.com/shuffle/shuffle-shared v0.8.39/go.mod h1:z+ISGBgNINmZvWNrtGTc51yVG+pMkpBFu9ZLVlTyuag= +github.com/shuffle/shuffle-shared v0.8.84 h1:ElIMQYjKBVOiadbiGkSzt/lPU5xqaQwRxQvk9wx/xYM= +github.com/shuffle/shuffle-shared v0.8.84/go.mod h1:RdfNxqCPI+zU4jQKy3E/p4Io2injm7LpSKQUCDHNtLk= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -352,9 +361,13 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -364,6 +377,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -373,38 +387,40 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= -go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -412,8 +428,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -454,7 +470,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -463,15 +478,15 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -480,8 +495,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -495,7 +510,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -507,14 +521,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -525,8 +539,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= @@ -572,8 +586,8 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs= -google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= +google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0= +google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -594,12 +608,12 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= -google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -607,8 +621,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= @@ -632,23 +646,23 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= +k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= +k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= +k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= +k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -656,7 +670,10 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/backend/go-app/main.go b/backend/go-app/main.go index fbd2a251..cfbc768a 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -11,17 +11,18 @@ import ( "crypto/md5" "strconv" + "os" + "io" + "log" + "fmt" + "errors" + "net/url" + "os/exec" + "net/http" + "io/ioutil" + "math/rand" "encoding/hex" "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - "os/exec" "net/http/httptest" "strings" @@ -35,9 +36,9 @@ import ( "github.com/go-git/go-git/v5" "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" http2 "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/storage/memory" // Random xj "github.com/basgys/goxml2json" @@ -61,6 +62,7 @@ var registryName = "registry.hub.docker.com" var runningEnvironment = "onprem" var syncUrl = "https://shuffler.io" +//var syncUrl = "http://localhost:5002" type retStruct struct { 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 { // Returns false if there is an issue // Use this for register - err := shuffle.CheckPasswordStrength(password) + err := shuffle.CheckPasswordStrength(username, password) if err != nil { log.Printf("[WARNING] Bad password strength: %s", err) return err @@ -460,8 +462,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) } ctx := context.Background() - //users, err := FindUser(ctx context.Context, username string) ([]User, error) { - users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(username))) if err != nil && len(users) == 0 { 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.Orgs = []string{org.Id} - // FIXME - Remove this later if role == "admin" { newUser.Role = "admin" newUser.Roles = []string{"admin"} @@ -1069,7 +1068,6 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { activatedAppIds = append(activatedAppIds, app.ID) } - returnValue := shuffle.HandleInfo{ Success: true, Username: userInfo.Username, @@ -1092,6 +1090,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { Priorities: orgPriorities, Licensed: licensed, ActiveApps: activatedAppIds, + Theme: userInfo.Theme, } returnData, err := json.Marshal(returnValue) @@ -1852,6 +1851,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { // return //} + log.Printf("[DEBUG] HOOKS: webhook callback: %s", request.URL.String()) + if request.Method != "POST" { request.Method = "POST" } @@ -1863,6 +1864,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { path := strings.Split(request.URL.String(), "/") if len(path) < 4 { + log.Printf("[DEBUG] HOOKS: Invalid webhook path: %s", request.URL.String()) resp.WriteHeader(403) resp.Write([]byte(`{"success": false}`)) return @@ -1878,7 +1880,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { if location[1] == "api" { if len(location) <= 4 { 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}`)) 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 userAgent := request.Header.Get("User-Agent") 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) hook, err := shuffle.GetHook(ctx, hookId) if err != nil { - log.Printf("[WARNING] Failed getting hook %s (callback): %s", hookId, err) - resp.WriteHeader(401) + log.Printf("[WARNING] HOOKS: Failed getting hook %s (callback): %s", hookId, err) + resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } @@ -1930,21 +1934,21 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { //resp.WriteHeader(200) //resp.Write([]byte(`{"success": true}`)) if hook.Status == "stopped" { - log.Printf("[WARNING] Not running %s because hook status is stopped", hook.Id) - resp.WriteHeader(401) + log.Printf("[WARNING] HOOKS: Not running %s because hook status is stopped", hook.Id) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Is it running?"}`))) return } if len(hook.Workflows) == 0 { - log.Printf("[DEBUG] Not running because hook isn't connected to any workflows") - resp.WriteHeader(401) + log.Printf("[DEBUG] HOOKS: Not running because hook isn't connected to any workflows") + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`))) return } 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 @@ -1960,7 +1964,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { body, err := ioutil.ReadAll(request.Body) if err != nil { - log.Printf("[DEBUG] Body data error: %s", err) + log.Printf("[DEBUG] HOOKS: data read error: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -2001,7 +2005,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { b, err := json.Marshal(newBody) 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.Write([]byte(`{"success": false}`)) return @@ -2017,7 +2021,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { } 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) } @@ -2029,7 +2033,6 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { // OrgId: activeOrgs[0].Id, workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest, hook.OrgId) - if err == nil { if hook.Version == "v2" { timeout := 15 @@ -2064,6 +2067,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { } else { resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId))) } + return } @@ -2071,6 +2075,10 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { 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) { @@ -3087,7 +3095,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s 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) } else { 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) if len(user.Id) > 0 { resp.WriteHeader(200) @@ -3799,30 +3806,57 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { } } - if org.SyncConfig.WorkflowBackup { - workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "") - if err != nil { - log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err) - } else { - backupJob.Workflows = workflows - } + // Check if it's 1/20 times (600 seconds - 10 min on average) + // Only problem: May take time to sync the first time, which is annoying + // This is to ensure that we don't spam the shuffle cloud servers with a lot of data + shouldBackupData := false + randomNumber := rand.Intn(20) + if randomNumber == 0 { + shouldBackupData = true } - if org.SyncConfig.AppBackup && len(org.Users) > 0 { - - apps, err := shuffle.GetPrioritizedApps(ctx, foundUser) - if err != nil { - log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err) - } else { - backupJob.Apps = apps + // Just to prevent it from spamming large outbound requests + if shouldBackupData { + if org.SyncConfig.WorkflowBackup { + workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "") + if err != nil { + log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err) + } else { + backupJob.Workflows = workflows + } } - } - 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 + if org.SyncConfig.AppBackup && len(org.Users) > 0 { + foundUser.ActiveOrg.Id = org.Id + apps, err := shuffle.GetPrioritizedApps(ctx, foundUser) + if err != nil { + 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) @@ -3859,6 +3893,7 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { //log.Printf("[ERROR] Failed cloud sync job controller run for '%s': %s", respBody, err) return err } + return nil } @@ -3996,6 +4031,8 @@ func runInitEs(ctx context.Context) { time.Sleep(30 * time.Second) } + // FIXME: This should ONLY run on one backend instance + schedules, err := shuffle.GetAllSchedules(ctx, "ALL") if err != nil { log.Printf("[WARNING] Failed getting schedules during service init: %s", err) @@ -4139,7 +4176,7 @@ func runInitEs(ctx context.Context) { } //interval := int(org.SyncConfig.Interval) - interval := 15 + interval := 30 if interval == 0 { log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id) continue @@ -4241,17 +4278,20 @@ func runInitEs(ctx context.Context) { 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 } - //respBody, err := ioutil.ReadAll(newresp.Body) - //if err != nil { - // log.Printf("[ERROR] Failed setting respbody %s", err) - // continue - //} - //log.Printf("[DEBUG] Successfully ran workflow cleanup request for %s. Body: %s", environment, string(respBody)) + if newresp.StatusCode != 200 { + if !strings.Contains(string(respBody), "is active") { + log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d. Body: %s", environment, newresp.StatusCode, string(respBody)) + } + + continue + } url = fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/rerun", backendPort, environment) req, err = http.NewRequest( @@ -4377,7 +4417,7 @@ func runInitEs(ctx context.Context) { } if os.Getenv("SHUFFLE_HEALTHCHECK_DISABLED") != "true" { - healthcheckInterval := 60 + healthcheckInterval := 60 log.Printf("[INFO] Starting healthcheck job every %d minute. Stats available on /api/v1/health/stats, and dashboard on /health. Disable with SHUFFLE_HEALTHCHECK_DISABLED=true", healthcheckInterval) job := func() { // Prepare a fake http.responsewriter @@ -4669,7 +4709,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { // If you want to disable cloud sync, see previous section. if org.CloudSync { 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."}`))) return } @@ -4746,6 +4786,9 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { org.SyncConfig = shuffle.SyncConfig{ Apikey: responseData.SessionKey, Interval: responseData.IntervalSeconds, + + WorkflowBackup: true, + AppBackup: true, } interval := int(responseData.IntervalSeconds) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 14c5ac28..2331a273 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -281,54 +281,18 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) { ctx := shuffle.GetContext(request) env, err := shuffle.GetEnvironment(ctx, orgId, "") + if err != nil { + log.Printf("[WARNING] No env found matching %s - continuing without updating orborus anyway: %s", orgId, err) + } + timeNow := time.Now().Unix() - if err == nil && len(env.Id) > 0 && len(env.Name) > 0 && request.Method == "POST" { - // Updates every 60 seconds~ - if time.Now().Unix() > env.Edited+60 { - env.RunningIp = shuffle.GetRequestIp(request) - - // Orborus label = custom label for Orborus - if len(orborusLabel) > 0 { - env.RunningIp = orborusLabel - } - - // Set the checkin cache - - - body, err := ioutil.ReadAll(request.Body) - if err == nil { - var envData shuffle.OrborusStats - err = json.Unmarshal(body, &envData) - if err == nil { - envData.RunningIp = env.RunningIp - - marshalled, err := json.Marshal(envData) - if err == nil { - cacheKey := fmt.Sprintf("queueconfig-%s-%s", env.Name, env.OrgId) - go shuffle.SetCache(context.Background(), cacheKey, marshalled, 2) - } - - - - if envData.Swarm { - env.Licensed = true - env.RunType = "docker" - } - - if envData.Kubernetes { - env.RunType = "k8s" - } - - envData.DataLake = env.DataLake - } - } - - env.Checkin = timeNow - err = shuffle.SetEnvironment(ctx, env) - if err != nil { - log.Printf("[ERROR] Failed updating environment: %s", err) - } + err = shuffle.HandleOrborusFailover(ctx, request, resp, env) + if err != nil { + if !strings.Contains(err.Error(), "mismatch") { + log.Printf("[WARNING] Failed handling Orborus failover: %s", err) } + + return } //log.Printf("Found env: %#v", env) @@ -1905,8 +1869,8 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { err = shuffle.SetSchedule(ctx, newSchedule) if err != nil { - log.Printf("Failed setting cloud schedule: %s", err) - resp.WriteHeader(401) + log.Printf("[ERROR] Failed setting cloud schedule: %s", err) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -1941,17 +1905,22 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { // FIXME - real error message lol if err != nil { - log.Printf("Failed creating schedule: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid argument. Try cron */15 * * * *"}`))) + log.Printf("[ERROR] Failed creating schedule: %s", err) + + 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 } //workflow.Schedules = append(workflow.Schedules, schedule) err = shuffle.SetWorkflow(ctx, *workflow, workflow.ID) if err != nil { - log.Printf("Failed setting workflow for schedule: %s", err) - resp.WriteHeader(401) + log.Printf("[ERROR] Failed setting workflow for schedule: %s", err) + resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } @@ -3039,7 +3008,13 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) { shouldRerun = true } - workflowExecution, err := shuffle.PrepareSingleAction(ctx, user, fileId, body, runValidationAction) + decisionId := "" + decision, decisionOk := query["decision_id"] + if decisionOk && len(decision) > 0 { + decisionId = decision[0] + } + + workflowExecution, err := shuffle.PrepareSingleAction(ctx, user, fileId, body, runValidationAction, decisionId) debugUrl := fmt.Sprintf("/workflows/%s?execution_id=%s", workflowExecution.Workflow.ID, workflowExecution.ExecutionId) resp.Header().Add("X-Debug-Url", debugUrl) @@ -3101,7 +3076,12 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) { return } - returnBody := shuffle.HandleRetValidation(ctx, workflowExecution, 1) + actionId := "" + if len(workflowExecution.Workflow.Actions) == 1 { + actionId = workflowExecution.Workflow.Actions[0].ID + } + + returnBody := shuffle.HandleRetValidation(ctx, workflowExecution, 1, actionId) returnBytes, err := json.Marshal(returnBody) if err != nil { log.Printf("[ERROR] Failed to marshal retStruct in single execution: %s", err) diff --git a/docker-compose.yml b/docker-compose.yml index adf6b533..0ff2c29c 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: frontend: - image: ghcr.io/shuffle/shuffle-frontend:latest + image: ghcr.io/shuffle/shuffle-frontend:nightly container_name: shuffle-frontend hostname: shuffle-frontend ports: @@ -14,7 +14,7 @@ services: depends_on: - backend backend: - image: ghcr.io/shuffle/shuffle-backend:latest + image: ghcr.io/shuffle/shuffle-backend:nightly container_name: shuffle-backend hostname: ${BACKEND_HOSTNAME} # Here for debugging: @@ -33,7 +33,7 @@ services: - SHUFFLE_FILE_LOCATION=/shuffle-files restart: unless-stopped orborus: - image: ghcr.io/shuffle/shuffle-orborus:latest + image: ghcr.io/shuffle/shuffle-orborus:nightly container_name: shuffle-orborus hostname: shuffle-orborus networks: @@ -55,13 +55,13 @@ services: - SHUFFLE_STATS_DISABLED=true - SHUFFLE_LOGS_DISABLED=true - SHUFFLE_SWARM_CONFIG=run - - SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:latest + - SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:nightly env_file: .env restart: unless-stopped security_opt: - seccomp:unconfined opensearch: - image: opensearchproject/opensearch:2.19.1 + image: opensearchproject/opensearch:3.0.0 hostname: shuffle-opensearch container_name: shuffle-opensearch environment: diff --git a/frontend/public/images/icons/shuffleLogo.svg b/frontend/public/images/icons/shuffleLogo.svg new file mode 100755 index 00000000..1024dd6a --- /dev/null +++ b/frontend/public/images/icons/shuffleLogo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/public/images/singul_green.png b/frontend/public/images/singul_green.png new file mode 100644 index 00000000..796ad6ef Binary files /dev/null and b/frontend/public/images/singul_green.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7fa7c847..13289352 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { Link, Route, Routes, BrowserRouter, useNavigate } from "react-router-dom"; import { CookiesProvider } from "react-cookie"; @@ -12,12 +12,16 @@ import Header from "./components/NewHeader.jsx"; import HealthPage from "./components/HealthPage.jsx"; //import Header from "./components/Header.jsx"; -import theme from "./theme.jsx"; +import theme, { getTheme } from "./theme.jsx"; import Apps from "./views/Apps.jsx"; import Apps2 from "./views/Apps2.jsx"; import AppCreator from "./views/AppCreator.jsx"; import DetectionDashBoard from "./views/DetectionDashboard.jsx"; +// LLM related tests +import ChatBot from "./components/ChatBot.jsx"; +import AgentUI from "./views/AgentUI.jsx"; + import Welcome from "./views/Welcome.jsx"; import Dashboard from "./views/Dashboard.jsx"; import DashboardView from "./views/DashboardViews.jsx"; @@ -59,7 +63,7 @@ import 'react-toastify/dist/ReactToastify.css'; import Drift from "react-driftjs"; -import { AppContext } from './context/ContextApi.jsx'; +import { Context } from './context/ContextApi.jsx'; import Navbar from "./components/Navbar.jsx"; import Workflows2 from "./views/Workflows2.jsx"; import AppExplorer from "./views/AppExplorer.jsx"; @@ -88,7 +92,10 @@ const App = (message, props) => { const [dataset, setDataset] = useState(false) const [isLoaded, setIsLoaded] = useState(false) const [curpath, setCurpath] = useState(typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname) - + const { themeMode, handleThemeChange, setBrandColor, brandColor,setThemeMode} = useContext(Context); + const currentTheme = getTheme(themeMode, brandColor); + const mainColor = currentTheme?.palette?.backgroundColor + const [isPreviousThemeLight, setIsPreviousThemeLight] = useState(false) useEffect(() => { if (dataset === false) { @@ -98,6 +105,29 @@ const App = (message, props) => { } }, []); + useEffect(() => { + const isDarkPath = curpath === "/" || curpath === "/pricing" || curpath === "/partners" || curpath === "/faq" || curpath === "/professional-services" || curpath === "/contact" || curpath === "/training"; + if (curpath && isDarkPath) { + if (themeMode === "light") { + setIsPreviousThemeLight(true) + } + handleThemeChange("dark") + }else if ( !isDarkPath && isPreviousThemeLight && userdata?.active_org?.branding?.theme === "light") { + handleThemeChange("light") + setIsPreviousThemeLight(false) + } + + if (isDarkPath && userdata && userdata?.active_org?.branding?.brand_color !== "#ff8544") { + setBrandColor("#ff8544") + }else if(!isDarkPath && userdata && userdata?.active_org?.branding?.brand_color !== "#ff8544" && userdata?.active_org?.branding?.brand_color?.length > 0) { + const brandColor = localStorage.getItem("brandColor") + if (brandColor !== null && brandColor !== undefined && brandColor.length > 0) { + setBrandColor(brandColor) + } + } + + }, [themeMode, curpath, userdata]) + if ( isLoaded && !isLoggedIn && @@ -162,7 +192,16 @@ const App = (message, props) => { { path: "/" } ); } - } + if (responseJson?.theme?.length > 0) { + handleThemeChange(responseJson.theme) + }else{ + handleThemeChange("dark") + } + }else { + handleThemeChange("dark") + setThemeMode("dark") + localStorage.removeItem("theme"); + } // Handling Ethereum update @@ -185,7 +224,7 @@ const App = (message, props) => { const includedData =
{ /> } /> - } /> + } /> { { checkLogin={checkLogin} userdata={userdata} globalUrl={globalUrl} - surfaceColor={theme.palette.surfaceColor} - inputColor={theme.palette.inputColor} + surfaceColor={currentTheme.palette.surfaceColor} + inputColor={currentTheme.palette.inputColor} {...props} /> } @@ -555,8 +594,8 @@ const App = (message, props) => { /> } /> - } /> - } /> + } /> + } /> { /> } /> - } /> - } /> - } /> + } /> + } /> + } /> - } /> - } /> - } /> + } /> + } /> + } /> { /> } /> - } /> + } /> { /> } /> + + } + /> + + } + /> { /> } /> + + + } + /> + {
return ( - - + @@ -866,11 +954,10 @@ const App = (message, props) => { pauseOnFocusLoss draggable pauseOnHover - theme="dark" + theme={themeMode} /> - ); }; diff --git a/frontend/src/components/AdminNavBar.jsx b/frontend/src/components/AdminNavBar.jsx index e0faec6e..2a480c95 100644 --- a/frontend/src/components/AdminNavBar.jsx +++ b/frontend/src/components/AdminNavBar.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useContext, memo } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import OrganizationTab from '../components/OrganizationTab.jsx'; +import PartnerTab from '../components/PartnerTab.jsx'; import UserManagmentTab from '../components/UserManagmentTab.jsx'; import CacheView from "../components/CacheView.jsx"; import Files from "../components/Files.jsx"; @@ -19,17 +20,104 @@ import { FmdGoodOutlined as FmdGoodOutlinedIcon, GroupOutlined as GroupOutlinedIcon } from '@mui/icons-material'; -import theme from '../theme.jsx'; -import { Button, Tooltip } from '@mui/material'; +import theme, { getTheme } from '../theme.jsx'; +import { Button, Skeleton, Tooltip } from '@mui/material'; import { Index } from 'react-instantsearch-dom'; import { Context } from '../context/ContextApi.jsx'; +import { toast } from 'react-toastify'; + +const PartnerIcon = ({ strokeColor, fillColor = 'transparent', width = 22, height = 22 }) => ( + + + +); const AdminNavBar = (props) => { const location = useLocation(); - const { globalUrl, userdata, isCloud, isLoaded,removeCookie, handleStatusChange, selectedStatus, setSelectedStatus, handleEditOrg, serverside, notifications, handleGetOrg, orgId, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } = props; + const { globalUrl, userdata, isCloud,isOrgLoaded, isLoaded,removeCookie, handleStatusChange, selectedStatus, setSelectedStatus, handleEditOrg, serverside, notifications, handleGetOrg, orgId, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } = props; const [selectedItem, setSelectedItem] = useState("Organization"); const [isSelectedFiles, setIsSelectedFiles] = useState(true); const [isSelectedDataStore, setIsSelectedDataStore] = useState(true); + const [isIntegrationPartner, setIsIntegrationPartner] = useState(false); + const [isChildOrg, setIsChildOrg] = useState(false); + const [isGlobalUser, setIsGlobalUser] = useState(false); + const [visibleItems, setVisibleItems] = useState([]); + const [isUserDataLoaded, setIsUserDataLoaded] = useState(false); + + + useEffect(() => { + if (userdata && userdata?.active_org?.id?.length > 0) { + setIsUserDataLoaded(true); + } + }, [userdata]); + + + + const { themeMode, brandColor } = React.useContext(Context); + const theme = getTheme(themeMode, brandColor); + + const HandlePartnerChange = () => { + if (userdata?.id?.length > 0) { + const isIntegrationPartner = userdata?.org_status?.includes("integration_partner") || false; + setIsIntegrationPartner(isIntegrationPartner); + const isChildOrg = userdata?.org_status?.includes("sub_org") || false; + setIsChildOrg(isChildOrg); + const isGlobalUser = userdata?.active_org?.branding?.global_user || false; + setIsGlobalUser(isGlobalUser); + } else { + setIsIntegrationPartner(false); + setIsChildOrg(false); + setIsGlobalUser(false); + } + } + + useEffect(() => { + if (userdata && userdata?.id?.length > 0) { + HandlePartnerChange(); + } + }, [userdata]); + + const HandleVisibleTabs = () => { + if (userdata?.id?.length > 0) { + if (userdata?.active_org?.role === "admin" || userdata?.support) { + setVisibleItems(items); + }else { + const filteredItems = items.filter(item => item.text !== "Users" && item.text !== "Files" && item.text !== "Datastore" && item.text !== "Triggers" && item.text !== "Locations"); + setVisibleItems(filteredItems); + } + } + } + + useEffect(() => { + if (isIntegrationPartner && isChildOrg && !isGlobalUser) { + // Filter out Users and Tenants tabs + if (userdata?.active_org?.role === "admin" || userdata?.support) { + const filteredItems = items.filter(item => + item.text !== "Users" && item.text !== "Tenants" + ); + setVisibleItems(filteredItems); + }else { + const filteredItems = items.filter(item => + item.text !== "Users" && item.text !== "Tenants" && item.text !== "Files" && item.text !== "Datastore" && item.text !== "Triggers" && item.text !== "Locations" + ); + setVisibleItems(filteredItems); + } + } else { + HandleVisibleTabs(); + } + }, [isIntegrationPartner, isChildOrg, isGlobalUser, selectedOrganization, userdata]); const navigate = useNavigate(); @@ -39,7 +127,17 @@ const AdminNavBar = (props) => { useEffect(() => { const queryParams = new URLSearchParams(location.search); const tabName = queryParams.get('tab'); - + const partnerTab = queryParams.get('partner_tab'); + + // if(!isCloud){ + // if(tabName === "partner" || partnerTab !== null) { + // setSelectedItem("Organization"); + // navigate(`?tab=organization`, { replace: true }); + // } + // } + if (partnerTab) { + setSelectedItem("Partner"); + } if (tabName === "environments") { setSelectedItem("Locations"); } else if (tabName === "suborgs") { @@ -48,14 +146,15 @@ const AdminNavBar = (props) => { setSelectedItem("Datastore"); }else if (tabName) { setSelectedItem(tabName.charAt(0).toUpperCase() + tabName.slice(1)); - } else { - setSelectedItem("Organization"); + }else if (tabName === "partner") { + setSelectedItem("Partner"); } + }, [location.search]); - const items = [ - { iconSrc: , alt: "Organization Icon", text: "Organization", component: OrganizationTab, props: { globalUrl,removeCookie, selectedStatus, isLoaded, setSelectedStatus, handleStatusChange, handleEditOrg, handleGetOrg, userdata, isCloud, serverside, notifications, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } }, + { iconSrc: , alt: "Organization Icon", text: "Organization", component: OrganizationTab, props: { isIntegrationPartner, isChildOrg, isGlobalUser,globalUrl,removeCookie, selectedStatus, isLoaded, setSelectedStatus, handleStatusChange, handleEditOrg, handleGetOrg, userdata, isCloud, serverside, notifications, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } }, + { iconSrc: undefined, alt: "Partner Icon", text: "Partner", component: PartnerTab, props: { globalUrl,removeCookie, isLoaded, handleGetOrg, userdata, isCloud, serverside, checkLogin, setSelectedOrganization, selectedOrganization } }, { iconSrc: , alt: "Users Icon", text: "Users", component: UserManagmentTab, props: { globalUrl, userdata, serverside, isCloud, selectedOrganization, setSelectedOrganization, handleEditOrg } }, { iconSrc: , alt: "App Auth Icon", text: "App_auth", component: AppAuthTab, props: { globalUrl, userdata, isCloud, selectedOrganization } }, { iconSrc: , alt: "Datastore Icon", text: "Datastore", component: CacheView, props: { globalUrl, userdata, selectedOrganization, serverside, isSelectedDataStore, orgId , isCloud} }, @@ -63,7 +162,7 @@ const AdminNavBar = (props) => { { iconSrc: , alt: "Trigger Icon", text: "Triggers", component: SchedulesTab, props: { globalUrl, userdata, isCloud, serverside } }, { iconSrc: , alt: "Environments Icon", text: "Locations", component: EnvironmentTab, props: { globalUrl, userdata, isCloud, selectedOrganization } }, { iconSrc: , alt: "Tenants Icon", text: "Tenants", component: TenantsTab, props: {isCloud, globalUrl, userdata, serverside, selectedOrganization, setSelectedOrganization, checkLogin } } - ]; + ].filter(Boolean); const setConfig = (newValue) => { setSelectedItem(newValue); @@ -76,19 +175,80 @@ const AdminNavBar = (props) => { } }; + useEffect(() => { + if (isIntegrationPartner && isChildOrg && !isGlobalUser && isOrgLoaded && isUserDataLoaded) { + const queryParams = new URLSearchParams(location.search); + const tabName = queryParams?.get('admin_tab')?.toLowerCase(); + if (tabName === "sso" || tabName === "branding") { + toast.info("You are not allowed to access this tab. Please contact your admin for more information. Redirecting to Organization Configuration tab."); + setTimeout(() => { + setSelectedItem("Organization"); + navigate(`?admin_tab=org_config`, { replace: true }); + window.location.reload(); + } + , 3000); + } + + const params = new URLSearchParams(location.search); + const tab = params?.get('tab')?.toLowerCase(); + if (tab === "users" || tab === "tenants") { + toast.info("You are not allowed to access this tab. Please contact your admin for more information. Redirecting to Organization Configuration tab."); + setTimeout(() => { + setSelectedItem("Organization"); + navigate(`?admin_tab=org_config`, { replace: true }); + window.location.reload(); + } + , 3000); + } + } else if (userdata && isOrgLoaded && isUserDataLoaded && userdata?.active_org?.role !== "admin" && !userdata?.support) { + const queryParams = new URLSearchParams(location.search); + const tabName = queryParams?.get('admin_tab')?.toLowerCase(); + if (tabName === "sso") { + toast.info("You are not allowed to access this tab. Please contact your admin for more information. Redirecting to Organization Configuration tab."); + setTimeout(() => { + setSelectedItem("Organization"); + navigate(`?admin_tab=org_config`, { replace: true }); + window.location.reload(); + } + , 3000); + } + + const params = new URLSearchParams(location.search); + const tab = params?.get('tab')?.toLowerCase(); + if (tab === "users" || tab === "locations" || tab === "environments" || tab === "files" || tab === "datastore" || tab === "triggers") { + toast.info("You are not allowed to access this tab. Please contact your admin for more information. Redirecting to Organization Configuration tab."); + setTimeout(() => { + setSelectedItem("Organization"); + navigate(`?admin_tab=org_config`, { replace: true }); + window.location.reload(); + } + , 3000); + } + } + }, [isIntegrationPartner, isChildOrg, isGlobalUser, location.search, userdata, isOrgLoaded, isUserDataLoaded]); + + const renderComponent = () => { - const selectedItemData = items.find(item => item.text === selectedItem); + const selectedItemData = visibleItems.find(item => item.text === selectedItem); if (!selectedItemData) { setSelectedItem("Organization"); // If no tab is specified, default to "Organization" tab - return ; + return ; }; const ComponentToRender = selectedItemData.component; const componentProps = selectedItemData.props; - return ; - }; + const updatedProps = { + ...componentProps, + notifications: notifications, + setNotifications: setNotifications, + userdata: userdata, + selectedOrganization: selectedOrganization + }; + + return ; + }; const defaultImage = "/images/logos/orange_logo.svg" const imageData = @@ -97,15 +257,16 @@ const AdminNavBar = (props) => { : selectedOrganization?.image; return ( + !isOrgLoaded && !isUserDataLoaded ? :
-
- {renderComponent()} + + + {renderComponent()} +
); }; export default AdminNavBar; +const Loader = () => { + const dummyItems = Array.from({ length: 6 }); + const dummyNavItems = Array.from({ length: 6 }); + const dummyTabItems = ['Org Configuration', 'SSO', 'Notifications', 'Billing & Stats', 'Branding']; + const { leftSideBarOpenByClick, windowWidth, themeMode } = useContext(Context); + const theme = getTheme(themeMode); + return ( +
+ +
+
+ + +
+ + {/* Divider */} + + + {/* Nav Items */} +
+ {dummyNavItems.map((_, index) => ( +
+ + +
+ ))} +
+
+
+
+ {dummyTabItems.map((_, index) => ( +
+ +
+ ))} +
+
+
+ + +
+ {dummyItems.map((_, index) => ( +
+ +
+ ))} +
+
+
+
+
+ ); +}; + + const PaddingWrapper2 = memo(({ children }) => { return ( -
+
{children}
) diff --git a/frontend/src/components/ApiExplorer.jsx b/frontend/src/components/ApiExplorer.jsx index e6080301..d1321220 100644 --- a/frontend/src/components/ApiExplorer.jsx +++ b/frontend/src/components/ApiExplorer.jsx @@ -29,7 +29,7 @@ import { Tooltip, } from "@mui/material"; import throttle from "lodash/throttle"; -import theme from "../theme.jsx"; +import {getTheme} from "../theme.jsx"; import { validateJson, collapseField, } from "../views/Workflows.jsx"; import DeleteIcon from "@mui/icons-material/Delete"; @@ -37,6 +37,8 @@ import { Context } from "../context/ContextApi.jsx"; function CustomTabPanel(props) { const { children, value, index, ...other } = props; + const {themeMode} = useContext(Context); + const theme = getTheme(themeMode) return (
{value === index && ( - + {children} )} @@ -100,6 +102,8 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se const [selectedActionIndex, setSelectedActionIndex] = useState(0); const [ExampleBody, setExampleBody] = useState({}); const [filteredActions, setFilteredActions] = useState([]); + const {themeMode} = useContext(Context); + const theme = getTheme(themeMode) const [firstSendDone, setFirstSendDone] = useState(false) @@ -1391,6 +1395,8 @@ const ActionsList = memo(({ const [searchQuery, setSearchQuery] = useState(""); const [visibleActions, setVisibleActions] = useState([]); + const {themeMode} = useContext(Context); + const theme = getTheme(themeMode); useEffect(() => { @@ -1457,13 +1463,13 @@ const ActionsList = memo(({
0 ? openapi?.info["x-logo"] : theme?.palette?.defaultImage} width={48} height={48} alt="app logo" style={{ marginLeft: 20, borderRadius: 8 }} /> - {info.title} @@ -1471,7 +1477,7 @@ const ActionsList = memo(({ ) : ( - Api Explorer @@ -1504,7 +1510,7 @@ const ActionsList = memo(({ style={{ marginLeft: 20, marginTop: 15, - backgroundColor: "#1a1a1a", + backgroundColor: theme.palette.backgroundColor, overflowY: "auto", height: (isLoaded && isLoggedIn) ? "calc(100vh - 190px)" : "calc(100vh - 260px)", paddingRight: 5, @@ -1522,7 +1528,7 @@ const ActionsList = memo(({ textTransform: "none", backgroundColor: selectedActionIndex === actionIndex - ? "#3f3f3f" + ? theme.palette.hoverColor : "transparent", border: "none", justifyContent: "flex-start", @@ -1535,7 +1541,7 @@ const ActionsList = memo(({ textWrap: "nowrap", textOverflow: "ellipsis", "&:hover": { - backgroundColor: "#2f2f2f", + backgroundColor: theme.palette.hoverColor, }, }} onClick={() => handleActionClick(actionIndex, action)} @@ -1554,7 +1560,7 @@ const ActionsList = memo(({ )) ) : ( -
+
No actions found
)} @@ -1602,6 +1608,8 @@ const Action = memo(( const [disableExecuteButton, setDisableExecuteButton] = useState(false); const [showResponseLoader, setShowResponseLoader] = useState(false); const [appAuthentication, setAppAuthentication] = useState([]) + const {themeMode} = useContext(Context); + const theme = getTheme(themeMode); const parseHeaders = (headersString) => { if (headersString?.length > 0) { const headersArray = headersString.split("\n"); @@ -2082,7 +2090,7 @@ const Action = memo(( fontWeight: 700, marginLeft: 40, marginBottom: 5, - color: "rgba(241, 241, 241, 1)", + color: theme.palette.textColor }} > {actionname} @@ -2095,7 +2103,7 @@ const Action = memo(( borderRadius: 6, marginLeft: "40px", marginTop: 2, - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, height: 51, alignItems: 'center', }} @@ -2109,7 +2117,7 @@ const Action = memo(( backgroundColor: "transparent", "& .MuiSelect-select": { color: RequestMethods.find((method) => method.value === selectedMethod) - ?.color || "#212121", + ?.color || theme.palette.textFieldStyle.backgroundColor, }, }} MenuProps={{ @@ -2117,14 +2125,14 @@ const Action = memo(( sx: { padding: 0, margin: 0, - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, }, }, MenuListProps: { sx: { padding: 0, margin: 0, - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, }, }, }} @@ -2135,20 +2143,20 @@ const Action = memo(( value={method.value} sx={{ color: method.color, - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, border: "none", marginBottom: 0.25, "&:hover": { backgroundColor: method.color, - color: "#f9fcf5", + color: theme.palette.textFieldStyle.color, }, "&.Mui-selected": { backgroundColor: method.color, - color: "#f9fcf5", + color: theme.palette.textFieldStyle.color, border: "none", "&:hover": { backgroundColor: method.color, - color: "#f9fcf5", + color: theme.palette.textFieldStyle.color, }, }, "&.Mui-focusVisible": { @@ -2167,9 +2175,8 @@ const Action = memo(( inputProps={{ style: { margin: "auto", - backgroundColor: "transparent", border: "none", - color: "rgba(241, 241, 241, 1)", + color: theme.palette.textFieldStyle.color, display: 'flex', height: '100%', alignItems: 'center', @@ -2331,7 +2338,7 @@ const Action = memo(( style={{ display: "flex", justifyContent: "center", - background: "rgba(26, 26, 26, 1)", + background: theme.palette.backgroundColor, }} > ), style: { - backgroundColor: "rgba(33, 33, 33, 1)", + backgroundColor: theme.palette.platformColor, padding: "4px 8px", }, }} @@ -2600,7 +2607,7 @@ const Action = memo(( - + {action.name.replaceAll("_", " ")} -

+

{action.description ? action.description : ""} @@ -2813,6 +2820,8 @@ const ActionResponse = memo(({ apiResponse, ExampleBody, isLoggedIn, isLoaded }) const [responseTabIndex, setResponseTabIndex] = useState(0) const [oldResponse, setOldResponse] = useState(apiResponse) const [highlight, setHighlight] = useState(false) + const {themeMode } = useContext(Context) + const theme = getTheme(themeMode) const MIN_HEIGHT = 50 @@ -2934,7 +2943,7 @@ const ActionResponse = memo(({ apiResponse, ExampleBody, isLoggedIn, isLoaded }) style={{ width: '100%', height: height, - backgroundColor: '#1a1a1a', + backgroundColor: theme.palette.backgroundColor, display: 'flex', flexDirection: 'column', borderTop: '1px solid rgba(255,255,255,0.2)', @@ -2988,7 +2997,7 @@ const ActionResponse = memo(({ apiResponse, ExampleBody, isLoggedIn, isLoaded }) { + + const {themeMode } = useContext(Context) + const theme = getTheme(themeMode) + const handleReactJsonClipboard = (copy) => { const elementName = "copy_element_shuffle"; let copyText = document.getElementById(elementName); @@ -3036,7 +3049,7 @@ const ResponseTabWrapper = memo(({ apiResponse }) => { { return collapseField(jsonField) }} @@ -3049,7 +3062,8 @@ const ResponseTabWrapper = memo(({ apiResponse }) => { )}) const PaddingWrapper = memo(({ isLoggedIn, isLoaded, children }) => { - const { leftSideBarOpenByClick, windowWidth } = useContext(Context); + const { leftSideBarOpenByClick, windowWidth, themeMode } = useContext(Context); + const theme = getTheme(themeMode) return (

{ ? windowWidth >= 1920 ? "calc(100% - 630px)" : "calc(100% - 570px)" : windowWidth >= 1920 ? "calc(100vw - 460px)": "calc(100% - 410px)" : windowWidth >= 1920 ? "calc(100% - 370px)" : "calc(100% - 320px)", - backgroundColor: "#1a1a1a", + backgroundColor: theme.palette.backgroundColor, position: "fixed", bottom: 0, right: 0, diff --git a/frontend/src/components/AppAuthTab.jsx b/frontend/src/components/AppAuthTab.jsx index f5821a1f..8c65366e 100644 --- a/frontend/src/components/AppAuthTab.jsx +++ b/frontend/src/components/AppAuthTab.jsx @@ -13,7 +13,7 @@ import { } from "@mui/icons-material"; import { useNavigate } from "react-router-dom"; import { toast } from "react-toastify"; -import theme from "../theme.jsx"; +import {getTheme} from "../theme.jsx"; import Markdown from "react-markdown"; import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; import { isMobile } from "react-device-detect" @@ -66,7 +66,7 @@ import { Context } from '../context/ContextApi.jsx'; const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" ) const AppAuthTab = memo((props) => { @@ -83,16 +83,17 @@ const AppAuthTab = memo((props) => { const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState(""); const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]); const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState(""); + const [selectedSubOrg, setSelectedSubOrg] = useState([]); const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState(""); const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]); const [searchQuery, setSearchQuery] = React.useState(""); const [showAppModal, setShowAppModal] = useState(false) + const [selectedAuthId, setSelectedAuthId] = useState(""); + const [showDistributionPopup, setShowDistributionPopup] = useState(false); const [showAuthenticationLoader, setShowAuthenticationLoader] = useState(true) - const [showAppAuthGroupLoader, setShowAppAuthGroupLoader] = useState(true) - const changeDistribution = (data) => { - //changeDistributed(data, !isDistributed) - editAuthenticationConfig(data.id, "suborg_distribute") - } + const [showAppAuthGroupLoader, setShowAppAuthGroupLoader] = useState(true) + const { themeMode, supportEmail, brandColor } = useContext(Context) + const theme = getTheme(themeMode, brandColor) useEffect(() => { getAppAuthentication(); @@ -212,11 +213,39 @@ const AppAuthTab = memo((props) => { }); }; - const editAuthenticationConfig = (id, parentAction) => { + const handleSelectSubOrg = (id, action) => { + if (action === "all") { + const childOrgs = userdata.orgs.filter( + (data) => data.creator_org === userdata.active_org.id + ); + setSelectedSubOrg((prev) => { + if (prev.length === childOrgs.length) { + // If all child orgs are already selected, clear the selection + return []; + } else { + // Otherwise, select all child org IDs + return childOrgs.map((data) => data.id); + } + }); + } else if (action === "none") { + setSelectedSubOrg([]); + } else { + setSelectedSubOrg((prev) => { + if (prev.includes(id)) { + return prev.filter((data) => data !== id); + } else { + return [...prev, id]; + } + }); + } + }; + + const editAuthenticationConfig = (id, parentAction, selectedSuborgs) => { const data = { id: id, action: parentAction !== undefined && parentAction !== null ? parentAction : "assign_everywhere", - } + selected_suborgs: selectedSuborgs !== undefined && selectedSuborgs !== null ? selectedSuborgs : [], + } const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config"; @@ -238,6 +267,7 @@ const AppAuthTab = memo((props) => { } else { toast("Successfully updated auth!"); setSelectedUserModalOpen(false); + setShowDistributionPopup(false); setTimeout(() => { getAppAuthentication(); }, 1000); @@ -249,6 +279,106 @@ const AppAuthTab = memo((props) => { }); }; + + const changeDistribution = (id, selectedSubOrg) => { + + editAuthenticationConfig(id, "suborg_distribute", [...new Set(selectedSubOrg)]) + } + + + const cacheDistributionModal = showDistributionPopup ? ( + {setShowDistributionPopup(false);setSelectedAuthId("")}} + PaperProps={{ + sx: { + borderRadius: theme?.palette?.DialogStyle?.borderRadius, + border: theme?.palette?.DialogStyle?.border, + fontFamily: theme?.typography?.fontFamily, + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + zIndex: 1000, + minWidth: "600px", + minHeight: "320px", + overflow: "auto", + '& .MuiDialogContent-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogTitle-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogActions-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + }, + }} + > + + + Select sub-org to distribute Datastore key + + + + {handleSelectSubOrg(null, "none")}}>None + {handleSelectSubOrg(null, "all")}}>All + {userdata.orgs.map((data, index) => { + if (data.creator_org !== userdata.active_org.id) { + return null; + } + + const imagesize = 22; + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + marginRight: 10, + marginLeft: data.id === userdata.active_org.id ? 0 : 20, + }; + + const image = data.image === "" ? ( + {data.name} + ) : ( + {data.name} + ); + + return ( + handleSelectSubOrg(data.id)} + style={{ display: "flex", alignItems: "center" }} + > + + {image} + {data.name} + + ); + })} + +
+ + +
+
+
+ ) : null; + const editAuthenticationModal = selectedAuthenticationModalOpen ? ( { }} > - + Edit authentication for {selectedAuthentication.app.name.replaceAll("_", " ")} ( {selectedAuthentication.label}) @@ -297,7 +427,7 @@ const AppAuthTab = memo((props) => { InputProps={{ style: { height: 50, - color: "white", + color: theme.palette.textColor, }, }} color="primary" @@ -349,7 +479,7 @@ const AppAuthTab = memo((props) => { InputProps={{ style: { height: 50, - color: "white", + color: theme.palette.textColor, }, }} color="primary" @@ -568,7 +698,7 @@ const AppAuthTab = memo((props) => { }) .then((responseJson) => { if (responseJson.success === false) { - toast("Failed to create. Please try again, or contact support@shuffler.io") + toast(`Failed to create. Please try again, or contact ${supportEmail}`) } else { // Close the modal setAppAuthenticationGroupModalOpen(false) @@ -660,7 +790,7 @@ const AppAuthTab = memo((props) => { }} > - App Authentication Groups + App Authentication Groups @@ -677,7 +807,7 @@ const AppAuthTab = memo((props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.textColor, fontSize: "1em", }, }} @@ -853,22 +983,23 @@ const AppAuthTab = memo((props) => { ) : null; return ( -
+
{appModal} -
+ {cacheDistributionModal} +
-

App Authentication

-
- + App Authentication +
+ Control the authentication options for individual apps. - +   Learn more about App Authentication @@ -877,7 +1008,7 @@ const AppAuthTab = memo((props) => { {isCloud ? @@ -2410,7 +2612,7 @@ const Hits = ({ style={{ height: "100%", width: 1, - backgroundColor: "white", + backgroundColor: theme.palette.textColor, marginLeft: 50, marginRight: 50, }} @@ -2505,7 +2707,7 @@ const Hits = ({ height: 480, overflowY: "auto", scrollbarWidth: "thin", - scrollbarColor: "#494949 #2f2f2f", + scrollbarColor: theme.palette.scrollbarColor, width: "100%", }} > @@ -2536,7 +2738,7 @@ const Hits = ({ elevation={0} style={{ ...paperStyle, - backgroundColor: mouseHoverIndex === index ? "#2F2F2F" : "rgba(26, 26, 26, 1)", + backgroundColor: mouseHoverIndex === index ? theme.palette.cardHoverColor : theme.palette.cardBackgroundColor, width: "100%", }} onMouseEnter={() => setMouseHoverIndex(index)} @@ -2595,7 +2797,7 @@ const Hits = ({ gap: 8, textOverflow: "ellipsis", whiteSpace: "nowrap", - color: "#F1F1F1", + color: theme.palette.textColor, }} > {normalizedString(data.name)} @@ -2638,7 +2840,7 @@ const Hits = ({ ))}
-
diff --git a/frontend/src/components/AppCreationModal.jsx b/frontend/src/components/AppCreationModal.jsx index a9f8f598..28dea461 100644 --- a/frontend/src/components/AppCreationModal.jsx +++ b/frontend/src/components/AppCreationModal.jsx @@ -42,9 +42,8 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { const parsedStyle = { flex: 1, - padding: 20, + padding: "30px 20px 20px", margin: 12, - paddingTop: 30, backgroundColor: hover && !makeFancy ? theme.palette.surfaceColor : "transparent", cursor: hover ? "pointer" : "default", textAlign: "center", @@ -346,23 +345,23 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { // Common dialog styles const dialogStyle = { borderRadius: 2, - border: "1px solid #494949", + border: theme.palette.DialogStyle.border, minWidth: '500px', fontFamily: theme?.typography?.fontFamily, - backgroundColor: "#1A1A1A", + backgroundColor: theme.palette.DialogStyle.backgroundColor, zIndex: 1000, '& .MuiDialogContent-root': { - backgroundColor: "#1A1A1A", + backgroundColor: theme.palette.DialogStyle.backgroundColor, padding: '24px', fontFamily: theme?.typography?.fontFamily, }, '& .MuiDialogTitle-root': { - backgroundColor: "#1A1A1A", + backgroundColor: theme.palette.DialogStyle.backgroundColor, padding: '24px', fontFamily: theme?.typography?.fontFamily, }, '& .MuiDialogActions-root': { - backgroundColor: "#1A1A1A", + backgroundColor: theme.palette.DialogStyle.backgroundColor, padding: '16px 24px', fontFamily: theme?.typography?.fontFamily, }, @@ -397,7 +396,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { pl: 4, pr: 3, }}> - + Create New App { onClose() }} sx={{ - color: 'rgba(255, 255, 255, 0.7)', - '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' } + color: theme.palette.text.primary, + '&:hover': { bgcolor: theme.palette.hoverColor }, }} > @@ -477,7 +476,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { px: 4, }}> @@ -502,7 +501,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
- + Paste in the URI for the OpenAPI or find out { py: 1, '&:hover': { borderColor: '#FF8544', + color: '#FF8544', bgcolor: 'rgba(255,133,68,0.1)' }, textTransform: 'none', @@ -665,7 +665,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { px: 4, }}> @@ -681,8 +681,8 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { setValidation(false) }} sx={{ - color: 'rgba(255,255,255,0.7)', - '&:hover': { bgcolor: 'rgba(255,255,255,0.1)' } + color: theme.palette.text.primary, + '&:hover': { bgcolor: theme.palette.hoverColor } }} > @@ -690,7 +690,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { { variant="outlined" placeholder="API Documentation URL" sx={{ - bgcolor: theme.palette.platformColor, + bgcolor: theme.palette.textFieldStyle.backgroundColor, '& .MuiOutlinedInput-root': { height: '40px', - color: 'white', + color: theme.palette.text.primary, '& fieldset': { borderWidth: '1px', borderImage: "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1", @@ -757,7 +757,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => { {circularLoader} { !validation && - + This may take multiple minutes based on the size of the documentation. } diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 8a5d9a18..e647085b 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -53,7 +53,7 @@ import { const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" ); //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index e9364913..38c8581f 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { memo, useCallback, useEffect, useState, useContext } from 'react'; import { useNavigate } from 'react-router'; import { @@ -25,16 +25,17 @@ import LaunchIcon from '@mui/icons-material/Launch'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import { CloudDownloadOutlined, Delete } from '@mui/icons-material'; import { findSpecificApp } from '../components/AppFramework.jsx'; -import theme from "../theme.jsx"; +import {getTheme} from "../theme.jsx"; import YAML from 'yaml'; import { toast } from 'react-toastify'; import { Link } from 'react-router-dom'; import { InstantSearch, connectHits, connectSearchBox } from 'react-instantsearch-dom'; import algoliasearch from "algoliasearch/lite"; +import { Context } from '../context/ContextApi.jsx'; const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" );; const AppModal = ({ open, onClose, app, globalUrl, getApps}) => { @@ -51,6 +52,9 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps}) => { const [deleteModalOpen, setDeleteModalOpen] = useState(false) const [sharingConfiguration, setSharingConfiguration] = React.useState("you"); const navigate = useNavigate(); + const {themeMode} = useContext(Context); + const theme = getTheme(themeMode); + const parseUsecase = (subcase) => { const srcdata = findSpecificApp(frameworkData, subcase.type) const dstdata = findSpecificApp(frameworkData, subcase.last) @@ -516,12 +520,12 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps}) => { border: "1px solid #494949", minWidth: '440px', fontFamily: theme?.typography?.fontFamily, - backgroundColor: "#212121", + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, '& .MuiDialogContent-root': { - backgroundColor: "#212121", + backgroundColor: theme.palette.DialogStyle.backgroundColor, }, '& .MuiDialogTitle-root': { - backgroundColor: "#212121", + backgroundColor: theme.palette.DialogStyle.backgroundColor, }, '& .MuiTypography-root': { fontFamily: theme?.typography?.fontFamily, @@ -553,7 +557,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps}) => { { > )} { Cancel
- + + { + setCurrentTab(-1) + + // Force re-render + setTimeout(() => { + setCurrentTab(newValue) + }, 100); + }} + style={{ marginTop: 20 }} + TabIndicatorProps={{ + style: { + height: 3, + backgroundColor: theme.palette.primary.main, + marginLeft: 12, + marginRight: 12, + } + }} + > + + + {isCloud ? + + : null} + + + + +
+ {currentTab === 0 ? +
+ +
+ : currentTab === 1 ? +
+ +
+ : + + } +
+
) @@ -2583,19 +2687,473 @@ const Billing = memo((props) => { export default memo(Billing); -const PaddingWrapper = memo(({ clickedFromOrgTab, children }) => { +const BillingStatsChildOrg = memo(({ userdata, globalUrl, selectedOrganization, allChildOrgs, setAllChildOrgs, allChildOrgsStats, setAllChildOrgsStats }) => { + const [subOrgStats, setSubOrgStats] = useState([]); + const [subOrgs, setSubOrgs] = useState([]); + const [subOrgStatsRows, setSubOrgStatsRows] = useState([]); + const [subOrgStatsColumns, setSubOrgStatsColumns] = useState([]); + const [allOrgLoaded, setAllOrgLoaded] = useState(false); + const [allOrgStatsLoaded, setAllOrgStatsLoaded] = useState(false); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [open, setOpen] = useState(false); + const [editing, setEditing] = useState("") + const [editingOrgId, setEditingOrgId] = useState("") + const [limit, setLimit] = useState("") + const [tableCreated, setTableCreated] = useState(false) + const [searchQuery, setSearchQuery] = useState(""); + const [filteredRows, setFilteredRows] = useState([]); + const { themeMode, brandColor, supportEmail } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + + // Handle page change + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + const HanldeLoadStats = async () => { + const childOrgs = selectedOrganization.child_orgs; + if (allChildOrgsStats.length > 0){ + setSubOrgStats(allChildOrgsStats) + setAllOrgStatsLoaded(true) + if (allChildOrgsStats.length > 0 && allChildOrgs.length > 0 && subOrgStatsRows.length === 0 && subOrgStatsColumns.length === 0) { + HandleCreateTable(allChildOrgsStats, allChildOrgs) + } + return + } + const promises = childOrgs.map((org) => { + // get org stats base on region url + const baseUrl = org?.region_url?.length > 0 && !window?.location?.origin?.includes("localhost") ? org?.region_url : globalUrl; + const url = `${baseUrl}/api/v1/orgs/${org.id}/stats`; + return fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }).then((res) => res.json()); + }); + + try { + const responses = await Promise.all(promises); + setSubOrgStats(responses); + setAllChildOrgsStats(responses); + setAllOrgStatsLoaded(true); + } catch (error) { + console.error("Error loading stats:", error); + } + }; + + + + const HandleGetSuborg = async () => { + const childOrgs = selectedOrganization.child_orgs + + if (allChildOrgs.length > 0){ + setSubOrgs(allChildOrgs) + setAllOrgLoaded(true) + if (allChildOrgsStats.length > 0 && allChildOrgs.length > 0 && subOrgStatsRows.length === 0 && subOrgStatsColumns.length === 0) { + HandleCreateTable(allChildOrgsStats, allChildOrgs) + } + return + } + + const promises = childOrgs.map((org) => { + const baseUrl = org?.region_url?.length > 0 && !window?.location?.origin?.includes("localhost") ? org?.region_url : globalUrl; + const url = `${baseUrl}/api/v1/orgs/${org.id}`; + return fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }).then((res) => res.json()); + }); + try { + const responses = await Promise.all(promises); + setSubOrgs(responses); + setAllChildOrgs(responses); + setAllOrgLoaded(true); + } catch (error) { + console.error("Error loading suborgs:", error); + } + }; + + useEffect(() => { + if (subOrgStats.length === 0 && selectedOrganization && selectedOrganization?.child_orgs?.length > 0) { + HanldeLoadStats() + } + if (subOrgs.length === 0 && selectedOrganization && selectedOrganization?.child_orgs?.length > 0) { + HandleGetSuborg() + } + + }, [selectedOrganization?.child_orgs]); + + + useEffect(() => { + + if (allOrgLoaded && allOrgStatsLoaded && !tableCreated) { + HandleCreateTable(subOrgStats, subOrgs) + } + } + , [allOrgLoaded, allOrgStatsLoaded, tableCreated]) + + + const HandleCreateTable = (subOrgStats, subOrgs) => { + + if (subOrgStats.length === 0 || subOrgs.length === 0) return; + + // check whether all of the suborg.success is false + const allSubOrgStatsSuccess = subOrgStats.every((stat) => stat.success === false); + const allSubOrgsSuccess = subOrgs.every((org) => org.success === false); + + if (allSubOrgStatsSuccess || allSubOrgsSuccess) { + setSubOrgStats([]) + setSubOrgs([]) + setTableCreated(true) + return + } + + const rows = subOrgStats.map((stat, index) => { + const subOrg = subOrgs[index] + if (!subOrg) return null; + return { + id: index, + name: subOrg.name, + orgId: subOrg.id, + limit: subOrg?.sync_features?.app_executions?.limit || "N/A", + usage: stat?.monthly_app_executions || "N/A", + workflows_usage: stat?.total_workflow_executions || "N/A", + workflow_usage_limit: subOrg?.sync_features?.workflow_executions?.limit || "N/A", + } + }) + + setSubOrgStatsRows(rows) + + const columns = [ + { field: "id", headerName: "ID", width: 100 }, + { field: "name", headerName: "Name", width: 200 }, + { field: "usage", headerName: "App Execution Usage", width: 200 }, + { + field: "limit", headerName: "App Execution Limit", width: 200, renderCell: (params) => { + return ( + <> + + {params.value} + { + setOpen(true) + setEditingOrgId(params.row.orgId) + setEditing("app_executions") + if (params.value === "N/A") { + setLimit("") + } else { + setLimit(params.value) + } + }} + > + + + + + ) + } + }, + { field: "workflows_usage", headerName: "Workflow Execution Usage", width: 200 }, + { field: "workflow_usage_limit", headerName: "Workflow Execution Limit", width: 200, renderCell: (params) => { + return ( + <> + + {params.value} + + { + setOpen(true) + setEditingOrgId(params.row.orgId) + setEditing("workflow_executions") + if (params.value === "N/A") { + setLimit("") + } else { + setLimit(params.value) + } + }} + > + + + + )} + }, + ] + + setSubOrgStatsColumns(columns) + + if (allOrgLoaded && allOrgStatsLoaded && !tableCreated) { + setTableCreated(true) + } + } + + + + const HandleEditLimit = (orgId, editing, limit) => { + + + // change limit as number if string + if (typeof limit === "string") { + limit = parseInt(limit, 10) + } + if (isNaN(limit)) { + toast.error("Please enter a valid number") + return + } + + if (selectedOrganization.sync_features.app_executions.limit <= 10000) { + toast.error("Insufficient app execution limit to increase child org limit") + return + } + + // check whether limit is greater than than parent org limit + if (editing === "app_executions" && limit > selectedOrganization.sync_features.app_executions.limit && !userdata.support) { + toast.error("App execution limit cannot be greater than parent org limit") + return + } + + + if (editing === "workflow_executions" && limit > selectedOrganization.sync_features.workflow_executions.limit && !userdata.support) { + toast.error("Workflow execution limit cannot be greater than parent org limit") + return + } + + // find the org in the subOrgs array + const orgIndex = subOrgs.findIndex((org) => org.id === orgId) + if (orgIndex === -1) { + toast.error("Organization not found") + return + } + + const org = subOrgs[orgIndex] + + org.sync_features[editing].limit = limit + org.sync_features.editing = true + const sync_features = org.sync_features + const data = { + org_id: orgId, + sync_features: sync_features, + } + + const url = `${globalUrl}/api/v1/orgs/${orgId}`; + fetch(url, { + method: "POST", + credentials: "include", + crossDomain: true, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }).then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed updating org: ", responseJson.reason); + } else { + toast("Successfully change suborg limit!"); + if (editing === "app_executions") { + setSubOrgStatsRows((prevRows) => { + const newRows = [...prevRows]; + newRows[orgIndex].limit = limit; + return newRows; + }); + }else if (editing === "workflow_executions") { + setSubOrgStatsRows((prevRows) => { + const newRows = [...prevRows]; + newRows[orgIndex].workflow_usage_limit = limit; + return newRows; + }); + } + } + }) + ) + .catch((error) => { + toast("Err: " + error.toString()); + }); + + } + + const HandleClosePopUP = () => { + setOpen(false) + setEditing("") + setEditingOrgId("") + setLimit("") + } + + return ( +
+ {open && ( + + )} + + Child Organizations + +
+ + View and configure execution limits for child organizations. Click the edit icon to modify app and workflow execution limits. + +
+ {tableCreated ? ( + subOrgStatsRows.length > 0 && subOrgStatsColumns.length > 0 ? ( + <> + + + + ), + }} + onChange={(e) => { + setSearchQuery(e.target.value.toLowerCase()); + const filtered = subOrgStatsRows.filter((row) => + row.name.toLowerCase().includes(e.target.value.toLowerCase().trim()) || + row.orgId.toLowerCase().includes(e.target.value.toLowerCase().trim()) + ); + setFilteredRows(filtered); + }} + /> + + + ) : ( + + {selectedOrganization.child_orgs.length === 0 ? "No child organizations exist." : "Unable to load child organization stats. Statistics may not be initialized yet." } + + ) + ) : ( +
+ +
+ )} +
+ ); +}); + +const IncreaseLimitPopUp = memo(({ open, onClose, limit, setLimit, HandleEditLimit, editingOrgId, editing}) => { + const [currentLimit, setCurrentLimit] = useState(limit) + + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + + return( + + + Increase {editing.replaceAll("_", " ").replace(/\b\w/g, c => c.toUpperCase())} Limit + + + setCurrentLimit(e.target.value)} + label={`${editing.replaceAll("_", " ").replace(/\b\w/g, c => c.toUpperCase())} Limit`} + type="string" + variant="outlined" + fullWidth + InputProps={{ + style: { + color: theme.palette.text.primary, + }, + }} + InputLabelProps={{ + style: { + color: theme.palette.text.primary, + }, + }} + margin="normal" + onKeyUp={(e) => { + if (e.key === "Enter") { + HandleEditLimit(editingOrgId, editing, currentLimit) + setLimit(currentLimit) + onClose() + }} + } + > + + + + + + + ) +}) + + +const PaddingWrapper = memo(({ clickedFromOrgTab, children }) => { + + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + const wrapperStyle = useMemo(() => ({ width: clickedFromOrgTab ? "100%" : "auto", padding: "27px 10px 19px 27px", - backgroundColor: '#212121', + backgroundColor: theme.palette.platformColor, height: '100%', boxSizing: 'border-box', overflow: 'hidden', - maxHeight: "1700px", overflowY: "auto",scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' - }), [clickedFromOrgTab]); + maxHeight: "1700px", + overflowY: "auto", + scrollbarColor: theme.palette.scrollbarColorTransparent, + scrollbarWidth: 'thin' + }), [clickedFromOrgTab, theme]); return (
@@ -2604,9 +3162,9 @@ const PaddingWrapper = memo(({ clickedFromOrgTab, children }) => { ); }); - const Wrapper = memo(({ children, clickedFromOrgTab }) => { +const Wrapper = memo(({ children, clickedFromOrgTab }) => { return ( - + {children} ); diff --git a/frontend/src/components/BillingStats.jsx b/frontend/src/components/BillingStats.jsx index 231b5686..5bb09ddb 100644 --- a/frontend/src/components/BillingStats.jsx +++ b/frontend/src/components/BillingStats.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useContext, memo, useMemo } from 'react'; -import theme from '../theme.jsx'; +import {getTheme} from '../theme.jsx'; import classNames from "classnames"; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid' @@ -28,12 +28,20 @@ import { Paper, Chip, Checkbox, + Box, } from "@mui/material"; import { BarChart, + BarSeries, + Bar, + BarLabel, + GridlineSeries, Gridline, + TooltipArea, + ChartTooltip, + TooltipTemplate, } from 'reaviz'; import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; @@ -42,31 +50,54 @@ import { Context } from '../context/ContextApi.jsx'; const LineChartWrapper = ({keys, inputname, height, width}) => { const [hovered, setHovered] = useState(""); const inputdata = keys.data === undefined ? keys : keys.data - + const {themeMode} = useContext(Context) + const theme = getTheme(themeMode) + + return (
- + {inputname} + + } + /> + } gridlines={ } /> } /> +
) } 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 [searches, setSearches] = useState([]); const [appRuns, setAppruns] = useState(undefined); + const [childOrgsAppRuns, setChildOrgsAppRuns] = useState(undefined); const [appRunCosts, setApprunCosts] = useState(undefined); const [workflowRuns, setWorkflowRuns] = useState(undefined); const [subflowRuns, setSubflowRuns] = useState(undefined); @@ -83,6 +114,8 @@ const AppStats = (defaultprops) => { const [workflows, setWorkflows] = useState(inputWorkflows === undefined ? [] : inputWorkflows) const [resultRows, setResultRows] = useState([]) const [resultLoading, setResultLoading] = useState(true) + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor) const includedExecutions = selectedOrganization?.sync_features?.app_executions !== undefined ? selectedOrganization?.sync_features?.app_executions?.limit : 0 @@ -94,9 +127,6 @@ const AppStats = (defaultprops) => { const getWorkflowStats = async (workflow, startTime, endTime) => { - if (!userdata.support) { - return workflow - } if (workflow.id === undefined || workflow.id === null || workflow.id === "") { return workflow @@ -161,12 +191,8 @@ const AppStats = (defaultprops) => { } const loadWorkflowStats = (foundWorkflows, startTime, endTime) => { - if (!userdata.support) { - return - } - if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) { - console.log("Not workflows") + setResultLoading(false) return } @@ -175,6 +201,9 @@ const AppStats = (defaultprops) => { const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime)); const allData = Promise.all(promises); + if (allData === undefined || allData === null) { + setResultLoading(false) + } allData.then((data) => { var total = 0 @@ -234,15 +263,16 @@ const AppStats = (defaultprops) => { 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) return } // Calculate month to date cost var mtd_cost = 0 - for (let key in statistics["daily_statistics"]) { - const item = statistics["daily_statistics"][key] + for (let key in statistics[statKey]) { + const item = statistics[statKey][key] if (item["date"] === undefined) { continue } @@ -300,8 +330,8 @@ const AppStats = (defaultprops) => { // Check if start time is before the daily statistics["date"] string var newlist = [] - for (let key in statistics["daily_statistics"]) { - const item = statistics["daily_statistics"][key] + for (let key in statistics[statKey]) { + const item = statistics[statKey][key] if (item["date"] === undefined) { continue } @@ -332,7 +362,7 @@ const AppStats = (defaultprops) => { var appexecutions = 0 var estimatedcost = 0 if (newlist.length > 0) { - tmpstats["daily_statistics"] = newlist + tmpstats[statKey] = newlist for (let key in newlist) { const item = newlist[key] @@ -386,7 +416,8 @@ const AppStats = (defaultprops) => { return } - const dailyStats = inputdata.daily_statistics + const statKey = syncStats === true ? "onprem_stats" : "daily_statistics" + const dailyStats = inputdata[statKey] if (dailyStats === undefined || dailyStats === null) { return } @@ -396,6 +427,11 @@ const AppStats = (defaultprops) => { "data": [] } + var childorgappRuns = { + "key": "Child Org App Runs", + "data": [] + } + var workflowRuns = { "key": "Workflow Runs (includes subflows)", "data": [] @@ -437,6 +473,13 @@ const AppStats = (defaultprops) => { }) } + if (item["child_app_executions"] !== undefined && item["child_app_executions"] !== null) { + childorgappRuns["data"].push({ + key: new Date(item["date"]), + data: inputdata["child_app_executions"] + }) + } + // Check if workflow_executions key in item if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) { workflowRuns["data"].push({ @@ -466,6 +509,15 @@ const AppStats = (defaultprops) => { }) } + if (inputdata["daily_child_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) { + childorgappRuns["data"].push({ + key: new Date(), + data: inputdata["daily_child_app_executions"] + }) + + //setApprunCosts(appcostRuns) + } + if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) { workflowRuns["data"].push({ key: new Date(), @@ -480,6 +532,11 @@ const AppStats = (defaultprops) => { }) } + // Only for parent orgs + if (childorgappRuns["data"].length > 0) { + setChildOrgsAppRuns(childorgappRuns) + } + setSubflowRuns(subflowRuns) setWorkflowRuns(workflowRuns) setAppruns(appRuns) @@ -529,11 +586,14 @@ const AppStats = (defaultprops) => { const paperStyle = { textAlign: "center", - padding: 40, - margin: 5, - backgroundColor: theme.palette.platformColor, - border: "1px solid rgba(255,255,255,0.3)", - maxWidth: 300, + padding: "40px", + margin: "5px", + backgroundColor: theme.palette.cardBackgroundColor, + border: theme.palette.defaultBorder, + maxWidth: "300px", + "&:hover": { + backgroundColor: theme.palette.cardHoverColor, + }, } const columns: GridColDef[] = [ @@ -646,85 +706,94 @@ const AppStats = (defaultprops) => {
All shown statistics are gathered from Your Organisation Statistics. It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. The billing tracker is in Beta, and is always calculated manually before being invoiced. + +
+ {syncStats !== true ? null : + "PS: You are currently looking at data from your onprem synced org"}
{filteredStatistics !== undefined ?
- - 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}. - - }> - - - ${selectedOrganization.lead_info.customer === false && selectedOrganization.lead_info.pov === false ? - 0 - : - apprunCost - } + + {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}. - - Period Cost - - - + }> + + + ${selectedOrganization?.lead_info?.customer === false && selectedOrganization?.lead_info?.pov === false ? + 0 + : + apprunCost + } + + + + Period Cost + + + + } + + {syncStats === true ? null : App runs in the selected period }> - - - {filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions} - - - App Runs - - + + + {filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions} + + + App Runs + + + } + + {syncStats === true ? null : Workflow runs in the selected period }> - + {filteredStatistics.monthly_workflow_executions === null || filteredStatistics.monthly_workflow_executions === undefined ? 0 : filteredStatistics.monthly_workflow_executions} Workflow Runs - + - - 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}. - - }> - - - ${monthTotalCost} + } + + {syncStats === true ? null : + + 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}. - - Estimated cost - - - + }> + + + ${monthTotalCost} + + + Estimated cost + + + + }
: null}
@@ -895,7 +964,13 @@ const AppStats = (defaultprops) => { {appRuns === undefined ? null : - + + } + + {childOrgsAppRuns === undefined ? + null + : + } {workflowRuns === undefined ? @@ -916,56 +991,58 @@ const AppStats = (defaultprops) => { */} + {syncStats === true ? null : +
+ {resultLoading ? +
+ + Loading usage for selected period (may take a while) + + + +
+ : + { + //setRowsPerPage(newPageSize) + //submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize) + }} + // event for when clicking next page + // Hide page changer + onPageChange={(params) => { + console.log("page params: ", params) + }} + onSelectionModelChange={(newSelection) => { + console.log("newSelection: ", newSelection) + //console.log("newSelection: ", newSelection) + //setSelectedWorkflowExecutionsIndexes(newSelection) + //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] + // }) -
- {resultLoading ? -
- - Loading usage for selected period (may take a while) - - -
- : - { - //setRowsPerPage(newPageSize) - //submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize) - }} - // event for when clicking next page - // Hide page changer - onPageChange={(params) => { - console.log("page params: ", params) - }} - onSelectionModelChange={(newSelection) => { - console.log("newSelection: ", newSelection) - //console.log("newSelection: ", newSelection) - //setSelectedWorkflowExecutionsIndexes(newSelection) - //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) { + // continue + // } - // if (selected === undefined || selected === null) { - // continue - // } + // found.push(selected) + //} - // found.push(selected) - //} - - //setSelectedWorkflowExecutions(found) - }} - // Track which items are selected - /> - } -
+ //setSelectedWorkflowExecutions(found) + }} + // Track which items are selected + /> + } +
+ }
) diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index e549d12e..f913a58a 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -1,8 +1,7 @@ import React, { useState, useEffect, useContext } from "react"; import ReactGA from 'react-ga4'; -import theme from "../theme.jsx"; +import {getTheme} from "../theme.jsx"; import { ToastContainer, toast } from "react-toastify" - import { CheckCircle as CheckCircleIcon, } from "@mui/icons-material"; @@ -13,8 +12,11 @@ import { Divider, Button, Tooltip, - Grid, - Card, + ToggleButtonGroup, + ToggleButton, + useMediaQuery, + TextField, + Box, } from "@mui/material"; import { @@ -27,18 +29,98 @@ import { Context } from "../context/ContextApi.jsx"; const Branding = (props) => { const { globalUrl, userdata, serverside, billingInfo,clickedFromOrgTab, stripeKey, selectedOrganization, handleGetOrg, } = props; + const { themeMode, handleThemeChange, supportEmail, setSupportEmail, logoutUrl, setLogoutUrl, brandColor, setBrandColor, setBrandName } = useContext(Context) //const alert = useAlert(); const [publishingInfo, setPublishingInfo] = useState(""); const [publishRequirements, setPublishRequirements] = useState([]) + const [currentSelectedTheme, setCurrentSelectedTheme] = useState(themeMode); + const [integrationPartner, setIntegrationPartner] = useState(false); + const [changingTheme, setChangingTheme] = useState(false); + const theme = getTheme(themeMode, brandColor) + const [selectedBrandColor, setSelectedBrandColor] = useState(theme?.palette?.main || "#FF8544") + const [selectedBrandName, setSelectedBrandName] = useState(selectedOrganization?.branding?.brand_name || "") - const { leftSideBarOpenByClick } = useContext(Context) - - const handleEditOrg = (joinStatus) => { + const [isLoading,setIsLoading] = useState(false); + + const handleEditOrg = (joinStatus) => { + setIsLoading(true) const data = { "org_id": selectedOrganization.id, - "creator_config": joinStatus, }; + if (joinStatus === "join" || joinStatus === "leave") { + data["creator_config"] = joinStatus + } + + if (joinStatus === "light" || joinStatus === "dark" || joinStatus === "system") { + data["branding"] = { + "theme": joinStatus, + "enable_chat": selectedOrganization?.branding?.enable_chat || false, + "home_url": selectedOrganization?.branding?.home_url || "", + "brand_color": selectedOrganization?.branding?.brand_color || theme.palette.primary.main, + "brand_name": selectedOrganization?.branding?.brand_name || "", + "logout_url": selectedOrganization?.branding?.logout_url || "", + "support_email": selectedOrganization?.branding?.support_email || "", + } + + data["editing_branding"] = true; + } + + if (joinStatus === "brand_color") { + data["branding"] = { + "theme": selectedOrganization?.branding?.theme || "dark", + "enable_chat": selectedOrganization?.branding?.enable_chat || false, + "home_url": selectedOrganization?.branding?.home_url || "", + "brand_color": selectedBrandColor, + "brand_name": selectedOrganization?.branding?.brand_name || "", + "logout_url": selectedOrganization?.branding?.logout_url || "", + "support_email": selectedOrganization?.branding?.support_email || "", + } + + data["editing_branding"] = true; + } + + if (joinStatus === "brand_name") { + data["branding"] = { + "theme": selectedOrganization?.branding?.theme || "dark", + "enable_chat": selectedOrganization?.branding?.enable_chat || false, + "home_url": selectedOrganization?.branding?.home_url || "", + "brand_color": selectedOrganization?.branding?.brand_color || theme.palette.primary.main, + "brand_name": selectedBrandName, + "logout_url": selectedOrganization?.branding?.logout_url || "", + "support_email": selectedOrganization?.branding?.support_email || "", + } + toast.info("Updating brand name to " + selectedBrandName + ". Please wait a moment.") + data["editing_branding"] = true; + } + + if (joinStatus === "support_email") { + data["branding"] = { + "theme": selectedOrganization?.branding?.theme || "dark", + "enable_chat": selectedOrganization?.branding?.enable_chat || false, + "home_url": selectedOrganization?.branding?.home_url || "", + "brand_color": selectedOrganization?.branding?.brand_color || theme.palette.primary.main, + "brand_name": selectedOrganization?.branding?.brand_name || "", + "support_email": supportEmail, + "logout_url": selectedOrganization?.branding?.logout_url || "", + } + data["editing_branding"] = true; + } + + if (joinStatus === "logout_url") { + data["branding"] = { + "theme": selectedOrganization?.branding?.theme || "dark", + "enable_chat": selectedOrganization?.branding?.enable_chat || false, + "home_url": selectedOrganization?.branding?.home_url || "", + "brand_color": selectedOrganization?.branding?.brand_color || theme.palette.primary.main, + "brand_name": selectedOrganization?.branding?.brand_name || "", + "support_email": selectedOrganization?.branding?.support_email || "", + "logout_url": logoutUrl, + } + data["editing_branding"] = true; + } + + const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; fetch(url, { mode: "cors", @@ -56,20 +138,65 @@ const Branding = (props) => { if (responseJson["success"] === false) { toast("Failed updating org: ", responseJson.reason); } else { - if (joinStatus == "join") { - setPublishingInfo("Your organization is now part of the Partner Program. You can now create, publish and manage content for your organization's public page.") - } else { - setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.") + if (joinStatus === "join" || joinStatus === "leave") { + if (joinStatus === "join") { + setPublishingInfo("Your organization is now part of the Partner Program. You can now create, publish and manage content for your organization's public page.") + } else { + setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.") + } } + + if (joinStatus === "light" || joinStatus === "dark" || joinStatus === "system") { + handleThemeChange(joinStatus) + setChangingTheme(false) + setCurrentSelectedTheme(joinStatus) + } + + if (joinStatus === "support_email") { + toast.info("Support email updated successfully.") + if (supportEmail?.length > 0) { + setSupportEmail(supportEmail) + }else { + setSupportEmail("support@shuffler.io") + } + } + + if (joinStatus === "logout_url") { + toast.info("Logout URL updated successfully.") + setLogoutUrl(logoutUrl) + } + + if (joinStatus === "brand_color") { + toast.info("Brand color updated successfully.") + setBrandColor(selectedBrandColor) + localStorage.setItem("brandColor", selectedBrandColor) + } + + if (joinStatus === "brand_name") { + toast.info("Brand name updated successfully.") + setBrandName(selectedBrandName) + localStorage.setItem("brandName", selectedBrandName) + } + handleGetOrg(selectedOrganization.id); - } + + setIsLoading(false) + } }) ) .catch((error) => { toast("Err: " + error.toString()); - }); + setIsLoading(false) + }) }; + useEffect(() => { + if (userdata && userdata?.active_org && userdata?.active_org?.branding?.theme?.length > 0) { + console.log("Setting current selected theme from userdata", userdata?.active_org?.branding?.theme); + setCurrentSelectedTheme(userdata?.active_org?.branding?.theme); + } + },[userdata]); + // Should enable / disable org branding const handleChangePublishing = () => { console.log("Handle change publishing"); @@ -117,16 +244,56 @@ const Branding = (props) => { const leadinfo = selectedOrganization.lead_info === undefined || selectedOrganization.lead_info === null || selectedOrganization.lead_info === "" ? "" : JSON.stringify(selectedOrganization.lead_info) const isPartner = leadinfo.includes("partner") + + useEffect(() => { + if (selectedOrganization?.branding?.theme && selectedOrganization?.creator_org?.length === 0) { + setCurrentSelectedTheme(selectedOrganization.branding.theme); + } + + if (selectedOrganization?.creator_org?.length > 0 && userdata?.active_org?.branding.theme) { + setCurrentSelectedTheme(userdata?.active_org?.branding.theme); + } + + if ( + selectedOrganization && + selectedOrganization?.branding?.brand_color && + selectedOrganization?.branding?.brand_color !== selectedBrandColor + ) { + setSelectedBrandColor(selectedOrganization.branding.brand_color); + } + + if (selectedOrganization?.branding?.brand_name && selectedOrganization?.branding?.brand_name !== selectedBrandName) { + setSelectedBrandName(selectedOrganization.branding.brand_name); + } + + }, [selectedOrganization, userdata]); + + useEffect(() => { + if ((userdata && userdata?.org_status?.includes("integration_partner") && !integrationPartner && !userdata?.org_status?.includes("sub_org")) || userdata?.support) { + setIntegrationPartner(true) + } + + },[userdata, integrationPartner]); + + const handleColorChange = (e) => { + setSelectedBrandColor(e.target.value); + }; + + const saveColorChanges = () => { + toast.info("Updating brand color to " + selectedBrandColor + ". Please wait a moment.") + handleEditOrg("brand_color"); + }; + return ( -
-
+
+
Partner Status & Branding - You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more. + Please note that same theme settings are applied to all sub organizations for partners. @@ -150,7 +317,8 @@ const Branding = (props) => { style={{ textDecoration: "none" }} // Optional: remove underline > )} - + - Partner Program + Public Partner Program
- - By changing publishing settings, you agree to our Terms of Service, and acknowledge that your organization's non-sensitive data will be added as a creator account. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization IS reversible.
Support: support@shuffler.io + + By changing publishing settings, you agree to our Terms of Service, and acknowledge that your organization's non-sensitive data will be added as a creator account. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization IS reversible.
Support: {supportEmail} {selectedOrganization.creator_id == "" ? @@ -195,7 +363,9 @@ const Branding = (props) => { }
+ + + {integrationPartner ? ( + <> + + + Parent & Sub Organization Branding + + + Sub organizations are not allowed to change their branding. The branding is inherited from the parent organization. + + { + if (newTheme === null) { + return; + } + if (newTheme === currentSelectedTheme) { + return; + } + if (changingTheme === true) { + return + } + setChangingTheme(true) + handleEditOrg(newTheme); + }} + aria-label="theme" + style={{ justifyContent: "flex-start", marginBottom: 10, marginTop: 20 }} + > + + Light + + + Dark + + + System + + + + ): null} + + {integrationPartner ? <> + + Brand Name +
+ { + const value = e.target.value; + setSelectedBrandName(value); + }} + size="small" + PaperProps={{ style: { backgroundColor: theme.palette.backgroundColor, borderRadius: 4, border: `1px solid ${theme.palette.defaultBorder}` } }} + style={{ + height: 36, + borderRadius: 4, + border: `1px solid ${theme.palette.defaultBorder}`, + width: 300 + }} + /> + +
+ : null} + + {integrationPartner ? <> + Brand Color +
+ + + +
+ : null} + + + {integrationPartner ? ( +
+ Support Email + + { + setSupportEmail(e.target.value) + }} + color="primary" + InputProps={{ + style: { + color: theme.palette.textFieldStyle.color, + height: "35px", + fontSize: "1em", + borderRadius: 4, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + }, + }} + /> + + +
+ ) : null} + + {integrationPartner ? ( +
+ Logout URL + + { + setLogoutUrl(e.target.value) + }} + color="primary" + InputProps={{ + style: { + color: theme.palette.textFieldStyle.color, + height: "35px", + fontSize: "1em", + borderRadius: 4, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + }, + }} + /> + + +
+ ) : null}
) diff --git a/frontend/src/components/CacheView.jsx b/frontend/src/components/CacheView.jsx index f4b8b9cb..1e94d131 100644 --- a/frontend/src/components/CacheView.jsx +++ b/frontend/src/components/CacheView.jsx @@ -1,8 +1,12 @@ import React, { useState, useEffect, useContext, memo } from "react"; -import theme from "../theme.jsx"; +import { makeStyles } from "@mui/styles"; +import { getTheme } from "../theme.jsx"; import { toast } from 'react-toastify'; -import ReactJson from "react-json-view-ssr"; +import ReactJson from "react-json-view-ssr"; +import { GetIconInfo } from "../views/Workflows2.jsx"; +import { red } from "../views/AngularWorkflow.jsx"; +import CollectIngestModal from "../components/CollectIngestModal.jsx"; import { Typography, Tooltip, @@ -11,7 +15,6 @@ import { Button, Tabs, Tab, - Grid, List, ListItem, ListItemText, @@ -26,8 +29,18 @@ import { DialogContent, FormControl, Select, + Autocomplete, + ButtonGroup, + InputLabel, + Pagination, + PaginationItem, } from "@mui/material"; +import { + DataGrid, + GridColDef, +} from '@mui/x-data-grid'; + import { Link as LinkIcon, AutoFixHigh as AutoFixHighIcon, @@ -35,6 +48,7 @@ import { Edit as EditIcon, FileCopy as FileCopyIcon, SelectAll as SelectAllIcon, + DeleteOutline as DeleteOutlineIcon, OpenInNew as OpenInNewIcon, CloudDownload as CloudDownloadIcon, Description as DescriptionIcon, @@ -43,7 +57,6 @@ import { Close as CloseIcon, Apps as AppsIcon, Image as ImageIcon, - Delete as DeleteIcon, Cached as CachedIcon, AccessibilityNew as AccessibilityNewIcon, Lock as LockIcon, @@ -55,6 +68,14 @@ import { VisibilityOff as VisibilityOffIcon, Clear as ClearIcon, Add as AddIcon, + Rocket as RocketIcon, + Webhook as WebhookIcon, + Air as AirIcon, + RocketLaunch as RocketLaunchIcon, + Send as SendIcon, + SmartToy as SmartToyIcon, + Settings as SettingsIcon, + FilterAlt as FilterAltIcon, } from "@mui/icons-material"; import { validateJson, } from "../views/Workflows.jsx"; import { Context } from "../context/ContextApi.jsx"; @@ -75,7 +96,15 @@ const scrollStyle2 = { overflow: "scroll", } +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important", + }, +}); +// + +//const CacheView = (props) => { const CacheView = memo((props) => { const { globalUrl, userdata, serverside, orgId, isSelectedDataStore, selectedOrganization } = props; const [orgCache, setOrgCache] = React.useState(""); @@ -86,7 +115,6 @@ const CacheView = memo((props) => { const [key, setKey] = React.useState(""); const [value, setValue] = React.useState(""); const [cacheInput, setCacheInput] = React.useState(""); - const [cacheCursor, setCacheCursor] = React.useState(""); const [dataValue, setDataValue] = React.useState({}); const [editCache, setEditCache] = React.useState(false); const [cachedLoaded, setCachedLoaded] = React.useState(false); @@ -94,26 +122,142 @@ const CacheView = memo((props) => { const [showDistributionPopup, setShowDistributionPopup] = useState(false); const [selectedSubOrg, setSelectedSubOrg] = useState([]); const [selectedCacheKey, setSelectedCacheKey] = useState(""); + const [totalAmount, setTotalAmount] = useState(0); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(50) + const [cursors, setCursors] = useState({ + 0: "", + }) + const [_, setUpdate] = useState(Math.random()) + const [selectedRows, setSelectedRows] = useState([]); // Direct category migration from ../components/Files.jsx const [selectAllChecked, setSelectAllChecked] = React.useState(false) const [renderTextBox, setRenderTextBox] = React.useState(false); - const [fileCategories, setFileCategories] = React.useState(["default"]); + const [datastoreCategories, setDatastoreCategories] = React.useState(["default"]); const [selectedCategory, setSelectedCategory] = React.useState("default"); const [selectedFileId, setSelectedFileId] = React.useState(""); const [updateToThisCategory, setUpdateToThisCategory] = useState("") - const [showFileCategoryPopup, setShowFileCategoryPopup] = React.useState(false); - const [selectedFiles, setSelectedFiles] = useState([]); + const [workflows, setWorkflows] = useState([]); + + const [selectedFiles, setSelectedFiles] = useState([]); + const [showAutomationMenu, setShowAutomationMenu] = useState(false); + const [showSettingsMenu, setShowSettingsMenu] = useState(false); + const [showCollectIngestMenu, setShowCollectIngestMenu] = useState(false); + + const defaultAutomation = [ + { + "name": "Run workflow", + "description": "Runs a workflow with the updated value.", + "options": [{ + "key": "workflow_id", + "value": "", + }], + "icon": , + "enabled": false, + }, + { + "name": "Send message", + "description": "", + "type": "singul", + "options": [{ + "key": "app", + "value": "", + }], + "icon": , + "disabled": true, + "enabled": false, + }, + { + "name": "Enrich", + "description": "", + "type": "singul", + "options": [{ + "key": "", + "value": "", + }], + "icon": "/images/logos/singul.svg", + "enabled": false, + "disabled": true, + }, + { + "name": "Run AI Agent", + "description": "", + "options": [{ + "key": "", + "value": "", + }], + "icon": , + "enabled": false, + "disabled": true, + }, + { + "name": "Send webhook", + "description": "Sends the updated value to a specified webhook URL.", + "options": [{ + "key": "webhook_url", + "value": "", + }], + "icon": , + "enabled": false, + }, + ] + + const [categoryAutomations, setCategoryAutomations] = useState(defaultAutomation) + const [categoryConfig, setCategoryConfig] = useState(undefined) + + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + const classes = useStyles(); + + + const getWorkflows = () => { + const url = `${globalUrl}/api/v1/workflows` + fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + return; + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson?.success !== true) { + setWorkflows(responseJson) + } else { + toast.warn("Failed to load workflows. Please try again or contact support@shuffler if this persists.") + } + }) + .catch((error) => { + toast(error.toString()); + }); + } + + useEffect(() => { + setCursors({ + 0: "", + }) + setPage(0) + setSelectedRows([]) + }, [selectedCategory]) useEffect(() => { - if (orgId?.length > 0) { - listOrgCache(orgId, selectedCategory) - } - }, [orgId]) + getWorkflows() + listOrgCache(orgId, selectedCategory, 0, pageSize, page) + }, []) + const handleKeyDown = (event) => { if (event.key === 'Enter') { - fileCategories.push(event.target.value); + datastoreCategories.push(event.target.value); setSelectedCategory(event.target.value); setRenderTextBox(false); } @@ -124,9 +268,41 @@ const CacheView = memo((props) => { } } + const listOrgCache = (orgId, category, index, amount, page, keyValue) => { + setCachedLoaded(false) + if (index === undefined || index === null) { + index = 0 + } + + var url = `${globalUrl}/api/v1/orgs/${orgId}/list_cache` + + if (category !== undefined && category !== null && category !== "default" && category !== "") { + url += "?category=" + category.replaceAll(" ", "_") + } else { + url += "?category=default" + category = "default" + } + + if (amount !== undefined && amount !== null && amount > 0) { + url += "&top=" + amount + } + + if (page !== undefined && page !== null && page >= 0) { + if (cursors[page-1] !== undefined && cursors[page-1] !== null && cursors[page-1] !== "") { + url += "&cursor=" + cursors[page-1] + } + } + + if (keyValue !== undefined && keyValue !== null && keyValue !== "") { + url += "&key=" + keyValue + + setCursors({ + 0: "", + }) + setPage(0) + setSelectedRows([]) + } - const listOrgCache = (orgId, category) => { - const url = `${globalUrl}/api/v1/orgs/${orgId}/list_cache${category !== undefined ? `?category=${category.replaceAll(" ", "%20")}` : ""}` fetch(url, { method: "GET", headers: { @@ -144,29 +320,98 @@ const CacheView = memo((props) => { return response.json(); }) .then((responseJson) => { - if (responseJson.success === true) { - setListCache(responseJson.keys); - setCachedLoaded(true); + setCachedLoaded(true); + if (responseJson?.success === true) { + setListCache(responseJson.keys) - if (fileCategories.length === 1 && fileCategories[0] === "default") { + if (responseJson.total_amount !== undefined && responseJson.total_amount !== null && responseJson.total_amount > 0) { + setTotalAmount(responseJson.total_amount) + } else { + setTotalAmount(responseJson.keys.length) + } + + if (responseJson?.cursor !== undefined && responseJson?.cursor !== null && responseJson?.cursor !== "") { + cursors[page] = responseJson.cursor + } + + // Especially important during first load + if (index < 2 && (category === "default" || category === "" || category === undefined)) { + // If it exists and isn't blank/default, load it + const urlParams = new URLSearchParams(window.location.search); + const categoryParam = urlParams.get("category"); + if (categoryParam && categoryParam !== undefined && categoryParam !== "default" && categoryParam !== "") { + setSelectedCategory(categoryParam); + if (index === undefined || index === null) { + index = 0 + } + + listOrgCache(orgId, categoryParam, index+1, amount, page) + } else { + setSelectedCategory("default"); + } + } + + if ((category === undefined || category === "default" || category === "") && datastoreCategories.length === 1 && datastoreCategories[0] === "default") { var newcategories = ["default"] for (var key in responseJson.keys) { - var category = responseJson.keys[key].category - if (category !== undefined && category !== null && category !== ""){ - category = category.replaceAll(" ", "_") + var foundcategory = responseJson.keys[key].category + if (foundcategory !== undefined && foundcategory !== null && foundcategory !== ""){ + foundcategory = category.replaceAll(" ", "_") - if (!newcategories.includes(category)) { - newcategories.push(category) + if (!newcategories.includes(foundcategory)) { + newcategories.push(foundcategory) } } } - setFileCategories(newcategories) - } - } + if (responseJson?.categories !== undefined && responseJson?.categories !== null && responseJson?.categories.length > 0) { + for (var i = 0; i < responseJson.categories.length; i++) { + const foundcategory = responseJson.categories[i].replaceAll(" ", "_") + if (foundcategory !== undefined && foundcategory !== null && foundcategory !== "" && foundcategory !== "default" && !newcategories.includes(foundcategory)) { + newcategories.push(responseJson.categories[i]); + } + } + } - if (responseJson.cursor !== undefined && responseJson.cursor !== null && responseJson.cursor !== "") { - setCacheCursor(responseJson.cursor); + setDatastoreCategories(newcategories) + } + + + if (responseJson?.category_config !== undefined && responseJson?.category_config !== null) { + + if (responseJson?.category_config?.id !== undefined && responseJson?.category_config?.id !== null && responseJson?.category_config?.id !== "") { + setCategoryConfig(responseJson.category_config) + } + + // Handle other configs here. + if (responseJson?.category_config?.automations !== undefined && responseJson?.category_config?.automations !== null && responseJson?.category_config?.automations.length > 0) { + // Find icons if they exist + for (var key in responseJson.category_config.automations) { + //if (responseJson.category_config.automations[key].icon === undefined || responseJson.category_config.automations[key].icon === null || responseJson.category_config.automations[key].icon === "") { + const foundItem = defaultAutomation.find((automation) => automation.name === responseJson.category_config.automations[key].name) + if (foundItem) { + responseJson.category_config.automations[key].disabled = foundItem.disabled + responseJson.category_config.automations[key].icon = foundItem.icon + responseJson.category_config.automations[key].type = foundItem?.type + } else { + responseJson.category_config.automations[key].icon = + } + } + + for (var key in defaultAutomation) { + if (!responseJson.category_config.automations.some((automation) => automation.name === defaultAutomation[key].name)) { + // If the automation doesn't exist in the response, add it with default values + responseJson.category_config.automations.push(defaultAutomation[key]) + } + } + + setCategoryAutomations(responseJson.category_config.automations) + } else { + setCategoryAutomations(defaultAutomation) + } + } + } else { + toast.warn("Failed to load keys. Please try again or contact support@shuffler if this persists.") } }) .catch((error) => { @@ -175,15 +420,19 @@ const CacheView = memo((props) => { }; - const deleteCache = (orgId, key) => { + const deleteEntry = (orgId, key, itemCategory, refreshList) => { const method = "POST" const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache` - const parsed = { + var parsed = { "org_id": orgId, "key": key, "category": selectedCategory === "" || selectedCategory === "default" ? "" : selectedCategory, } + if (itemCategory !== undefined) { + parsed["category"] = itemCategory.replaceAll(" ", "_"); + } + fetch(url, { method: method, headers: { @@ -194,12 +443,15 @@ const CacheView = memo((props) => { }) .then((response) => { if (response.status === 200) { - toast("Successfully deleted Cache"); - setTimeout(() => { - listOrgCache(orgId, selectedCategory) - }, 1000); + if (refreshList === undefined || refreshList === null || refreshList === true) { + + toast.success("Deleted datastore entry"); + setTimeout(() => { + listOrgCache(orgId, selectedCategory, 0, pageSize, page) + }, 1000); + } } else { - toast("Failed deleting Cache. Does it still exist?"); + toast.error(`Failed deleting entry ${key} in category ${itemCategory || selectedCategory}. If this persists, please contact support@shuffler.io.`) } }) .catch((error) => { @@ -208,13 +460,25 @@ const CacheView = memo((props) => { }; const editOrgCache = (orgId) => { - const cache = { + var entry = { key: dataValue.key, value: value, - category: selectedCategory, + category: selectedCategory, } - setCacheInput([cache]); + if (dataValue?.category !== "" && dataValue?.category !== "default") { + entry.category = dataValue.category.replaceAll(" ", "_"); + + } + + if (listCache.length > 0) { + const selectedCache = listCache.find((data) => data.key === dataValue.key); + if (selectedCache?.suborg_distribution?.length > 0) { + entry.suborg_distribution = selectedCache.suborg_distribution; + } + } + + setCacheInput([entry]); fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, { @@ -224,7 +488,7 @@ const CacheView = memo((props) => { Accept: "application/json", }, credentials: "include", - body: JSON.stringify(cache), + body: JSON.stringify(entry), }) .then((response) => { if (response.status !== 200) { @@ -236,8 +500,8 @@ const CacheView = memo((props) => { }) .then((responseJson) => { setAddCache(responseJson); - toast("Cache Edited Successfully!"); - listOrgCache(orgId, selectedCategory); + toast.success("Edit saved"); + listOrgCache(orgId, selectedCategory, 0, pageSize, page); setModalOpen(false); }) .catch((error) => { @@ -275,7 +539,7 @@ const CacheView = memo((props) => { .then((responseJson) => { setAddCache(responseJson); toast("New key added Successfully!"); - listOrgCache(orgId, selectedCategory); + listOrgCache(orgId, selectedCategory, 0, pageSize, page); setModalOpen(false); }) .catch((error) => { @@ -327,6 +591,19 @@ const CacheView = memo((props) => { }; + const timestamp = (timestamp) => { + if (timestamp === undefined || timestamp === null || timestamp === "") { + return null + } + + const date = new Date(timestamp * 1000); + if (date.toString() === "Invalid Date" || date.toString() === "Invalid Date NaN") { + return null + } + + return date.toISOString()?.slice(0, 19)?.replace("T", " ") + } + const modalView = ( // console.log("key:", dataValue.key), //console.log("value:",dataValue.value), @@ -357,12 +634,12 @@ const CacheView = memo((props) => { }} > - + { editCache ? "Edit Key" : "Add Key"}{selectedCategory === "" || selectedCategory === "default" ? "" : ` in category '${selectedCategory}'`} -
+
Key { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.textFieldStyle.color, fontSize: "1em", }, }} @@ -387,7 +664,7 @@ const CacheView = memo((props) => { onChange={(e) => setKey(e.target.value)} />
-
+
Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON) @@ -408,7 +685,7 @@ const CacheView = memo((props) => { style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor, marginTop: 0, }} InputProps={{ style: { - color: "white", + color: theme.palette.textFieldStyle.color, fontSize: "1em", }, }} @@ -426,23 +703,45 @@ const CacheView = memo((props) => { value={value} onChange={(e) => setValue(e.target.value)} /> + + + {editCache ? +
+ + Created: {timestamp(dataValue?.created)} + + + Edited: {timestamp(dataValue?.edited)} + + {dataValue?.workflow_id !== "" ? + + Workflow: {dataValue.workflow_id} + + : null} + {dataValue?.category !== "" && dataValue?.category !== "default" ? + + Category: {dataValue.category} + + : null} + +
+ : null}
+
); @@ -520,11 +820,11 @@ const CacheView = memo((props) => { .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - toast("Failed overwriting datastore"); + toast.error("Failed overwriting datastore"); } else { - toast("Successfully updated datastore!"); + toast.success("Successfully updated datastore!"); setTimeout(() => { - listOrgCache(orgId, selectedCategory); + listOrgCache(orgId, selectedCategory, 0, pageSize, page); setShowDistributionPopup(false); }, 1000); } @@ -563,9 +863,9 @@ const CacheView = memo((props) => { }} > -
+ Select sub-org to distribute Datastore key -
+
{handleSelectSubOrg(null, "none")}}>None @@ -608,7 +908,7 @@ const CacheView = memo((props) => {
+ : null} +
+ + {showOptions && ( + updatedAutomation.options.map((option, optionIndex) => { + if (option?.key === "workflow_id") { + return ( + option?.value.includes(w.id)) || []} + classes={{ inputRoot: classes.inputRoot }} + ListboxProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: theme.palette.text.primary, + borderRadius: theme.palette.borderRadius, + }, + }} + onChange={(event, newValue) => { + option.value = "" + for (var i = 0; i < newValue.length; i++) { + option.value += newValue[i].id + "," + } + + if (newValue.length > 0) { + updatedAutomation.enabled = true + } else { + updatedAutomation.enabled = false + } + + updatedAutomation.options[optionIndex] = option + setUpdatedAutomation(updatedAutomation) + setUpdated(true) + + setUpdate(Math.random()) // Force re-render + }} + + getOptionLabel={(option) => { + if ( + option === undefined || + option === null || + option?.name === undefined || + option?.name === null + ) { + return "No Workflows Selected"; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " ") + + return newname + }} + options={workflows} + fullWidth + style={{ + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + borderRadius: theme.palette.textFieldStyle.borderRadius, + color: theme.palette.textFieldStyle.color, + height: 35, + marginBottom: 40, + }} + renderOption={(props, data, state) => { + /* + if (data.id === option?.value) { + data = workflow; + } + */ + + return ( + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.name} + : null} + + Choose {data.name} + + + } > + + {data.name} + + + ) + }} + renderInput={(params) => { + return ( + + ) + }} + /> + ) + } + + return ( + { + if (e.target.value === "") { + updatedAutomation.enabled = false + } else { + updatedAutomation.enabled = true + } + + updatedAutomation.options[optionIndex].value = e.target.value; + setUpdatedAutomation(updatedAutomation) + setUpdated(true) + }} + /> + ) + }) + )} +
+ ) + } + + + const setCategorySettingsField = (field, value) => { + // Check if categoryConfig.settings is set or not. Otherwise set it. + var categoryConfig2 = categoryConfig + if (categoryConfig === undefined || categoryConfig === null) { + categoryConfig2 = {} + } + + if (categoryConfig?.settings === undefined || categoryConfig?.settings === null) { + categoryConfig2.settings = {} + } + + categoryConfig2.settings[field] = value + setCategoryConfig(categoryConfig2) + + saveAutomation( + categoryAutomations, + categoryConfig2.settings, + ) + } + + const columns: GridColDef<(typeof rows)[number]>[] = [ + { + field: 'key', + headerName: 'Key', + width: 200, + filterable: true, + sortable: true, + }, + { + width: 600, + field: 'value', + filterable: true, + headerName: 'Value', + renderCell: (props) => { + const data = props.row + const validate = validateJson(data.value) + + return ( +
{ + e.preventDefault() + e.stopPropagation() + }} + > + {validate.valid ? + { + // handleReactJsonClipboard(copy); + }} + collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength} + iconStyle={theme.palette.jsonIconStyle} + displayDataTypes={false} + onSelect={(select) => { + + // HandleJsonCopy(showResult, select, data.action.label); + console.log("SELECTED!: ", select); + }} + name={null} + /> + : + + {data.value} + + } +
+ ) + } + }, + { + field: 'actions', + headerName: 'Actions', + description: 'Actions for this key.', + width: 175, + filterable: false, + sortable: false, + + renderCell: (props) => { + const data = props.row + + return ( + + {data?.workflow_id === "" || data?.workflow_id === null || data?.workflow_id === undefined ? + + + + : ( + + + + + + + + + + )} + + + + { + // Try to make the value JSON indented + const valid = validateJson(data.value) + var newvalue = data.value + if (valid.valid) { + // JSON stringify with indentation + newvalue = JSON.stringify(valid.result, null, 2) + } + + + setEditCache(true) + setDataValue({ + "key": data.key, + "value": newvalue, + + "edited": data.edited, + "created": data.created, + "workflow_id": data.workflow_id, + "category": data.category, + }) + setValue(newvalue) + setModalOpen(true) + }} + > + + + + + + + + + { + window.open(`${globalUrl}/api/v1/orgs/${orgId}/cache/${data.key}?type=text&authorization=${data.public_authorization}`, "_blank"); + }} + > + + + + + + + { + deleteEntry(orgId, data.key, data.category) + }} + > + + + + + + + + + ) + } + }, + { + field: 'distribution', + headerName: 'Distribution', + description: 'Controls whether this key is distributed to sub-organizations.', + width: 100, + filterable: false, + sortable: false, + + renderCell: (props) => { + const data = props.row + const isDistributed = data?.suborg_distribution?.length > 0 ? true : false; + + return ( +
+ {selectedOrganization.id !== undefined && data?.org_id !== selectedOrganization.id ? + + + + } + style={{display: "table-cell", textAlign: 'center', verticalAlign: 'middle', }} + /> + : + + { + setShowDistributionPopup(true) + if(data?.suborg_distribution?.length > 0){ + setSelectedSubOrg(data.suborg_distribution) + }else{ + setSelectedSubOrg([]) + } + setSelectedCacheKey(data.key) + }} + /> + + } + style={{display: "table-cell", textAlign: 'center', verticalAlign: 'middle', }} + /> + } +
+ ) + } + }, + ]; + + + const isAutomating = categoryAutomations?.find((automation) => automation.enabled) !== undefined return ( -
+
{modalView} + + + {cacheDistributionModal} -
-
+ +
+
-

Shuffle Datastore {selectedCategory === "" || selectedCategory === "default" ? "" : `- Category '${selectedCategory}'`}

- - Datastore is a permanent key-value database for storing data that can be used cross-workflow.
You can store anything from lists of IPs to complex configurations.  + +
+ + + Shuffle Datastore + + + {userdata?.support === true ? + + + + + + : null} +
+ + + Datastore is a permanent key-value database for storing data which can be used for automation.   Learn more -
+
- - +
+ + + + : + + + + } + + {renderTextBox && { + handleKeyDown(event); + if(event.key === 'Enter' && selectedFileId.length > 0){ + setUpdateToThisCategory(event.target.value) + } + + }} + style={{ + height: 35, + width: 200, + marginTop: 0, + }} + InputProps={{ + style: { + color: theme.palette.textFieldStyle.color, height: 35, - float: "right", - position: 'relative', - top: 8 - }} - value={selectedCategory} - onChange={(event) => { - //if (selectAllChecked || listCache.length > 0) { - if (selectAllChecked || selectedFiles.length > 0) { - setUpdateToThisCategory(event.target.value) - setShowFileCategoryPopup(true) - return - } + fontSize: 16, + borderRadius: 4, + paddingTop: 0, + }, + }} + color="primary" + placeholder="Category name" + required + margin="dense" + defaultValue={""} + autoFocus + />} +
+ - setSelectedCategory(event.target.value) - if (event.target.value === "all" || event.target.value === "default") { - listOrgCache(orgId) - } else { - listOrgCache(orgId, event.target.value) - } - - // Add it to the url as a query - if (window.location.search.includes("category=")) { - const newurl = window.location.href.replace(/category=[^&]+/, `category=${event.target.value}`) - window.history.pushState({ path: newurl }, "", newurl) - } else { - window.history.pushState({ path: window.location.href }, "", `${window.location.href}&category=${event.target.value}`) - } - }} - > - {fileCategories.map((data, index) => { - return ( - - {data.replaceAll("_", " ")} - - ); - })} - - { - setShowFileCategoryPopup(false) - }} - > - File Categories - - Please note that your selected files ({selectedFileId?.length}) will be moved to the {updateToThisCategory} category. - - - - - - - - ) : null} + + + + - - : - - + + + + + - } + - {renderTextBox && { - handleKeyDown(event); - if(event.key === 'Enter' && selectedFileId.length > 0){ - //setShowFileCategoryPopup(true) - setUpdateToThisCategory(event.target.value) - } + {showAutomationMenu || showSettingsMenu ? + { + setShowAutomationMenu(false) + setShowSettingsMenu(false) + }} + PaperProps={{ + sx: { + borderRadius: theme?.palette?.DialogStyle?.borderRadius, + border: theme?.palette?.DialogStyle?.border, + minWidth: 500, + minHeight: 700, + fontFamily: theme?.typography?.fontFamily, + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + zIndex: 1000, + '& .MuiDialogContent-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogTitle-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogActions-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + }, + }} + > + + + + {showSettingsMenu === true ? +
+ + + Settings for category '{selectedCategory}' + + +
+
+ + Timeout + + + You can set a timeout for the category. This will delete all keys in this category after the specified time. Timeout is in seconds and based on last EDITED time. + +
+ { + // Check if it's a number or not + var timeoutValue = 0 + if (isNaN(e.target.value) || e.target.value === "" || e.target.value === null) { + toast.info("Timeout must be a number. Setting to 0.") + } else { + timeoutValue = parseInt(e.target.value, 10) + if (timeoutValue < 60) { + toast.info("Timeout must be between 60 seconds or more. Setting to 0.") + } + + if (timeoutValue === categoryConfig?.settings?.timeout) { + return + } + } + + setCategorySettingsField("timeout", timeoutValue) + }} + /> +
+ +
+
+ + {categoryConfig?.settings?.public === true ? "" : "NOT"} Public + + + This will make the url for this category public. Metadata will be cleared, except for timestamps. Types: keys,ndjson,csv,values,json,meta + +
+
URL (when public):
{globalUrl}/api/v2/datastore/category/{selectedCategory}?top=10000&type=keys&org_id={orgId} +
+
+
+
+ + { + setCategorySettingsField("public", e.target.checked) + setUpdate(Math.random()) // Force update to re-render the component + }} + style={{marginTop: 10, }} + color="secondary" + /> + +
+
+ +
+
+ + Subscribing + + + Enabling this feature will allow other organizations to subscribe to this category. This is NOT fully available yet. + +
+
+
+ : +
+ + + Automation for category '{selectedCategory}' + + + + When + + + A key is edited + + + + Do + + {categoryAutomations.map((automation, index) => { + + return ( + + ) + })} +
+ } - }} - style={{ - height: 35, - width: 200, - marginTop: 0, - }} - InputProps={{ - style: { - color: "white", - height: 35, - fontSize: 16, - borderRadius: 4, - paddingTop: 0, - }, - }} - color="primary" - placeholder="Category name" - required - margin="dense" - defaultValue={""} - autoFocus - />}
+ + + : null} +
+ {isSelectedDataStore? null :} -
- - - {["Key", "Value", "Actions", "Updated", "Distribution"].map((header, index) => ( - - ))} - - {cachedLoaded === false - ? [...Array(6)].map((_, rowIndex) => ( - - {Array(5) - .fill() - .map((_, colIndex) => ( - - - - ))} - - )) - : listCache?.length === 0 ? ( - - {Array(5).fill().map((_, index) => ( - - ))} - - ): listCache?.map((data, index) => { - var category = selectedCategory - if (selectedCategory === "default") { - category = "" + { + setSelectedRows(newSelection) + }} + keepNonExistentRowsSelected={false} + getRowId={(row) => row.key} + + autoHeight={true} + sx={{ + marginTop: 1, + height: listCache.length*52+500, + width: "100%", + '.MuiTablePagination-selectLabel, .MuiTablePagination-select, .MuiTablePagination-selectIcon': { + display: 'none', + }, + marginBottom: 20, + }} + + loading={cachedLoaded === false} + pagination + paginationMode="server" + page={page} + rowCount={totalAmount} + onPageChange={(newPage, second) => { + listOrgCache(orgId, selectedCategory, 0, pageSize, newPage) + + setPage(newPage) + }} + onPageSizeChange={(newSize) => { + setPageSize(newSize); + setPage(0) + setSelectedRows([]) + + setCursors({ + 0: "", + }) + }} + + + filterMode="client" + onFilterModelChange={(model) => { + // Specific search for the key itself to find it fast across the index + if (model?.items?.length === 1) { + if (model?.items[0]?.operatorValue === "equals" && model?.items[0]?.columnField === "key") { + // Run backend search for a specific key + listOrgCache(orgId, selectedCategory, 0, pageSize, page, model?.items[0]?.value) + } + } + }} + + getRowHeight={() => { + return "auto" + }} + + hideFooterSelectedRowCount={true} + hideFooter={true} + /> + +
+
+ + {page * pageSize + 1} - {Math.min((page + 1) * pageSize, totalAmount)} of {totalAmount} + + + { + var disabled = false + if (item?.type === "page") { + if (cursors[item.page-1] === undefined) { + disabled = true + } + } + if (item?.type === "previous") { + disabled = page === 0 + } + + if (cachedLoaded === false) { + disabled = true + } + + return ( + + ) + + }} + onChange={(e, value) => { + if (value < 1) { + return } - if (data?.category === undefined && category === "") { - } else if (data?.category !== category) { - return null - } + const newPage = value-1 - var bgColor = isSelectedDataStore? "#212121":"#27292d"; - if (index % 2 === 0) { - bgColor = isSelectedDataStore? "#1A1A1A":"#1f2023"; - } + listOrgCache(orgId, selectedCategory, 0, pageSize, newPage) - const validate = validateJson(data.value); - const isDistributed = data?.suborg_distribution?.length > 0 ? true : false; - return ( - - - { - // handleReactJsonClipboard(copy); - }} - collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength} - iconStyle={theme.palette.jsonIconStyle} - displayDataTypes={false} - onSelect={(select) => { - // HandleJsonCopy(showResult, select, data.action.label); - console.log("SELECTED!: ", select); - }} - name={"value"} - /> - : - data.value + setPage(newPage) + }} + /> + + {selectedRows.length > 0 ? +
+ + deleteEntry(orgId, selectedRows[key], foundCategory, false) + } + + setSelectedRows([]) + setTimeout(() => { + // Refresh the list + listOrgCache(orgId, selectedCategory, 0, pageSize, page) + toast.success("Deleted " + selectedRows.length + " keys from datastore") + }, 2500) + + }} + variant={"outlined"} + color="secondary" + > + + Delete {selectedRows.length} Key{ selectedRows.length > 1 ? "s" : "" } + + : null} +
+
+
); -}); +}) +//export default CacheView; export default memo(CacheView); diff --git a/frontend/src/components/ChatBot.jsx b/frontend/src/components/ChatBot.jsx new file mode 100644 index 00000000..4e491ea4 --- /dev/null +++ b/frontend/src/components/ChatBot.jsx @@ -0,0 +1,846 @@ +import React, { useState, useEffect, useContext } from "react"; + +import { useParams, useNavigate, Link } from "react-router-dom"; +import { v4 as uuidv4 } from "uuid"; +import theme from '../theme.jsx'; +import Markdown from 'react-markdown' +import { isMobile } from "react-device-detect"; +import { Context } from '../context/ContextApi.jsx'; + +import AppSearch from "../components/AppSearch1.jsx"; + +import { + Divider, + ButtonGroup, + TextField, + Button, + IconButton, + Typography, + CircularProgress, + Card, + CardContent, +} from "@mui/material"; + +import { + Send as SendIcon, +} from "@mui/icons-material"; + +import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; +import AuthenticationWindow from "../components/AuthenticationWindow.jsx"; + +const ChatBot = (props) => { + const { globalUrl } = props + const { supportEmail } = useContext(Context) + const [messages, setMessages] = useState([]) + const [message, setMessage] = useState(""); + const [loading, setLoading] = useState(false); + const [appAuthentication, setAppAuthentication] = React.useState([]); + const [inputAuth, setInputAuth] = useState([]) + const [forceReauthentication, setForceReauthentication] = useState(false); + const [selectedType, setSelectedType] = useState("atomic"); + + const [appname, setAppname] = useState(""); + const [threadId, setThreadId] = useState(""); + const [runId, setRunId] = useState(""); + + const [showAppSearch, setShowAppSearch] = useState(false); + + + const waitingMsg = "Processing..." + const viewWidth = isMobile ? "92%" : 800 + + useEffect(() => { + // Check if loading and remove Waiting... from messages + const newmessages = messages + const foundmessages = messages.filter((msg) => msg.message !== waitingMsg) + if (foundmessages.length < newmessages.length) { + setMessages(foundmessages); + } + + // Wait 0.5 second + const objDiv = document.getElementById("messages-window"); + if (objDiv !== undefined && objDiv !== null) { + setTimeout(() => { + objDiv.scrollTop = objDiv.scrollHeight; + }, 250); + } + }, [messages]); + + useEffect(() => { + if (appname === undefined || appname === null || appname === "") { + return + } + + // Find the last message that was sent by us and reuse the same message content + // with added app stuff only + for (var i = messages.length-1; i >= 0; i--) { + const msg = messages[i] + if (msg.status === "sent") { + handleSubmit(undefined, msg.message) + break + } + } + }, [appname]) + + window.title = "Shuffle - New Chat" + let navigate = useNavigate(); + + // Automatic submit handler based on a lot of stuff :) + const handleSubmit = (e, inputmsg) => { + if (e !== undefined) { + e.preventDefault(); + e.stopPropagation(); + } + + setLoading(true) + setMessage(""); + + const sentId = uuidv4(); + var parsedData = { + "query": inputmsg, + "thread_id": threadId, + "run_id": runId, + } + + if (appname !== undefined && appname !== null && appname !== "") { + parsedData["app_name"] = appname + } + + if (inputAuth !== undefined && inputAuth.length > 0) { + // Forcing first auth app to be used in request + try { + parsedData["app_name"] = inputAuth[0].name + parsedData["app_id"] = inputAuth[0].id + parsedData["category"] = inputAuth[0].category + parsedData["action_name"] = inputAuth[0].action_name + } catch (e) { + } + + try { + parsedData["app_name"] = inputAuth.apps[0].name + parsedData["app_id"] = inputAuth.apps[0].id + parsedData["category"] = inputAuth.apps[0].category + parsedData["action_name"] = inputAuth.apps[0].action_name + } catch (e) { + } + } + + if (selectedType !== "default") { + if (selectedType == "workflow") { + parsedData["output_format"] = "workflow_suggestion" + } else { + parsedData["output_format"] = selectedType + } + } + + console.log("INPUT: ", parsedData) + + setInputAuth([]) + + var newmessages = messages; + newmessages.push({ + "id": sentId, + "status": "sent", + "message": inputmsg, + }) + newmessages.push({ + "id": sentId, + "status": "received", + "message": waitingMsg, + }) + + setMessages(newmessages); + + //fetch(`http://localhost:8080/api/v1/conversation`, { + fetch(`${globalUrl}/api/v1/conversation`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify(parsedData), + }) + .then((res) => res.text()) + .then((resText) => { + setLoading(false) + var data = {} + + // JSON parse + try { + data = JSON.parse(resText); + } catch (e) { + console.log("Error parsing response as JSON: ", e); + + newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); + newmessages.push({ + "status": "received", + "message": resText, + "id": uuidv4(), + }); + + setMessages(newmessages); + + return; + } + + + if (data.run_id !== undefined && data.run_id !== null && data.run_id !== "") { + setRunId(data.run_id) + } + + if (data.thread_id !== undefined && data.thread_id !== null && data.thread_id !== "") { + setThreadId(data.thread_id) + } + + if (data.success === undefined) { + newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); + newmessages.push({ + "status": "received", + "message": resText, + "id": uuidv4(), + }); + + setMessages(newmessages); + + return; + } + + // authentication for app + // app validation (choose one) + const defaultMessage = `Default output. The feature you're interacting with may not have been implemented yet. Contact ${supportEmail} with a screenshot of this and your input please.` + + var outputmessage = defaultMessage; + var status = "received"; + var action = "" + if (data.success === false) { + if (data.reason !== undefined) { + outputmessage = data.reason + } + + status = "error" + } else { + if (data.reason !== undefined) { + outputmessage = data.reason + } + } + + if (data.action !== undefined) { + //console.log("Action is defined: ", data.action); + action = data.action + + if (data.action === "app_authentication") { + // If success & app auth -> say auth success and show available labels + // If !success & app auth -> do authentication + if (data.success === true) { + newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); + // No action for this. + // "action": action, + var appname = "" + if (data.apps !== undefined && data.apps !== null && data.apps.length > 0) { + appname = data.apps[0].name.replaceAll("_", " ") + } + + var outputmessage = `**Please specify which ${appname} action you want to use**: \n` + if (data.available_labels !== undefined && data.available_labels !== null && data.available_labels.length > 0) { + for (var i = 0; i < data.available_labels.length; i++) { + outputmessage += "* " + data.available_labels[i] + "\n" + } + outputmessage += "* Reauthenticate ([see auth](/admin?tab=app_auth))" + } + //Some opavailable actions: " + data.apps.map((app) => app.name).join(", ") + const parsedmessage = { + "status": status, + "message": outputmessage, + "id": uuidv4(), + "category": data.category, + + "thread_id": data.thread_id, + "run_id": data.run_id, + } + newmessages.push(parsedmessage); + setMessages(newmessages); + + return + + } else { + if (data.apps !== undefined) { + setInputAuth(data.apps) + + setMessage(inputmsg); + + setForceReauthentication(true) + } + } + } else if (data.action === "select_category" || data.action === "select_app") { + console.log("[DEBUG] APP SELECTION! Should help them choose an app to use") + // Show a search field + setShowAppSearch(true) + } + } + + newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); + const parsedmessage = { + "status": status, + "message": outputmessage, + "id": uuidv4(), + "action": action, + "category": data.category, + + "thread_id": data.thread_id, + "run_id": data.run_id, + } + newmessages.push(parsedmessage); + setMessages(newmessages); + console.log("New message: ", parsedmessage) + }) + .catch((err) => { + setLoading(false) + console.log("Problem: ", err); + + setMessage(message); + newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); + + // Find the message with the sentId and change the status to error + newmessages.push({ + "status": "error", + "message": message, + "error_message": "Failed to send: "+err, + "id": sentId, + }); + setMessages(newmessages); + }); + }; + + // Used to verify if the user is logged in after auth is done + const getAppAuthentication = () => { + console.log("Continue chat from the previous stage!"); + + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for app auth :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + console.log("Failed to get app auth!"); + return; + } + + var newauth = []; + for (let authkey in responseJson.data) { + if (responseJson.data[authkey].defined === false) { + continue; + } + + newauth.push(responseJson.data[authkey]); + } + + if (newauth.length > appAuthentication.length) { + console.log("New auth is longer than old auth. Set new auth!"); + + setForceReauthentication(false) + + // Check if last message contains "reauth" + if (messages.length > 0) { + const lastmessage = messages[messages.length-1]; + if (lastmessage.message.toLowerCase().includes("re-auth")) { + console.log("Skipping resend due to reauth") + + var newmessages = messages + newmessages.push({ + "status": "received", + "message": "Authentication done. What do you want to do?", + "id": uuidv4(), + }); + setMessages(newmessages); + + } else { + handleSubmit(undefined, message) + } + } else { + handleSubmit(undefined, message) + } + } + + setAppAuthentication(newauth) + + }) + .catch((err) => { + console.log("Error in getAppAuthentication: ", err); + }) + } + + const AuthWrapper = (props) => { + const { app } = props; + const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false); + + console.log("AUTH: ", app) + + return ( +
+ {app.authentication.type === "oauth2" || app.authentication.type === "oauth2-app" ? + + : + + } + + {app.authentication.type !== "oauth2" && authenticationModalOpen ? + + : null} +
+ ) + } + + var amountfinished = 0; + const showAuthentication = inputAuth.map((app, index) => { + const authexists = appAuthentication.find((auth) => auth.app.id === app.id); + if (authexists !== undefined && forceReauthentication === false) { + console.log("Auth exists: ", authexists); + amountfinished += 1 + return null + } + + return ( +
+ +
+ ) + }) + + if (amountfinished === inputAuth.length && amountfinished > 0) { + + setInputAuth([]) + setAppAuthentication([]) + } + + const showSamples = +
+ + + + How many incidents did we get last week? + + + + + + + Answer the last email from Jim about the new project, and say we're on it + + + + + + + Is the IP 1.2.3.4 blocked? If not, block it. + + + +
+ + function OuterLink(props) { + return ( + + {props.children} + + ); + } + + function Img(props) { + return {props.alt}; + } + + function CodeHandler(props) { + //console.log("Codehandler PROPS: ", props) + + const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : "" + + return ( +
+ {propvalue} +
+ ); + } + + + const markdownStyle = { + color: "rgba(255, 255, 255, 0.65)", + overflow: "hidden", + paddingBottom: 100, + margin: "auto", + maxWidth: "100%", + minWidth: "100%", + overflow: "hidden", + fontSize: isMobile ? "1.3rem" : "1.0rem", + } + + const Heading = (props) => { + const element = React.createElement( + `h${props.level}`, + { style: { marginTop: props.level === 1 ? 20 : 50 } }, + props.children + ); + const [hover, setHover] = useState(false); + + var extraInfo = ""; + return ( + { + setHover(true); + }} + > + {props.level !== 1 ? ( + + ) : null} + {element} + {/*hover ? {setHover(true)}} style={{cursor: "pointer", display: "inline", }} onClick={() => { + window.location.href += "#hello" + console.log(window.location) + //window.history.pushState('page2', 'Title', '/page2.php'); + //window.history.replaceState('page2', 'Title', '/page2.php'); + }} /> + : "" + */} + {extraInfo} + + ); + } + + const OrderedList = (props) => { + var parsedchildren = [] + for (var i = 0; i < props.children.length; i++) { + const child = props.children[i] + if (child === "\n") { + continue + } + + if (child.props !== undefined && child.props.children !== undefined) { + // Remove

from the child wrapper + var parsedchild = [] + for (var j = 0; j < child.props.children.length; j++) { + const childchild = child.props.children[j] + // print the raw childchild bytes, not string + if (childchild === "\n") { + continue + } + + // If the childchild has

around it, remove it + parsedchild.push(childchild) + + /* + // Not doing this as it breaks links + if (childchild.props !== undefined && childchild.props.children !== undefined) { + parsedchild.push(childchild.props.children) + } else { + parsedchild.push(childchild) + } + */ + } + + parsedchildren.push(parsedchild) + } + } + + return ( +

    + {parsedchildren.map((child, index) => { + return ( +
  1. +

    + {index+1}. {child} +

    +
  2. + ) + })} +
+ ) + } + + const Paragraph = (props) => { + return ( +

+ {props.children} +

+ ) + } + + const markdownComponents = { + ol: OrderedList, + ul: OrderedList, + img: Img, + code: CodeHandler, + h1: Heading, + h2: Heading, + h3: Heading, + h4: Heading, + h5: Heading, + h6: Heading, + a: OuterLink, + p: Paragraph, + } + + const chatWindow = +
+ {messages.length === 0 ? + +

Shuffle AI

+ + + + {showSamples} +
+ : null} +
+ {messages.map((message, index) => { + const float = message.status === "sent" ? "left" : "right"; + const border = message.status === "error" ? "red" : "rgba(255,255,255,0.3)" + + const hasAction = message.action !== undefined && message.action !== null && message.action !== "" + + return ( + // Make a chat bubble component +
+ { + if (!hasAction) { + return + } + + if (message.action === "login") { + navigate("/login?view=/conversation&message=You must log in to use ShuffleGPT") + } else if (message.action === "app_authentication") { + console.log("App auth action!") + //setAuthenticationModalOpen(true) + } else { + console.log("\n\nUnknown click action: ", message.action) + } + }}> + {message.message === waitingMsg ? : null} + + + {message.message} + + + {message.thread_id !== undefined && message.thread_id !== null && message.thread_id !== "" ? + + Thread: {message.thread_id} + + : null + } + + + + {message.status === "error" && message.error_message ? + + {message.error_message} + + : null} + + {(message.action === "select_category" || message.action === "select_app") && showAppSearch && index === messages.length-1 ? +
+ +
+ : null} +
+ ) + })} +
+ {showAuthentication} + +
+
+ {messages.length === 0 ? + + + Query Type + + + {/* + + */} + + + + + : null} + + handleSubmit(e, message)} style={{bottom: 20, marginTop: 10, marginBottom: isMobile ? 0 : 10, maxWidth: viewWidth, minWidth: viewWidth, }}> + setMessage(e.target.value)} + variant="outlined" + autoFocus + InputProps={{ + endAdornment: ( + handleSubmit(e, message)} + > + + + ) + }} + /> + + + {isMobile ? null : + + {`The Shuffle AI is a test system for automatic workflow generation and atomic functions for the future of Shuffle. Shuffle AI may use your organization info in the query, and attempts to auto-correct any failed behavior. If you have any questions, please contact us at ${supportEmail}`} + + } +
+
+
+ +return ( +
+ {chatWindow} +
+) +} + +export default ChatBot; diff --git a/frontend/src/components/CloudSyncTab.jsx b/frontend/src/components/CloudSyncTab.jsx index 08926570..88df2610 100644 --- a/frontend/src/components/CloudSyncTab.jsx +++ b/frontend/src/components/CloudSyncTab.jsx @@ -26,7 +26,7 @@ import { Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon, } from "@mui/icons-material"; -import theme from "../theme.jsx"; +import { getTheme } from "../theme.jsx"; import { styled } from '@mui/styles'; import { Context } from "../context/ContextApi.jsx"; @@ -48,13 +48,21 @@ const CloudSyncTab = (props) => { const [, forceUpdate] = React.useState(); const itemColor = "white"; const isCloud = window?.location?.host === "localhost:3002" || window?.location?.host === "shuffler.io"; + + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + useEffect(() => { getSettings(); }, []); + const GridItem = (props) => { const [expanded, setExpanded] = React.useState(false); const [showEdit, setShowEdit] = React.useState(false); 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 primaryIcon = props.data.icon; const secondaryIcon = props.data.active ? @@ -167,9 +175,9 @@ const CloudSyncTab = (props) => {
{ {primaryIcon} {isCloud && userdata.support === true ? { e.preventDefault(); @@ -475,7 +483,7 @@ const CloudSyncTab = (props) => { } else { toast("Cloud Syncronization successfully set up!"); setOrgSyncResponse( - "Successfully started syncronization. Cloud features you now have access to can be seen below." + "Successfully started syncronization. Cloud/Hybrid features are available below." ); } @@ -527,20 +535,20 @@ const CloudSyncTab = (props) => { return (
-

Cloud syncronization -

- - What does cloud sync 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 cloud sync 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. +
{isCloud ? (
- + Currently syncronizing:{" "} {selectedOrganization.cloud_sync_active === true ? True @@ -561,28 +569,31 @@ const CloudSyncTab = (props) => { marginRight: 10, fontSize: 16, fontWeight: 400, + color: theme.palette.text.primary, fontFamily: theme.typography.fontFamily, }} > Your Api key {userSettings?.apikey === undefined || userSettings?.apikey === null || userSettings?.apikey?.length <=0 ? ( - + ):
@@ -639,14 +650,15 @@ const CloudSyncTab = (props) => { {
)} -

- Features -

- + + {isCloud ? "Cloud" : "Hybrid"} Features + + 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. - + {selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? + {[...Array(18)].map((_, i) => ( +
{ variant="rectangular" height={50} width={343} - sx={{ backgroundColor: '#1a1a1a', display: 'flex', borderRadius: 1 }} + sx={{ backgroundColor: theme.palette.loaderColor, display: 'flex', borderRadius: 1 }} animation="wave" />
@@ -750,6 +764,13 @@ const CloudSyncTab = (props) => { } const newkey = key.replaceAll("_", " "); + + // Rewrites to frontend names + var newname = newkey + if (newkey === "app executions") { + newname = "app runs" + } + const griditem = { primary: newkey, secondary: @@ -764,6 +785,8 @@ const CloudSyncTab = (props) => { data_collection: "None", active: item.active, icon: , + + newname: newname, }; return ( diff --git a/frontend/src/components/CollectIngestModal.jsx b/frontend/src/components/CollectIngestModal.jsx new file mode 100644 index 00000000..afba4afa --- /dev/null +++ b/frontend/src/components/CollectIngestModal.jsx @@ -0,0 +1,205 @@ +import React, { useState, useEffect, useContext, memo } from "react"; + +import { Context } from "../context/ContextApi.jsx"; +import { getTheme } from "../theme.jsx"; +import { GetIconInfo } from "../views/Workflows2.jsx"; +import { toast } from 'react-toastify'; + +import { + Dialog, + DialogTitle, + DialogContent, + Typography, + Paper, + LinearProgress, + Grid, + Button, +} from '@mui/material'; + +import { + Rocket as RocketIcon, + FilterAlt as FilterAltIcon, +} from '@mui/icons-material'; + +const CollectIngestModal = (props) => { + const { globalUrl, open, setOpen } = props; + + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + + if (open === undefined || open === null) { + console.error("CollectIngestModal: 'open' prop is required."); + return null + } + + if (setOpen === undefined || setOpen === null) { + console.error("CollectIngestModal: 'setOpen' prop is required."); + return null + } + + const startIngestion = (appname, index) => { + console.log("APPNAME:", appname, "INDEX:", index) + + const body = { + "app_name": appname, + "label": appname, + } + + const url = `${globalUrl}/api/v2/workflows/generate` + fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + credentials: "include", + }) + .then((response) => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then((data) => { + console.log("Ingestion started successfully:", data); + toast.success(`Ingestion for ${appname} started successfully!`); + }) + .catch((error) => { + console.error("Error starting ingestion:", error); + toast.error(`Failed to start ingestion for ${appname}. Please try again.`); + }); + + } + + const IngestItem = (props) => { + const { type, index } = props + + const [hovering, setHovering] = useState(false); + const [isFinished, setIsFinished] = useState(false); + + const appname = type + const ingestedAmount = 20 + + const iconDetails = GetIconInfo({ + "app_name": appname, + "name": appname, + }) + + return ( + // setHovering(true)} + onMouseLeave={() => setHovering(false)} + > +
+ {iconDetails?.originalIcon && ( + iconDetails?.originalIcon + )} + + + + {appname} + +
+ + + {hovering ? +
+
+ : null} + + {isFinished ? +
+ + {ingestedAmount} / X + + +
+ : null} +
+ ) + } + + return ( + { + setOpen(false) + }} + > + + + + + + + Collection and Ingestion + + + + + + + + + + + + + + ) +} + +export default CollectIngestModal diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index a6ac8623..b6f0c734 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { useInterval } from "react-powerhooks"; import { toast } from 'react-toastify'; -import theme from "../theme.jsx"; +import {getTheme} from "../theme.jsx"; import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx" import { @@ -20,7 +20,7 @@ import { Collapse, IconButton, } from "@mui/material"; - +import { Context } from "../context/ContextApi.jsx"; import { FavoriteBorder as FavoriteBorderIcon, Error as ErrorIcon, @@ -76,6 +76,8 @@ const ConfigureWorkflow = (props) => { const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false); const [loopRunning, setLoopRunning] = useState(false) const [checkStarted, setCheckStarted] = React.useState(false); + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); useEffect(() => { if (requiredActions.length === 0) { @@ -631,7 +633,7 @@ const ConfigureWorkflow = (props) => { if (aa !== undefined) { aa('init', { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }) const timestamp = new Date().getTime() @@ -799,7 +801,7 @@ const ConfigureWorkflow = (props) => { >
diff --git a/frontend/src/components/Countries.jsx b/frontend/src/components/Countries.jsx index 2cb9c853..590e4caf 100644 --- a/frontend/src/components/Countries.jsx +++ b/frontend/src/components/Countries.jsx @@ -142,7 +142,6 @@ const countries = [ suggested: true, }, { code: 'GA', label: 'Gabon', phone: '241' }, - { code: 'GB', label: 'United Kingdom', phone: '44' }, { code: 'GD', label: 'Grenada', phone: '1-473' }, { code: 'GE', label: 'Georgia', phone: '995' }, { code: 'GF', label: 'French Guiana', phone: '594' }, @@ -178,7 +177,6 @@ const countries = [ { code: 'IE', label: 'Ireland', phone: '353' }, { code: 'IL', label: 'Israel', phone: '972' }, { code: 'IM', label: 'Isle of Man', phone: '44' }, - { code: 'IN', label: 'India', phone: '91' }, { code: 'IO', label: 'British Indian Ocean Territory', @@ -390,12 +388,6 @@ const countries = [ }, { code: 'UA', label: 'Ukraine', phone: '380' }, { code: 'UG', label: 'Uganda', phone: '256' }, - { - code: 'US', - label: 'United States', - phone: '1', - suggested: true, - }, { code: 'UY', label: 'Uruguay', phone: '598' }, { code: 'UZ', label: 'Uzbekistan', phone: '998' }, { diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx index f3922e3e..96e9c059 100644 --- a/frontend/src/components/CreatorGrid.jsx +++ b/frontend/src/components/CreatorGrid.jsx @@ -37,7 +37,7 @@ import { AvatarGroup, } from "@mui/material" -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const CreatorGrid = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, isHeader } = props const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows diff --git a/frontend/src/components/DocsGrid.jsx b/frontend/src/components/DocsGrid.jsx index 7a32001c..d22beaa4 100644 --- a/frontend/src/components/DocsGrid.jsx +++ b/frontend/src/components/DocsGrid.jsx @@ -29,7 +29,7 @@ import { -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const DocsGrid = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, } = props const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows diff --git a/frontend/src/components/EditOrgTab.jsx b/frontend/src/components/EditOrgTab.jsx index c1cd11fc..0e2b5176 100644 --- a/frontend/src/components/EditOrgTab.jsx +++ b/frontend/src/components/EditOrgTab.jsx @@ -3,6 +3,8 @@ import OrgHeaderexpanded from "../components/OrgHeaderexpandedNew.jsx"; import OrgHeader from '../components/OrgHeaderNew.jsx'; import { toast } from "react-toastify"; import CloudSyncTab from '../components/CloudSyncTab.jsx'; +import { Context } from '../context/ContextApi.jsx'; +import { getTheme } from '../theme.jsx'; import { FileCopy as FileCopyIcon, } from "@mui/icons-material"; @@ -10,6 +12,7 @@ import { Button, Tooltip, IconButton, + Typography, } from "@mui/material"; const EditOrgTab = (props) => { @@ -33,6 +36,9 @@ const EditOrgTab = (props) => { } }, []); + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + const handleStatusChange = (event) => { const { value } = event.target; setSelectedStatus(value); @@ -283,23 +289,23 @@ If you're interested, please let me know a time that works for you, or set up a return ( -
-
+
+
-

Organization overview

- + Organization overview + On this page organization admins can configure organisations, and sub-orgs (MSSP).{" "} Learn more - +
{ const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, setRealtimeMarkdown, boxWidth, setBoxWidth, } = props const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove - + const {themeMode, brandColor} = useContext(Context) + const theme = getTheme(themeMode, brandColor) const [submitLoading, setSubmitLoading] = React.useState(false); const [showMoreClicked, setShowMoreClicked] = React.useState(isEditing !== false ? true : false); @@ -187,7 +188,7 @@ const EditWorkflow = (props) => { } 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 total_count = 0 @@ -203,26 +204,24 @@ const EditWorkflow = (props) => { setModalOpen(false); }} PaperProps={{ - style: { - color: "white", - minWidth: isMobile ? "90%" : 650, - maxWidth: isMobile ? "90%" : 650, - minHeight: 400, - paddingTop: 25, - paddingLeft: 50, + sx: { + color: theme.palette.DialogStyle.color, + minWidth: isMobile ? "90%" : "650px", + maxWidth: isMobile ? "90%" : "650px", + minHeight: "400px", //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, - borderRadius: theme.palette.borderRadius, - backgroundColor: "black", + borderRadius: theme.palette.DialogStyle.borderRadius, + backgroundColor: themeMode === "dark" ? "black" : theme?.palette?.DialogStyle?.backgroundColor, }, }} > - +
-
+
- - {newWorkflow ? "New" : "Editing"} workflow + + {newWorkflow ? "New" : "Editing"} Workflow {newWorkflow === true ? null : @@ -248,7 +247,7 @@ const EditWorkflow = (props) => {
- Workflows can be built from scratch, or from templates. Usecases can help you discover next steps, and you can search for them directly. Learn more + Workflows can be built from scratch, or from templates. Usecases can help you discover next steps, and you can search for them directly. Learn more
@@ -288,16 +287,16 @@ const EditWorkflow = (props) => {
- +
{ @@ -396,7 +395,7 @@ const EditWorkflow = (props) => { }} InputProps={{ style: { - color: "white", + color: theme.palette.textFieldStyle.color, }, }} color="primary" @@ -469,7 +468,7 @@ const EditWorkflow = (props) => { style={{ flex: 1, maxHeight: 120, overflow: "auto", }} InputProps={{ style: { - color: "white", + color: theme.palette.textFieldStyle.color, }, }} placeholder="Tags" @@ -1287,7 +1286,7 @@ const EditWorkflow = (props) => { {newWorkflow === true ? - + Relevant Workflows diff --git a/frontend/src/components/EnvironmentTab.jsx b/frontend/src/components/EnvironmentTab.jsx index 15efd2fc..6aefcf89 100644 --- a/frontend/src/components/EnvironmentTab.jsx +++ b/frontend/src/components/EnvironmentTab.jsx @@ -1,5 +1,5 @@ import React, { memo, useContext, useEffect, useState } from 'react'; -import theme from "../theme.jsx"; +import { getTheme } from "../theme.jsx"; import { Tooltip, Typography, @@ -62,6 +62,9 @@ const EnvironmentTab = memo((props) => { const [selectedSubOrg, setSelectedSubOrg] = React.useState([]); const [showLocationActionModal, setShowLocationActionModal] = React.useState(undefined) + const { themeMode, supportEmail, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + useEffect(() => { getEnvironments(); @@ -370,7 +373,7 @@ const EnvironmentTab = memo((props) => { }) .catch((error) => { toast( - "Failed dismissing alert. Please contact support@shuffler.io if this persists.", + `Failed dismissing alert. Please contact ${supportEmail} if this persists.`, ); }); }; @@ -512,11 +515,11 @@ const EnvironmentTab = memo((props) => { }} > - Add Location + Add Location
- Location Name + Location Name { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.textFieldStyle.color, fontSize: "1em", }, }} @@ -539,19 +542,18 @@ const EnvironmentTab = memo((props) => { } />
- {loginInfo} {/* Assuming loginInfo is part of the relevant content */} + {loginInfo}
@@ -1443,7 +1446,7 @@ const EnvironmentTab = memo((props) => { > + label= { /> + label= Scale /> + label= k8s /> {installationTab === 2 ? - + Check our Kubernetes documentation for more information on how to run Shuffle on Kubernetes. The status of the node will change when connected. - + : - + 1. Ensure Docker is installed and the target server can reach '{globalUrl}' - + } @@ -1501,13 +1504,19 @@ const EnvironmentTab = memo((props) => { >
{getOrborusCommand(environment)} @@ -1532,7 +1541,8 @@ const EnvironmentTab = memo((props) => {
- Configure HTTP Proxies: + Configure HTTP Proxies: { if (commandController.proxies === undefined) { @@ -1545,8 +1555,10 @@ const EnvironmentTab = memo((props) => { setUpdate(Math.random()) }} /> +
- Disable Pipelines & Data Lake: + Disable Pipelines & Data Lake: { if (commandController.pipelines === undefined) { @@ -1559,6 +1571,7 @@ const EnvironmentTab = memo((props) => { }} />
+
} diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index f825fb7f..71c3af28 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -43,7 +43,7 @@ import { import Dropzone from "../components/Dropzone.jsx"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; -import theme from "../theme.jsx"; +import {getTheme} from "../theme.jsx"; import { Context } from "../context/ContextApi.jsx"; const Files = memo((props) => { @@ -58,6 +58,8 @@ const Files = memo((props) => { const [openEditor, setOpenEditor] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false); const [loadFileModalOpen, setLoadFileModalOpen] = React.useState(false); + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); const [field1, setField1] = React.useState(""); const [field2, setField2] = React.useState(""); @@ -442,7 +444,7 @@ const Files = memo((props) => { @@ -958,9 +959,9 @@ const Files = memo((props) => { }} /> - + + @@ -1055,8 +1054,9 @@ const Files = memo((props) => { {renderTextBox ? @@ -930,19 +1106,19 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { "& input": { padding: "8px 14px", fontSize: "14px", - color: "#C8C8C8", + color: themeMode === "dark" ? lightText : darkText, }, backgroundColor: "transparent", }} variant="outlined" InputProps={{ startAdornment: ( - + ), endAdornment: ( { }} onClick={() => { setSearchBarModalOpen(true); + setIsDocSearchModalOpen(false); }} onChange={() => { setSearchBarModalOpen(true); + setIsDocSearchModalOpen(false); }} /> ):( <> - + )} @@ -977,7 +1155,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { height: "100%", overflowY: expandLeftNav ? "auto" : "hidden", scrollbarWidth: 'thin', - scrollbarColor: "#494949 transparent", + scrollbarColor: theme.palette.scrollbarColorTransparent, overflowX: "hidden", }} > @@ -990,55 +1168,73 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { }} > - - { setOpenautomateTab((prev) => !prev); setOpenSecurityTab(false); }} style={{ - color: "#FFFFFF", + color: themeMode === "dark" ? lightText : darkText, marginLeft: 0.625, }} onMouseOver={(event)=>{ - event.currentTarget.style.backgroundColor = "#2f2f2f"; + event.currentTarget.style.backgroundColor = themeMode === "dark" ? darkHoverColor : lightHoverColor; }} onMouseOut={(event)=>{ event.currentTarget.style.backgroundColor = "transparent"; @@ -1074,8 +1270,9 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { gap: 10, }} > - - - - - - { }} > - - + { setOpenSecurityTab((prev) => !prev); @@ -1251,10 +1440,10 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { }} style={{ marginLeft: 0.625, - color: "#FFFFFF", + color: themeMode === "dark" ? lightText : darkText, }} onMouseOver={(event)=>{ - event.currentTarget.style.backgroundColor = "#2f2f2f"; + event.currentTarget.style.backgroundColor = themeMode === "dark" ? darkHoverColor : lightHoverColor; }} onMouseOut={(event)=>{ event.currentTarget.style.backgroundColor = "transparent"; @@ -1275,257 +1464,327 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { )} - + + - { + setCurrentOpenTab("forms"); + localStorage.setItem("lastTabOpenByUser", "forms"); + }} + sx={{ + width: "100%", + height: 35, + color: themeMode === "dark" ? lightText : darkText, + justifyContent: "flex-start", + textTransform: "none", + backgroundColor: + currentOpenTab === "forms" || currentPath.includes("/forms") + ? themeMode === "dark" ? darkHoverColor : lightHoverColor + : "transparent", + "&:hover": { + backgroundColor: themeMode === "dark" ? darkHoverColor : lightHoverColor, + }, + cursor: "pointer", + }} + > + + • + + - - - - - - - + Forms + + + {userdata && (userdata?.support || userdata?.active_org?.role === "admin") ? ( + <> + + + - - - - - + + + - - - - - + + + + + + + + + + + + ): null} - - - - {recentworkflows?.length > 0 ? @@ -1576,7 +1833,6 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { > { > {userdata?.licensed !== true && !userdata?.org_status?.includes("integration_partner") && expandLeftNav && - + +
} { marginLeft: option.margin_left ? option.margin_left : 0, }} onMouseOver={(e) => { - e.currentTarget.style.backgroundColor = "#444444"; + e.currentTarget.style.backgroundColor = theme.palette.hoverColor; }} onMouseOut={(e) => { - e.currentTarget.style.backgroundColor = option.name === selectedOrg ? "#696969" : "transparent"; + e.currentTarget.style.backgroundColor = option.name === selectedOrg ? theme.palette.hoverColor : "transparent"; }} onClick={(e) => { if (option.id !== userdata?.active_org?.id) { @@ -1673,7 +1931,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { isCloud ? ( { /> @@ -1772,7 +2030,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { display: "flex", width: 48, height: 51, - border: "1px solid #494949", + border: theme.palette.defaultBorder, borderRadius: 8, justifyContent: "center", alignItems: "center", @@ -1787,6 +2045,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { fontSize: "24px", opacity: expandLeftNav ? 0 : 1, transition: "opacity 0.3s ease", + color: themeMode === "dark" ? lightText : darkText, }} /> @@ -1802,112 +2061,106 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { width: "100%", marginTop: 16, gap: 10, - justifyContent: "center", + justifyContent: "space-between", alignItems: "center", marginBottom: expandLeftNav ? 0 : 10, height: 55, borderRadius: 8, }} - onMouseOver ={(event)=>{event.currentTarget.style.backgroundColor = "#2f2f2f";setHoverOnAvatar(true)}} - onMouseLeave ={(event)=>{event.currentTarget.style.backgroundColor = "transparent";setHoverOnAvatar(false)}} + onMouseOver={(event) => { + event.currentTarget.style.backgroundColor = themeMode === "dark" ? darkHoverColor : lightHoverColor; + setHoverOnAvatar(true); + }} + onMouseLeave={(event) => { + event.currentTarget.style.backgroundColor = "transparent"; + setHoverOnAvatar(false); + }} > {expandLeftNav ? ( <> + {avatarMenu} ) : ( - <> - - + {userdata?.username?.substring(0, 1).toUpperCase()} + + )} @@ -1918,6 +2171,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { export default LeftSideBar; const ModalView = memo(({searchBarModalOpen, setSearchBarModalOpen, globalUrl, serverside, userdata, isDocSearchModalOpen}) => { + const {themeMode} = useContext(Context); + const theme = getTheme(themeMode); return ( ( - + diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx index 0e354966..0267267d 100644 --- a/frontend/src/components/LicencePopup.jsx +++ b/frontend/src/components/LicencePopup.jsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import ReactGA from 'react-ga4'; -import theme from "../theme.jsx"; +import {getTheme} from "../theme.jsx"; import countries from "../components/Countries.jsx"; import { Box, @@ -32,7 +32,7 @@ import { import { useNavigate, Link } from "react-router-dom"; import { Autocomplete } from "@mui/material"; import { toast } from "react-toastify" - +import { Context } from "../context/ContextApi.jsx"; import { Cached as CachedIcon, ContentCopy as ContentCopyIcon, @@ -74,6 +74,9 @@ const LicencePopup = (props) => { const [errorMessage, setErrorMessage] = useState("") const [highlight, setHighlight] = useState(false) + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); + // Cloud const [calculatedApps, setCalculatedApps] = useState(600) const [calculatedCost, setCalculatedCost] = useState("$600") @@ -145,11 +148,12 @@ const LicencePopup = (props) => { padding: 20, paddingBottom: 30, borderRadius: theme.palette?.borderRadius, - height: "100%" + height: "100%", + } - const userInScalePlan = userdata?.app_execution_limit > 10000 - const appRuns = userInScalePlan ? (userdata?.app_execution_limit / 1000) + "K App Runs" : userdata?.app_execution_limit === 10000 ? "10,000 App Runs" : "2,000 App Runs" + const userInScalePlan = userdata?.app_execution_limit > 2000 + const appRuns = (userdata?.app_execution_limit / 1000) + "K App Runs" // Add this function to format the limit value const formatLimit = (limit) => { @@ -277,7 +281,7 @@ const LicencePopup = (props) => { } }; - console.log("selectedOrganization: ", selectedOrganization) + // Update the subscription features section billingInfo.subscription = { "active": true, @@ -480,7 +484,6 @@ const LicencePopup = (props) => { }); } - console.log("OrgSyncFeatures: ", selectedOrganization?.sync_features) const extraFeatures = Object.entries(features || {}) .filter(([_, featureData]) => { @@ -503,9 +506,9 @@ const LicencePopup = (props) => { style={{ borderRadius: theme.palette?.borderRadius, }} placement="bottom" > -
- +
setHovered(true)} // onMouseLeave={() => setHovered(false)} > @@ -591,9 +594,9 @@ const LicencePopup = (props) => {
- {subscription.active === true && !isScale && }
@@ -849,13 +852,12 @@ const LicencePopup = (props) => { ) : null} - +
{/*
@@ -1125,7 +1127,6 @@ const LicencePopup = (props) => { color: "white", } - console.log("Priceitem: ", shuffleVariant) // const isLoggedInHandler = () => { // if (calculatedCost === payasyougo) { // handlePayasyougo(props.userdata) @@ -1235,12 +1236,11 @@ const LicencePopup = (props) => { }); }; - console.log("Selected Organization: ", selectedOrganization.subscriptions) return (
- {selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0 ? + {(selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0) && isCloud ? action: "partners_click", label: "go_to_partners", }) - handleItemClick('/partners') + handleItemClick('/become-partner') }else{ - window.open("https://shuffler.io/partners", '_blank'); + window.open("https://shuffler.io/become-partner", '_blank'); return; } }} @@ -751,7 +753,9 @@ const Navbar = (props) => { const topbar_var = "topbar_closed10" const theme = useTheme(); - const {searchBarModalOpen, setSearchBarModalOpen, isDocSearchModalOpen} = useContext(Context) + const {themeMode} = useContext(Context); + const currentTheme = getTheme(themeMode); + const {searchBarModalOpen, setSearchBarModalOpen, isDocSearchModalOpen, setIsDocSearchModalOpen} = useContext(Context) const [pricingModalOpen, setPricingModalOpen] = useState(false); const isTabletOrMobile = useMediaQuery(theme.breakpoints.down("lg")); const isMobile = useMediaQuery(theme.breakpoints.down("md")); @@ -772,7 +776,6 @@ const Navbar = (props) => { window.location.host === "shuffler.io" || window.location.host === "localhost:5002"; - const stripeKey = typeof window === 'undefined' || window.location === undefined ? "" : window.location.origin === "https://shuffler.io" ? "pk_live_51PXYYMEJjT17t98N20qEqItyt1fLQjrnn41lPeG2PjnSlZHTDNKHuisAbW00s4KAn86nGuqB9uSVU4ds8MutbnMU00DPXpZ8ZD" : "pk_test_51PXYYMEJjT17t98NbDkojZ3DRvsFUQBs35LGMx3i436BXwEBVFKB9nCvHt0Q3M4MG3dz4mHheuWvfoYvpaL3GmsG00k1Rb2ksO" useEffect(() => { @@ -853,51 +856,54 @@ const Navbar = (props) => { setSearchBarModalOpen(false); }} PaperProps={{ - style: { + sx: { color: "white", - minWidth: 750, - height: 785, - borderRadius: 16, + minWidth: "750px", + height: "785px", + borderRadius: "16px", border: "1px solid var(--Container-Stroke, #494949)", - background: "var(--Container, #000000)", + background: currentTheme.palette.DialogStyle.backgroundColor, boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)", }, + '& .MuiDialogContent-root': { + backgroundColor: currentTheme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogTitle-root': { + backgroundColor: currentTheme?.palette?.DialogStyle?.backgroundColor, + }, }} sx={{ zIndex: 50005, - '& .MuiBackdrop-root': { - backgroundColor: 'rgba(0, 0, 0, 0.8)', - }, }} > - - - Search for Docs, Apps, Workflows and more - - setSearchBarModalOpen(false)} - sx={{ - color: 'white', - '&:hover': { - backgroundColor: 'rgba(255, 255, 255, 0.1)' - } - }} - > - - - - + + + Search for Docs, Apps, Workflows and more + + setSearchBarModalOpen(false)} + sx={{ + color: 'white', + '&:hover': { + backgroundColor: 'rgba(255, 255, 255, 0.1)' + } + }} + > + + + + - + ); @@ -974,6 +980,10 @@ const Navbar = (props) => { transform: "rotate(180deg)", }, }, + // Add this to hide the ripple effect's container + "& .MuiTouchRipple-root": { + display: "none", + }, }; // Render menu content based on type @@ -1362,6 +1372,8 @@ const Navbar = (props) => { ))} - + + + + + + + + ); @@ -1451,10 +1590,12 @@ const Navbar = (props) => { useEffect(() => { Mousetrap.bind(['command+k', 'ctrl+k'], () => { setSearchBarModalOpen(true); + setIsDocSearchModalOpen(false); return false; // Prevent the default action }); Mousetrap.bind(['esc'], () => { setSearchBarModalOpen(false); + setIsDocSearchModalOpen(false); return false; // Prevent the default action }); @@ -1537,9 +1678,10 @@ const Navbar = (props) => { const topbar = !isCloud || !showTopbar ? null : curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/training" || curpath === "/professional-services" ? -
+ {/* uncommit this to show topbar for release */} + {/*
- {/* Shuffle 1.4.0 is out! Read more about  */} + // Shuffle 1.4.0 is out! Read more about  Shuffle 2.0.0 is out now!  { @@ -1564,7 +1706,37 @@ const Navbar = (props) => { }}> -
+
*/} + {/* commit below div if we need to show release stuff */} +
+ + {/* Shuffle 1.4.0 is out! Read more about  */} + New  + + { + ReactGA.event({ + category: "landingpage", + action: "click_header_training", + label: "", + }) + + navigate("/training") + + }} style={{ cursor: "pointer", textDecoration: "none", fontWeight: 600, color: "rgba(255,255,255,0.9)" }}> + Public Training + + +  Dates Released! + + { + setShowTopbar(false) + + // Set storage that it's clicked + localStorage.setItem(topbar_var, "true") + }}> + + +
: null @@ -1784,7 +1956,10 @@ const Navbar = (props) => { setSearchBarModalOpen(true)} + onClick={() => { + setSearchBarModalOpen(true); + setIsDocSearchModalOpen(false); + }} > @@ -1866,6 +2041,7 @@ const Navbar = (props) => { }) } setSearchBarModalOpen(true); + setIsDocSearchModalOpen(false); }} > @@ -1910,6 +2086,7 @@ const Navbar = (props) => { padding: "8px 20px", "&:hover": { backgroundColor: "#494949", + color: "white", border: "1px solid white", }, }} @@ -2263,6 +2440,7 @@ const Navbar = (props) => { sx={{ width: 20, height: 20 }} /> @@ -2304,6 +2482,7 @@ const Navbar = (props) => { /> Settings @@ -2344,6 +2523,7 @@ const Navbar = (props) => { /> Notifications ({notifications === undefined || notifications === null ? 0 : @@ -2388,6 +2568,7 @@ const Navbar = (props) => { /> About @@ -2427,6 +2608,7 @@ const Navbar = (props) => { /> Logout @@ -2457,7 +2639,7 @@ const Navbar = (props) => { margin: 0 }} > - Version 1.4.5 + Version 2.0.2 @@ -2503,6 +2685,7 @@ const Navbar = (props) => { }) } setSearchBarModalOpen(true); + setIsDocSearchModalOpen(false); }} > @@ -2543,14 +2726,14 @@ const Navbar = (props) => { {isCloud && @@ -399,7 +401,7 @@ const OrgHeader = (props) => { style={appIconStyle} onClick={zoomIn} > - + @@ -409,7 +411,7 @@ const OrgHeader = (props) => { style={appIconStyle} onClick={zoomOut} > - + @@ -419,7 +421,7 @@ const OrgHeader = (props) => { style={appIconStyle} onClick={rotation} > - +
@@ -427,7 +429,7 @@ const OrgHeader = (props) => {
+ + ); + + const [imageUploadError, setImageUploadError] = React.useState(""); + const [openImageModal, setOpenImageModal] = React.useState(false); + const [openLandscapeImageModal, setOpenLandscapeImageModal] = React.useState(false); + const [scale, setScale] = React.useState(1); + const [rotate, setRotation] = React.useState(0); + const [disableImageUpload, setDisableImageUpload] = React.useState(true); + const [croppedData, setCroppedData] = React.useState( + partnerData?.image_url || defaultImage + ); + const [landscapeCroppedData, setLandscapeCroppedData ] = React.useState( + partnerData?.landscape_image_url || defaultLandscapeImage + ) + + + React.useEffect(() => { + if (file.length > 0) { + setCroppedData(file); + } else if (fileBase64 !== undefined && fileBase64 !== null && fileBase64.length > 0) { + setCroppedData(fileBase64); + } else { + setCroppedData(partnerData?.image_url || defaultImage); + } + + if (landscapeFile.length > 0) { + setLandscapeCroppedData(landscapeFile); + } else if (landscapeFileBase64 !== undefined && landscapeFileBase64 !== null && landscapeFileBase64.length > 0) { + setLandscapeCroppedData(landscapeFileBase64); + } else { + setLandscapeCroppedData(partnerData?.landscape_image_url || defaultLandscapeImage); + } + + }, [partnerData, file, landscapeFile]); + + + const alternateImg = ( + { + upload.click(); + }} + /> + ); + + const zoomIn = () => { + setScale(scale + 0.1); + }; + + const zoomOut = () => { + setScale(scale - 0.1); + }; + + const rotation = () => { + setRotation(rotate + 10); + }; + + const onPositionChange = () => { + setDisableImageUpload(false); + }; + + const onCancelSaveAppIcon = () => { + setOpenImageModal(false); + setOpenLandscapeImageModal(false); + setImageUploadError(""); + }; + + let editor; + let landscapeEditor; + const setEditorRef = (imgEditor) => { + editor = imgEditor; + }; + const setLandscapeEditorRef = (imgEditor) => { + landscapeEditor = imgEditor; + }; + + + const onSaveAppIcon = () => { + const canvas = editor.getImageScaledToCanvas(); + const newImageData = canvas.toDataURL(); + setCroppedData(newImageData); // Update croppedData with the new image data + setOpenImageModal(false); + setDisableImageUpload(true); + setPartnerData({ + ...partnerData, + image_url: newImageData, + }) + }; + + const onSaveLandscapeImage = () => { + const canvas = landscapeEditor.getImageScaledToCanvas(); + const newImageData = canvas.toDataURL(); + setLandscapeCroppedData(newImageData); + setOpenLandscapeImageModal(false); + setDisableImageUpload(true); + setPartnerData({ + ...partnerData, + landscape_image_url: newImageData, + }) + } + + const imageInfo = ( + + ); + + const landScapeImageInfo = ( + + ); + + const errorText = imageUploadError.length > 0 ? ( +
Error: {imageUploadError}
+ ) : null; + + + const imageUploadModalView = openImageModal && !isDisabled ? ( + + + +
Upload Partner Image
+
+ {errorText} + + setRotation(0)} + /> + +
+ + + + + + + + + + + + +
+ +
+ + + + +
+
+ ) : null; + + const imageUploadModalViewLandscape = openLandscapeImageModal && !isDisabled ? ( + + + +
Upload Partner Landscape Image
+
+ {errorText} + + setRotation(0)} + /> + +
+ + + + + + + + + + + + +
+ +
+ + + + +
+
+ ) : null; + + + + if (loadingPartnerData) { + return ( +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+ ); + } + + return ( +
+
+ +
{ + setOpenImageModal(true); + }} + > + (upload = ref)} + onChange={(e) => { + const reader = new FileReader(); + reader.onload = (event) => { + setCroppedData(event.target.result); + }; + reader.readAsDataURL(e.target.files[0]); + }} + /> + {imageInfo} +
+ {imageUploadModalView} +
+
+
+ +
+
+ +
+
+
+
+ +
{ + setOpenLandscapeImageModal(true); + }} + > + (landScapeUpload = ref)} + onChange={(e) => { + const reader = new FileReader(); + reader.onload = (event) => { + setLandscapeCroppedData(event.target.result); + }; + reader.readAsDataURL(e.target.files[0]); + }} + /> + {landScapeImageInfo} +
+ {imageUploadModalViewLandscape} +
+
+
+ +
+
+ +
+
+
+
+ ); +}; + +export default PartnerHeader; diff --git a/frontend/src/components/PartnerSettings.jsx b/frontend/src/components/PartnerSettings.jsx new file mode 100644 index 00000000..a979c7d7 --- /dev/null +++ b/frontend/src/components/PartnerSettings.jsx @@ -0,0 +1,391 @@ +import React, { useEffect, useState, useContext } from 'react'; +import { toast } from "react-toastify"; +import { Context } from '../context/ContextApi.jsx'; +import { getTheme } from '../theme.jsx'; +import { + Button, + Typography, + Box, + IconButton, + Tooltip, +} from "@mui/material"; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import { useNavigate } from 'react-router'; +import PartnerHeader from '../components/PartnerHeader.jsx'; +import PartnerDetails from '../components/PartnerDetails.jsx'; + +const PartnerSettings = (props) => { + const { + isCloud, + userdata, + globalUrl, + serverside, + loadingPartnerData, + selectedOrganization, + setSelectedOrganization, + handleGetOrg, + partnerData, + setPartnerData, + } = props; + + const [isPartner, setIsPartner] = React.useState(isCloud ? userdata?.active_org?.is_partner ? true : false : false); + const [partnerTypes, setPartnerTypes] = React.useState({}); + const [isPublishing, setIsPublishing] = React.useState(false); + const [isToggling, setIsToggling] = React.useState(false); + + useEffect(() => { + setIsPartner(isCloud ? userdata?.active_org?.is_partner ? true : false : false); + if(userdata?.support){ + setIsPartner(true); + } + }, [userdata, isCloud]); + // Partner Types handling : Getting from org status + useEffect(() => { + const partnerTypes = {}; + userdata?.org_status.forEach(status => { + if (status.includes("_partner")) { + partnerTypes[status] = true; + } + }); + setPartnerTypes(partnerTypes); + }, [selectedOrganization]); + // Partner Type Colors + const partnerTypeColors = { + "tech_partner": "#ff8544", + "distribution_partner": "#2BC07E", + "service_partner": "#a99cf9", + "integration_partner": "#fb47a0" + } + + const handleSendUpdateRequest = () => { + toast("Your request has been sent to the support team. They will review your request and get back to you as soon as possible.") + } + + // Open partner page in new tab + const handleOpenPartnerPage = () => { + if (partnerData?.id) { + if(partnerData?.public){ + window.open(`${window.location.origin}/partners/${partnerData.name.toLowerCase().replaceAll(" ", "_")}`) + }else{ + window.open(`${window.location.origin}/partners/${partnerData?.id}`, "_blank"); + } + } else { + toast.error("Partner ID not found"); + } + } + + // Update Partner Details + const handleUpdatePartnerDetails = () => { + // Validate required fields before publishing + const requiredStringFields = [ + { field: partnerData?.name, name: "Partner Name" }, + { field: partnerData?.description, name: "Description" }, + { field: selectedOrganization?.id, name: "Organization Id" }, + { field: partnerData?.image_url, name: "Logo Image" }, + { field: partnerData?.landscape_image_url, name: "Landscape Image" }, + { field: partnerData?.website_url, name: "Website URL" }, + { field: partnerData?.article_url, name: "Article URL" }, + { field: partnerData?.country, name: "Country" }, + { field: partnerData?.region, name: "Region" }, + ]; + + // Required array fields (multi-select dropdowns) + const requiredArrayFields = [ + { field: partnerData?.solutions, name: "Solutions" }, + ]; + + // Check if any required string fields are empty + const emptyStringFields = requiredStringFields.filter(item => + !item.field || item.field.trim() === "" + ); + + // Check if any required array fields are empty + const emptyArrayFields = requiredArrayFields.filter(item => + !item.field || !Array.isArray(item.field) || item.field.length === 0 + ); + + // Combine all empty fields + const allEmptyFields = [...emptyStringFields, ...emptyArrayFields]; + + // If there are empty required fields, show error and return + if (allEmptyFields.length > 0) { + const missingFields = allEmptyFields.map(item => item.name).join(", "); + toast.error(`Please fill in all required fields: ${missingFields}`); + return; + } + + // Check if at least one partner type is selected + if (Object.keys(partnerTypes).length === 0) { + toast.error("There should be at least one partner type"); + return; + } + + setIsPublishing(true); + const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id; + const data = { + id: partnerData.id?.trim() || null, + name: partnerData.name?.trim(), + org_id: selectedOrganization?.id?.trim(), + description: partnerData.description?.trim(), + website_url: partnerData.website_url?.trim(), + article_url: partnerData.article_url?.trim(), + partner_type: partnerTypes, + expertise: partnerData?.expertise || [], + services: partnerData?.services || [], + solutions: partnerData?.solutions || [], + country: partnerData?.country || "", + region: partnerData?.region || "", + image_url: partnerData?.image_url?.trim(), + landscape_image_url: partnerData?.landscape_image_url?.trim(), + public: partnerData?.public + } + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => { + setIsPublishing(false); + if (response.status !== 200) { + toast.error("Failed to publish partner"); + } + return response.json(); + }) + .then((responseJson) => { + toast.success("Partner details successfully updated"); + }) + .catch((error) => { + setIsPublishing(false); + toast.error("Failed to update partner details: " + error?.message); + }) + } + + // Toggle Partner Publish Status + const handleTogglePublishStatus = () => { + setIsToggling(true); + const newPublishStatus = !partnerData?.public; + const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id; + + const data = { + id: partnerData.id?.trim() || null, + name: partnerData.name?.trim(), + org_id: selectedOrganization?.id?.trim(), + description: partnerData.description?.trim(), + website_url: partnerData.website_url?.trim(), + article_url: partnerData.article_url?.trim(), + partner_type: partnerTypes, + usecases: partnerData?.usecases || [], + expertise: partnerData?.expertise || [], + services: partnerData?.services || [], + solutions: partnerData?.solutions || [], + country: partnerData?.country || "", + region: partnerData?.region || "", + image_url: partnerData?.image_url?.trim(), + landscape_image_url: partnerData?.landscape_image_url?.trim(), + public: newPublishStatus + } + + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => { + setIsToggling(false); + if (response.status !== 200) { + toast.error("Failed to update publish status"); + } + return response.json(); + }) + .then((responseJson) => { + // Update local state + setPartnerData(prev => ({ + ...prev, + public: newPublishStatus + })); + toast.success(`Partner ${newPublishStatus ? 'published' : 'unpublished'} successfully`); + }) + .catch((error) => { + setIsToggling(false); + toast.error("Failed to update publish status: " + error?.message); + }) + } + + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); + const navigate = useNavigate(); + + return ( +
+
+
+
+
+ + Configuration + {Object?.entries(partnerTypes)?.map(([key, value]) => ( + + {key.replace("_", " ").replace(/\b\w/g, char => char.toUpperCase())} + + ))} + + + + Set up and manage partner details and information to be displayed on the Partners page. + + + {isPartner && ( + <> + {/* View Partner Page Button */} + + + + + + + {/* Update Details Button */} + + {/* Toggle Publish/Unpublish Button */} + + + )} + + {!isPartner && ( + + )} + + +
+
+
+ + +
+
+ ) +} + +export default PartnerSettings; diff --git a/frontend/src/components/PartnerTab.jsx b/frontend/src/components/PartnerTab.jsx new file mode 100644 index 00000000..10c21f06 --- /dev/null +++ b/frontend/src/components/PartnerTab.jsx @@ -0,0 +1,245 @@ +import React, { useEffect, useState, useCallback, useContext } from 'react'; +import { useNavigate, useLocation } from "react-router-dom"; +import Branding from "../components/Branding.jsx"; +import { ToastContainer, toast } from "react-toastify"; +import { Button } from '@mui/material'; +import { getTheme } from '../theme.jsx'; +import { Context } from '../context/ContextApi.jsx'; +import PartnerSettings from '../components/PartnerSettings.jsx'; +import PartnersUsecasesTab from '../components/PartnersUsecasesTab.jsx'; +import PartnersApps from '../components/PartnerApps.jsx'; +import PartnerArticles from '../components/PartnersArticles.jsx'; +const PartnerTab = (props) => { + const location = useLocation(); + const navigate = useNavigate(); + const { + userdata, + globalUrl, + serverside, + isCloud, + setSelectedOrganization, + selectedOrganization, handleGetOrg, + handleStatusChange, + isLoaded, + removeCookie + } = props; + + + const [selectedTab, setSelectedTab] = useState('partner_settings'); + const [loadingPartnerData, setLoadingPartnerData] = useState(false); + const [curIndex, setCurIndex] = React.useState(0); + const [partnerData, setPartnerData] = React.useState({ + name: "", + description: "", + image_url: "", + landscape_image_url: "", + website_url: "", + article_url: "", + expertise: [], + usecases: [], + services: [], + solutions: [], + country: "", + region: "", + partner_type: {}, + }); + + const tabsOnPartnerTab = [ 'Partner Settings', 'Usecases', 'Apps', 'AI Agents', 'Articles', 'Branding']; + + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); + + useEffect(() => { + if(!isCloud || !userdata?.active_org?.is_partner) { + // If the user is not a partner or if it's not a cloud environment do not make api call :) + return; + } + setLoadingPartnerData(true); + const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id; + fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + toast("Failed to get partner data") + } + return response.json(); + }) + .then((responseJson) => { + if(responseJson.success) { + setPartnerData(responseJson?.partner); + console.log("responseJson", responseJson) + setLoadingPartnerData(false); + }else{ + setLoadingPartnerData(false); + toast(responseJson?.reason) + } + }) + .catch((error) => { + setLoadingPartnerData(false); + }) + }, [globalUrl]); + + // Used to auto select the tab based on the url : partner_tab + useEffect(() => { + const queryParams = new URLSearchParams(location.search); + const tabName = queryParams.get('partner_tab'); + if (tabName) { + const decodedTabName = decodeURIComponent(tabName); + setSelectedTab(decodedTabName); + if (decodedTabName === 'partner_settings') { + setCurIndex(0); + } else if(decodedTabName === 'usecases'){ + setCurIndex(1) + }else if (decodedTabName === 'apps'){ + setCurIndex(2); + } else if (decodedTabName === 'aiagents') { + setCurIndex(3); + } else if (decodedTabName === 'articles') { + setCurIndex(4); + } else if (decodedTabName === 'branding') { + setCurIndex(5); + } + } + }, [location.search]); + + // Tab click on partner tab + const handleTabClick = (tabName) => { + const formattedTabName = tabName.toLowerCase().replace(/[\s&]+/g, ''); + const encodedTabName = encodeURIComponent(formattedTabName); + setSelectedTab(formattedTabName); + document.title = `Shuffle - partner - ${formattedTabName}`; + navigate(`?partner_tab=${encodedTabName}`); + }; + + // Rendering the content based on the selected tab + const renderContent = () => { + switch (selectedTab) { + case 'partner_settings': + return ; + case 'usecases': + return ; + case `apps`: + return ; + case 'articles' : + return ; + case `ai_agents`: + return ; + case 'branding': + return ; + default: + return ; + } + }; + + const isTabDisabled = (tabName) => { + // If user is a support user, enable all tabs + if (userdata?.support) { + return false; + } + + // For non-support users, apply the following restrictions: + + // For onPrem only partner settings and branding are enabled + if ( + !isCloud && + (tabName === "Usecases" || + tabName === "Apps" || + tabName === "AI Agents" || + tabName === "Articles") + ) { + return true; + } + + // Disable Apps, Articles, and AI Agents for all non-support users + if ( + tabName === "Apps" || + tabName === "Articles" || + tabName === "AI Agents" + ) { + return true; + } + + // Disable Usecases tab for cloud users if not a partner or is in a sub-org + if (isCloud && tabName === "Usecases") { + if ( + !userdata?.active_org?.is_partner || + userdata?.active_org?.is_sub_org + ) { + return true; + } + } + + // Disable Branding tab if not an integration partner + const isIntegrationPartner = + userdata && + userdata?.org_status?.includes("integration_partner") && + !userdata?.org_status?.includes("sub_org"); + if (tabName === "Branding" && !isIntegrationPartner) { + return true; + } + + // Enable the tab by default + return false; + } + + return ( +
+
+ {tabsOnPartnerTab?.map((tabName, index) => ( +
+ +
+ ))} +
+
+ {renderContent()} +
+
+ ); +}; + +export default PartnerTab; diff --git a/frontend/src/components/PartnersArticles.jsx b/frontend/src/components/PartnersArticles.jsx new file mode 100644 index 00000000..80653231 --- /dev/null +++ b/frontend/src/components/PartnersArticles.jsx @@ -0,0 +1,21 @@ +import { Box, Typography } from '@mui/material' +import React from 'react' + +const PartnersArticles = () => { + return ( + + Partner Articles + + ) +} + +export default PartnersArticles diff --git a/frontend/src/components/PartnersUsecasesTab.jsx b/frontend/src/components/PartnersUsecasesTab.jsx new file mode 100644 index 00000000..87e34b52 --- /dev/null +++ b/frontend/src/components/PartnersUsecasesTab.jsx @@ -0,0 +1,1761 @@ +import { + Box, + Tooltip, + Typography, + Switch, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Select, + MenuItem, + FormControl, + InputLabel, + IconButton, + Menu, + ListItemIcon, + ListItemText, + Checkbox, + CircularProgress, + Skeleton, +} from "@mui/material"; +import React, { useContext, useEffect, useState } from "react"; +import { getTheme } from "../theme.jsx"; +import { Context } from "../context/ContextApi.jsx"; +import AddIcon from "@mui/icons-material/Add"; +import CloseIcon from "@mui/icons-material/Close"; +import { toast } from "react-toastify"; +import MoreVertIcon from "@mui/icons-material/MoreVert"; +import EditIcon from "@mui/icons-material/Edit"; +import StarIcon from "@mui/icons-material/Star"; +import DeleteIcon from "@mui/icons-material/Delete"; +import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; +import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; +import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; +import { Link, useNavigate } from "react-router-dom"; + +// Helper function to get the correct image path based on app category +export const getCategoryImagePath = (category) => { + if (!category) return "/images/appCategories/other.svg"; + + const lowerCategory = category.toLowerCase(); + + if (lowerCategory.includes("communication")) { + return "/images/appCategories/cases.svg"; + } else if (lowerCategory.includes("cases")) { + return "/images/appCategories/cases.svg"; + } else if (lowerCategory.includes("network")) { + return "/images/appCategories/network.svg"; + } else if (lowerCategory.includes("siem")) { + return "/images/appCategories/siem.svg"; + } else if (lowerCategory.includes("edr") || lowerCategory.includes("eradication")) { + return "/images/appCategories/edr.svg"; + } else if (lowerCategory.includes("iam")) { + return "/images/appCategories/iam.svg"; + } else if (lowerCategory.includes("assets")) { + return "/images/appCategories/assets.svg"; + } else if (lowerCategory.includes("intel")) { + return "/images/appCategories/intel.svg"; + } else if (lowerCategory.includes("email")) { + return "/images/appCategories/email.svg"; + } else { + return "/images/appCategories/other.svg"; + } +}; + +// Skeleton component for loading state +const UsecaseCardSkeleton = () => { + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); + + return ( + + +
+ {/* Source App Icon Skeleton */} + + + {/* Destination App Icon Skeleton */} + +
+
+ + +
+
+ +
+ ); +}; + +const UsecaseCard = ({ usecase, handleToggle, handleOpenDialog, handleDeleteUsecase }) => { + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); + const navigate = useNavigate(); + + const handleClick = (event) => { + event.stopPropagation(); + event.preventDefault(); + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleEdit = () => { + console.log("Edit usecase:", usecase.id); + handleClose(); + }; + + const handleStar = () => { + console.log("Star usecase:", usecase.id); + handleClose(); + }; + + const handleCardClick = (e) => { + // Navigate to usecase detail page + navigate(`/usecases/${usecase.id}`); + }; + + return ( + + +
+ {/* Source App Icon */} + + Source + + + {/* Destination App Icon */} + + Destination + +
+
+ { + e.stopPropagation(); + }} + onChange={(e) => { + e.preventDefault(); + e.stopPropagation(); + handleToggle(usecase.id); + }} + size="medium" + sx={{ + "& .MuiSwitch-switchBase.Mui-checked": { + color: "#4CAF50", + }, + "& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": { + backgroundColor: "#4CAF50", + }, + }} + /> + { + e.stopPropagation(); + handleClick(e); + }} + size="small" + sx={{ + color: theme.palette.text.primary, + zIndex: 20, + "&:hover": { + backgroundColor: themeMode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.05)", + }, + }} + > + + + e.stopPropagation()} + PaperProps={{ + sx: { + backgroundColor: theme.palette.DialogStyle.backgroundColor, + border: theme.palette.defaultBorder, + borderRadius: "8px", + boxShadow: theme.palette.DialogStyle.boxShadow, + "& .MuiMenuItem-root": { + fontSize: "14px", + color: theme.palette.text.primary, + "&:hover": { + backgroundColor: themeMode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.05)", + }, + }, + }, + }} + transformOrigin={{ horizontal: "right", vertical: "top" }} + anchorOrigin={{ horizontal: "right", vertical: "bottom" }} + > + { + e.stopPropagation(); + handleOpenDialog(usecase); + setAnchorEl(null); + }} + > + + + + Edit Usecase + + { + e.stopPropagation(); + handleDeleteUsecase(usecase.id); + }}> + + + + Delete Usecase + + +
+
+ + {usecase.name} + +
+ ); +}; + +const PartnersUsecasesTab = ({ isCloud, globalUrl, userdata, partnerData, setPartnerData }) => { + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); + + // Dialog state + const [openDialog, setOpenDialog] = useState(false); + const [publicWorkflows, setPublicWorkflows] = useState([]); + const [usecaseData, setUsecaseData] = useState([]); + const [isSubmitting, setIsSubmitting] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [isWorkflowLoading, setIsWorkflowLoading] = useState(false); + const [formData, setFormData] = useState({ + id: "", + mainContent: { + title: "", + description: "", + categories: [], + publicWorkflowId: "", + sourceAppType: "", + destinationAppType: "", + }, + navigation: { + items: [ + { + name: "About Usecase", + content: [""], + }, + ], + }, + public: false, + }); + + // App categories for dropdowns + const appCategories = [ + { value: "communication", label: "Communication" }, + { value: "cases", label: "Cases" }, + { value: "network", label: "Network" }, + { value: "siem", label: "SIEM" }, + { value: "edr", label: "EDR" }, + { value: "eradication", label: "Eradication" }, + { value: "iam", label: "IAM" }, + { value: "assets", label: "Assets" }, + { value: "intel", label: "Intel" }, + { value: "email", label: "Email" }, + { value: "other", label: "Other" } + ]; + + // Sample workflow options + const workflowOptions = [ + "Email Analysis Workflow", + "Incident Response Workflow", + "Alert Triage Workflow", + "Threat Hunting Workflow", + "Vulnerability Management", + ]; + + useEffect(() => { + // Set loading state to true when fetching starts + setIsLoading(true); + if(!isCloud || !userdata?.active_org?.is_partner) { + // If the user is not a partner or if it's not a cloud environment do not make api call :) + return; + } + // Load usecase data from API + fetch(`${globalUrl}/api/v1/partners/${partnerData?.id}/usecases`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for PARTNER USECASES :O!"); + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + const usecases = responseJson.usecases.map((usecase) => ({ + id: usecase.id, + sourceAppType: usecase.mainContent?.sourceAppType || "", + destinationAppType: usecase.mainContent?.destinationAppType || "", + srcImg: getCategoryImagePath(usecase.mainContent?.sourceAppType), + dstImg: getCategoryImagePath(usecase.mainContent?.destinationAppType), + publicWorkflowId: usecase.mainContent?.publicWorkflowId || "", + name: usecase.mainContent?.title, + description: usecase.mainContent?.description, + navigation: usecase.navigation || { items: [] }, + categories: usecase.mainContent?.categories || [], + public: usecase?.public || false, + })); + if (usecases.length > 0) { + setUsecaseData(usecases); + } else { + setUsecaseData([]); + } + } + }) + .catch((error) => { + console.log(error); + }) + .finally(() => { + // Set loading state to false when fetching completes (success or error) + setIsLoading(false); + }); + }, [partnerData?.id]); + + // Sample categories + const categoryOptions = ["Collect","Enrich", "Detect", "Respond", "Verify"]; + + const getUserProfileWorkflows = (orgId) => { + setIsWorkflowLoading(true); + fetch(`${globalUrl}/api/v1/partners/${orgId}/workflows`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + setPublicWorkflows(responseJson || []); + setIsWorkflowLoading(false); + }else { + toast.info(responseJson.message || "No public workflows found for this organization."); + setPublicWorkflows([]); + } + }) + .catch((error) => { + console.log(error); + setIsWorkflowLoading(false); + }); + } + + useEffect(() => { + const orgId = userdata?.active_org?.id; + if(!isCloud || !userdata?.active_org?.is_partner) { + // If the user is not a partner or if it's not a cloud environment do not make api call :) + return; + } + getUserProfileWorkflows(orgId); + }, []); + + + const handleUpdateUsecase = async (usecaseId, updatedData) => { + try { + // Transform the frontend data structure to match the backend expected structure + const backendUsecaseData = { + id: updatedData.id, + companyInfo: { + id: partnerData?.id.trim(), + name: partnerData?.name.trim(), + }, + mainContent: { + title: updatedData.name.trim(), + description: updatedData.description.trim(), + categories: updatedData.categories, + publicWorkflowId: updatedData.publicWorkflowId.trim(), + sourceAppType: updatedData.sourceAppType.trim(), + destinationAppType: updatedData.destinationAppType.trim(), + }, + navigation: updatedData.navigation, + public: updatedData.public, + edited: Date.now(), + }; + + + // return + const response = await fetch(`${globalUrl}/api/v1/usecases/${usecaseId}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(backendUsecaseData), + }); + + if (response.status !== 200) { + console.error("Failed to update usecase"); + toast.error("Failed to update usecase"); + return false; + } + + const responseData = await response.json(); + toast.success("Usecase published status updated successfully"); + return true; + } catch (error) { + console.error("Error updating usecase:", error); + toast.error("Error updating usecase"); + return false; + } + }; + + const handleDeleteUsecase = async (usecaseId) => { + try { + const response = await fetch( + `${globalUrl}/api/v1/usecases/${usecaseId}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + } + ); + if (response.status !== 200) { + console.error("Failed to delete usecase"); + toast.error("Failed to delete usecase"); + return false; + } + const responseData = await response.json(); + toast.success("Usecase deleted successfully"); + // Remove the deleted usecase from the state + setUsecaseData((prevData) => + prevData?.filter((usecase) => usecase.id !== usecaseId) + ); + // Remove the usecase ID from partnerData + setPartnerData((prevData) => ({ + ...prevData, + usecases: prevData.usecases?.filter((id) => id !== usecaseId), + })); + return true; + } catch (error) { + console.error("Error deleting usecase:", error); + toast.error("Error deleting usecase"); + return false; + } + }; + + const handleToggle = async (currentId) => { + // Find the current usecase + const currentUsecase = usecaseData.find(usecase => usecase.id === currentId); + if (!currentUsecase) return; + + // Optimistically update the UI + setUsecaseData((prevData) => + prevData.map((usecase) => + usecase.id === currentId + ? { ...usecase, public: !usecase.public } + : usecase + ) + ); + + // Prepare the updated data + const updatedPublishedStatus = !currentUsecase.public; + + // Create a copy of the usecase with updated published status + const updatedUsecase = { + ...currentUsecase, + public: updatedPublishedStatus + }; + toast.info(`Updating usecase ${updatedUsecase.name}...`,{ + autoClose: 1000, + }) + // Send the update to the server + const success = await handleUpdateUsecase(currentId, updatedUsecase); + + // If the update failed, revert the UI change + if (!success) { + setUsecaseData((prevData) => + prevData.map((usecase) => + usecase.id === currentId + ? { ...usecase, public: currentUsecase.public } + : usecase + ) + ); + } + }; + + const handleOpenDialog = (usecase) => { + setFormData({ + id: usecase?.id || undefined, + companyInfo: { + id: usecase.companyInfo?.id || "", + name: usecase.companyInfo?.name || "", + }, + mainContent: { + title: usecase?.name, + description: usecase?.description, + categories: usecase?.categories || [], + publicWorkflowId: usecase?.publicWorkflowId || "", + sourceAppType: usecase?.sourceAppType || "", + destinationAppType: usecase?.destinationAppType || "", + }, + navigation: usecase?.navigation || { + items: [ + { + name: "About Usecase", + content: [""], + }, + ], + }, + public: usecase?.public || false, + }); + setOpenDialog(true); + }; + + const handleCloseDialog = () => { + setOpenDialog(false); + setIsSubmitting(false); // Reset loading state when closing dialog + setFormData({ + id: "", + mainContent: { + title: "", + description: "", + categories: [], + publicWorkflowId: "", + sourceAppType: "", + destinationAppType: "", + }, + navigation: { + items: [ + { + name: "About Usecase", + content: [""], + }, + ], + }, + public: false, + }); + }; + + const handleSubmit = () => { + // Validate required fields + const validationErrors = []; + + // Check for public workflow selection + if (!formData.mainContent.publicWorkflowId || formData.mainContent.publicWorkflowId.trim() === "") { + validationErrors.push("Please select a public workflow"); + } + + if (!formData.mainContent.sourceAppType || formData.mainContent.sourceAppType.trim() === "") { + validationErrors.push("Source app type is required"); + } + + if (!formData.mainContent.destinationAppType || formData.mainContent.destinationAppType.trim() === "") { + validationErrors.push("Destination app type is required"); + } + + // Validate that source and destination are different + if (formData.mainContent.sourceAppType && formData.mainContent.destinationAppType && + formData.mainContent.sourceAppType.trim() === formData.mainContent.destinationAppType.trim()) { + validationErrors.push("Source and destination app types must be different"); + } + + // Check main content fields + if (!formData.mainContent.title || formData.mainContent.title.trim() === "") { + validationErrors.push("Title is required"); + } else if (formData.mainContent.title.length < 5) { + validationErrors.push("Title must be not be less than 5 characters"); + } + + if (!formData.mainContent.description || formData.mainContent.description.trim() === "") { + validationErrors.push("Description is required"); + } + + // Check categories + if (!formData.mainContent.categories || !Array.isArray(formData.mainContent.categories) || formData.mainContent.categories.length === 0) { + validationErrors.push("At least one category must be selected"); + } + + // Check navigation items + if (!formData.navigation.items || !Array.isArray(formData.navigation.items) || formData.navigation.items.length === 0) { + validationErrors.push("Please add at least one section"); + } else { + // Validate each section + for (let i = 0; i < formData.navigation.items.length; i++) { + const item = formData.navigation.items[i]; + + if (!item.name || item.name.trim() === "") { + validationErrors.push(`Section ${i+1} must have a name`); + } + + if (!item.content || !Array.isArray(item.content) || item.content.length === 0 || + !item.content.some(content => content && content.trim() !== "")) { + validationErrors.push(`Section "${item.name || i+1}" must have content`); + } + } + } + + // If there are validation errors, show the first one and return + if (validationErrors.length > 0) { + toast.error(validationErrors[0]); + return; + } + + // Set loading state to true + setIsSubmitting(true); + + // Get the image paths for source and destination app types + const srcImg = getCategoryImagePath(formData.mainContent.sourceAppType); + const dstImg = getCategoryImagePath(formData.mainContent.destinationAppType); + + const response = { + ...formData, + companyInfo: { + id: partnerData?.id, + name: partnerData?.name, + }, + } + + fetch(`${globalUrl}/api/v1/usecases/${formData.id}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(response), + }) + .then((res) => { + if (res.status !== 200) { + console.error("Failed to submit usecase"); + toast.error("Failed to submit usecase"); + setIsSubmitting(false); + return null; + } + return res.json(); + }) + .then((responseData) => { + console.log("Response data:", responseData); + // Reset loading state + setIsSubmitting(false); + + if (responseData) { + // Only close the dialog if the request was successful + const usecaseData = { + id: responseData.usecaseId.trim(), + sourceAppType: formData.mainContent?.sourceAppType.trim() || "", + destinationAppType: formData.mainContent?.destinationAppType.trim() || "", + srcImg: getCategoryImagePath(formData.mainContent?.sourceAppType.trim()), + dstImg: getCategoryImagePath(formData.mainContent?.destinationAppType.trim()), + publicWorkflowId: formData.mainContent?.publicWorkflowId.trim() || "", + name: formData.mainContent?.title.trim(), + description: formData.mainContent?.description.trim(), + categories: formData.mainContent?.categories || [], + navigation: formData.navigation || {}, + public: formData?.public || false, + } + + setUsecaseData((prevData) => { + // Handle case when prevData is null or undefined + if (!prevData) { + return [usecaseData]; + } + + // Check if the usecase already exists + const existingIndex = prevData.findIndex( + (usecase) => usecase.id === responseData.usecaseId + ); + if (existingIndex !== -1) { + // Update existing usecase + const updatedData = [...prevData]; + updatedData[existingIndex] = usecaseData; + return updatedData; + } else { + // Add new usecase + return [...prevData, usecaseData]; + } + }); + + setPartnerData((prevData) => { + // Handle case when prevData is null or undefined + if (!prevData) { + return { + usecases: [responseData.usecaseId.trim()] + }; + } + + return { + ...prevData, + usecases: [ + ...(prevData.usecases || []), + responseData.usecaseId.trim(), + ], + }; + }); + + // Show success message + if(formData?.id){ + toast.success("Usecase updated successfully"); + }else{ + toast.success("Usecase created successfully, publish it with toggle button"); + } + handleCloseDialog(); + } + }) + .catch((error) => { + console.error("Error submitting usecase:", error); + toast.error("Error submitting usecase"); + // Reset loading state on error + setIsSubmitting(false); + }); + }; + + return ( + + {/* Add Usecase Button */} + + + Usecases + + + + + {/* Usecases Grid */} + {isLoading ? ( + // Skeleton loading state + + {/* Display 4 skeleton cards while loading */} + {[...Array(4)].map((_, index) => ( + + ))} + + ) : usecaseData.length > 0 ? ( + // Actual data display + + {usecaseData.map((usecase) => ( + + ))} + + ) : ( + // Empty state + + + No usecases found + + + )} + + {/* Add Usecase Dialog */} + + + + {formData?.id ? "Update" : "Add New"} Usecase : {formData?.public ? "Published" : "Draft"} + + + + + + + + + + + Public Workflow + + + + + {/* App Type Selection */} + + + + Source App Type + + + + + + + Destination App Type + + + + + + + + Main Content + + + + + Title + + + setFormData({ + ...formData, + mainContent: { + ...formData.mainContent, + title: e.target.value, + }, + }) + } + sx={{ + "& .MuiOutlinedInput-root": { + height: 40, + backgroundColor: theme.palette.usecaseDialogFieldColor, + color: theme.palette.text.primary, + "& fieldset": { border: theme.palette.defaultBorder }, + }, + }} + /> + + + + Description + + + setFormData({ + ...formData, + mainContent: { + ...formData.mainContent, + description: e.target.value, + }, + }) + } + sx={{ + "& .MuiOutlinedInput-root": { + backgroundColor: theme.palette.usecaseDialogFieldColor, + p: 1.5, + color: theme.palette.text.primary, + "& fieldset": { border: theme.palette.defaultBorder }, + }, + }} + /> + + + + Categories + + + + + + + {/* Navigation Items Section */} + + + Navigation Items + + {formData.navigation.items.map((item, itemIndex) => ( + + + { + const newItems = [...formData.navigation.items]; + newItems[itemIndex].name = e.target.value; + setFormData({ + ...formData, + navigation: { items: newItems }, + }); + }} + placeholder="Section Name" + sx={{ + flex: 1, + "& .MuiOutlinedInput-root": { + height: 40, + backgroundColor: theme.palette.usecaseDialogFieldColor, + color: theme.palette.text.primary, + "& fieldset": { border: theme.palette.defaultBorder }, + }, + }} + /> + + { + if (itemIndex > 0) { + const newItems = [...formData.navigation.items]; + [newItems[itemIndex - 1], newItems[itemIndex]] = [ + newItems[itemIndex], + newItems[itemIndex - 1], + ]; + setFormData({ + ...formData, + navigation: { items: newItems }, + }); + } + }} + disabled={itemIndex === 0} + sx={{ + color: theme.palette.text.secondary, + "&:not(:disabled):hover": { + backgroundColor: themeMode === "dark" ? "rgba(255, 255, 255, 0.08)" : "rgba(0, 0, 0, 0.05)", + }, + }} + > + + + { + if ( + itemIndex < + formData.navigation.items.length - 1 + ) { + const newItems = [...formData.navigation.items]; + [newItems[itemIndex], newItems[itemIndex + 1]] = [ + newItems[itemIndex + 1], + newItems[itemIndex], + ]; + setFormData({ + ...formData, + navigation: { items: newItems }, + }); + } + }} + disabled={ + itemIndex === formData.navigation.items.length - 1 + } + sx={{ + color: theme.palette.text.secondary, + "&:not(:disabled):hover": { + backgroundColor: themeMode === "dark" ? "rgba(255, 255, 255, 0.08)" : "rgba(0, 0, 0, 0.05)", + }, + }} + > + + + { + const newItems = formData.navigation.items?.filter( + (_, i) => i !== itemIndex + ); + setFormData({ + ...formData, + navigation: { items: newItems }, + }); + }} + sx={{ + color: "#ff4444", + "&:hover": { + backgroundColor: themeMode === "dark" ? "rgba(255, 68, 68, 0.08)" : "rgba(255, 68, 68, 0.15)", + }, + }} + > + + + + + + {item.content.map((content, contentIndex) => ( + + { + const newItems = [...formData.navigation.items]; + newItems[itemIndex].content[contentIndex] = + e.target.value; + setFormData({ + ...formData, + navigation: { items: newItems }, + }); + }} + placeholder="Content" + fullWidth + sx={{ + "& .MuiOutlinedInput-root": { + backgroundColor: theme.palette.usecaseDialogFieldColor, + color: theme.palette.text.primary, + "& fieldset": { border: theme.palette.defaultBorder }, + }, + }} + /> + {item.content.length > 1 && ( + + )} + + ))} + + + + ))} + + + + + + + + + + + + + ); +}; + +export default PartnersUsecasesTab; diff --git a/frontend/src/components/Priorities.jsx b/frontend/src/components/Priorities.jsx index 0362a59f..639c6263 100644 --- a/frontend/src/components/Priorities.jsx +++ b/frontend/src/components/Priorities.jsx @@ -1,27 +1,38 @@ -import React, { useState, useEffect, useContext, memo } from "react"; - +import React, { useState, useEffect, useContext, memo, useCallback } from "react"; import { toast } from "react-toastify"; -import theme from "../theme.jsx"; +import { getTheme } from "../theme.jsx"; import { v4 as uuidv4, v5 as uuidv5, validate as isUUID, } from "uuid"; import { Paper, Tooltip, - Typography, + Typography, Divider, Button, ButtonGroup, Grid, Card, - Chip, - Switch, + Switch, Autocomplete, TextField, MenuItem, IconButton, + Dialog, + FormControl, + DialogContent, + DialogActions, + DialogTitle, + InputLabel, + Box, + Select } from "@mui/material"; +import AuthenticationData from "../components/AuthenticationWindow.jsx"; +import Stack from "@mui/material/Stack"; +import Chip from "@mui/material/Chip"; + import { OpenInNew as OpenInNewIcon, + Info as InfoIcon, } from "@mui/icons-material"; import { makeStyles } from "@mui/styles"; @@ -40,19 +51,20 @@ const useStyles = makeStyles({ }); const Priorities = memo((props) => { - const { globalUrl, userdata,clickedFromOrgTab,selectedOrganization, handleEditOrg, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props; - - const [showDismissed, setShowDismissed] = React.useState(false); - const [showRead, setShowRead] = React.useState(false); - const [appFramework, setAppFramework] = React.useState({}); - const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT"); - const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT"); - const [highlightKMS, setHighlightKMS] = React.useState(false) + const { globalUrl, userdata, clickedFromOrgTab, selectedOrganization, handleEditOrg, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props; + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + const [showDismissed, setShowDismissed] = React.useState(false); + const [showRead, setShowRead] = React.useState(false); + const [appFramework, setAppFramework] = React.useState({}); + const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT"); + const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT"); + const [highlightKMS, setHighlightKMS] = React.useState(false) - const [workflows, setWorkflows] = React.useState([]) - const [openNotification, setOpenNotification] = React.useState(false); - const [workflow, setWorkflow] = React.useState({}) - const [notificationWorkflow, setNotificationWorkflow] = React.useState( + const [workflows, setWorkflows] = React.useState([]) + const [openNotification, setOpenNotification] = React.useState(false); + const [workflow, setWorkflow] = React.useState({}) + const [notificationWorkflow, setNotificationWorkflow] = React.useState( selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.notification_workflow === undefined || @@ -60,9 +72,973 @@ const Priorities = memo((props) => { ? "" : selectedOrganization.defaults.notification_workflow ); - - let navigate = useNavigate(); - const classes = useStyles(); + + + // notification workflow + const [notificationWorkflowModal, setNotificationWorkflowModal] = React.useState(false); + const [selectedAppDetails, setSelectedAppDetails] = React.useState({}); + const [notificationWorkflowTestModal, setNotificationWorkflowTestModal] = React.useState(false); + const [selectedAuth, setSelectedAuth] = React.useState(''); + const [emailData, setEmailData] = React.useState([]); + const [notificationAppsDetails, setnotificationAppsDetails] = React.useState({}); + const [generatedWorkflow, setGeneatedWorkflow] = React.useState({}); + + // for jira & email modal + const [textFieldValue, setTextFieldValue] = React.useState(""); + const [textFieldOneValue, setTextFieldOneValue] = React.useState(""); + + useEffect(() => { + prepareNotificationAppList() + + if (notifications === undefined || notifications === null || notifications.length === 0) { + getNotifications() + } + }, []); + + let navigate = useNavigate(); + const classes = useStyles(); + + + // getting comms & cases app from app framework + var notificationAppList = []; + const mergeAuthData = (result, responseJson) => { + // result is only getting: Jira AND discord. + + const updatedResult = result.map(item => { + const matches = responseJson.filter(authItem => (authItem.app.name === item.name) + ); + return { + ...item, + authentication_data: matches.length > 0 ? matches : null + }; + }); + return updatedResult; + }; + + const prepareNotificationAppList = () => { + // getting App ID,Authentication fields and saved auths for each app + var result = [] + fetch(globalUrl + "/api/v1/apps", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }).then((response) => { + if (response.status !== 200) { + toast("Failed getting app ids: ", response.reason); + console.log("Status not 200 for app ids :O!"); + return; + } + return response.json(); + }).then((responseJson) => { + if (responseJson !== undefined) { + const filteredApps = responseJson.filter(app => app.categories?.includes("Communication") || app.categories?.includes("Cases")); + const emailData = responseJson.filter(app => app.name === "email") + setEmailData(emailData) + const appDetails = filteredApps.map(app => ({ name: app.name, id: app.id })); //mapped apps with IDs as sometime Ids were not correct in security framework + // console.log("appDetails: ", appDetails) + // result = appDetails + + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + toast(`Failed getting auth for : `, response.reason); + console.log("Status not 200 for app auth :O!"); + return; + } + return response.json(); + }).then((responseJson) => { + if (!responseJson.success) { + console.log("Could not get app auth") + return; + } + // console.log("responseJson of auth: ", responseJson.data) + result = mergeAuthData(appDetails, responseJson.data) + // console.log("merged auth data: ", result) + // console.log("result", result) + + result.map(item => { + fetch(globalUrl + `/api/v1/apps/${item.id}/config`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }).then((response) => { + if (response.status !== 200) { + toast(`Failed getting config for ${item.id}: `, response.reason); + console.log("Status not 200 for app config :O!"); + return; + } + return response.json(); + }).then((responseJson) => { + if (!responseJson.success) { + console.log("Could not get app config") + return; + } + var decodedString = JSON.parse(atob(responseJson.app)); + + // why is auth_config appearing like this? + // this is not very clean. + item.auth_config = decodedString.authentication + item.large_image = decodedString.large_image + item.categories = decodedString.categories + + let newJSON = notificationAppList; + + + // keeping state changes to a minimum + if ((newJSON[item.name] === undefined) && (item?.authentication_data !== null)) { + newJSON[item.name] = item; + setnotificationAppsDetails(newJSON); + console.log("notificationAppsDetails: ", newJSON) + } + }).then(() => { + // removing this for now, + // checkIfAlreadyGenerated(filteredApps, workflows); + + }).catch((error) => { + console.log("Error getting app config: " + error); + toast("Error getting app config: " + error); + }) + }) + }) + } + }).catch((error) => { + console.log("Error getting app ids: " + error); + }) + } + + + const executeTestWorkflow = async (workflowid) => { + const data = { "execution_argument": '{"title":"THIS IS TEST ALERT","description":"TEST ALERT FROM SHUFFLE","reference_url": "shuffler.io"}' } + fetch(globalUrl + `/api/v1/workflows/${workflowid}/execute`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + toast("Failed setting notification workflow: ", response.reason); + console.log("Status not 200 for workflows :O!"); + return; + } + toast("Notification workflow ran successfully"); + return response.json(); + }).catch((error) => { + console.log("Error getting workflows: " + error); + }) + } + + useEffect(() => { + console.log("Apps: ", notificationAppsDetails); + }, [notificationAppsDetails]) + + const generateCommsNotificationWorkflow = async (app, sender, recepient) => { + var appname = app.name + var appImage = app.large_image + + //currently only supports figure out a way to support more apps + var workflowName = `[GENERATED] ${appname} notification workflow` + var workflowDescription = "Generated by Shuffle for sending info/error notifications." + var data = { + "name": workflowName, + "description": workflowDescription, + } + + fetch(globalUrl + "/api/v1/workflows", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + toast("Failed setting notification workflow: ", response.reason); + console.log("Status not 200 for workflows :O!"); + return; + } + return response.json(); + }).then((responseJson) => { + toast("Trying to create notification workflow..") + if (responseJson !== undefined) { + console.log("Notification workflow created successfully") + + var commsParameter = [ + { + "key": "recipient", + "value": recepient + }, + { + "key": "sender", + "value": sender + }, + { + "key": "subject", + "value": "$exec.title" + }, + { + "key": "body", + "value": "$exec.description" + } + ] + + if (app.name.toLowerCase() === "gmail") { + // this is a special case and we know it + commsParameter = `[ +{ +"key": "content", +"value": {% python %} +import base64 + +# Set your email variables +sender = "${sender}" +recipient = "${recepient}" +subject = "$exec.title" +body = "$exec.description" + +raw_email = f"""FROM: {sender}\\nTO: {recipient}\\nsubject: {subject}\\n{body}""" + +# Encode for Gmail API +encoded = base64.urlsafe_b64encode(raw_email.encode()).decode() + +print('"' + encoded + '"') +{% endpython %}, +}, +{ +"key": "userId", +"value": "me" +}]` + } + + var workflow_id = responseJson.id; + + + var commsAction = { + "app_name": "Singul", + "app_version": "1.0.0", + "description": "Available actions for communication", + "app_id": "integration", + "errors": [], + "is_valid": true, + "isStartNode": true, + "label": "Send Notification Message", + "public": false, + "generated": false, + "large_image": app.large_image, + "environment": "Cloud", + "name": "Communication", + "parameters": [ + { + "description": "", + "id": "", + "name": "action", + "example": "", + "value": "send_message", + "multiline": false, + "multiselect": false, + "options": [ + "list_messages", + "get_message", + "send_message", + "search_messages", + "list_attachments", + "get_attachment", + "create_contact", + "get_contact" + ], + "action_field": "", + "variant": "", + "required": true, + "configuration": false, + "tags": null, + "schema": { + "type": "" + }, + "skip_multicheck": false, + "value_replace": null, + "unique_toggled": false, + "error": "", + "hidden": false + }, + { + "description": "", + "id": "", + "name": "fields", + "example": "", + "value": `${commsParameter}`, + "multiline": true, + "multiselect": false, + "options": null, + "action_field": "", + "variant": "", + "required": false, + "configuration": false, + "tags": null, + "schema": { + "type": "" + }, + "skip_multicheck": false, + "value_replace": null, + "unique_toggled": false, + "error": "", + "hidden": false + }, + { + "description": "", + "id": "", + "name": "app_name", + "example": "", + "value": app.name, + "multiline": false, + "multiselect": false, + "options": null, + "action_field": "", + "variant": "", + "required": false, + "configuration": false, + "tags": null, + "schema": { + "type": "" + }, + "skip_multicheck": false, + "value_replace": null, + "unique_toggled": false, + "error": "", + "hidden": false + } + ], + "execution_variable": { + "description": "", + "id": "", + "name": "", + "value": "" + }, + "position": { + "x": 67.96428571428578, + "y": 263.14385714285714 + }, + "authentication_id": "", + "category": "", + "reference_url": "", + "sub_action": false, + "run_magic_output": false, + "run_magic_input": false, + "execution_delay": 0, + "category_label": null, + "suggestion": false, + "parent_controlled": false, + "source_workflow": "", + "source_execution": "" + } + + console.log("updating workflow for email") + var workflowBody = { + "name": workflowName, + "Description": workflowDescription, + "id": workflow_id, + "actions": [ + commsAction, + ] + } + + fetch(globalUrl + `/api/v1/workflows/${workflow_id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(workflowBody), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + toast("Failed setting notification workflow: ", response.reason); + console.log("Status not 200 for workflows :O!"); + return; + } + return response.json(); + }).then((responseJson) => { + if (responseJson !== undefined) { + toast("Saving as notification workflow now..") + handleEditOrg( + selectedOrganization.name, + selectedOrganization.description, + selectedOrganization.id, + selectedOrganization.image, + { + documentation_reference: selectedOrganization?.defaults?.documentation_reference, + workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo, + workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch, + workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username, + workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token, + newsletter: selectedOrganization?.defaults?.newsletter, + weekly_recommendations: selectedOrganization?.defaults?.weekly_recommendations, + notification_workflow: workflow_id, + }, + { + sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint, + sso_certificate: selectedOrganization?.sso_config?.sso_certificate, + client_id: selectedOrganization?.sso_config?.client_id, + client_secret: selectedOrganization?.sso_config?.client_secret, + openid_authorization: selectedOrganization?.sso_config?.openid_authorization, + openid_token: selectedOrganization?.sso_config?.openid_token, + SSORequired: selectedOrganization?.sso_config?.SSORequired, + auto_provision: selectedOrganization?.sso_config?.auto_provision, + } + ) + + console.log("Notification workflow updated successfully") + toast("Notification workflow updated successfully") + } + }) + } + }).catch((error) => { + console.log("Error setting workflows: " + error); + }) + } + + const generateCasesNotificationWorkflow = async (app, projectId) => { + var appname = app.name + var appImage = app.large_image + + //currently only supports figure out a way to support more apps + var workflowName = `[GENERATED] ${appname} notification workflow` + var workflowDescription = "Generated by Shuffle for sending info/error notifications." + var data = { + "name": workflowName, + "description": workflowDescription, + } + + fetch(globalUrl + "/api/v1/workflows", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + toast("Failed setting notification workflow: ", response.reason); + console.log("Status not 200 for workflows :O!"); + return; + } + return response.json(); + }).then((responseJson) => { + toast("Trying to create notification workflow..") + if (responseJson !== undefined) { + console.log("Notification workflow created successfully") + + var ticketParameter = [ + { + "key": "project", + "value": projectId, + }, + { + "key": "content", + "value": "$exec.title" + }, + { + "key": "title", + "value": "$exec.description" + } + ] + + // fields = fields.concat(otherFields) + + var workflow_id = responseJson.id; + + var commsAction = { + "app_name": "Singul", + "app_version": "1.0.0", + "description": "Available actions for case management", + "app_id": "integration", + "errors": [ + + ], + "is_valid": true, + "isStartNode": true, + "label": "create_case_from_notification", + "public": false, + "generated": false, + "large_image": app.large_image, + "environment": "Cloud", + "name": "Cases", + "parameters": [ + { + "description": "", + "id": "", + "name": "action", + "example": "", + "value": "create_ticket", + "multiline": false, + "multiselect": false, + "options": [ + "list_tickets", + "get_ticket", + "create_ticket", + "close_ticket", + "add_comment", + "update_ticket", + "search_tickets" + ], + "action_field": "", + "variant": "", + "required": true, + "configuration": false, + "tags": null, + "schema": { + "type": "" + }, + "skip_multicheck": false, + "value_replace": null, + "unique_toggled": false, + "error": "", + "hidden": false + }, + { + "description": "", + "id": "", + "name": "fields", + "example": "", + "value": ticketParameter, + "multiline": true, + "multiselect": false, + "options": null, + "action_field": "", + "variant": "", + "required": false, + "configuration": false, + "tags": null, + "schema": { + "type": "" + }, + "skip_multicheck": false, + "value_replace": null, + "unique_toggled": false, + "error": "", + "hidden": false + }, + { + "description": "", + "id": "", + "name": "app_name", + "example": "", + "value": app.name, + "multiline": false, + "multiselect": false, + "options": null, + "action_field": "", + "variant": "", + "required": false, + "configuration": false, + "tags": null, + "schema": { + "type": "" + }, + "skip_multicheck": false, + "value_replace": null, + "unique_toggled": false, + "error": "", + "hidden": false + } + ], + "execution_variable": { + "description": "", + "id": "", + "name": "", + "value": "" + }, + "position": { + "x": -111.59751960534861, + "y": 180.34429863506278 + }, + "authentication_id": "", + "category": "", + "reference_url": "", + "sub_action": false, + "run_magic_output": false, + "run_magic_input": false, + "execution_delay": 0, + "category_label": null, + "suggestion": false, + "parent_controlled": false, + "source_workflow": "", + "source_execution": "" + } + + console.log("updating workflow for email") + var workflowBody = { + "name": workflowName, + "Description": workflowDescription, + "id": workflow_id, + "actions": [ + commsAction, + ] + } + + fetch(globalUrl + `/api/v1/workflows/${workflow_id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(workflowBody), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + toast("Failed setting notification workflow: ", response.reason); + console.log("Status not 200 for workflows :O!"); + return; + } + return response.json(); + }).then((responseJson) => { + if (responseJson !== undefined) { + handleEditOrg( + selectedOrganization.name, + selectedOrganization.description, + selectedOrganization.id, + selectedOrganization.image, + { + documentation_reference: selectedOrganization?.defaults?.documentation_reference, + workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo, + workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch, + workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username, + workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token, + newsletter: selectedOrganization?.defaults?.newsletter, + weekly_recommendations: selectedOrganization?.defaults?.weekly_recommendations, + notification_workflow: workflow_id, + }, + { + sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint, + sso_certificate: selectedOrganization?.sso_config?.sso_certificate, + client_id: selectedOrganization?.sso_config?.client_id, + client_secret: selectedOrganization?.sso_config?.client_secret, + openid_authorization: selectedOrganization?.sso_config?.openid_authorization, + openid_token: selectedOrganization?.sso_config?.openid_token, + SSORequired: selectedOrganization?.sso_config?.SSORequired, + auto_provision: selectedOrganization?.sso_config?.auto_provision, + } + ) + + console.log("Notification workflow updated successfully") + toast("Notification workflow updated successfully") + } + }) + } + }).catch((error) => { + console.log("Error setting workflows: " + error); + }) + } + + const testWorkflowModal = notificationWorkflowTestModal ? + ( { + setNotificationWorkflowTestModal(false); + }} + > + + {/* +
+ Notification workflow +
+
*/} + + We have updated the Notification workflow. Do you want to test it? + + + + + +
+
) : null + + const modalView = notificationWorkflowModal ? ( + { + setNotificationWorkflowModal(false); + }} + > + + +
+ {`Configure ${selectedAppDetails?.name?.replaceAll("_", " ")} workflow`} +
+
+ + + {console.log("len Selected app details: ", selectedAppDetails)} + + {(selectedAppDetails.authentication_data || (selectedAppDetails.auth_config && selectedAppDetails.auth_config.required == false) || (selectedAppDetails.authentication && selectedAppDetails.authentication.required == false)) ? + <> + + {true || (selectedAppDetails.auth_config && selectedAppDetails.auth_config.required == false || (selectedAppDetails.authentication && selectedAppDetails.authentication.required == false)) ? "No authentication required" : + <> + + Pick an authentication method from the list + + + Available authentications + + } + + + + Provide additional required details: + + { + setTextFieldOneValue(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} /> + { + setTextFieldValue(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} /> + + : + <> + 0) ? false : true} + // // setAuthenticationModalOpen={false} + selectedApp={{ ...selectedAppDetails, authentication: selectedAppDetails.auth_config }} + // getAppAuthentication={selectedAppDetails.name} + /> + + } + + + + + +
+
+ ) : null + + + const checkIfAlreadyGenerated = async (appList, workflows) => { // fixxxxxxxxxxxxxxxxxxxxx + + var workflowName = workflows.find(workflow => workflow.id === notificationWorkflow) + if (workflowName) { + workflowName = workflowName.name + } + else { + console.log("no workflow set") + return + } + if (workflowName) { + const parts = workflowName.split(' '); + console.log("parts", parts) + if (parts[0].toString() === "[GENERATED]" && parts.length > 1) { + console.log("parts1", parts[1]) + if ((appList.includes(parts[1]))) { + console.log("workflow already generated") + setGeneatedWorkflow({ "app_name": parts[1] }) + } + } + } + else { + return + } + } + + const renderChips = useCallback(() => { + const appList = Object.values(notificationAppsDetails); + + return ( + + + + {appList.map((app) => ( + { + console.log(`Clicked ${app.name}`) + console.log("app: ", app) + setSelectedAppDetails(app) + if (app.authentication_data && app.authentication_data.length > 0) { //fixxxxxxxx + console.log("authdata: ", app.authentication_data[0]) + setSelectedAuth(app.authentication_data[app.authentication_data.length - 1].id) + } + setNotificationWorkflowModal(true) + // getAppAuth(app.name) + console.log("selectedAppDEtails", selectedAppDetails) + }} + avatar={{app.name}} + /> + ))} + + + + + + + Want access to more templates? + + Set up app authentication + + to show additional workflow options. + + + + ); + }, [notificationAppsDetails]) useEffect(() => { getFramework() @@ -82,7 +1058,7 @@ const Priorities = memo((props) => { setSelectedExecutionId(execution_id) //toast.info("Execution-related notifications are highlighted.") - } + } if (workflow !== null) { setSelectedWorkflow(workflow) @@ -96,7 +1072,7 @@ const Priorities = memo((props) => { return } - if(workflows?.length === 0) { + if (workflows?.length === 0) { getAvailableWorkflows() } @@ -106,7 +1082,7 @@ const Priorities = memo((props) => { }, [selectedOrganization]) if (userdata === undefined || userdata === null) { - return + return } const getFramework = () => { @@ -118,67 +1094,95 @@ const Priorities = memo((props) => { }, credentials: "include", }) - .then((response) => { + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + setAppFramework({}) + if (responseJson.reason !== undefined) { + //toast("Failed loading: " + responseJson.reason) + } else { + //toast("Failed to load framework for your org.") + } + } else { + setAppFramework(responseJson) + } + }) + .catch((error) => { + console.log("err in framework: ", error.toString()); + }) + } + + const getNotifications = () => { + fetch(`${globalUrl}/api/v1/notifications`, { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then(function (response) { if (response.status !== 200) { - console.log("Status not 200 for framework!"); + console.log("Error in response"); } return response.json(); }) - .then((responseJson) => { - if (responseJson.success === false) { - setAppFramework({}) - if (responseJson.reason !== undefined) { - //toast("Failed loading: " + responseJson.reason) - } else { - //toast("Failed to load framework for your org.") - } + .then(function (responseJson) { + if (responseJson?.success !== false && responseJson?.notifications !== undefined && responseJson?.notifications !== null) { + setNotifications(responseJson.notifications || []) } else { - setAppFramework(responseJson) + toast("Failed loading notifications. Please try again later."); } }) .catch((error) => { - console.log("err in framework: ", error.toString()); - }) + console.log("error in notification loading: ", error); + }); + } - const clearNotifications = () => { - // Don't really care about the logout + const clearNotifications = () => { + // Don't really care about the logout - toast("Clearing notifications") - fetch(`${globalUrl}/api/v1/notifications/clear`, { - credentials: "include", - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } + toast.info("Marking all notifications as read. This may take a while.") + fetch(`${globalUrl}/api/v1/notifications/clear`, { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success === true) { - // Reload the UI - const newNotifications = notifications.map((notification) => { - notification.read = true - return notification - }) + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + // Reload the UI + const newNotifications = notifications.map((notification) => { + notification.read = true + return notification + }) - setNotifications(newNotifications) - setShowRead(true) - } else { - toast("Failed dismissing notifications. Please try again later."); - } - }) - .catch((error) => { - console.log("error in notification dismissal: ", error); - //removeCookie("session_token", {path: "/"}) - }); - }; + setNotifications(newNotifications) + setShowRead(true) + } else { + toast("Failed dismissing notifications. Please try again later."); + } + }) + .catch((error) => { + console.log("error in notification dismissal: ", error); + //removeCookie("session_token", {path: "/"}) + }); + }; const dismissNotification = (alert_id, disabled) => { var notificationurl = `${globalUrl}/api/v1/notifications/${alert_id}/markasread` @@ -188,82 +1192,82 @@ const Priorities = memo((props) => { notificationurl += "?disabled=false" } - fetch(notificationurl , { - credentials: "include", - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } - - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success === true) { - // Mark current one as read - var newNotifications = notifications.map((notification) => { - if (notification.id === alert_id) { - notification.read = true + fetch(notificationurl, { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); } - return notification + return response.json(); }) + .then(function (responseJson) { + if (responseJson.success === true) { + // Mark current one as read + var newNotifications = notifications.map((notification) => { + if (notification.id === alert_id) { + notification.read = true + } + + return notification + }) - if (disabled === true) { - toast("Notification disabled, and will not be shown again.") + if (disabled === true) { + toast("Notification disabled, and will not be shown again.") - newNotifications = newNotifications.map((notification) => { - if (notification.id === alert_id) { - notification.ignored = true + newNotifications = newNotifications.map((notification) => { + if (notification.id === alert_id) { + notification.ignored = true + } + + return notification + }) + + console.log("NEW NOTIFICATIONS: ", newNotifications); + } else if (disabled === false) { + toast("Notification re-enabled successfully") + + newNotifications = newNotifications.map((notification) => { + if (notification.id === alert_id) { + notification.ignored = false + } + + return notification + }) + + } else { + toast("Notification dismissed successfully") } - return notification - }) + //const newNotifications = notifications.filter( + // (data) => data.id !== alert_id + //) - console.log("NEW NOTIFICATIONS: ", newNotifications); - } else if (disabled === false) { - toast("Notification re-enabled successfully") + //console.log("NEW NOTIFICATIONS: ", newNotifications); - newNotifications = newNotifications.map((notification) => { - if (notification.id === alert_id) { - notification.ignored = false + if (setNotifications !== undefined && newNotifications !== undefined) { + setNotifications(newNotifications) } - - return notification - }) - - } else { - toast("Notification dismissed successfully") - } - - //const newNotifications = notifications.filter( - // (data) => data.id !== alert_id - //) - - //console.log("NEW NOTIFICATIONS: ", newNotifications); - - if (setNotifications !== undefined && newNotifications !== undefined) { - setNotifications(newNotifications) - } - } else { - toast("Failed dismissing notification. Please try again later."); - } - }) - .catch((error) => { - console.log("error in notification dismissal: ", error); - //removeCookie("session_token", {path: "/"}) - }) + } else { + toast("Failed dismissing notification. Please try again later."); + } + }) + .catch((error) => { + console.log("error in notification dismissal: ", error); + //removeCookie("session_token", {path: "/"}) + }) } - - const notificationWidth = "100%" + + const notificationWidth = "100%" const imagesize = 22 - const boxColor = "#86c142" + const boxColor = "#86c142" const getAvailableWorkflows = () => { @@ -353,131 +1357,168 @@ const Priorities = memo((props) => { } return ( -
-
-
- - Notification Workflow - - - The notification workflow triggers when an error occurs in one of your workflows. Each individual one will only start a workflow once every 2 minutes. You can point child org notifications into the parent org notification by choosing it in the list. - - -
+
+
+
+ + Notification Workflow + + + The notification workflow triggers when an error occurs in one of your workflows. Each individual one will only start a workflow once every 2 minutes. You can point child org notifications into the parent org notification by choosing it in the list. + - {workflows !== undefined && workflows !== null && workflows.length > 0 ? - { - setOpenNotification(true); - }} - onClose={() => { - setOpenNotification(false); - }} - freeSolo - //autoSelect - value={workflows?.find(w => w.id === notificationWorkflow) || null} - classes={{ inputRoot: classes.inputRoot }} - ListboxProps={{ - style: { - backgroundColor: "#212121", - color: "white", - }, - }} - getOptionLabel={(option) => { - if ( - option === undefined || - option === null || - option.name === undefined || - option.name === null - ) { - return "No Workflow Selected"; - } + {modalView} + {/*{testWorkflowModal} */} +
+ {renderChips()} +
- const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - return newname; - }} - options={workflows} - fullWidth - style={{ - backgroundColor: "#212121", - borderRadius: theme.palette?.borderRadius, - height: 35, - marginBottom: 40, - }} - onChange={(event, newValue) => { - console.log("Found value: ", newValue) +
- var parsedinput = { target: { value: newValue } } - - // For variables - if (typeof newValue === 'string' && newValue.startsWith("$")) { - parsedinput = { - target: { - value: { - "name": newValue, - "id": newValue, - "actions": [], - "triggers": [], - } - } - } - } - - handleWorkflowSelectionUpdate(parsedinput) - }} - renderOption={(props, data, state) => { - if (data.id === workflow.id) { - data = workflow; - } - - return ( - - {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} - : null} - - Choose {data.name} - - - } placement="bottom"> - { - props.onMouseDown?.(null); - var parsedinput = { target: { value: data } } - handleWorkflowSelectionUpdate(parsedinput) - }} - > - {data.name} - - - ) - }} - renderInput={(params) => { - return ( - 0 ? + { + setOpenNotification(true); }} + onClose={() => { + setOpenNotification(false); + }} + freeSolo + //autoSelect + value={workflows?.find(w => w.id === notificationWorkflow) || null} + classes={{ inputRoot: classes.inputRoot }} + ListboxProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: theme.palette.text.primary, + borderRadius: theme.palette.borderRadius, + }, + }} + getOptionLabel={(option) => { + if ( + option === undefined || + option === null || + option.name === undefined || + option.name === null + ) { + return "No Workflow Selected"; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + return newname; + }} + options={workflows} + fullWidth + style={{ + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + borderRadius: theme.palette.textFieldStyle.borderRadius, + color: theme.palette.textFieldStyle.color, + height: 35, + marginBottom: 40, + }} + onChange={(event, newValue) => { + console.log("Found value: ", newValue) + + var parsedinput = { target: { value: newValue } } + + // For variables + if (typeof newValue === 'string' && newValue.startsWith("$")) { + parsedinput = { + target: { + value: { + "name": newValue, + "id": newValue, + "actions": [], + "triggers": [], + } + } + } + } + + handleWorkflowSelectionUpdate(parsedinput) + }} + renderOption={(props, data, state) => { + if (data.id === workflow.id) { + data = workflow; + } + + return ( + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.name} + : null} + + Choose {data.name} + + + } placement="bottom"> + { + props.onMouseDown?.(null); + var parsedinput = { target: { value: data } } + handleWorkflowSelectionUpdate(parsedinput) + }} + > + {data.name} + + + ) + }} + renderInput={(params) => { + return ( + + ); + }} + /> + : + { borderRadius: 4, }, inputProps: { - ...params.inputProps, style: { height: "100%", boxSizing: "border-box", @@ -495,197 +1535,171 @@ const Priorities = memo((props) => { } }} - // label="Find a notification workflow" - variant="outlined" - placeholder="Select a notification workflow" + style={{ + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, + borderRadius: 4, + height: 35, + fontSize: 16, + marginBottom: 30 + }} + fullWidth={true} + type="name" + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="ID of the workflow to receive notifications" + value={notificationWorkflow} + onChange={(e) => { + setNotificationWorkflow(e.target.value); + }} /> - ); - }} - /> - : - { - setNotificationWorkflow(e.target.value); - }} - /> - } - {/*
+ {/*
{orgSaveButton}
*/} -
+
- {notificationWorkflow === undefined || notificationWorkflow === null || notificationWorkflow.length === 0 ? null : -
- + { + if (notificationWorkflow === "parent") { + toast.error("Can't open parent org's notification workflow from here.") + return + } + + window.open(`/workflows/${notificationWorkflow}?view=executions`, "_blank") + }} + > + + +
} - fetch(`${globalUrl}/api/v1/workflows/${notificationWorkflow}/execute`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - }, - credentials: "include", - body: JSON.stringify({ - "title": "Test Notification", - "description": "This is a test notification to check if the notification workflow is working correctly.", - "org_id": selectedOrganization.id, - "id": uuidv4(), - "reference_url": "/admin?type=test&admin_tab=notifications", - "created_at": Math.floor(new Date().getTime() / 1000), - "updated_at": Math.floor(new Date().getTime() / 1000), + Notifications ({ + notifications?.filter((notification) => showRead === true || notification.read === false).length + }) + + + Notifications help you find potential problems with your workflows and apps.  + + Learn more + + +
+
+ { + setShowRead(!showRead); + }} + />  Show read + {notifications !== undefined && notifications !== null && notifications.length > 1 ? ( + + ) : null} +
+ + + + {clickedFromOrgTab ? null : } + + Suggestions + + Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company.
These range from simple configurations in Shuffle to Usecases you may have missed.  + + Learn more + +
+
+ { + setShowDismissed(!showDismissed); + }} + />  Show dismissed + {userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ? + + No Suggestions found + + : + userdata.priorities.map((priority, index) => { + if (showDismissed === false && priority.active === false) { + return null + } + + return ( + + ) }) - }) - .then((response) => { - if (response.status === 200) { - toast.success("Test notification sent successfully.") - } else { - toast.error("Failed to send test notification. Please contact support if this persists") - } - }).catch((error) => { - toast.error("Failed to send test notification (2). Please contact support if this persists") - }) - }}> - Send test notification - - { - if (notificationWorkflow === "parent") { - toast.error("Can't open parent org's notification workflow from here.") - return - } - - window.open(`/workflows/${notificationWorkflow}?view=executions`, "_blank") - }} - > - - -
- } - - Notifications ({ - notifications?.filter((notification) => showRead === true || notification.read === false).length - }) - - - Notifications help you find potential problems with your workflows and apps.  - - Learn more - - -
-
- { - setShowRead(!showRead); - }} - />  Show read - {notifications !== undefined && notifications !== null && notifications.length > 1 ? ( - - ) : null} -
- - - - {clickedFromOrgTab? null : } - -

Suggestions

- - Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company.
These range from simple configurations in Shuffle to Usecases you may have missed.  - - Learn more - -
-
- { - setShowDismissed(!showDismissed); - }} - />  Show dismissed - {userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ? - - No Suggestions found - - : - userdata.priorities.map((priority, index) => { - if (showDismissed === false && priority.active === false) { - return null } - - return ( - - ) - }) - } -
-
+
+
) }) @@ -694,14 +1708,15 @@ export default Priorities; const NotificationItem = memo((props) => { - const {data, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification} = props + const { data, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification } = props var image = ""; var orgName = ""; var orgId = ""; + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); - - var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow) + var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow) if (!highlighted && highlightKMS) { if (data.title !== undefined && data.title !== null && data.title.toLowerCase().includes("kms")) { @@ -713,239 +1728,242 @@ const NotificationItem = memo((props) => { } if (userdata.orgs !== undefined) { - const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); - if (foundOrg !== undefined && foundOrg !== null) { - //position: "absolute", bottom: 5, right: -5, - const imageStyle = { - width: imagesize, - height: imagesize, - pointerEvents: "none", - marginLeft: - data.creator_org !== undefined && data.creator_org.length > 0 - ? 20 - : 0, - borderRadius: 10, - border: - foundOrg.id === userdata.active_org.id - ? `3px solid ${boxColor}` - : null, - cursor: "pointer", - marginRight: 10, - }; + const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + marginLeft: + data.creator_org !== undefined && data.creator_org.length > 0 + ? 20 + : 0, + borderRadius: 10, + border: + foundOrg.id === userdata.active_org.id + ? `3px solid ${boxColor}` + : null, + cursor: "pointer", + marginRight: 10, + }; - image = - foundOrg.image === "" ? ( - {foundOrg.name} - ) : ( - {foundOrg.name} {}} - /> - ); + image = + foundOrg.image === "" ? ( + {foundOrg.name} + ) : ( + {foundOrg.name} { }} + /> + ); - orgName = foundOrg.name; - orgId = foundOrg.id; - } + orgName = foundOrg.name; + orgId = foundOrg.id; + } } return ( - -
- {data.amount === 1 && data.read === false ? - - : null} - {data.ignored === true ? - - : null} - {data.read === false ? - - : - - } - - {data.title} - -
- - {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.title} - : - null - } - - {data.description} - -
- - - - {data.read === false ? ( - - ) : null} - - - - - +
+ {data.amount === 1 && data.read === false ? + + : null} + {data.ignored === true ? + + : null} + {data.read === false ? + + : + + } + + {data.title} + +
- 0 ? + {data.title} + : + null + } + + {data.description} + +
+ + - }} - > - First seen:{" "} - {new Date(data.created_at * 1000).toISOString().slice(0, 19)} - + {data.read === false ? ( + + ) : null} - - Last seen:{" "} - {new Date(data.updated_at * 1000).toISOString().slice(0, 19)} - + + + + - - Times seen: {data.amount} - -
+ + }} + > + First seen:{" "} + {new Date(data.created_at * 1000).toISOString().slice(0, 19)} + + + + Last seen:{" "} + {new Date(data.updated_at * 1000).toISOString().slice(0, 19)} + + + + Times seen: {data.amount} + +
+ + ); }) -const NotificationComponent = memo(({notifications, showRead, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification}) => { +const NotificationComponent = memo(({ notifications, showRead, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification }) => { - return( + return (
{notifications === null || notifications === undefined || notifications?.length === 0 ? ( - null - ) : -
- {notifications?.map((notification, index) => { - if (showRead === false && notification.read === true) { - return null - } + null + ) : +
+ {notifications?.map((notification, index) => { + if (showRead === false && notification.read === true) { + return null + } - return ( - - ) - })} -
- } + return ( + + ) + })} +
+ }
) }) diff --git a/frontend/src/components/Priority.jsx b/frontend/src/components/Priority.jsx index 4bd3a6f2..7cc0f3da 100644 --- a/frontend/src/components/Priority.jsx +++ b/frontend/src/components/Priority.jsx @@ -1,10 +1,10 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { toast } from 'react-toastify'; - +import { getTheme } from "../theme.jsx"; import ReactGA from 'react-ga4'; -import theme from "../theme.jsx"; import { useNavigate, Link } from "react-router-dom"; import { findSpecificApp } from "../components/AppFramework.jsx" +import { Context } from "../context/ContextApi.jsx"; import { Paper, Typography, @@ -23,7 +23,8 @@ import { const Priority = (props) => { const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props; - + const { themeMode, supportEmail } = useContext(Context); + const theme = getTheme(themeMode); const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true"); let navigate = useNavigate(); @@ -113,7 +114,7 @@ const Priority = (props) => { } }) .catch((error) => { - toast("Failed dismissing alert. Please contact support@shuffler.io if this persists."); + toast(`Failed dismissing alert. Please contact ${supportEmail} if this persists.`); }); } @@ -121,10 +122,10 @@ const Priority = (props) => { const srcSize = realignedSrc ? 35 : 30 const dstSize = realignedDst ? 35 : 30 return ( -
+
- {priority.type === "usecase" || priority.type == "apps" ? : null} + {priority.type === "usecase" || priority.type == "apps" ? : null} {priority.name} @@ -138,7 +139,7 @@ const Priority = (props) => { {newdescription.split("&").length > 3 ? - + {priority.name+"2"} {newdescription.split("&")[2]} @@ -154,7 +155,7 @@ const Priority = (props) => { }
- {priority.active === true ? -
- {isFileEditor ? null : + {isFileEditor || isWorkflowEditor ? null :
{ paddingLeft: 10, paddingTop: 0, display: "flex", - cursor: "move" + cursor: "move", + color: theme.palette.DialogStyle.color, + backgroundColor: "transparent", }} >
@@ -2294,7 +2304,7 @@ const CodeEditor = (props) => { { selectedEdge && Object.keys(selectedEdge).length > 0 ? { }
: - + {selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ? "Code to run" : triggerId ? @@ -2337,9 +2347,7 @@ const CodeEditor = (props) => {
-
+
+
+
+ + ); + }; + const modalView = ( { }} > - Add Sub-Organization + Add Sub-Organization
@@ -625,7 +855,7 @@ const TenantsTab = memo((props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.textFieldStyle.color, fontSize: "1em", }, }} @@ -644,15 +874,14 @@ const TenantsTab = memo((props) => {
@@ -859,7 +1087,7 @@ const TenantsTab = memo((props) => { /> */} -
+
{ display: "table-cell", padding: "0px 8px 8px 8px", textAlign: "center", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -903,7 +1131,7 @@ const TenantsTab = memo((props) => { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", textAlign: "center", }} @@ -915,7 +1143,7 @@ const TenantsTab = memo((props) => { minWidth: 100, maxWidth: 100, display: "table-cell", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, padding: "0px 8px 8px 8px", }} /> @@ -929,7 +1157,7 @@ const TenantsTab = memo((props) => { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -942,7 +1170,7 @@ const TenantsTab = memo((props) => { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -954,7 +1182,7 @@ const TenantsTab = memo((props) => { key={rowIndex} style={{ display: "flex", - backgroundColor: "#212121", + backgroundColor: theme.palette.platformColor, height: 30, }} > @@ -976,7 +1204,7 @@ const TenantsTab = memo((props) => { variant="text" animation="wave" sx={{ - backgroundColor: "#1a1a1a", + backgroundColor: theme.palette.loaderColor, borderRadius: "4px", }} /> @@ -987,7 +1215,7 @@ const TenantsTab = memo((props) => { ) : parentOrg?.id?.length > 0 ? ( { padding: "10px", whiteSpace: "nowrap", }} - primary={index === 1 ? "Parent Organization not found or May be you are not part of parent org. Please contact support@shuffler.io." : null} + primary={index === 1 ? `Parent Organization not found or May be you are not part of parent org. Please contact ${supportEmail}` : null} colSpan={index === 0 ? 5 : undefined} /> ))} @@ -1123,17 +1351,15 @@ const TenantsTab = memo((props) => { marginTop: 20, }} > -

Sub Organizations of the Current Organization ({subOrgs.length}) -

+
{/* { }} /> */} -
+
{ display: "table-cell", padding: "0px 8px 8px 8px", textAlign: "center", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -1214,7 +1440,7 @@ const TenantsTab = memo((props) => { display: "table-cell", padding: "0px 8px 8px 8px", textAlign: "center", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", textAlign: "center", }} /> @@ -1237,7 +1463,7 @@ const TenantsTab = memo((props) => { minWidth: 100, maxWidth: 100, display: "table-cell", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, padding: "0px 8px 8px 8px", }} /> @@ -1251,7 +1477,7 @@ const TenantsTab = memo((props) => { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -1264,7 +1490,7 @@ const TenantsTab = memo((props) => { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -1290,9 +1516,13 @@ const TenantsTab = memo((props) => { } } } + var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF"; + if (index % 2 === 0) { + bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA"; + } return ( - + } style={{ width: 100, minWidth: 100, maxWidth: 100, @@ -1332,7 +1562,8 @@ const TenantsTab = memo((props) => { + <> + + + {selectedOrganization?.creator_org?.length > 0 ? null : + } + + } style={{ display: "table-cell", verticalAlign: "middle" }} /> @@ -1374,17 +1632,15 @@ const TenantsTab = memo((props) => { />
-

All Tenants -

+
{/* { style={{ borderRadius: 4, marginTop: 24, - border: "1px solid #494949", + border: theme.palette.defaultBorder, width: "100%", overflowX: "auto", paddingBottom: 0, @@ -1448,7 +1704,7 @@ const TenantsTab = memo((props) => { display: "table-cell", padding: "0px 8px 8px 8px", textAlign: "center", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -1475,7 +1731,7 @@ const TenantsTab = memo((props) => { display: "table-cell", padding: "0px 8px 8px 8px", textAlign: "center", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -1488,7 +1744,7 @@ const TenantsTab = memo((props) => { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", textAlign: "center", }} @@ -1500,7 +1756,7 @@ const TenantsTab = memo((props) => { minWidth: 100, maxWidth: 100, display: "table-cell", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, padding: "0px 8px 8px 8px", }} /> @@ -1514,7 +1770,7 @@ const TenantsTab = memo((props) => { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -1527,7 +1783,7 @@ const TenantsTab = memo((props) => { padding: "0px 8px 8px 8px", whiteSpace: "nowrap", textOverflow: "ellipsis", - borderBottom: "1px solid #494949", + borderBottom: theme.palette.defaultBorder, verticalAlign: "middle", }} /> @@ -1590,6 +1846,11 @@ const TenantsTab = memo((props) => { } } + var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF"; + if (index % 2 === 0) { + bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA"; + } + return ( { display: "table-row", verticalAlign: "middle", padding: 8, - backgroundColor: index % 2 === 0 ? "#1A1A1A" : "#212121", + backgroundColor: bgColor, borderBottomLeftRadius: userdata?.orgs?.length - 1 === index ? 8 : 0, borderBottomRightRadius: diff --git a/frontend/src/components/UserManagmentTab.jsx b/frontend/src/components/UserManagmentTab.jsx index bb6870ff..81b65b94 100644 --- a/frontend/src/components/UserManagmentTab.jsx +++ b/frontend/src/components/UserManagmentTab.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useContext, memo } from "react"; import { toast } from 'react-toastify'; - +import { Context } from "../context/ContextApi.jsx"; import { FormControl, InputLabel, @@ -35,7 +35,7 @@ import { import ModeEditOutlineOutlinedIcon from '@mui/icons-material/ModeEditOutlineOutlined'; import ContentCopyOutlinedIcon from '@mui/icons-material/ContentCopyOutlined'; -import theme from "../theme.jsx"; +import {getTheme} from "../theme.jsx"; const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; const MenuProps = { @@ -75,12 +75,16 @@ const UserManagmentTab = memo((props) => { const [logsViewModal, setLogsViewModal] = React.useState(false); const [ipSelected, setIpSelected] = React.useState(""); const [userLogViewing, setUserLogViewing] = React.useState({}); + const { themeMode, supportEmail, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + useEffect(() => { if (selectedOrganization?.mfa_required !== MFARequired) { setMFARequired(selectedOrganization?.mfa_required); } }, [selectedOrganization]); + useEffect(() => { if(users?.length === 0){ getUsers(); } }, []); @@ -363,7 +367,7 @@ const UserManagmentTab = memo((props) => { toast("Failed to deactivate user: " + responseJson.reason); } else if (responseJson.success === false) { toast( - "Failed to deactivate user. Please contact support@shuffler.io if this persists.", + `Failed to deactivate user. Please contact ${supportEmail} if this persists.`, ); } else { toast("Changed activation for user " + data.id); @@ -602,7 +606,7 @@ const UserManagmentTab = memo((props) => { }} > - + Add user @@ -619,7 +623,7 @@ const UserManagmentTab = memo((props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.textFieldStyle.color, fontSize: "1em", }, }} @@ -652,7 +656,7 @@ const UserManagmentTab = memo((props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.textFieldStyle.color, fontSize: "1em", }, }} @@ -682,17 +686,17 @@ const UserManagmentTab = memo((props) => {
{loginInfo} - + @@ -751,7 +754,7 @@ const UserManagmentTab = memo((props) => { }} > - + Editing {selectedUser.username} @@ -768,7 +771,7 @@ const UserManagmentTab = memo((props) => { InputProps={{ style: { height: 50, - color: "white", + color: theme.palette.textFieldStyle.color, }, }} color="primary" @@ -811,7 +814,7 @@ const UserManagmentTab = memo((props) => { InputProps={{ style: { height: 50, - color: "white", + color: theme.palette.textFieldStyle.color, }, }} color="primary" @@ -845,9 +848,10 @@ const UserManagmentTab = memo((props) => { backgroundColor: theme.palette.inputColor, }} /> -
- +
{isCloud && userdata.support && selectedUser.id !== userdata.id ? (
- + {parsedTitle}
diff --git a/frontend/src/components/Workflowsearch.jsx b/frontend/src/components/Workflowsearch.jsx index b8f4c022..1b342645 100644 --- a/frontend/src/components/Workflowsearch.jsx +++ b/frontend/src/components/Workflowsearch.jsx @@ -10,7 +10,7 @@ import algoliasearch from 'algoliasearch'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; 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 { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, selectAble, } = props const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows diff --git a/frontend/src/components/ssoTab.jsx b/frontend/src/components/ssoTab.jsx index 5034f521..e220ea6f 100644 --- a/frontend/src/components/ssoTab.jsx +++ b/frontend/src/components/ssoTab.jsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useContext } from "react"; import React from "react"; import { Typography, @@ -13,6 +13,8 @@ import { makeStyles } from "@mui/styles"; import { Link } from "react-router-dom"; import theme from "../theme.jsx"; import { toast } from "react-toastify"; +import { Context } from "../context/ContextApi.jsx"; +import { getTheme } from "../theme.jsx"; const useStyles = makeStyles({ notchedOutline: { @@ -26,8 +28,16 @@ const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handle const classes = useStyles(); const [show2faSetup, setShow2faSetup] = React.useState(false); const [autoPrivision, setAutoProvision] = React.useState(selectedOrganization?.sso_config?.auto_provision) + const [roleRequired, setRoleRequired] = React.useState(selectedOrganization?.sso_config?.role_required || false); const [showOpenIdCred, setShowOpenIdCred] = React.useState(false); const [showSamlCred, setShowSamlCred] = React.useState(false); + const [skipSSOForAdmin, setSkipSSOForAdmin] = React.useState( + selectedOrganization?.sso_config === undefined + ? false + : selectedOrganization?.sso_config?.skip_sso_for_admins === undefined + ? false + : selectedOrganization?.sso_config?.skip_sso_for_admins + ); const [ssoEntrypoint, setSsoEntrypoint] = React.useState( selectedOrganization.sso_config === undefined ? "" @@ -84,6 +94,9 @@ const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handle : selectedOrganization.sso_config.openid_token ) + const { themeMode, supportEmail, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + useEffect(()=>{ if (openidClientSecret !== selectedOrganization?.sso_config?.client_secret) { @@ -116,12 +129,21 @@ const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handle if (autoPrivision !== selectedOrganization?.sso_config?.auto_provision) { setAutoProvision(selectedOrganization?.sso_config?.auto_provision) } + + if (roleRequired !== selectedOrganization?.sso_config?.role_required) { + setRoleRequired(selectedOrganization?.sso_config?.role_required) + } + + if (skipSSOForAdmin !== selectedOrganization?.sso_config?.skip_sso_for_admins) { + setSkipSSOForAdmin(selectedOrganization?.sso_config?.skip_sso_for_admins) + } + },[selectedOrganization]) const orgSaveButton = (
- OpenID connect - + OpenID connect + Configure and Authorize SAML / SSO or OpenID connect. {" "} Learn more - - IdP URL for Shuffle OpenID: {`${globalUrl}/api/v1/login_openid`} + + IdP URL for Shuffle OpenID: {`${globalUrl}/api/v1/login_openid`}
- @@ -409,20 +514,19 @@ const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handle onChange={(e) => setShowOpenIdCred(e.target.checked)} name="showOpenIdCred" color="primary" - style={{ color: "rgba(255, 255, 255, 1)", }} />
- Client ID + Client ID @@ -452,14 +556,14 @@ const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handle - Client Secret + Client Secret @@ -494,14 +598,14 @@ const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handle - Authorization URL + Authorization URL - Token URL + Token URL - SAML SSO (v1.1) - - IdP URL for Shuffle SAML/SSO: {`${globalUrl}/api/v1/login_sso`} + SAML SSO (v1.1) + + IdP URL for Shuffle SAML/SSO: {`${globalUrl}/api/v1/login_sso`}
@@ -598,20 +702,19 @@ const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handle onChange={(e) => setShowSamlCred(e.target.checked)} name="showSamlCred" color="primary" - style={{ color: "rgba(255, 255, 255, 1)", }} />
- SSO Entrypoint (IdP) + SSO Entrypoint (IdP) - SSO Certificate (X509) + SSO Certificate (X509) { @@ -11,6 +13,14 @@ export const AppContext = (props) => { const [isDocSearchModalOpen, setIsDocSearchModalOpen] = useState(false); const [leftSideBarOpenByClick, setLeftSideBarOpenByClick] = useState(currentLocation?.includes('/workflows/') ? false : true) const [windowWidth, setWindowWidth] = useState(serverside === true ? 100 : window.innerWidth); + const [brandColor, setBrandColor] = useState(() => localStorage.getItem("brandColor") || "#ff8544"); + const [brandName, setBrandName] = useState(()=> localStorage.getItem("brandName") || "Shuffle"); + + const [themeMode, setThemeMode] = useState( + () => localStorage.getItem("theme") || "dark" + ); + const [supportEmail, setSupportEmail] = useState("support@shuffler.io"); + const [logoutUrl, setLogoutUrl] = useState(""); useEffect(() => { if (currentLocation?.includes('/workflows/') && leftSideBarOpenByClick === true) { @@ -35,6 +45,57 @@ export const AppContext = (props) => { }; }, []); + const handleThemeChange = (theme) => { + if (!theme || theme === "null" || theme === "undefined") { + localStorage.setItem("theme", "dark"); + setThemeMode("dark"); + return; + } + + const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + + const applySystemTheme = () => { + const isDark = darkMediaQuery.matches; + setThemeMode(isDark ? "dark" : "light"); + localStorage.setItem("theme", isDark ? "dark" : "light"); + }; + + if (theme === "system") { + applySystemTheme(); + darkMediaQuery.addEventListener("change", applySystemTheme); + return () => { + darkMediaQuery.removeEventListener("change", applySystemTheme); + }; + } else { + setThemeMode(theme); + localStorage.setItem("theme", theme); + } + }; + + + useEffect(() => { + if (serverside === true) return; + + const theme = localStorage.getItem("theme"); + + if (!theme || theme === "null" || theme === "undefined") { + localStorage.setItem("theme", "dark"); + setThemeMode("dark"); + return; + } + + let cleanup; + if (theme === "system") { + cleanup = handleThemeChange("system"); + } else { + handleThemeChange(theme); + } + + return () => { + if (cleanup) cleanup(); + }; + }, []); + return ( { setIsDocSearchModalOpen, searchBarModalOpen, setSearchBarModalOpen, + supportEmail, + setSupportEmail, + logoutUrl, + setLogoutUrl, leftSideBarOpenByClick, setLeftSideBarOpenByClick, - windowWidth + windowWidth, + themeMode, + setThemeMode, + handleThemeChange, + brandColor, + setBrandColor, + brandName, + setBrandName, }}> {props.children} diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx index 8a2623f6..79d35e35 100644 --- a/frontend/src/defaultCytoscapeStyle.jsx +++ b/frontend/src/defaultCytoscapeStyle.jsx @@ -1,619 +1,626 @@ -const data = [ - { - selector: "node", - css: { - label: function(element) { - var elementname = element.data("label") - if (elementname === null || elementname === undefined) { - return "" - } - - elementname = elementname.replaceAll("_", " ", -1) - elementname = elementname.charAt(0).toUpperCase() + elementname.slice(1) - return elementname - }, - "text-valign": "center", - "text-margin-x": function(element) { - // Attempt at bottom-positioning - // Required text-valign: bottom - // FIXME: Disabled for now. - return "15px" - - - - const name = element.data("label") - console.log("Name: ", name) - if (name === null || name === undefined || name == "" || document=== undefined || document === null) { - return "0px" - } - - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d') - - context.font = '18px Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif' - - const textWidth = context.measureText(name).width - return textWidth + "px" - //return -1*(textWidth) + "px" - }, - - "font-family": "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif", - "font-weight": "lighter", - "font-size": "18px", - "margin-right": "10px", - width: "80px", - height: "80px", - color: "white", - padding: "10px", - margin: "5px", - "border-width": "1px", - "z-index": 5001, - }, - }, - { - selector: "edge", - css: { - "target-arrow-shape": "triangle", - "target-arrow-color": "grey", - "curve-style": "unbundled-bezier", - label: "data(label)", - "text-margin-y": "-15px", - width: "2px", - color: "white", - "line-fill": "linear-gradient", - "line-gradient-stop-positions": ["0.0", "100"], - "line-gradient-stop-colors": ["grey", "grey"], - "z-index": 5001, - }, - }, - { - selector: `node[buttonType="ACTIONSUGGESTION"]`, - css: { - label: "data(label)", - shape: "roundrectangle", - "height": "18px", - "width": "145px", - "background-color": "#212121", - "border-color": "#81c784", - "z-index": 10000, - "border-radius": "10px", - "text-margin-x": "0px", - }, - }, - { - selector: `node[type="ACTION"]`, - css: { - shape: "roundrectangle", - "background-color": "#213243", - "border-color": "#81c784", - "background-width": "100%", - "background-height": "100%", - "border-radius": "5px", - "z-index": 5001, - }, - }, - { - selector: `node[type="COMMENT"]`, - css: { - label: function (element) { - return element.data("label") +export default function defaultCytoscapeStyle(theme) { + return [ + { + selector: "node", + css: { + label: function(element) { + var elementname = element.data("label") + if (elementname === null || elementname === undefined) { + return "" + } + + elementname = elementname.replaceAll("_", " ", -1) + elementname = elementname.charAt(0).toUpperCase() + elementname.slice(1) + return elementname + }, + "text-valign": "center", + "text-margin-x": function(element) { + // Attempt at bottom-positioning + // Required text-valign: bottom + // FIXME: Disabled for now. + return "15px" + + + + const name = element.data("label") + console.log("Name: ", name) + if (name === null || name === undefined || name == "" || document=== undefined || document === null) { + return "0px" + } + + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d') + + context.font = '18px Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif' + + const textWidth = context.measureText(name).width + return textWidth + "px" + //return -1*(textWidth) + "px" + }, + + "font-family": "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif", + "font-weight": "lighter", + "font-size": "18px", + "margin-right": "10px", + width: "80px", + height: "80px", + color: theme.palette.text.primary || "white", + padding: "10px", + margin: "5px", + "border-width": "1px", + "z-index": 5001, }, - shape: "roundrectangle", - color: "data(color)", - width: "data(width)", - height: "data(height)", - padding: "5px", - margin: "0px", - "background-color": "data(backgroundcolor)", - "background-image": "data(backgroundimage)", - "border-color": "#ffffff", - "text-margin-x": "data(textMarginX)", - "text-margin-y": "data(textMarginY)", - "z-index": 4999, - "border-radius": "5px", - "background-opacity": "0.5", - "text-wrap": "wrap", - "text-max-width": "data(width)", - "text-halign": function(element) { - const align = element?.data("textHalign") - if (align === null || align === undefined || align === "") { - return "center" - } - - return align - }, - "text-valign": function(element) { - const align = element?.data("textValign") - if (align === null || align === undefined || align === "") { - return "center" - } - - return align - } }, - }, - { - selector: `node[type="RESIZE-HANDLE"]`, - css: { - shape: "ellipse", - width: "8px", - height: "8px", - "border-width": 1, - "border-color": "white", - "z-index": 5002, - "overlay-opacity": 0, - "cursor": "nwse-resize", - "opacity": 0, - "pointer-events": "auto", + { + selector: "edge", + css: { + "target-arrow-shape": "triangle", + "target-arrow-color": "grey", + "curve-style": "unbundled-bezier", + label: "data(label)", + "text-margin-y": "-15px", + width: "2px", + color: theme.palette.text.primary || "white", + "line-fill": "linear-gradient", + "line-gradient-stop-positions": ["0.0", "100"], + "line-gradient-stop-colors": ["grey", "grey"], + "z-index": 5001, + }, }, - }, - { - selector: `node[app_name="Integration Framework"]`, - css: { - width: "60px", - height: "60px", - "z-index": 5000, - - //'border-width': 3, - //'border-color': 'transparent', - //'border-style': 'solid', - //'border-gradient': 'linear-gradient(to right, #FF0000, #FF7F00, #FFFF00, #00FF00, #0000FF, #4B0082, #8A2BE2)' - }, - }, - { - selector: `node[example="noapp"]`, - css: { - // Make background image padding on the left side 20px - "background-width": "100%", - "background-height": "100%", - - "background-color": "data(iconBackground)", - "background-fill": "data(fillstyle)", - "background-gradient-direction": "to-bottom-right", - "background-gradient-stop-colors": "data(fillGradient)", - // Change transparency of background - "background-opacity": "0.3", - }, - }, - { - selector: `node[app_name="Shuffle Tools"], node[app_name="email"], node[app_name="http"]`, - css: { - width: "35px", - height: "35px", - "z-index": 5000, - "font-size": "0px", - "background-width": "75%", - "background-height": "75%", - "background-color": "data(iconBackground)", - "background-fill": "data(fillstyle)", - "background-gradient-direction": "to-right", - "background-gradient-stop-colors": "data(fillGradient)", - }, - }, - { - selector: `node[app_id="shuffle_agent"]`, - css: { - "height": "74px", - "width": "222px", - "background-image": "data(large_image)", - "label": function(element) { - var elementname = element.data("label") - if (elementname === null || elementname === undefined) { - return "" - } - - if (elementname.length > 15) { - elementname = elementname.substring(0, 15) + ".." - } - - return elementname - }, - "background-width": "65px", - "background-height": "65px", - "background-position-x": "20px", - //"background-position-x": "center", // Crashes - "background-repeat": "no-repeat", - - "font-size": "14px", - "text-halign": "center", - "text-valign": "center", - "text-margin-x": "-140px", - "text-margin-y": "0px", - - }, - }, - { - selector: `node[app_name="Testing"]`, - css: { - width: "30px", - height: "30px", - "z-index": 5000, - "font-size": "0px", - }, - }, - { - selector: `node[?small_image]`, - css: { - "background-image": "data(small_image)", - "text-halign": "right", - }, - }, - { - selector: `node[?large_image]`, - css: { - "background-image": "data(large_image)", - "text-halign": "right", - }, - }, - { - selector: `node[type="CONDITION"]`, - css: { - shape: "diamond", - "border-color": "##FFEB3B", - padding: "30px", - }, - }, - { - selector: `node[type="eventAction"]`, - css: { - "background-color": "#edbd21", - }, - }, - { - selector: `node[type="TRIGGER"]`, - css: { - shape: "round-octagon", - "border-radius": "5px", - "border-color": "orange", - "background-color": "#213243", - "background-width": "100px", - "background-height": "100px", - }, - }, - { - selector: `node[status="running"]`, - css: { - "border-color": "#81c784", - }, - }, - { - selector: `node[status="stopped"]`, - css: { - "border-color": "orange", - }, - }, - { - selector: 'node[type="mq"]', - css: { - "background-color": "#edbd21", - }, - }, - { - selector: "node[?isButton]", - css: { - shape: "ellipse", - width: "15px", - height: "15px", - "z-index": "5002", - "font-size": "0px", - border: "1px solid rgba(255,255,255,0.9)", - "background-image": "data(icon)", - "background-color": "data(iconBackground)", - }, - }, - { - selector: "node[?isSuggestion]", - css: { - shape: "roundrectangle", - width: "30px", - height: "30px", - "z-index": "5002", - filter: "grayscale(100%)", - border: "1px solid rgba(255,255,255,0.9)", - "background-image": "data(large_image)", - "background-fit": "cover", - "font-size": "20px", - label: "data(label_replaced)", - }, - }, - { - selector: "node[?canConnect]", - css: { - "border-color": "#f86a3e", - "border-width": "10px", - "z-index": "5002", - "background-color": "#f86a3e", - }, - }, - { - selector: "node[?isDescriptor]", - css: { - shape: "ellipse", - "border-color": "#80deea", - width: "5px", - height: "5px", - "z-index": "5002", - "font-size": "10px", - "text-valign": "center", - "text-halign": "center", - border: "1px solid black", - "margin-right": "0px", + { + selector: `node[buttonType="ACTIONSUGGESTION"]`, + css: { + label: "data(label)", + shape: "roundrectangle", + "height": "18px", + "width": "145px", + "background-color": "#212121", + "border-color": "#81c784", + "z-index": 10000, + "border-radius": "10px", "text-margin-x": "0px", - "background-color": "data(imageColor)", - "background-image": "data(image)", - label: "data(label)", + }, }, - }, - { - selector: "node[?isStartNode]", - css: { - shape: function(element) { - return "ellipse" - }, - "border-color": "#80deea", - width: "80px", - height: "80px", - "font-size": "18px", - "background-width": "100%", - "background-height": "100%", + { + selector: `node[type="ACTION"]`, + css: { + shape: "roundrectangle", + "background-color": "#213243", + "border-color": "#81c784", + "background-width": "100%", + "background-height": "100%", + "border-radius": "5px", + "z-index": 5001, + }, }, - }, - { - selector: "node[!is_valid]", - css: { - "border-color": "#f53434", - "border-width": "5px", + { + selector: `node[type="COMMENT"]`, + css: { + label: function (element) { + return element.data("label") + }, + shape: "roundrectangle", + color: "data(color)", + width: "data(width)", + height: "data(height)", + padding: "5px", + margin: "0px", + "background-color": "data(backgroundcolor)", + "background-image": "data(backgroundimage)", + "border-color": "#ffffff", + "text-margin-x": "data(textMarginX)", + "text-margin-y": "data(textMarginY)", + "z-index": 4999, + "border-radius": "5px", + "background-opacity": "0.5", + "text-wrap": "wrap", + "text-max-width": "data(width)", + "text-halign": function(element) { + const align = element?.data("textHalign") + if (align === null || align === undefined || align === "") { + return "center" + } + + return align + }, + "text-valign": function(element) { + const align = element?.data("textValign") + if (align === null || align === undefined || align === "") { + return "center" + } + + return align + } + }, }, - }, - { - selector: ":selected", - css: { - "background-color": "#77b0d0", - "border-color": "#77b0d0", - "border-width": "20px", + { + selector: `node[type="RESIZE-HANDLE"]`, + css: { + shape: "ellipse", + width: "8px", + height: "8px", + "border-width": 1, + "border-color": theme.palette.text.primary || "white", + "z-index": 5002, + "overlay-opacity": 0, + "cursor": "nwse-resize", + "opacity": 0, + "pointer-events": "auto", + }, }, - }, - { - selector: ".skipped-highlight", - css: { - "background-color": "grey", - "border-color": "grey", - "border-width": "8px", - "transition-property": "background-color", - "transition-duration": "0.5s", + { + selector: `node[app_name="Integration Framework"]`, + css: { + width: "60px", + height: "60px", + "z-index": 5000, + + //'border-width': 3, + //'border-color': 'transparent', + //'border-style': 'solid', + //'border-gradient': 'linear-gradient(to right, #FF0000, #FF7F00, #FFFF00, #00FF00, #0000FF, #4B0082, #8A2BE2)' + }, }, - }, - { - selector: ".success-highlight", - css: { - "background-color": "#41dcab", - "border-color": "#41dcab", - "border-width": "5px", - "transition-property": "background-color", - "transition-duration": "0.5s", + { + selector: `node[example="noapp"]`, + css: { + // Make background image padding on the left side 20px + "background-width": "100%", + "background-height": "100%", + + "background-color": "data(iconBackground)", + "background-fill": "data(fillstyle)", + "background-gradient-direction": "to-bottom-right", + "background-gradient-stop-colors": "data(fillGradient)", + // Change transparency of background + "background-opacity": "0.3", + }, }, - }, - { - selector: ".hover-highlight", - css: { - "background-color": "#5f9265", - "border-color": "#5f9265", - "border-width": "5px", - "transition-property": "background-color", - "transition-duration": "0.5s", + { + selector: `node[app_name="Shuffle Tools"], node[app_name="email"], node[app_name="http"]`, + css: { + width: "35px", + height: "35px", + "z-index": 5000, + "font-size": "0px", + "background-width": "75%", + "background-height": "75%", + "background-color": "data(iconBackground)", + "background-fill": "data(fillstyle)", + "background-gradient-direction": "to-right", + "background-gradient-stop-colors": "data(fillGradient)", + }, }, - }, - { - selector: ".failure-highlight", - css: { - "background-color": "#8e3530", - "border-color": "#8e3530", - "border-width": "5px", - "transition-property": "background-color", - "transition-duration": "0.5s", + { + selector: `node[app_name="Testing"]`, + css: { + width: "30px", + height: "30px", + "z-index": 5000, + "font-size": "0px", + }, }, - }, - { - selector: ".not-executing-highlight", - css: { - "background-color": "grey", - "border-color": "grey", - "border-width": "5px", - "transition-property": "#ffef47", - "transition-duration": "0.25s", + { + selector: `node[?small_image]`, + css: { + "background-image": "data(small_image)", + "text-halign": "right", + }, }, - }, - { - selector: ".executing-highlight", - css: { - "background-color": "#ffef47", - "border-color": "#ffef47", - "border-width": "8px", - "transition-property": "border-width", - "transition-duration": "0.25s", + { + selector: `node[?large_image]`, + css: { + "background-image": "data(large_image)", + "text-halign": "right", + }, }, - }, - { - selector: ".awaiting-data-highlight", - css: { - "background-color": "#f4ad42", - "border-color": "#f4ad42", - "border-width": "5px", - "transition-property": "border-color", - "transition-duration": "0.5s", + { + selector: `node[type="CONDITION"]`, + css: { + shape: "diamond", + "border-color": "##FFEB3B", + padding: "30px", + }, }, - }, - { - selector: ".shuffle-hover-highlight", - css: { - "background-color": "#f85a3e", - "border-color": "#f85a3e", - "border-width": "7px", - "transition-property": "border-width", - "transition-duration": "0.25s", + { + selector: `node[type="eventAction"]`, + css: { + "background-color": "#edbd21", + }, + }, + { + selector: `node[type="TRIGGER"]`, + css: { + shape: "round-octagon", + "border-radius": "5px", + "border-color": "orange", + "background-color": "#213243", + "background-width": "100px", + "background-height": "100px", + }, + }, + { + selector: `node[status="running"]`, + css: { + "border-color": "#81c784", + }, + }, + { + selector: `node[status="stopped"]`, + css: { + "border-color": "orange", + }, + }, + { + selector: 'node[type="mq"]', + css: { + "background-color": "#edbd21", + }, + }, + { + selector: "node[?isButton]", + css: { + shape: "ellipse", + width: "15px", + height: "15px", + "z-index": "5002", + "font-size": "0px", + border: "1px solid rgba(255,255,255,0.9)", + "background-image": "data(icon)", + "background-color": "data(iconBackground)", + }, + }, + { + selector: "node[?isSuggestion]", + css: { + shape: "roundrectangle", + width: "30px", + height: "30px", + "z-index": "5002", + filter: "grayscale(100%)", + border: "1px solid rgba(255,255,255,0.9)", + "background-image": "data(large_image)", + "background-fit": "cover", + "font-size": "20px", + label: "data(label_replaced)", + }, + }, + { + selector: "node[?canConnect]", + css: { + "border-color": "#f86a3e", + "border-width": "10px", + "z-index": "5002", + "background-color": "#f86a3e", + }, + }, + { + selector: "node[?isDescriptor]", + css: { + shape: "ellipse", + "border-color": "#80deea", + width: "5px", + height: "5px", + "z-index": "5002", + "font-size": "10px", + "text-valign": "center", + "text-halign": "center", + border: "1px solid black", + "margin-right": "0px", + "text-margin-x": "0px", + "background-color": "data(imageColor)", + "background-image": "data(image)", label: "data(label)", - "font-size": "18px", - color: "white", + }, }, - }, - { - selector: "$node > node", - css: { - "padding-top": "10px", - "padding-left": "10px", - "padding-bottom": "10px", - "padding-right": "10px", + { + selector: "node[?isStartNode]", + css: { + shape: function(element) { + return "ellipse" + }, + "border-color": "#80deea", + width: "80px", + height: "80px", + "font-size": "18px", + "background-width": "100%", + "background-height": "100%", + }, }, - }, - { - selector: "edge.executing-highlight", - css: { - width: "5px", - "target-arrow-color": "#ffef47", - "line-color": "#ffef47", - "transition-property": "line-color, width", - "transition-duration": "0.25s", + { + selector: `node[app_id="shuffle_agent"]`, + css: { + "shape": function(element) { + return "roundrectangle" + }, + "height": "74px", + "width": "222px", + "background-image": "data(large_image)", + "label": function(element) { + var elementname = element.data("label") + if (elementname === null || elementname === undefined) { + return "" + } + + elementname = elementname.replaceAll("_", " ", -1) + + if (elementname.length > 15) { + elementname = elementname.substring(0, 15) + ".." + } + + return elementname + }, + "background-width": "65px", + "background-height": "65px", + "background-position-x": "20px", + "background-repeat": "no-repeat", + "background-opacity": "0.5", + + "font-size": "14px", + "text-halign": "center", + "text-valign": "center", + "text-margin-x": "20px", + "text-margin-y": "0px", + + }, }, - }, - { - selector: `edge[?decorator]`, - css: { - width: "1px", - "line-style": "dashed", - "line-fill": "linear-gradient", - "target-arrow-color": "#555555", - "line-gradient-stop-positions": ["0.0", "100"], - "line-gradient-stop-colors": ["#555555", "#555555"], + { + selector: "node[!is_valid]", + css: { + "border-color": "#f53434", + "border-width": "5px", + }, }, - }, - { - selector: "edge.success-highlight", - css: { - width: "3px", - "target-arrow-color": "#41dcab", - "line-color": "#41dcab", - "transition-property": "line-color, width", - "transition-duration": "0.5s", - "line-fill": "linear-gradient", - "line-gradient-stop-positions": ["0.0", "100"], - "line-gradient-stop-colors": ["#41dcab", "#41dcab"], + { + selector: ":selected", + css: { + "background-color": "#77b0d0", + "border-color": "#77b0d0", + "border-width": "20px", + }, }, - }, - { - selector: ".eh-handle", - style: { - "background-color": "#337ab7", - width: "1px", - height: "1px", - shape: "circle", - "border-width": "1px", - "border-color": "black", + { + selector: ".skipped-highlight", + css: { + "background-color": "grey", + "border-color": "grey", + "border-width": "8px", + "transition-property": "background-color", + "transition-duration": "0.5s", + }, }, - }, - { - selector: ".eh-source", - style: { - "border-width": "3", - "border-color": "#337ab7", + { + selector: ".success-highlight", + css: { + "background-color": "#41dcab", + "border-color": "#41dcab", + "border-width": "5px", + "transition-property": "background-color", + "transition-duration": "0.5s", + }, }, - }, - { - selector: ".eh-target", - style: { - "border-width": "3", - "border-color": "#337ab7", + { + selector: ".hover-highlight", + css: { + "background-color": "#5f9265", + "border-color": "#5f9265", + "border-width": "5px", + "transition-property": "background-color", + "transition-duration": "0.5s", + }, }, - }, - { - selector: ".eh-preview, .eh-ghost-edge", - style: { - "background-color": "#337ab7", - "line-color": "#337ab7", - "target-arrow-color": "#337ab7", - "source-arrow-color": "#337ab7", + { + selector: ".failure-highlight", + css: { + "background-color": "#8e3530", + "border-color": "#8e3530", + "border-width": "5px", + "transition-property": "background-color", + "transition-duration": "0.5s", + }, }, - }, - { - selector: "edge:selected", - css: { - "target-arrow-color": "#f85a3e", + { + selector: ".not-executing-highlight", + css: { + "background-color": "grey", + "border-color": "grey", + "border-width": "5px", + "transition-property": "#ffef47", + "transition-duration": "0.25s", + }, }, - }, - { - selector: `edge[?source_workflow]`, - css: { - "background-opacity": "1", - "font-size": "0px", + { + selector: ".executing-highlight", + css: { + //"background-color": "#ffef47", + "border-color": "#ffef47", + "border-width": "8px", + "transition-property": "border-width", + "transition-duration": "0.25s", + }, }, - }, - { - selector: `node[?source_workflow]`, - css: { - "background-opacity": "0", - "font-size": "0px", + { + selector: ".awaiting-data-highlight", + css: { + "background-color": "#f4ad42", + "border-color": "#f4ad42", + "border-width": "5px", + "transition-property": "border-color", + "transition-duration": "0.5s", + }, }, - }, - { - selector: "node:selected", - css: { - "border-color": "#f86a3e", - "border-width": "7px", - }, - }, - { - selector: `node[buttonType="condition-drag"]`, - css: { - "width": "5px", - "height": "5px", - "background-color": "#f85a3e", + { + selector: ".shuffle-hover-highlight", + css: { + "background-color": "#f85a3e", + "border-color": "#f85a3e", + "border-width": "7px", + "transition-property": "border-width", + "transition-duration": "0.25s", + label: "data(label)", + "font-size": "18px", + color: theme.palette.text.primary || "white", + }, }, - }, - { - selector: `node[name="switch"]`, - css: { - label: function(element) { - // Load from the actual element - var nodeheight = 400 - var conditions = [{ - "name": "Condition 1", - "check": "X equals Y", - }, - { - "name": "Condition 2", - "check": "X2 equals Y2", - }, - { - "name": "Condition 3", - "check": "X3 equals Y3", - }] - - conditions.push({ - "name": "Else", - "check": "If all else fails", - }) - - const newlines = nodeheight / conditions.length - console.log("Newlines: ", newlines) - - const label = conditions.map((condition) => { - return `${condition.name}\n\n\n` - }).join("\n") - - return label - }, - color: "white", - "border-color": "#f85a3e", - "background-color": "#1f1f1f", - "font-size": "19px", - "text-margin-x": "-110px", - "text-wrap": "wrap", - shape: "roundrectangle", - width: "100", - height: "300", - + { + selector: "$node > node", + css: { + "padding-top": "10px", + "padding-left": "10px", + "padding-bottom": "10px", + "padding-right": "10px", + }, }, - }, -]; + { + selector: "edge.executing-highlight", + css: { + width: "5px", + "target-arrow-color": "#ffef47", + "line-color": "#ffef47", + "transition-property": "line-color, width", + "transition-duration": "0.25s", + }, + }, + { + selector: `edge[?decorator]`, + css: { + width: "1px", + "line-style": "dashed", + "line-fill": "linear-gradient", + "target-arrow-color": "#555555", + "line-gradient-stop-positions": ["0.0", "100"], + "line-gradient-stop-colors": ["#555555", "#555555"], + }, + }, + { + selector: "edge.success-highlight", + css: { + width: "3px", + "target-arrow-color": "#41dcab", + "line-color": "#41dcab", + "transition-property": "line-color, width", + "transition-duration": "0.5s", + "line-fill": "linear-gradient", + "line-gradient-stop-positions": ["0.0", "100"], + "line-gradient-stop-colors": ["#41dcab", "#41dcab"], + }, + }, + { + selector: ".eh-handle", + style: { + "background-color": "#337ab7", + width: "1px", + height: "1px", + shape: "circle", + "border-width": "1px", + "border-color": "black", + }, + }, + { + selector: ".eh-source", + style: { + "border-width": "3", + "border-color": "#337ab7", + }, + }, + { + selector: ".eh-target", + style: { + "border-width": "3", + "border-color": "#337ab7", + }, + }, + { + selector: ".eh-preview, .eh-ghost-edge", + style: { + "background-color": "#337ab7", + "line-color": "#337ab7", + "target-arrow-color": "#337ab7", + "source-arrow-color": "#337ab7", + }, + }, + { + selector: "edge:selected", + css: { + "target-arrow-color": "#f85a3e", + }, + }, + { + selector: `edge[?source_workflow]`, + css: { + "background-opacity": "1", + "font-size": "0px", + }, + }, + { + selector: `node[?source_workflow]`, + css: { + "background-opacity": "0", + "font-size": "0px", + }, + }, + { + selector: "node:selected", + css: { + "border-color": "#f86a3e", + "border-width": "7px", + }, + }, + { + selector: `node[buttonType="condition-drag"]`, + css: { + "width": "5px", + "height": "5px", + "background-color": "#f85a3e", + }, + }, + { + selector: `node[name="switch"]`, + css: { + label: function(element) { + // Load from the actual element + var nodeheight = 400 + var conditions = [{ + "name": "Condition 1", + "check": "X equals Y", + }, + { + "name": "Condition 2", + "check": "X2 equals Y2", + }, + { + "name": "Condition 3", + "check": "X3 equals Y3", + }] + + conditions.push({ + "name": "Else", + "check": "If all else fails", + }) + + const newlines = nodeheight / conditions.length + console.log("Newlines: ", newlines) + + const label = conditions.map((condition) => { + return `${condition.name}\n\n\n` + }).join("\n") + + return label + }, + color: theme.palette.text.primary || "white", + "border-color": "#f85a3e", + "background-color": "#1f1f1f", + "font-size": "19px", + "text-margin-x": "-110px", + "text-wrap": "wrap", + shape: "roundrectangle", + width: "100", + height: "300", + + }, + }, + ]; +} //{ // selector: 'edge[?hasErrors]', @@ -626,5 +633,3 @@ const data = [ // "line-gradient-stop-colors": ["#991818", "#991818"], // }, //}, - -export default data; diff --git a/frontend/src/index.js b/frontend/src/index.js index d005b55a..20b3ae48 100755 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,6 +1,7 @@ import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; +import { AppContext } from "./context/ContextApi"; //import "./index.css"; //import reportWebVitals from "./reportWebVitals"; @@ -9,7 +10,9 @@ const rootElement = document.getElementById("root"); const root = createRoot(rootElement); root.render( - + + + ); diff --git a/frontend/src/theme.jsx b/frontend/src/theme.jsx index 1621dc42..4835e4f1 100644 --- a/frontend/src/theme.jsx +++ b/frontend/src/theme.jsx @@ -1,4 +1,3 @@ -import React from "react"; import { createTheme, adaptV4Theme } from "@mui/material/styles"; const theme = createTheme(adaptV4Theme({ @@ -153,3 +152,302 @@ const theme = createTheme(adaptV4Theme({ })); export default theme; + +export const getTheme = (themeMode, brandColor) => + createTheme({ + palette: { + mode: themeMode, + main: brandColor || "#FF8544", + primary: { + main: brandColor || "#FF8544", + contrastText: "#ffffff", + }, + secondary: { + main: "rgba(255,255,255,0.7)", + contrastText:"#000000", + }, + text: { + primary: themeMode === "dark" ? "#ffffff" : "#1A1A1A", + secondary: themeMode === "dark" ? "#9E9E9E" : "#616161", + }, + type: themeMode, + inputColor: themeMode === "dark" ? "rgba(39,41,45,1)" : "rgba(245, 245, 245, 1)", + textColor: themeMode === "dark" ? "#F1F1F1" : "#1A1A1A", + textPrimary: themeMode === "dark" ? "rgba(255, 255, 255, 0.8)" : "rgba(26, 26, 26, 0.8)", + surfaceColor: themeMode === "dark" ? "#27292d" : "#EFEFEF", + platformColor: themeMode === "dark" ? "#212121" : "#ffffff", + backgroundColor: themeMode === "dark" ? "#1a1a1a" : "#f1f1f1", + distributionColor: themeMode === "dark" ? "#40E0D0" : "#008080", + cardBackgroundColor: themeMode === "dark" ? "#1e1e1e" : "#eaeaea", + cardHoverColor: themeMode === "dark" ? "#323232" : "#F0F0F0", + hoverColor: themeMode === "dark" ? "#323232" : "#D6D6D6", + usecaseCardColor: themeMode === "dark" ? "#2f2f2f" : "rgba(245, 245, 245, 1)", + usecaseCardHoverColor: themeMode === "dark" ? "#2F2F2F" : "rgba(245, 245, 245, 1)", + usecaseDialogFieldColor: themeMode === "dark" ? "#2B2B2B" : "#F5F5F5", + accentColor: themeMode === "dark" ? "#ff8544" : "#ff8544", + green: themeMode === "dark" ? "#5cc879" : "#008000", + defaultBorder: themeMode === "dark" ? '1px solid #494949' : '1px solid #CCCCCC', + linkColor: brandColor === "#ff8544" ? "#f86a3e" : brandColor, + + borderRadius: 10, + loaderColor: themeMode === "dark" ? "#1a1a1a" : "#E0E0E0", + jsonIconStyle: "round", + jsonTheme: themeMode === "dark" ? "summerfruit" : { + base00: "#ffffff", // background + base01: "#f0f0f0", // very light grey + base02: "#f5f5f5", // light grey + base03: "#999999", // dim text + base04: "#444444", // bold keys + base05: "#333333", // normal text + base06: "#1a1a1a", // darker text + base07: "#000000", // black + base08: "#f14c4c", // red + base09: "#f58c1f", // orange + base0A: "#f2c032", // yellow + base0B: "#51975d", // green + base0C: "#2aa198", // teal + base0D: "#007acc", // blue (keys!) + base0E: "#c586c0", // purple + base0F: "#d16969", // brown + }, + jsonCollapseStringsAfterLength: 100, + drawer: { + backgroundColor: themeMode === "dark" ? "#262626" : "#f9f9f9" + }, + reactJsonStyle: { + padding: 5, + width: "98%", + borderRadius: 5, + border: themeMode === "dark" ? "1px solid rgba(255,255,255,0.7)" : "1px solid rgba(0,0,0,0.3)", + backgroundColor: themeMode === "dark" + ? "#1A1A1A" + : "#f1f1f1", + color: themeMode === "dark" + ? "#F1F1F1" + : "#1A1A1A", + overflowX: "auto", + }, + textFieldStyle: { + backgroundColor: themeMode === "dark" ? "#212121" : "#FFFFFF", + color: themeMode === "dark" ? "#ffffff" : "#000000", + borderRadius: "5px", + height: 40, + border: themeMode === "dark" ? "1px solid #4D4D4D" : "1px solid #E0E0E0", + }, + DialogStyle: { + backgroundColor: themeMode === "dark" ? "#212121" : "#ffffff", + borderRadius: 2, + boxShadow: themeMode === "dark" ? "0px 0px 10px 0px rgba(0,0,0,0.75)" : "0px 0px 10px 0px rgba(0,0,0,0.2)", + border: themeMode === "dark" ? "1px solid #494949" : "1px solid #cccccc", + }, + innerTextfieldStyle: { + height: 40, + fontSize: 16, + backgroundColor: themeMode === "dark" ? "#212121" : "#f5f5f5", + }, + tooltip: { + backgroundColor: themeMode === "dark" ? "#212121" : "#ffffff", + color: themeMode === "dark" ? "#ffffff" : "#000000", + border: themeMode === "dark" ? "1px solid #494949" : "1px solid #cccccc", + }, + chipStyle: { + backgroundColor: themeMode === "dark" ? "#333333" : "#F5F5F5", + borderColor: themeMode === "dark" ? "#444444" : "#E0E0E0", + color: themeMode === "dark" ? "#FFFFFF" : "#333333", + }, + defaultImage: "/images/no_image.png", + singulOrange: "/images/singul_orange.png", + singulGreen: "/images/singul_green.png", + singulBlackWhite: "/images/singul_black_white.png", + scrollbarColor: themeMode === "dark" ? "#494949 #2f2f2f": "#c1c1c1 #f1f1f1", + scrollbarColorTransparent: themeMode === "dark" ? '#494949 transparent': "#c1c1c1 transparent", + }, + typography: { + fontFamily: `"inter", "Roboto", "Helvetica", "Arial", sans-serif`, + color: themeMode === "dark" ? "#ffffff" : "#000000", + useNextVariants: true, + fontWeightLight: 300, + fontWeightRegular: 400, + fontWeightMedium: 500, + fontWeightSemiBold: 600, + fontWeightBold: 700, + allVariants: { + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A", + }, + h1: { + fontSize: 40, + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A" + }, + h2: { + fontSize: 36, + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A" + }, + h3: { + fontSize: 32, + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A" + }, + h4: { + fontSize: 30, + fontWeight: 500, + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A" + }, + h6: { + fontSize: 22, + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A" + }, + body1: { + fontSize: 16, + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A" + }, + body2: { + fontSize: 14, + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A" + }, + }, + components: { + MuiButton: { + styleOverrides: { + root: { + textTransform: 'none', + borderRadius: '4px', + }, + }, + variants: [ + { + props: { variant: 'text', color: 'primary' }, + style: { + color: themeMode === "dark" ? "#ffffff" : "#1A1A1A", + whiteSpace: "nowrap", + textWrap: "normal", + }, + }, + { + props: { variant: 'text', color: 'secondary' }, + style: { + color: themeMode === "dark" ? "#9E9E9E" : "#616161", + whiteSpace: "nowrap", + textWrap: "normal", + }, + }, + { + props: { variant: 'contained', color: 'primary' }, + style: { + backgroundColor: themeMode === "dark" ? brandColor || '#ff8544' : brandColor || '#FF7C35', + color: themeMode === "dark" ? '#1a1a1a': '#FFFFFF', + borderRadius: '4px', + whiteSpace: "nowrap", + textWrap: "normal", + '&:hover': { + fontWeight: 600, + backgroundColor: themeMode === 'dark' ? brandColor || "#ff955c" : brandColor || '#FF8D4F', + color: themeMode === "dark" ? '#1a1a1a': '#FFFFFF', + }, + }, + }, + { + props: { variant: 'contained', color: 'secondary' }, + style: { + backgroundColor: themeMode === "dark" ? '#494949' : '#C9C9C9', + color: themeMode === "dark" ? '#ffffff' : '#4C4C4C', + borderRadius: '4px', + boxShadow: 'none', + whiteSpace: "nowrap", + textWrap: "normal", + '&:hover': { + fontWeight: 600, + border: themeMode === "dark" ? '1px solid #f1f1f1' : 'none', + backgroundColor: themeMode === "dark" ? '#494949' : '#C9C9C9', + color: themeMode === "dark" ? '#ffffff' : '#4C4C4C', + }, + }, + }, + { + props: { variant: 'outlined', color: 'primary' }, + style: { + borderColor: themeMode === "dark" ? brandColor || "#ff8544" : brandColor || "#cc5f1f", + color: themeMode === "dark" ? brandColor || "#ff8544" : brandColor || "#cc5f1f", + whiteSpace: "nowrap", + fontWeight: 'normal', + textWrap: "normal", + '&:hover': { + backgroundColor: themeMode === "dark" ? brandColor || "#ff8544" : "#ffe8dc", + color: themeMode === "dark" ? "#1a1a1a" : "#8a3d00", + fontWeight: 600, + }, + }, + }, + { + props: { variant: 'outlined', color: 'secondary' }, + style: { + border: '1px solid #C5C5C5', + color: themeMode === "dark" ? '#C5C5C5' : '#2D2D2D', + whiteSpace: "nowrap", + textWrap: "normal", + '&:hover': { + backgroundColor: themeMode === "dark" ? '#C5C5C5' : '#EFEFEF', + borderColor: themeMode === "dark" ? '#C5C5C5' : '#2D2D2D', + fontWeight: 600, + color: themeMode === "dark" ? '#1a1a1a' : '#1A1A1A', + }, + }, + }, + ], + }, + MuiTab: { + styleOverrides: { + root: { + color: themeMode === "dark" ? "#C5C5C5" : "#1A1A1A", + }, + }, + }, + }, + + overrides: { + MuiMenu: { + list: { + backgroundColor: themeMode === "dark" ? "#27292d" : "#ffffff", + }, + }, + MuiCssBaseline: { + MuiCssBaseline: { + styleOverrides: ` + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'); + } + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'); + } + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 500; + src: local('Roboto Medium'), local('Roboto-Medium'); + } + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 600; + src: local('Roboto SemiBold'), local('Roboto-SemiBold'); + } + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Roboto Bold'), local('Roboto-Bold'); + } + `, + }, + }, + }, + }); diff --git a/frontend/src/views/Admin2.jsx b/frontend/src/views/Admin2.jsx index 30a356e5..f94c3441 100644 --- a/frontend/src/views/Admin2.jsx +++ b/frontend/src/views/Admin2.jsx @@ -1,6 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import AdminNavBar from '../components/AdminNavBar.jsx'; import { toast } from "react-toastify"; +import { Context } from '../context/ContextApi.jsx'; const Admin2 = (props) => { // Destructure props if needed @@ -10,13 +11,15 @@ const Admin2 = (props) => { const [selectedOrganization, setSelectedOrganization] = useState({}); const [organizationFeatures, setOrganizationFeatures] = useState({}); const [orgRequest, setOrgRequest] = React.useState(true); + const [isOrgLoaded, setIsOrgLoaded] = React.useState(false); + const {brandName} = useContext(Context) const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; if (document !== undefined) { - if (selectedOrganization?.name !== undefined) { - document.title = selectedOrganization?.name + " - Admin - Shuffle" + if (selectedOrganization?.name !== undefined) { + document.title = brandName?.length > 0 ? selectedOrganization?.name + ` - Admin - ${brandName}` : selectedOrganization?.name + ` - Admin - Shuffle`; } else { - document.title = "Admin - Shuffle" + document.title = brandName?.length > 0 ? `Admin - ${brandName}` : `Admin - Shuffle`; } } @@ -151,6 +154,8 @@ const Admin2 = (props) => { .catch((error) => { console.log("Error getting org: ", error); toast("Error getting current organization"); + }).finally(() => { + setIsOrgLoaded(true) }); }; @@ -232,7 +237,8 @@ const Admin2 = (props) => { defaults, sso_config, lead_info, - { mfa_required } = {} + { mfa_required } = {}, + editing, ) => { const data = { name: name, @@ -243,6 +249,7 @@ const Admin2 = (props) => { sso_config: sso_config, lead_info: lead_info, mfa_required: mfa_required !== undefined ? mfa_required : selectedOrganization?.mfa_required, + editing: editing?.length > 0 ? editing : "", }; const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; @@ -280,8 +287,8 @@ const Admin2 = (props) => { const handleStatusChange = (event) => { - const { value } = event.target; - setSelectedStatus(value); + const { value } = event.target + setSelectedStatus(value) handleEditOrg( selectedOrganization?.name, @@ -329,8 +336,9 @@ const Admin2 = (props) => { } return ( -
- + //
+
+
); }; diff --git a/frontend/src/views/AdminSetup.jsx b/frontend/src/views/AdminSetup.jsx index 0365cf2f..65467300 100755 --- a/frontend/src/views/AdminSetup.jsx +++ b/frontend/src/views/AdminSetup.jsx @@ -68,13 +68,19 @@ const AdminAccount = (props) => { .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]); + setLoginInfo(responseJson["reason"]) + + if (responseJson?.reason?.toLowerCase().includes("connection refused")) { + navigate("/loginsetup") + } + } else { if (responseJson.reason === "redirect") { setTimeout(() => { - window.location.pathname = "/login"; + window.location.pathname = "/login" }, 2500) } + } }) ) @@ -111,7 +117,7 @@ const AdminAccount = (props) => { if (responseJson["success"] === false) { setLoginInfo(responseJson["reason"]); } else { - setLoginInfo("Successful register :)"); + setLoginInfo("Successful register! Redirecting in a moment..."); setTimeout(() => { window.location.pathname = "/login"; diff --git a/frontend/src/views/AgentUI.jsx b/frontend/src/views/AgentUI.jsx new file mode 100644 index 00000000..ebc5a558 --- /dev/null +++ b/frontend/src/views/AgentUI.jsx @@ -0,0 +1,542 @@ +import React, { useState, useEffect, useContext, memo } from "react"; +import { Context } from "../context/ContextApi.jsx"; +import { getTheme } from "../theme.jsx"; +import { toast } from "react-toastify" +import ReactJson from "react-json-view-ssr"; +import { validateJson, collapseField, handleReactJsonClipboard, HandleJsonCopy } from "../views/Workflows.jsx"; + +import { + Button, + ButtonGroup, + Typography, + Chip, + CircularProgress, + Tooltip, + IconButton, +} from '@mui/material' + +import { + CheckCircle as CheckCircleIcon, + HourglassDisabled as HourglassDisabledIcon, + RestartAlt as RestartAltIcon, + ExpandMore as ExpandMoreIcon, + ExpandLess as ExpandLessIcon, +} from '@mui/icons-material' + +import { + green, + red, +} from '../views/AngularWorkflow.jsx' + +const AgentUI = (props) => { + const { globalUrl, isLoggedIn, isLoaded, userdata, } = props + const [buttonState, setButtonState] = useState("timeline") + const [execution, setExecution] = useState(null) + const [agentActionResult, setAgentActionResult] = useState(null) + const [data, setData] = useState({}) + const [openIndexes, setOpenIndexes] = useState([]) + const [disableButtons, setDisableButtons] = useState(false) + + const [originalStartTime, setOriginalStartTime] = useState(0) + const [latestEndTime, setLatestEndTime] = useState(0) + + const {themeMode} = useContext(Context) + const theme = getTheme(themeMode) + + const agentWrapperStyle = { + width: 1000, + height: 1000, + margin: "auto", + paddingTop: 100, + } + + if (data.input === undefined || data.input === null) { + data.input = "" + } else { + const verifiedInput = validateJson(data.input) + if (verifiedInput.valid === true) { + data.input = JSON.stringify(verifiedInput.result, null, 2) + } + } + + const findNodeData = (execution_data, node_id) => { + if (execution_data === undefined || execution_data === null) { + return + } + + var found = false + for (var key in execution_data.results) { + const item = execution_data.results[key] + if (item?.action?.id !== node_id) { + continue + } + + setAgentActionResult(item) + + found = true + const validate = validateJson(item.result) + if (validate.valid) { + setData(validate.result) + } else { + toast.warn("Action output result is not valid JSON!") + } + + break + } + + if (found === false) { + toast.warn("Failed to find the relevant AI Agent result") + } + } + + const GetExecution = (execution_id, node_id, authorization) => { + if (execution_id === undefined || execution_id === null) { + toast.error("No execution ID provided. Please provide execution_id in the URL.") + return + } + + if (node_id === undefined || node_id === null) { + toast.error("No node ID provided. Please provide node_id in the URL.") + return + } + + if (authorization === undefined || authorization === null) { + toast.error("No authorization provided. Please provide authorization in the URL.") + return + } + + const headers = {} + const executionRequest = { + "execution_id": execution_id, + "authorization": authorization, + } + + const url = `${globalUrl}/api/v1/streams/results` + fetch(url, { + method: "POST", + headers: headers, + body: JSON.stringify(executionRequest), + credentials: "include", + cors: "no-cors", + }) + .then((response) => { + return response.json() + }) + .then((responseJson) => { + if (responseJson.success !== false) { + if (responseJson.status === "EXECUTING") { + // Recursively looking for updates until it's not executing anymore + setTimeout(() => { + GetExecution(execution_id, node_id, authorization) + }, 3000) + } else { + setDisableButtons(false) + setDisableButtons(false) + } + + setExecution(responseJson) + findNodeData(responseJson, node_id) + } else { + setDisableButtons(false) + if (responseJson.reason === undefined || responseJson.reason === null) { + toast.error("Failed to load the agent data. Please try again and contact support@shuffler.io if this persists") + } else { + toast.error("Error: " + responseJson.reason) + } + } + }) + .catch((error) => { + setDisableButtons(false) + toast.error("Error: " + error) + }) + } + + const RerunDecision = (decision) => { + if (execution.execution_id === undefined || execution.execution_id === null) { + toast.error("No workflow run loaded. Please try again and contact support@shuffler.io if this persists.") + return + } + + if (agentActionResult === undefined || agentActionResult === null) { + toast.error("Failed to find the relevant agent action. Please try again, and contact support@shuffler.io if it persists.") + return + } + + console.log("DECISION: ", decision) + + const url = `${globalUrl}/api/v1/apps/agent/run?rerun=true&decision_id=${decision?.run_details?.id}` + var body = agentActionResult.action + body.source_execution = execution.execution_id + body.source_workflow = execution.workflow.id + + fetch(url, { + method: "POST", + body: JSON.stringify(body), + credentials: "include", + cors: "no-cors", + }) + .then((response) => { + return response.json() + }) + .then((responseJson) => { + console.log("RESP: ", responseJson) + if (responseJson.success !== false) { + } else { + if (responseJson.reason === undefined || responseJson.reason === null) { + toast.warn("Failed to restart the agent decision. Please try again and contact support@shuffler.io if this persists") + } else { + toast.warn(responseJson.reason) + + } + } + + GetExecution(execution.execution_id, agentActionResult.action.id, execution.authorization) + }) + .catch((error) => { + toast.error("Error: " + error) + }) + } + + useEffect(() => { + const params = new URLSearchParams(window.location.search) + const executionId = params.get("execution_id") + const nodeId = params.get("node_id") + const authorization = params.get("authorization") + if (executionId !== undefined && executionId !== null && nodeId !== undefined && nodeId !== null && authorization !== undefined && authorization !== null) { + GetExecution(executionId, nodeId, authorization) + } else { + toast.warn("No execution ID or node ID provided. Please provide execution_id and node_id in the URL.") + } + }, []) + + const maxTimelineWidth = 150 + const TimelineItem = (props) => { + const { item, index } = props; + const [hovered, setHovered] = useState(false); + + const parsedStatus = item.status === "RUNNING" || item.status === "WAITING" ? + + : + item.status === "FINISHED" ? + + + + : + + + + + const categoryStyle = { + width: 20, + height: 20, + marginRight: 10, + } + + const parsedCategory = item.category === "singul" ? + + + + : item.category === "ask" ? + + + + : +
+ + const validate = validateJson(item.details) + const itemStartTime = item.start_time + var itemEndTime = item.end_time + if (itemStartTime !== undefined && (itemStartTime < originalStartTime || originalStartTime === 0)) { + setOriginalStartTime(itemStartTime) + } + + if (itemEndTime !== undefined && itemEndTime > latestEndTime) { + setLatestEndTime(itemEndTime) + } + + if (itemEndTime === undefined || itemEndTime === null) { + // Set it to now + itemEndTime = latestEndTime + } + + const totalDuration = latestEndTime - originalStartTime + const currentDuration = itemStartTime - itemEndTime + var timelineMarginLeft = ((itemStartTime - originalStartTime) / totalDuration) * maxTimelineWidth + var timelineWidth = ((itemEndTime - itemStartTime) / totalDuration) * maxTimelineWidth + + if (totalDuration === currentDuration) { + timelineMarginLeft = 0 + timelineWidth = maxTimelineWidth + } + + const defaultTopPadding = 10 + const open = openIndexes.includes(index) + + return ( +
+
setHovered(true)} + onMouseLeave={() => setHovered(false)} + onClick={(e) => { + if (item.details === undefined || item.details === null || item.details === "") { + toast("No details to open") + return + } + + if (openIndexes.includes(index)) { + setOpenIndexes(openIndexes.filter((i) => i !== index)) + } else { + setOpenIndexes([...openIndexes, index]) + } + }} + > +
+ {parsedStatus} +
+
+ {parsedCategory} +
+
+ {/* To ISO string from unix time */} + {new Date(item.start_time * 1000).toLocaleString()} +
+
+ +
+
+ {item.label} +
+ + +
+ {currentDuration !== 0 && !isNaN(timelineMarginLeft) && !isNaN(timelineWidth) && timelineWidth > 0 ? +
+ : null} +
+ + +
+ + + { + e.preventDefault() + e.stopPropagation() + + toast.info("Attempting to rerun this decision by itself.") + setDisableButtons(true) + RerunDecision(item.details) + }} + > + + + + + + + + {open ? + + : + + } + + + + +
+
+ + {open ? +
+ {validate.valid === true ? + { + return collapseField(jsonField) + }} + iconStyle={theme.palette.jsonIconStyle} + collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength} + displayArrayKey={false} + enableClipboard={(copy) => { + handleReactJsonClipboard(copy); + }} + displayDataTypes={false} + onSelect={(select) => { + HandleJsonCopy(validate.result, select, "exec") + }} + name={false} + /> + : + + {item.details} + + } +
+ : null } +
+ ) + } + + const TimelineRender = (props) => { + const { agent_data } = props; + var timelineItems = [ + { + "label": "AI Agent 2", + "type": "agent", + "category": "agent", + + "status": agent_data.status, + "start_time": agent_data.started_at, + "end_time": agent_data.completed_at, + }, + ] + + var sortedTimelineItems = [] + for (var key in agent_data?.decisions) { + const item = agent_data.decisions[key] + + if (item.run_details.started_at === undefined || item.run_details.started_at === null) { + item.run_details.started_at = originalStartTime + } + + if (item.run_details.completed_at === undefined || item.run_details.completed_at === null) { + item.run_details.completed_at = Math.floor(Date.now() / 1000) + } + + var newTimelineItem = { + "label": item.action, + "type": "decision", + "category": item.category, + + "status": item.run_details.status, + "start_time": item.run_details.started_at, + "end_time": item.run_details.completed_at, + } + + newTimelineItem.details = item + timelineItems.push(newTimelineItem) + } + + timelineItems.sort((a, b) => { + if (a.start_time < b.start_time) { + return 1; + } + + return 0; + }) + + return ( +
+ {/* +
+ {agent_data?.status === "RUNNING" || agent_data?.status === "WAITING" ? + + : + agent_data?.status === "FINISHED" ? + + : + + } + + + {agent_data?.status} + +
+ */} + +
+ {timelineItems?.map((item, index) => { + return ( + + ) + })} + +
+ ) + } + + return ( +
+ {/* + + Agent Input: {data.input} + + */} + + + + + + + {buttonState === "timeline" ? + + : + null + } +
+ ) +} + +export default AgentUI diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index d1bd8c51..0466fb85 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2,10 +2,11 @@ import React, { useState, useEffect, useLayoutEffect, memo, useMemo, useRef, useContext } from "react"; import ReactDOM from "react-dom" -import theme from "../theme.jsx"; +import { getTheme } from "../theme.jsx"; import { useInterval } from "react-powerhooks"; import { makeStyles, } from "@mui/styles"; +import YAML from "yaml"; import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx" import { v4 as uuidv4, v5 as uuidv5, validate as isUUID, } from "uuid"; import { useNavigate, Link, useParams } from "react-router-dom"; @@ -132,6 +133,7 @@ import { ArrowForward as ArrowForwardIcon, OpenInFull as OpenInFullIcon, Difference as DifferenceIcon, + DataObject as DataObjectIcon, } from "@mui/icons-material"; import SwapHorizIcon from '@mui/icons-material/SwapHoriz'; //import * as cytoscape from "cytoscape"; @@ -143,11 +145,11 @@ import edgehandles from "cytoscape-edgehandles"; import CytoscapeComponent from "react-cytoscapejs"; import Draggable from "react-draggable"; -import cytoscapestyle from "../defaultCytoscapeStyle.jsx"; +import defaultCytoscapeStyle from "../defaultCytoscapeStyle.jsx"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx" -import { validateJson, collapseField, GetIconInfo } from "../views/Workflows.jsx"; +import { validateJson, collapseField, GetIconInfo, handleReactJsonClipboard, HandleJsonCopy, } from "../views/Workflows.jsx"; import { GetParsedPaths, internalIds, } from "../views/Apps.jsx"; import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; @@ -164,6 +166,33 @@ export const triggers = [ { name: "Webhook", type: "TRIGGER", + parameters : [ + { + "name": "url", + "example": "", + "value": "" + }, + { + "name": "tmp", + "example": "", + "value": "", + }, + { + "name": "auth_headers", + "example": "", + "value": "", + }, + { + "name": "custom_response_body", + "example": "", + "value": "", + }, + { + "name": "await_response", + "example": "", + "value": "v1", + }, + ], status: "uninitialized", trigger_type: "WEBHOOK", errors: null, @@ -179,6 +208,18 @@ export const triggers = [ { name: "Schedule", type: "TRIGGER", + parameters : [ + { + "name": "cron", + "example": "", + "value": "*/25 * * * *" + }, + { + "name": "execution_argument", + "example": "", + "value": "", + }, + ], status: "uninitialized", trigger_type: "SCHEDULE", errors: null, @@ -223,6 +264,38 @@ export const triggers = [ { name: "User Input", type: "TRIGGER", + parameters: [ + { + "name": "alertinfo", + "example": "", + "value": "Do you want to continue the workflow? Start parameters: $exec", + }, + { + "name": "options", + "example": "", + "value": "boolean", + }, + { + "name": "type", + "example": "", + "value": "subflow", + }, + { + "name": "email", + "example": "", + "value": "test@test.com", + }, + { + "name": "sms", + "example": "", + "value": "0000000", + }, + { + "name": "subflow", + "example": "", + "value": "", + } + ], status: "running", large_image: "/images/workflows/UserInput2.svg", description: "Wait for user input trigger", @@ -324,6 +397,7 @@ export function SetJsonDotnotation(jsonInput, inputKey) { return jsonInput; } + //export const green = "#86c142"; export const green = "#02CB70" export const yellow = "#FECC00"; @@ -359,31 +433,6 @@ export function removeParam(key, sourceURL) { return rtn; } -const useStyles = makeStyles({ - notchedOutline: { - borderColor: "#FF8544 !important", - }, - root: { - "& .MuiAutocomplete-listbox": { - border: "2px solid #FF8544", - color: "white", - fontSize: 18, - "& li:nth-child(even)": { - backgroundColor: "#CCC", - }, - "& li:nth-child(odd)": { - backgroundColor: "#FFF", - }, - }, - }, - inputRoot: { - color: "white", - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: "#f86a3e", - }, - }, -}); - const splitter = "|~|"; const svgSize = 24; const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); @@ -391,9 +440,11 @@ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); //const referenceUrl = "https://shuffler.io/functions/webhooks/" //const referenceUrl = window.location.origin+"/api/v1/hooks/" -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const AngularWorkflow = (defaultprops) => { const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id, ReactGA, } = defaultprops; + const {themeMode, supportEmail, brandColor} = useContext(Context) + const theme = getTheme(themeMode, brandColor) const referenceUrl = globalUrl + "/api/v1/hooks/"; //const alert = useAlert() let navigate = useNavigate(); @@ -407,10 +458,36 @@ const AngularWorkflow = (defaultprops) => { var to_be_copied = ""; const [firstrequest, setFirstrequest] = React.useState(true); - const [cystyle] = useState(cytoscapestyle); + const cystyle = useMemo(() => defaultCytoscapeStyle(theme), [themeMode]); + // const cystyle = useMemo(() => defaultCytoscapeStyle, [themeMode]); const [cy, setCy] = React.useState(); + const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#FF8544 !important", + }, + root: { + "& .MuiAutocomplete-listbox": { + border: "2px solid #FF8544", + color: theme.palette.text.primary, + fontSize: 18, + "& li:nth-child(even)": { + backgroundColor: "#CCC", + }, + "& li:nth-child(odd)": { + backgroundColor: "#FFF", + }, + }, + }, + inputRoot: { + color: theme.palette.text.primary, + "&:hover .MuiOutlinedInput-notchedOutline": { + borderColor: "#f86a3e", + }, + }, + }); + const [toolsApp, setToolsApp] = React.useState({}); const [currentView, setCurrentView] = React.useState(0); const [triggerAuthentication, setTriggerAuthentication] = React.useState({}); @@ -434,6 +511,7 @@ const AngularWorkflow = (defaultprops) => { "default", ] }); + const [appGroup, setAppGroup] = React.useState([]); const [triggerGroup, setTriggerGroup] = React.useState([]); const [executionText, setExecutionText] = React.useState(""); @@ -649,6 +727,7 @@ const AngularWorkflow = (defaultprops) => { "Nothing", "Create ticket", "List tickets", + "Send Email", "Get specific ticket", ], "multiselect": true, @@ -701,28 +780,22 @@ const AngularWorkflow = (defaultprops) => { "name": "action", "value": "list_tickets", "options": [ + "create_ticket", "list_tickets", "get_ticket", - "create_ticket", + "close_ticket", + "add_comment", + "update_ticket", + "search_tickets" ], "required": true, }, { "name": "fields", - "value": "", + "value": '{\n "ticket_id": "123456",\n "comment": "This is a comment"\n}', "required": false, "multiline": true, }, - /*{ - "name": "options", - "value": "deduplicate,enrich", - "required": false, - "multiselect": true, - "options": [ - "deduplicate", - "enrich", - ] - }*/ ] }, { "name": "Communication", @@ -732,8 +805,14 @@ const AngularWorkflow = (defaultprops) => { "name": "action", "value": "list_messages", "options": [ - "list_messages", "send_message", + "list_messages", + "get_message", + "search_messages", + "list_attachments", + "get_attachment", + "create_contact", + "get_contact" ], "required": true, }, @@ -750,9 +829,134 @@ const AngularWorkflow = (defaultprops) => { "label": "IAM", "parameters": [{ "name": "action", - "value": "get_kms_key", + "value": "get_asset", + "options": [ + "reset_password", + "enable_user", + "disable_user", + "get_identity", + "get_asset", + "search_identity" + ], + "required": true, + }, + { + "name": "fields", + "value": "", + "required": false, + "multiline": true, + }] + }, + { + "name": "Assets", + "description": "Available actions for Assets", + "label": "Assets", + "parameters": [{ + "name": "action", + "value": "list_assets", "options": [ - "get_kms_key", + "list_assets", + "get_asset", + "search_assets", + "search_users", + "search_endpoints", + "search_vulnerabilities" + ], + "required": true, + }, + { + "name": "fields", + "value": "", + "required": false, + "multiline": true, + }] + }, + { + "name": "Eradication", + "description": "Available actions for Eradication", + "label": "Eradication", + "parameters": [{ + "name": "action", + "value": "list_alerts", + "options": [ + "list_alerts", + "close_alert", + "get_alert", + "create_detection", + "block_hash", + "search_hosts", + "isolate_host", + "unisolate_host", + "trigger_host_scan" + ], + "required": true, + }, + { + "name": "fields", + "value": "", + "required": false, + "multiline": true, + }] + }, + { + "name": "Intel", + "description": "Available actions for Intel", + "label": "Intel", + "parameters": [{ + "name": "action", + "value": "get_ioc", + "options": [ + "get_ioc", + "create_ioc", + "search_ioc", + "update_ioc", + "delete_ioc" + ], + "required": true, + }, + { + "name": "fields", + "value": "", + "required": false, + "multiline": true, + }] + }, + { + "name": "Network", + "description": "Available actions for Network", + "label": "Network", + "parameters": [{ + "name": "action", + "value": "get_rules", + "options": [ + "get_rules", + "allow_ip", + "block_ip" + ], + "required": true, + }, + { + "name": "fields", + "value": "", + "required": false, + "multiline": true, + }] + }, + { + "name": "SIEM", + "description": "Available actions for SIEM", + "label": "SIEM", + "parameters": [{ + "name": "action", + "value": "search", + "options": [ + "search", + "list_alerts", + "close_alert", + "get_alert", + "create_detection", + "add_to_lookup_list", + "isolate_endpoint" ], "required": true, }, @@ -766,23 +970,6 @@ const AngularWorkflow = (defaultprops) => { ] }] - /* - { - "name": "Email", - "label": "Email", - "parameters": [{ - "name": "action", - "value": "list_email", - "options": [ - "list_email", - "send_mail", - ], - "required": true, - }], - }] - }] - */ - // For code editor const [codeEditorModalOpen, setCodeEditorModalOpen] = React.useState(false); const [codedata, setcodedata] = React.useState(""); @@ -818,6 +1005,14 @@ const AngularWorkflow = (defaultprops) => { return } + // Ensures reloads don't randomly happen + const appsearchValue = document.getElementById("appsearch") + if (appsearchValue !== undefined && appsearchValue !== null) { + if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) { + return + } + } + if (loadedApps.includes(appId)) { //console.log("App already loaded: ", appId) @@ -991,6 +1186,7 @@ const AngularWorkflow = (defaultprops) => { if (workflow?.suborg_distribution !== undefined && workflow?.suborg_distribution !== null && workflow?.suborg_distribution.length > 0) { getChildWorkflows(workflow.id) } + }, [workflow]); // Event for making sure app is correct @@ -2136,7 +2332,6 @@ const AngularWorkflow = (defaultprops) => { } // Controls the colors and direction of execution results. - // Style is in defaultCytoscapeStyle.js const handleUpdateResults = (responseJson, executionRequest) => { if (responseJson === undefined || responseJson === null || responseJson.success === false) { stop() @@ -2285,9 +2480,11 @@ const AngularWorkflow = (defaultprops) => { console.log("Should redirect to register with redirect.") setTimeout(() => { - toast("You may not have access to this workflow.") - //window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle` - window.location.href = `/workflows` + // toast("You may not have access to this workflow.") + localStorage.setItem("redirectId", props.match.params.key) + navigate("/register?view=workflows&message=You need sign up to use workflows with Shuffle"); + // window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle` + // window.location.href = `/workflows` }, 2500) return @@ -2657,9 +2854,9 @@ const AngularWorkflow = (defaultprops) => { setSavingState(0); console.log("Workflow failed loading: ", responseJson); if (responseJson.reason !== undefined && responseJson.reason !== null) { - toast("Failed to save: " + responseJson.reason); + toast.error("Failed to save: " + responseJson.reason); } else { - toast("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.") + toast.error(`Failed to save. Please contact your ${supportEmail} or your local admin if this is unexpected.`) } } else { setSavingState(1); @@ -2845,7 +3042,7 @@ const AngularWorkflow = (defaultprops) => { const runFromHere = (curAction) => { if (curAction.app_id === undefined || curAction.app_id === null || curAction.app_id.length === 0) { - toast.error("No app id found for action. Please contact support@shuffler.io if this persists") + toast.error(`No app id found for action. Please contact ${supportEmail} if this persists`) return } @@ -2904,7 +3101,7 @@ const AngularWorkflow = (defaultprops) => { if (responseJson?.reason !== undefined && responseJson?.reason !== null && responseJson?.reason.length > 0) { toast.error(responseJson.reason) } else { - toast.error("Failed to run the action. Please try again or contact support@shuffler.io") + toast.error(`Failed to run the action. Please try again or contact ${supportEmail}`) } return @@ -3352,6 +3549,12 @@ const AngularWorkflow = (defaultprops) => { if (curapp?.actions === undefined || curapp?.actions === null || curapp?.actions?.length === 0 || curapp?.actions?.length === 1) { 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, @@ -3595,6 +3798,7 @@ const AngularWorkflow = (defaultprops) => { "border-opacity": ".7", "font-size": "25px", "border-color": color, + "color": theme.palette.text.primary } const animationDuration = 150 @@ -4352,16 +4556,19 @@ const AngularWorkflow = (defaultprops) => { if (execFound === null && sessionToken === null) { if (isCloud) { - toast.error(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds. If you recently deleted this workflow, speak with support@shuffler.io to recover it from a revision.`, { + toast.error(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds. If you recently deleted this workflow, speak with ${supportEmail} to recover it from a revision.`, { autoClose: 10000, }) } else { - toast.error(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds. Contact support@shuffler.io if this is unexpected.`, { + toast.error(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds. Contact ${supportEmail} if this is unexpected.`, { autoClose: 10000, }) } setTimeout(() => { + if(!isLoggedIn){ + localStorage.setItem("redirectId", props.match.params.key); + } window.location.pathname = "/workflows"; }, 2500); @@ -4652,6 +4859,17 @@ const AngularWorkflow = (defaultprops) => { setOriginalWorkflow(responseJson) } + const tmpUi = new URLSearchParams(cursearch).get("ui"); + if ( + tmpUi !== undefined && + tmpUi !== null && + tmpUi === "yaml" + ) { + setTimeout(() => { + setupWorkflowYaml(responseJson) + }, 2500) + } + setWorkflow(responseJson); setWorkflowDone(true); @@ -4670,6 +4888,9 @@ const AngularWorkflow = (defaultprops) => { setConfigureWorkflowModalOpen(true) } + + + } }) .catch((error) => { @@ -6778,7 +6999,11 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { 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 { if (refresh === true) { setHighlightedApp(appid) @@ -8642,9 +8867,9 @@ const AngularWorkflow = (defaultprops) => { } // Set the edge to be dashed - edge.style("target-arrow-color", "white") + edge.style("target-arrow-color", theme.palette.text.primary) edge.style("line-style", "solid") - edge.style("line-gradient-stop-colors", ["white", "white"]) + edge.style("line-gradient-stop-colors", [theme.palette.text.primary, theme.palette.text.primary]) } } @@ -8788,7 +9013,7 @@ const AngularWorkflow = (defaultprops) => { } if (nodedata.type !== "COMMENT") { - parsedStyle.color = "white"; + parsedStyle.color = theme.palette.text.primary; } if (event.target !== undefined && event.target !== null) { @@ -8847,9 +9072,9 @@ const AngularWorkflow = (defaultprops) => { const edgeData = event.target.data(); if (edgeData.decorator === true) { // Set color of it to white and not stripled - event.target.style("target-arrow-color", "white") + event.target.style("target-arrow-color", theme.palette.text.primary) event.target.style("line-style", "solid") - event.target.style("line-gradient-stop-colors", ["white", "white"]) + event.target.style("line-gradient-stop-colors", [theme.palette.text.primary, theme.palette.text.primary]) return; } @@ -9727,6 +9952,191 @@ const AngularWorkflow = (defaultprops) => { }); } + const setupWorkflowYaml = (inputworkflow) => { + toast.warn("YAML exploring is an experimental feature - only visible to support users. The goal of this is to make it EASY to edit the workflow as YAML instead of just using the UI") + if (userdata?.support !== true) { + console.log("Not support: ", userdata) + return + } + + // This should be getting the workflow based on actual nodes in the workflow + // as the goal is to have it be live + var copiedWorkflow = JSON.parse(JSON.stringify(inputworkflow)) + + const removeObjects = [ + "execution_org", + "categories", + "example_argument", + "public", + "contact_info", + "published_id", + "revision_id", + "usecase_ids", + "input_questions", + "form_control", + "blogpost", + "video", + "status", + "generated", + "hidden", + "updated_by", + "validated", + "validation", + "childorg_workflow_ids", + "backup_config", + "auth_groups", + "isValid", + "workflow_as_code", + "image", + "sharing", + "owner", + "configuration", + "created", + "edited", + "last_runtime", + "due_date", + "is_valid", + "execution_environment", + "default_return_value", + "visual_branches", + "previously_saved", + "workflow_type", + "parentorg_workflow", + "suborg_distribution", + "id", + "comments", + "org_id", + + // Failing test + "spalabi", + ] + + for (var i = 0; i < removeObjects.length; i++) { + delete copiedWorkflow[removeObjects[i]] + } + + const removeActionValues = [ + "_id", + "id", + + "large_image", + "description", + "is_valid", + "isStartNode", + "sharing", + "public", + "generated", + "execution_variable", + "position", + "category", + "reference_url", + "sub_action", + "run_magic_output", + "run_magic_input", + "category_label", + "suggestions", + "parent_controlled", + "source_workflow", + "source_executions", + "app_association", + "suggestion", + "small_image", + "long_description", + "tags", + "errors", + "source_executions", + "source_execution", + "required", + "example", + "type", + + "test2", + ] + + const removeActionParamValues = [ + "id", + "multiline", + "multiselect", + "options", + "action_field", + "variant", + "configuration", + "tags", + "schema", + "skip_multicheck", + "value_replace", + "unique_toggled", + "hidden", + "error", + "example", + + "test3", + ] + + if (copiedWorkflow?.actions !== undefined && copiedWorkflow?.actions !== null && copiedWorkflow?.actions.length > 0) { + for (var key in copiedWorkflow.actions) { + for (var i = 0; i < removeActionValues.length; i++) { + delete copiedWorkflow.actions[key][removeActionValues[i]] + } + + + if (copiedWorkflow.actions[key].parameters === undefined || copiedWorkflow.actions[key].parameters === null) { + continue + } + + for (var j = 0; j < copiedWorkflow.actions[key].parameters.length; j++) { + for (var k = 0; k < removeActionParamValues.length; k++) { + delete copiedWorkflow.actions[key].parameters[j][removeActionParamValues[k]] + } + } + } + } + + if (copiedWorkflow?.triggers !== undefined && copiedWorkflow?.triggers !== null && copiedWorkflow?.triggers.length > 0) { + for (var key in copiedWorkflow.triggers) { + for (var i = 0; i < removeActionValues.length; i++) { + delete copiedWorkflow.triggers[key][removeActionValues[i]] + } + + delete copiedWorkflow.triggers[key]["app_name"] + delete copiedWorkflow.triggers[key]["app_version"] + delete copiedWorkflow.triggers[key]["name"] + delete copiedWorkflow.triggers[key]["priority"] + delete copiedWorkflow.triggers[key]["replacement_for_trigger"] + + if (copiedWorkflow.triggers[key].parameters === undefined || copiedWorkflow.triggers[key].parameters === null) { + continue + } + + for (var j = 0; j < copiedWorkflow.triggers[key].parameters.length; j++) { + for (var k = 0; k < removeActionParamValues.length; k++) { + delete copiedWorkflow.triggers[key].parameters[j][removeActionParamValues[k]] + } + } + } + } + + if (copiedWorkflow?.workflow_variables === undefined || copiedWorkflow?.workflow_variables === null || copiedWorkflow?.workflow_variables.length === 0) { + delete copiedWorkflow.workflow_variables + } + + if (copiedWorkflow?.branches === undefined || copiedWorkflow?.branches === null || copiedWorkflow?.branches.length === 0) { + delete copiedWorkflow.branches + } + + + // YAML + const sampledata = YAML.stringify(copiedWorkflow) + + navigate("?ui=yaml", { replace: true }) + + setCodeEditorModalOpen(true) + setEditorData({ + "name": "workflow yaml", + "value": sampledata, + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps //useEffect(() => { if (firstrequest) { @@ -9768,6 +10178,7 @@ const AngularWorkflow = (defaultprops) => { //navigate(`?execution_highlight=${parsed_url}`) //props.history.push(curpath + newitem); } + return; } @@ -10090,7 +10501,7 @@ const AngularWorkflow = (defaultprops) => { } } - toast("Creating schedule") + toast.info("Creating schedule") var data = { name: trigger.name, frequency: workflow.triggers[triggerindex].parameters[0].value, @@ -10139,9 +10550,9 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (!responseJson.success) { - toast("Failed to set schedule: " + responseJson.reason); + toast.error("Failed to set schedule: " + responseJson.reason); } else { - toast("Successfully created schedule"); + toast.success("Successfully created schedule"); workflow.triggers[triggerindex].status = "running"; trigger.status = "running"; setSelectedTrigger(trigger); @@ -10200,7 +10611,7 @@ const AngularWorkflow = (defaultprops) => { minWidth: isMobile ? 50 : "100%", maxWidth: isMobile ? 50 : "100%", marginTop: "5px", - color: "white", + color: theme.palette.text.primary, backgroundColor: theme.palette.surfaceColor, cursor: "pointer", display: "flex", @@ -10213,7 +10624,7 @@ const AngularWorkflow = (defaultprops) => { minWidth: "100%", maxWidth: "100%", marginTop: "5px", - color: "white", + color: theme.palette.text.primary, backgroundColor: theme.palette.surfaceColor, cursor: "pointer", display: "flex", @@ -10305,7 +10716,7 @@ const AngularWorkflow = (defaultprops) => { aria-controls="long-menu" aria-haspopup="true" onClick={menuClick} - style={{ color: "white" }} + style={{ color: theme.palette.text.primary }} > @@ -10327,7 +10738,7 @@ const AngularWorkflow = (defaultprops) => { { setOpen(false); @@ -10353,7 +10764,7 @@ const AngularWorkflow = (defaultprops) => { { deleteVariable(type, index); @@ -10389,7 +10800,7 @@ const AngularWorkflow = (defaultprops) => { rel="noopener noreferrer" href="https://shuffler.io/docs/workflows#workflow_variables" target="_blank" - style={{ textDecoration: "none", color: "#FF8544" }} + style={{ textDecoration: "none", color: theme.palette.linkColor }} > Workflow variables? @@ -10439,7 +10850,7 @@ const AngularWorkflow = (defaultprops) => { rel="noopener noreferrer" href="https://shuffler.io/docs/workflows#execution_variables" target="_blank" - style={{ textDecoration: "none", color: "#FF8544" }} + style={{ textDecoration: "none", color: theme.palette.linkColor }} > Runtime variables? @@ -10754,6 +11165,7 @@ const AngularWorkflow = (defaultprops) => { large_image: data.large_image, status: "uninitialized", name: data.name, + parameters: data?.parameters, isStartNode: false, position: newposition, } @@ -10955,6 +11367,10 @@ const AngularWorkflow = (defaultprops) => { } const handleAppDrag = (e, app) => { + if (cy === undefined || cy === null) { + return + } + const cycontainer = cy.container() // Handling drag of public apps @@ -11482,11 +11898,11 @@ const AngularWorkflow = (defaultprops) => { 17 ? -3 : 8, + color: theme.palette.textPrimary }} > {newAppname} @@ -11625,7 +12041,7 @@ const AngularWorkflow = (defaultprops) => { if (queryID !== undefined && queryID !== null) { aa('init', { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }) const timestamp = new Date().getTime() @@ -11649,8 +12065,9 @@ const AngularWorkflow = (defaultprops) => { var type = "app" const baseImage = + const width = 230 return ( -
+
{hits.length === 0 ? @@ -11671,7 +12088,7 @@ const AngularWorkflow = (defaultprops) => { overflowX: "hidden", overflowY: "hidden", borderBottom: "1px solid rgba(255,255,255,0.4)", - backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", + backgroundColor: mouseHoverIndex === index ? theme.palette.hoverColor : "inherit", cursor: "pointer", marginLeft: 0, marginRight: 0, @@ -11744,7 +12161,7 @@ const AngularWorkflow = (defaultprops) => { }} defaultPosition={{ x: 0, y: 0 }} > -
{ +
{ clickedApp(hit) }}> @@ -11791,8 +12208,7 @@ const AngularWorkflow = (defaultprops) => {
{title} @@ -11882,7 +12298,7 @@ const AngularWorkflow = (defaultprops) => { )} {visibleApps.length > extraApps.length ? - + Your Apps : null} @@ -11899,7 +12315,10 @@ const AngularWorkflow = (defaultprops) => { } if ((app.id === "integration" || app.id === "shuffle_agent") && userdata.support !== true) { - return null + if (isCloud === false && app.id === "integration") { + } else { + return null + } } if (viewedApps.includes(app.id)) { @@ -11964,7 +12383,7 @@ const AngularWorkflow = (defaultprops) => {
) : apps.length > 0 ? (
{ console.log("Should load in extra apps?") }} @@ -11972,6 +12391,7 @@ const AngularWorkflow = (defaultprops) => { Couldn't find the apps you were looking for? Searching unactivated apps. Click one of these apps to Activate it for your organisation. + { console.log("CLICKED") }}> @@ -12049,7 +12469,7 @@ const AngularWorkflow = (defaultprops) => { const newaction = selectedApp.actions.find((a) => a.name === e.target.value) if (newaction === undefined || newaction === null) { - toast("Failed to find the action you selected. Please try again or contact support@shuffler.io if it persists."); + toast(`Failed to find the action you selected. Please try again or contact ${supportEmail} if it persists.`); return; } @@ -12306,6 +12726,8 @@ const AngularWorkflow = (defaultprops) => { const selectedTriggerChange = (event) => { selectedTrigger.label = event.target.value; setSelectedTrigger(selectedTrigger); + workflow.triggers[selectedTriggerIndex].label = event.target.value; + setWorkflow(workflow); }; // Starts on current node and climbs UP the tree to the root object. @@ -12327,7 +12749,7 @@ const AngularWorkflow = (defaultprops) => { // maxiter = max amount of parent nodes to loop // also handles breaks if there are issues var iterations = 0; - var maxiter = 10; + var maxiter = 100; while (true) { for (let parentkey in allkeys) { var currentnode = cy.getElementById(allkeys[parentkey]); @@ -12395,8 +12817,8 @@ const AngularWorkflow = (defaultprops) => { const appApiViewStyle = { display: "flex", flexDirection: "column", - backgroundColor: "#1F2023", - color: "white", + backgroundColor: theme.palette.DialogStyle.backgroundColor, + color: theme.palette.text.primary, paddingRight: 15, paddingLeft: 15, minHeight: "100%", @@ -12475,16 +12897,19 @@ const AngularWorkflow = (defaultprops) => { }; const setTriggerCronWrapper = (value) => { - console.log("Cron Value: ", value) if (selectedTrigger.parameters === null) { selectedTrigger.parameters = []; } - + selectedTrigger.parameters[0] = { + value: value, + name: "cron", + }; workflow.triggers[selectedTriggerIndex].parameters[0] = { value: value, name: "cron", }; setWorkflow(workflow); + setSelectedTrigger(selectedTrigger); }; const setTriggerOptionsWrapper = (value) => { @@ -12530,11 +12955,14 @@ const AngularWorkflow = (defaultprops) => { if (selectedTrigger.parameters === null) { selectedTrigger.parameters = []; } - + selectedTrigger.parameters[0] = { + value : value + } workflow.triggers[selectedTriggerIndex].parameters[0] = { value: value, name: "alertinfo", }; + setSelectedTrigger(selectedTrigger) setWorkflow(workflow); }; @@ -12787,7 +13215,7 @@ const AngularWorkflow = (defaultprops) => { event.preventDefault() setExpansionModalOpen(true) setActiveDialog("codeeditor") - navigate(`?condition_id=${data.id}&field=${data.name}`) + navigate(`?condition_id=${data.id}&condition_field=${data.name}`) setEditorData({ "name": data.name, "value": data.value || "", @@ -12798,7 +13226,7 @@ const AngularWorkflow = (defaultprops) => { > @@ -12815,7 +13243,7 @@ const AngularWorkflow = (defaultprops) => { data.value !== undefined && data.value !== null && data.value.includes(".#") ? ( - + Use "Shuffle Tools" app with "Filter List" action to handle loops ) : null @@ -12872,9 +13300,9 @@ const AngularWorkflow = (defaultprops) => { // Update the field value based on type if (type === "source") { - handleConditionFieldChange("source", "value", toComplete); + handleConditionFieldChange("source", toComplete); } else if (type === "destination") { - handleConditionFieldChange("destination", "value", toComplete); + handleConditionFieldChange("destination", toComplete); } handleMenuClose(); @@ -12966,7 +13394,7 @@ const AngularWorkflow = (defaultprops) => { startIcon={} sx={{ marginLeft: 10, - color: "white", + color: theme.palette.text.primary, fontSize: "15px", fontFamily: theme?.typography?.fontFamily, textTransform: "none", @@ -12978,6 +13406,7 @@ const AngularWorkflow = (defaultprops) => { justifyContent: "flex-start", "&:hover": { backgroundColor: "transparent", + color: theme.palette.text.primary, border: "none" }, // Disable ripple effect @@ -12987,7 +13416,7 @@ const AngularWorkflow = (defaultprops) => { }} > - Auto Complete + Auto Complete { onClose={handleMenuClose} PaperProps={{ style: { - backgroundColor: "#82ccc3", - color: "white", + backgroundColor: theme.palette.backgroundColor, + color: theme.palette.text.primary, marginTop: 2, maxHeight: 400, }, @@ -13121,7 +13550,7 @@ const AngularWorkflow = (defaultprops) => { } parentMenuOpen={!!menuPosition} style={{ - color: "white", + color: theme.palette.text.primary, minWidth: 250, maxWidth: 250, maxHeight: 50, @@ -13140,7 +13569,7 @@ const AngularWorkflow = (defaultprops) => { style={{ // backgroundColor: theme.palette.inputColor, marginLeft: 15, - color: "white", + color: theme.palette.text.primary, minWidth: 250, maxWidth: 250, padding: 0, @@ -13183,7 +13612,7 @@ const AngularWorkflow = (defaultprops) => { key={pathdata.name} style={{ // backgroundColor: theme.palette.inputColor, - color: "white", + color: theme.palette.text.primary, minWidth: 250, maxWidth: 250, padding: boxPadding, @@ -13224,7 +13653,7 @@ const AngularWorkflow = (defaultprops) => { key={innerdata.name} style={{ // backgroundColor: theme.palette.inputColor, - color: "white", + color: theme.palette.text.primary, padding: "10px 12px", // Add padding here }} value={innerdata} @@ -13257,7 +13686,7 @@ const AngularWorkflow = (defaultprops) => { }; const menuItemStyle = { - color: "white", + color: theme.palette.text.primary, backgroundColor: theme.palette.inputColor, }; @@ -13321,7 +13750,7 @@ const AngularWorkflow = (defaultprops) => { style: { padding: 30, pointerEvents: "auto", - color: "white", + color: theme.palette.text.primary, minWidth: isMobile ? "90%" : 800, border: theme.palette.defaultBorder, @@ -13344,11 +13773,11 @@ const AngularWorkflow = (defaultprops) => { setAuthgroupModalOpen(false) }} > - + - Authgroup Selection + Authgroup Selection @@ -13445,7 +13874,7 @@ const AngularWorkflow = (defaultprops) => { style: { padding: 30, pointerEvents: "auto", - color: "white", + color: theme.palette.text.primary, minWidth: isMobile ? "90%" : 650, border: theme.palette.defaultBorder, @@ -13468,11 +13897,11 @@ const AngularWorkflow = (defaultprops) => { setExecutionArgumentModalOpen(false) }} > - + - Provide an execution argument + Provide an execution argument @@ -13491,7 +13920,7 @@ const AngularWorkflow = (defaultprops) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, fontSize: "1em", }, }} @@ -13656,7 +14085,7 @@ const AngularWorkflow = (defaultprops) => { open={aiQueryModalOpen} PaperProps={{ style: { - color: "white", + color: theme.palette.text.primary, minWidth: isMobile ? "90%" : 450, border: theme.palette.defaultBorder, padding: 50, @@ -13686,7 +14115,7 @@ const AngularWorkflow = (defaultprops) => { onClick={(e) => { }} > - + { position: "absolute", top: 6, right: 6, - color: "white", + color: theme.palette.text.primary, }} onClick={() => { setAiQueryModalOpen(false) @@ -13706,7 +14135,7 @@ const AngularWorkflow = (defaultprops) => { Shuffle AI - What you write here will be fed to the Shuffle AI to generate a change for the selected action or field. Best used for when you are stuck with formatting. Uses your AI credits (resets monthly). Beta feature. Please give feedback to support@shuffler.io {"<"}3 + What you write here will be fed to the Shuffle AI to generate a change for the selected action or field. Best used for when you are stuck with formatting. Uses your AI credits (resets monthly). Beta feature. Please give feedback to {supportEmail} {"<"}3 { />
- const handleConditionFieldChange = (fieldType, fieldName, value) => { + const handleConditionFieldChange = (fieldType, value) => { if (fieldType === "source") { setSourceValue({ ...sourceValue, @@ -13780,14 +14209,23 @@ const AngularWorkflow = (defaultprops) => { aria-labelledby="draggable-dialog-title" open={conditionsModalOpen} PaperProps={{ - style: { + sx: { pointerEvents: "auto", - color: "white", - minWidth: isMobile ? "90%" : 800, + color: theme.palette.DialogStyle.color, + minWidth: isMobile ? "90%" : "800px", border: theme.palette.defaultBorder, - borderRadius: theme.palette.borderRadius, - backgroundColor: "black", + borderRadius: theme.palette.DialogStyle.borderRadius, + backgroundColor: theme.palette.DialogStyle.backgroundColor, + '& .MuiDialogContent-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogTitle-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogActions-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, }, }} onClose={() => { @@ -13798,7 +14236,8 @@ const AngularWorkflow = (defaultprops) => { position: "absolute", bottom: 10, left: 10, - color: "rgba(255,255,255,0.6)", + color: theme.palette.textColor, + backgroundColor: 'inherit', zIndex: 10000, }} > @@ -13809,7 +14248,7 @@ const AngularWorkflow = (defaultprops) => { href="/docs/workflows#conditions" style={{ textDecoration: "none", - color: "#FF8544", + color: theme.palette.linkColor, }} > Learn more @@ -13817,7 +14256,7 @@ const AngularWorkflow = (defaultprops) => { - Condition + Condition
@@ -14027,7 +14466,7 @@ const AngularWorkflow = (defaultprops) => {
- + - : 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 && userdata?.active_org?.creator_org?.length === 0 && userdata?.active_org?.id == workflow?.org_id ? - : null - + : null} +
: + Save the workflow first : null} arrow placement="right"> @@ -18515,7 +18947,7 @@ const AngularWorkflow = (defaultprops) => { Select an Org ({originalWorkflow?.suborg_distribution?.length}) @@ -18523,7 +18955,7 @@ const AngularWorkflow = (defaultprops) => { style={{ pointerEvents: "auto", backgroundColor: theme.palette.inputColor, - color: "white", + color: theme.palette.text.primary, maxWidth: 300, minWidth: 300, borderRadius: theme.palette?.borderRadius, @@ -18880,7 +19312,7 @@ const AngularWorkflow = (defaultprops) => { Runtime Location @@ -18888,6 +19320,12 @@ const AngularWorkflow = (defaultprops) => { labelId="execution_location" disabled={savingState !== 0} MenuProps={{ + PaperProps: { + sx: { + '& .MuiList-root': { + backgroundColor: "transparent", + }, + }} }} value={ selectedActionEnvironment === undefined || selectedActionEnvironment === null || selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null ? isCloud ? "Cloud" : "Shuffle" : selectedActionEnvironment.Name @@ -18920,13 +19358,13 @@ const AngularWorkflow = (defaultprops) => { }} style={{ pointerEvents: "auto", - color: "white", + color: theme.palette.text.primary, maxWidth: 250, minWidth: 250, borderRadius: theme.palette?.borderRadius, marginLeft: 35, - backgroundColor: theme.palette.inputColor, + backgroundColor: "transparent", height: 40, }} > @@ -18940,9 +19378,9 @@ const AngularWorkflow = (defaultprops) => { return ( @@ -18987,7 +19425,7 @@ const AngularWorkflow = (defaultprops) => { {data.default === true ? { }} >

This menu is used to control the workflow itself.

Skip Notifications
} + style={{ marginBottom: 15, color: theme.palette.text.primary }} + label={
Skip Notifications
} control={ { } /> Exit on Error
} + style={{ marginBottom: 15, color: theme.palette.text.primary }} + label={
Exit on Error
} control={ { } /> Start from top
} + style={{ marginBottom: 15, color: theme.palette.text.primary }} + label={
Start from top
} control={ { const shownErrors = !isMobile && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 && showErrors && (!workflow.public || userdata.support === true) ?
{ > { e.preventDefault(); @@ -19339,17 +19782,17 @@ const AngularWorkflow = (defaultprops) => { setShowErrors(false) }} > - + - + {/**/} {workflow.errors.length} Workflow Issue{workflow.errors.length > 1 ? "s" : ""} {workflow.errors.slice(0, 3).map((error, index) => { // Loop through each word, and if it matches "Action " then replace it with a link to the action @@ -19671,7 +20114,7 @@ const AngularWorkflow = (defaultprops) => { .catch((error) => { console.log("Dupe workflow for suborg error: ", error.toString()) }) - } + } const BottomCytoscapeBar = () => { if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) { @@ -19706,6 +20149,7 @@ const AngularWorkflow = (defaultprops) => { height: boxSize, width: boxSize + 5, backgroundColor: green, + color: theme.palette.text.primary, }} color="primary" variant="contained" @@ -19801,7 +20245,7 @@ const AngularWorkflow = (defaultprops) => { style={{ marginLeft: 25, marginTop: 2, - border: "1px solid rgba(255,255,255,0.3)", + border: theme.palette.DialogStyle.border, borderRadius: theme.palette?.borderRadius / 2, maxHeight: buttonHeights, @@ -19816,6 +20260,10 @@ const AngularWorkflow = (defaultprops) => { style={{ height: buttonHeights, width: 64, + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: 0, }} variant={ lastSaved && !workflow.public ? "text" : "contained" @@ -19833,7 +20281,7 @@ const AngularWorkflow = (defaultprops) => { if (queryID !== undefined && queryID !== null) { aa('init', { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }) const timestamp = new Date().getTime() @@ -19855,7 +20303,7 @@ const AngularWorkflow = (defaultprops) => { }} > {savingState === 2 ? ( - + ) : savingState === 1 ? ( ) : ( @@ -20019,6 +20467,29 @@ const AngularWorkflow = (defaultprops) => { + + + {userdata?.support === true ? + + + + + + : null} @@ -20150,10 +20621,11 @@ const AngularWorkflow = (defaultprops) => { position, backgroundcolor: "#1f2023", color: "#ffffff", - textHalign: "center", - textValign: "center", - textMarginX: "0px", - textMarginY: "0px", + + textHalign: "right", + textValign: "bottom", + textMarginX: "-250px", + textMarginY: "-150px", }, position, }); @@ -20359,7 +20831,7 @@ const AngularWorkflow = (defaultprops) => { return ( { @@ -20647,10 +21119,10 @@ const AngularWorkflow = (defaultprops) => { minWidth: "95%", maxWidth: "95%", marginTop: 5, - color: "white", + color: theme.palette.text.primary, marginBottom: 10, padding: 5, - backgroundColor: theme.palette.backgroundColor, + backgroundColor: theme.palette.platformColor, borderRadius: theme.palette.borderRadius, cursor: "pointer", display: "flex", @@ -20682,6 +21154,7 @@ const AngularWorkflow = (defaultprops) => { height: 30, paddingLeft: 0, width: 30, + color: theme.palette.text.primary, }} onClick={() => { if (validate.valid) { @@ -20715,7 +21188,7 @@ const AngularWorkflow = (defaultprops) => { placement="top" style={{ zIndex: 10011 }} > - + { ); }; - const handleReactJsonClipboard = (copy) => { - - const elementName = "copy_element_shuffle"; - var copyText = document.getElementById(elementName); - if (copyText !== null && copyText !== undefined) { - if ( - copy.namespace !== undefined && - copy.name !== undefined && - copy.src !== undefined - ) { - copy = copy.src; - } - - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast("Can only copy over HTTPS (port 3443)"); - return; - } - - var stringified = JSON.stringify(copy); - if (stringified.startsWith('"') && stringified.endsWith('"')) { - stringified = stringified.substring(1, stringified.length - 1); - } - - navigator.clipboard.writeText(stringified); - copyText.select(); - copyText.setSelectionRange(0, 99999); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - - console.log("COPYING!"); - toast("Copied value to clipboard, NOT json path.") - } else { - console.log("Failed to copy from " + elementName + ": ", copyText); - } - }; - - const HandleJsonCopy = (base, copy, base_node_name) => { - if (typeof copy.name === "string") { - copy.name = copy.name.replaceAll(" ", "_"); - } - - //lol - if (typeof base === 'object' || typeof base === 'dict') { - base = JSON.stringify(base) - } - - if (base_node_name === "execution_argument" || base_node_name === "Runtime Argument") { - base_node_name = "exec" - } - - //console.log("COPY: ", base_node_name, copy); - - //var newitem = JSON.parse(base); - var newitem = validateJson(base).result - - // Check if base_node_name has changed - if (cy !== undefined && cy !== null) { - //console.log("Change name?") - //const allNodes = cy.nodes().jsons(); - //for (var key in allNodes) { - //const currentNode = allNodes[key]; - - //if (currentNode. - //} - - //const nodedata = cy.getElementById(data.action.id).data(); - //base_node_name = - } - - to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_"); - for (let copykey in copy.namespace) { - if (copy.namespace[copykey].includes("Results for")) { - continue; - } - - if (newitem !== undefined && newitem !== null) { - newitem = newitem[copy.namespace[copykey]]; - if (!isNaN(copy.namespace[copykey])) { - to_be_copied += ".#"; - } else { - to_be_copied += "." + copy.namespace[copykey]; - } - } - } - - if (newitem !== undefined && newitem !== null) { - newitem = newitem[copy.name]; - if (!isNaN(copy.name)) { - to_be_copied += ".#"; - } else { - to_be_copied += "." + copy.name; - } - } - - to_be_copied = to_be_copied.replaceAll(" ", "_"); - console.log("COPY: ", to_be_copied); - const elementName = "copy_element_shuffle"; - var copyText = document.getElementById(elementName); - if (copyText !== null && copyText !== undefined) { - //console.log("NAVIGATOR: ", navigator); - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast("Can only copy over HTTPS (port 3443)"); - return; - } - - navigator.clipboard.writeText(to_be_copied); - copyText.select(); - copyText.setSelectionRange(0, 99999); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - //console.log("COPYING!"); - toast("Copied JSON path to clipboard.") - } else { - console.log("Couldn't find element ", elementName); - } - } - // Not used because of issue with state updates. const ShowReactJsonField = (props) => { const { validate, jsonValue, collapsed, label, autocomplete } = props @@ -21245,12 +21597,12 @@ const AngularWorkflow = (defaultprops) => { overflow: "auto", minWidth: isMobile ? "100%" : 490, maxWidth: isMobile ? "100%" : 490, - color: "white", + color: theme.palette.text.primary, fontSize: 18, borderLeft: theme.palette.defaultBorder, borderRadius: theme.palette.borderRadius, - backgroundColor: "black", + backgroundColor: themeMode === "dark" ? "black" : theme.palette.drawer.backgroundColor, }, }} > @@ -21267,19 +21619,19 @@ const AngularWorkflow = (defaultprops) => { setExecutionModalOpen(false) }} > - + : null} {executionModalView === 0 ? ( -
+
) : null} @@ -21559,7 +21911,7 @@ const AngularWorkflow = (defaultprops) => { {foundnotifications > 0 ? { e.preventDefault() e.stopPropagation() @@ -21593,19 +21945,24 @@ const AngularWorkflow = (defaultprops) => {
) : ( -
- +
+ No executions found for the '{executionFilter}' filter.
) : ( -
+
{ stop() }} > - +

{ const newitem = removeParam("execution_id", cursearch); navigate(curpath + newitem) @@ -21665,12 +22022,12 @@ const AngularWorkflow = (defaultprops) => { -
+

Details

{ @@ -21752,7 +22109,7 @@ const AngularWorkflow = (defaultprops) => { @@ -21789,7 +22146,7 @@ const AngularWorkflow = (defaultprops) => {

+ + {data.action.app_name === "AI Agent" || data.action.app_name === "Shuffle Agent" ? + + + { }} + > + + + + + : null} + {data.action.app_name === "shuffle-subflow" && validate.result.success !== undefined && validate.result.success === true ? ( @@ -22432,7 +22813,7 @@ const AngularWorkflow = (defaultprops) => { target="_blank" style={{ textDecoration: "none", - color: "#FF8544", + color: theme.palette.linkColor, }} onClick={(event) => { }} > @@ -22564,7 +22945,7 @@ const AngularWorkflow = (defaultprops) => { Action Logs - More log details for this action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode. + More log details for this action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode.
) @@ -22644,7 +23025,7 @@ const AngularWorkflow = (defaultprops) => { variant="body2" style={{ whiteSpace: 'pre-line', - color: showlink ? "#FF8544" : "white", + color: showlink ? "#FF8544" : theme.palette.text.primary, cursor: showlink ? "pointer" : "default", }} onClick={(e) => { @@ -22699,7 +23080,7 @@ const AngularWorkflow = (defaultprops) => { } if (result.status === 405) { - return "Method not allowed. Check the URL to ensure it has all the required parameters. If you keep getting a 405, please forward a screenshot of this to support@shuffler.io" + return `Method not allowed. Check the URL to ensure it has all the required parameters. If you keep getting a 405, please forward a screenshot of this to ${supportEmail}` } if (result.status === 415) { @@ -22766,7 +23147,7 @@ const AngularWorkflow = (defaultprops) => { } if (stringjson.includes("kms/")) { - return "KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=notifications. If you need help with KMS, please contact support@shuffler.io" + return `KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=notifications. If you need help with KMS, please contact ${supportEmail}` } if (stringjson.includes("invalidurl")) { @@ -22794,7 +23175,7 @@ const AngularWorkflow = (defaultprops) => { if (stringjson.includes("connectionerror")) { if (stringjson.includes("kms")) { - return "KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=notifications&kms=true. If you need help with KMS, please contact support@shuffler.io" + return `KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=notifications&kms=true. If you need help with KMS, please contact ${supportEmail}` } return "The URL is incorrect, or Shuffle can't reach it. Set up a Shuffle Environment in the same VLAN, or whitelist Shuffle's IPs." @@ -22815,23 +23196,25 @@ const AngularWorkflow = (defaultprops) => { open={codeModalOpen} PaperProps={{ onClick: () => setActiveDialog("result"), - style: { + sx: { pointerEvents: "auto", - color: "white", - minWidth: isMobile ? "90%" : 750, - padding: 30, - maxHeight: 550, + color: theme.palette.text.primary, + minWidth: isMobile ? "90%" : "750px", + maxHeight: "550px", overflowY: "auto", overflowX: "hidden", border: theme.palette.defaultBorder, - borderRadius: theme.palette.borderRadius, - backgroundColor: "black", + borderRadius: theme.palette.DialogStyle.borderRadius, + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + '& .MuiDialogContent-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + padding: "30px 30px 30px 30px", + }, }, }} > - {/* Have a sticky top bar */} - + { } }} > - + { } }} > - + { } }} > - + { onClick={(e) => { }} > - + { setCodeModalOpen(false); }} > - + +
{curapp === null ? null : ( @@ -23030,12 +23414,11 @@ const AngularWorkflow = (defaultprops) => { fontSize: 24, marginTop: "auto", marginBottom: "auto", - cursor: "move", }} > {selectedResult.action.label.replaceAll("_", " ")}
-
{selectedResult.action.name}
+
{selectedResult.action.name}
@@ -23154,11 +23537,12 @@ const AngularWorkflow = (defaultprops) => { ) : null}
+ ); const newView = ( -
+
@@ -23399,7 +23783,7 @@ const AngularWorkflow = (defaultprops) => { PaperProps={{ style: { pointerEvents: "auto", - color: "white", + color: theme.palette.text.primary, border: theme.palette.defaultBorder, maxWidth: isMobile ? bodyWidth - 100 : 800, minWidth: isMobile ? bodyWidth - 100 : 800, @@ -23411,7 +23795,7 @@ const AngularWorkflow = (defaultprops) => { > - Runtime Variable + Runtime Variable Runtime Variables are TEMPORARY variables that you can only be set @@ -23420,7 +23804,7 @@ const AngularWorkflow = (defaultprops) => { rel="noopener noreferrer" href="https://shuffler.io/docs/workflows#execution_variables" target="_blank" - style={{ textDecoration: "none", color: "#FF8544" }} + style={{ textDecoration: "none", color: theme.palette.linkColor }} > here @@ -23431,7 +23815,7 @@ const AngularWorkflow = (defaultprops) => { style={{ marginTop: 25 }} InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, }, }} margin="dense" @@ -23446,7 +23830,7 @@ const AngularWorkflow = (defaultprops) => { style={{ marginTop: 25 }} InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, }, }} margin="dense" @@ -23514,7 +23898,7 @@ const AngularWorkflow = (defaultprops) => { { PaperProps={{ style: { pointerEvents: "auto", - color: "white", + color: theme.palette.text.primary, border: theme.palette.defaultBorder, maxWidth: isMobile ? bodyWidth - 100 : "100%", @@ -23589,7 +23973,7 @@ const AngularWorkflow = (defaultprops) => { > - Workflow Variable + Workflow Variable { placeholder="Name" InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, }, }} margin="dense" @@ -23613,7 +23997,7 @@ const AngularWorkflow = (defaultprops) => { fullWidth InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, }, }} defaultValue={newVariableDescription} @@ -23627,7 +24011,7 @@ const AngularWorkflow = (defaultprops) => { margin="dense" InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, }, }} fullWidth @@ -23873,7 +24257,7 @@ const AngularWorkflow = (defaultprops) => { return (
-
+
Authentication for {selectedApp.name.replaceAll("_", " ", -1)}
@@ -23882,7 +24266,7 @@ const AngularWorkflow = (defaultprops) => { target="_blank" rel="noopener noreferrer" href="https://shuffler.io/docs/apps#authentication" - style={{ textDecoration: "none", color: "#FF8544" }} + style={{ textDecoration: "none", color: theme.palette.linkColor }} > What is app authentication? @@ -23965,7 +24349,7 @@ const AngularWorkflow = (defaultprops) => { }} style={{ backgroundColor: theme.palette.surfaceColor, - color: "white", + color: theme.palette.text.primary, height: 50, }} > @@ -23973,7 +24357,7 @@ const AngularWorkflow = (defaultprops) => { key={"false"} style={{ backgroundColor: theme.palette.inputColor, - color: "white", + color: theme.palette.text.primary, }} value={"false"} > @@ -23983,7 +24367,7 @@ const AngularWorkflow = (defaultprops) => { key={"true"} style={{ backgroundColor: theme.palette.inputColor, - color: "white", + color: theme.palette.text.primary, }} value={"true"} > @@ -24047,7 +24431,7 @@ const AngularWorkflow = (defaultprops) => { open={configureWorkflowModalOpen} PaperProps={{ style: { - color: "white", + color: theme.palette.text.primary, minWidth: 650, border: theme.palette.defaultBorder, @@ -24062,7 +24446,7 @@ const AngularWorkflow = (defaultprops) => { position: "absolute", top: 14, right: 14, - color: "white", + color: theme.palette.text.primary, }} onClick={() => { setConfigureWorkflowModalOpen(false); @@ -24104,25 +24488,35 @@ const AngularWorkflow = (defaultprops) => { hideBackdrop={true} disableEnforceFocus={true} disableBackdropClick={true} - style={{ pointerEvents: "none" }} + style={{ + pointerEvents: "none", + }} open={authenticationModalOpen} onClose={() => { setSelectedMeta(undefined) }} PaperProps={{ - style: { + sx: { pointerEvents: "auto", - color: "white", - minWidth: 1100, - minHeight: 700, - maxHeight: 700, - padding: 15, + color: theme.palette.DialogStyle.color, + minWidth: "1100px", + minHeight: "700px", + maxHeight: "700px", overflow: "hidden", zIndex: 10012, - border: theme.palette.defaultBorder, + border: theme.palette.DialogStyle.border, - borderRadius: theme.palette.borderRadius, - backgroundColor: "black", + borderRadius: theme.palette.DialogStyle.borderRadius, + backgroundColor: theme.palette.DialogStyle.backgroundColor, + '& .MuiDialogContent-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogTitle-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogActions-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, }, }} > @@ -24134,6 +24528,7 @@ const AngularWorkflow = (defaultprops) => { right: 75, height: 50, width: 50, + backgroundColor: theme.palette.DialogStyle.backgroundColor, }} > {selectedApp.reference_info === undefined || @@ -24146,7 +24541,7 @@ const AngularWorkflow = (defaultprops) => { rel="noopener noreferrer" target="_blank" href={"https://github.com/shuffle/python-apps"} - style={{ textDecoration: "none", color: "#f86a3e" }} + style={{ textDecoration: "none", color: theme.palette.linkColor }} > {`Documentation { rel="noopener noreferrer" target="_blank" href={selectedApp.reference_info.github_url} - style={{ textDecoration: "none", color: "#f86a3e" }} + style={{ textDecoration: "none", color: theme.palette.linkColor }} > {`Documentation { > -
+
{ selectedApp.authentication.parameters === null || selectedApp.authentication.parameters === undefined || selectedApp.authentication.parameters.length === 0 ? 0 : 2, - padding: 0, + paddingLeft: 15, + paddingTop: 15, + paddingBottom: 15, minHeight: isMobile ? "90%" : 700, maxHeight: isMobile ? "90%" : 700, overflowY: "auto", overflowX: isMobile ? "auto" : "hidden", + backgroundColor: theme.palette.DialogStyle.backgroundColor, }} > {authenticationType.type === "oauth2" || authenticationType.type === "oauth2-app" ? @@ -24266,6 +24664,7 @@ const AngularWorkflow = (defaultprops) => { minHeight: 630, overflowY: "auto", overflowX: "hidden", + backgroundColor: theme.palette.DialogStyle.backgroundColor, }} onLoad={() => { /* @@ -24353,7 +24752,7 @@ const AngularWorkflow = (defaultprops) => { rel="noopener noreferrer" target="_blank" href="https://discord.gg/B2CBzUm" - style={{ textDecoration: "none", color: "#f86a3e" }} + style={{ textDecoration: "none", color: theme.palette.linkColor }} > Join the community on Discord! @@ -24373,7 +24772,7 @@ const AngularWorkflow = (defaultprops) => { rel="noopener noreferrer" target="_blank" href={"https://github.com/shuffle/python-apps"} - style={{ textDecoration: "none", color: "#f86a3e" }} + style={{ textDecoration: "none", color: theme.palette.linkColor }} > Check it out on Github! @@ -24415,7 +24814,7 @@ const AngularWorkflow = (defaultprops) => { href={selectedMeta.link} style={{ textDecoration: "none", color: "#FF8544" }} > - @@ -24426,13 +24825,13 @@ const AngularWorkflow = (defaultprops) => { style={{ height: "100%", width: 1, - backgroundColor: "white", + backgroundColor: theme.palette.text.primary, marginLeft: 50, marginRight: 50, }} /> )} - + {selectedMeta.read_time} minute {selectedMeta.read_time === 1 ? "" : "s"} to read @@ -24512,7 +24911,7 @@ const AngularWorkflow = (defaultprops) => { PaperProps={{ style: { pointerEvents: "auto", - color: "white", + color: theme.palette.text.primary, minWidth: 600, minHeight: 250, maxHeight: 250, @@ -24527,7 +24926,7 @@ const AngularWorkflow = (defaultprops) => { }} > -
Run a Tenzir Pipeline
+
Run a Tenzir Pipeline
Runs a Tenzir pipeline. You can use the output of the pipeline in your workflow. @@ -24579,7 +24978,7 @@ const AngularWorkflow = (defaultprops) => { return (
{/* - + */} { }); }} > - +
{ @@ -24950,7 +25349,7 @@ const AngularWorkflow = (defaultprops) => { minWidth: isMobile ? "100%" : 360, maxWidth: isMobile ? "100%" : 360, backgroundColor: theme.palette.platformColor, - color: "white", + color: theme.palette.text.primary, fontSize: 18, zIndex: 15001, borderRight: theme.palette.defaultBorder, @@ -24980,7 +25379,7 @@ const AngularWorkflow = (defaultprops) => { : null*/}
-
+
{selectedVersion?.name} @@ -25302,7 +25701,7 @@ const AngularWorkflow = (defaultprops) => { // selectedTrigger={selectedTrigger} aiSubmit={aiSubmit} toolsAppId={toolsApp.id} - handleSubflowParamChange={handleSubflowParamChange} + handleTriggerParamChange={handleTriggerParamChange} codedata={editorData.value} setcodedata={setcodedata} selectedEdge={selectedEdge} @@ -25324,6 +25723,7 @@ const AngularWorkflow = (defaultprops) => { setAiQueryModalOpen={setAiQueryModalOpen} + isWorkflowEditor={editorData?.name === "workflow yaml"} /> : null} @@ -25350,7 +25750,7 @@ const AngularWorkflow = (defaultprops) => { position: "absolute", top: 6, right: 6, - color: "white", + color: theme.palette.text.primary, }} onClick={() => { setSelectionOpen(false) @@ -25384,7 +25784,7 @@ const AngularWorkflow = (defaultprops) => { position: "absolute", top: 6, right: 6, - color: "white", + color: theme.palette.text.primary, }} onClick={() => { setShowVideo("") diff --git a/frontend/src/views/ApiExplorerWrapper.jsx b/frontend/src/views/ApiExplorerWrapper.jsx index 96298437..d104d43d 100644 --- a/frontend/src/views/ApiExplorerWrapper.jsx +++ b/frontend/src/views/ApiExplorerWrapper.jsx @@ -50,7 +50,7 @@ import { green } from "../views/AngularWorkflow.jsx" const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" ) // Lazy loading of ApiExplorer component to reduce initial load time @@ -59,6 +59,7 @@ const ApiExplorer = React.lazy(() => import("../components/ApiExplorer.jsx")); const ApiExplorerWrapper = (props) => { const { globalUrl, serverside, userdata, isLoggedIn, isLoaded} = props; + const { supportEmail } = useContext(Context); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" const location = useLocation(); const navigate = useNavigate(); @@ -165,7 +166,7 @@ const ApiExplorerWrapper = (props) => { } if (!found) { - toast.error(`Failed to get API data for '${appname}' (1). Contact support@shuffler.io if this persists.`, { + toast.error(`Failed to get API data for '${appname}' (1). Contact ${supportEmail} if this persists.`, { "autoClose": 10000, }) @@ -174,7 +175,7 @@ const ApiExplorerWrapper = (props) => { },3000) } } else { - toast.error(`Failed to get API data for '${appname}' (2). Contact support@shuffler.io if this persists.`, { + toast.error(`Failed to get API data for '${appname}' (2). Contact ${supportEmail} if this persists.`, { "autoClose": 10000, }) setTimeout(()=>{ @@ -539,7 +540,7 @@ const ApiExplorerWrapper = (props) => { }else if (openapi?.id?.length > 0) { appid = openapi?.id; }else{ - toast.error("App id is missing and we can't run the API. Please contact support@shuffler.io if this persists."); + toast.error(`App id is missing and we can't run the API. Please contact ${supportEmail} if this persists.`); return; } @@ -673,7 +674,7 @@ const ApiExplorerWrapper = (props) => { if (data.result.includes("custom_action doesn't exist")) { // No timeout error - toast.info("This API is being rebuilt due to missing functionality. Please wait a minute or two, then try again. If this persists, please report to support@shuffler.io", { + toast.info(`This API is being rebuilt due to missing functionality. Please wait a minute or two, then try again. If this persists, please report to ${supportEmail}`, { "autoClose": 90000, }) } else if (data.result.includes("authentication") && data.result.includes("Oauth2")) { diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index ae234e9e..1eaf5fcb 100755 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { makeStyles } from "@mui/styles"; import { BrowserView, MobileView } from "react-device-detect"; -import theme from '../theme.jsx'; +import { getTheme } from '../theme.jsx'; import { Paper, @@ -57,6 +57,7 @@ import { ToastContainer, toast } from "react-toastify" import words from "shellwords"; import AvatarEditor from "react-avatar-editor"; +import { Context } from "../context/ContextApi.jsx"; const surfaceColor = "#27292D"; const inputColor = "#383B40"; @@ -67,33 +68,7 @@ const bodyDivStyle = { zoom: 0.8, }; -const actionListStyle = { - paddingLeft: "10px", - paddingRight: "10px", - paddingBottom: "10px", - paddingTop: "10px", - marginTop: "5px", - display: "flex", - color: "white", - position: "relative", - backgroundColor: theme.palette.platformColor, -}; - -const boxStyle = { - color: "white", - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - display: "flex", - flexDirection: "column", - - backgroundColor: theme.palette.backgroundColor, -}; const dividerStyle = { marginBottom: "10px", @@ -427,9 +402,40 @@ const AppCreator = (defaultprops) => { const [actionAmount, setActionAmount] = useState(increaseAmount); const [newAppGroup, setNewAppGroup] = useState("") + const { themeMode, supportEmail, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + const [oauth2Scopes, setOauth2Scopes] = useState([]); const [oauth2Type, setOauth2Type] = useState("delegated"); + const actionListStyle = { + paddingLeft: "10px", + paddingRight: "10px", + paddingBottom: "10px", + paddingTop: "10px", + marginTop: "5px", + display: "flex", + color: theme.palette.text.primary, + position: "relative", + + backgroundColor: theme.palette.platformColor, + }; + + const boxStyle = { + color: theme.palette.text.primary, + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + display: "flex", + flexDirection: "column", + + backgroundColor: theme.palette.platformColor, + }; + //client_credentials const [oauth2GrantType, setOauth2GrantType] = useState(""); const defaultAuth = { @@ -739,7 +745,7 @@ const AppCreator = (defaultprops) => { } setBasedata(data); - console.log("Info: ", data) + console.log("Loaded Info: ", data) try { if (data.info !== null && data.info !== undefined) { @@ -753,7 +759,12 @@ const AppCreator = (defaultprops) => { if (data.info.title.length > 29) { setName(data.info.title.slice(0, 29)); } else { - setName(data.info.title); + // Check if fork=true in URL + if (window.location.search.includes("fork=true")) { + setName(data.info.title + " Fork"); + } else { + setName(data.info.title) + } } } @@ -1967,7 +1978,7 @@ const AppCreator = (defaultprops) => { // Saving the app that's been configured. // Save SAVE app const submitApp = () => { - toast("Uploading and building app " + name); + toast.info("Uploading and building app " + name + ". This may take a minute or two."); setAppBuilding(true); setErrorCode(""); @@ -1977,6 +1988,8 @@ const AppCreator = (defaultprops) => { const schemes = [splitBase[0]]; const basePath = "/" + splitBase.slice(3).join("/"); + const newBaseUrl = baseUrl.replaceAll("http//", "http://").replaceAll("https//", "https://") + const data = { openapi: "3.0.0", info: { @@ -1985,7 +1998,7 @@ const AppCreator = (defaultprops) => { version: "1.0", "x-logo": fileBase64, }, - servers: [{ url: baseUrl }], + servers: [{ url: newBaseUrl }], host: host, basePath: basePath, schemes: schemes, @@ -2594,15 +2607,23 @@ const AppCreator = (defaultprops) => { } if (setExtraAuth.length > 0) { + const invalidfieldnames = ["apikey", "username", "password", "username_basic", "password_basic", "url", "access_token"] for (let authkey in extraAuth) { const curauth = extraAuth[authkey]; - if (curauth.name.length === 0 || curauth.name.toLowerCase() == "url") { toast("Can't add extra auth with empty name or Name URL"); setAppBuilding(false); return; } + // Additional comparisonchecks + if (authenticationOption !== "No authentication" && authenticationOption !== "") { + if (invalidfieldnames.includes(curauth.name.toLowerCase())) { + toast.warn("Skipping extra auth field: " + curauth.name + ". This is not valid.") + continue + } + } + data.components.securitySchemes[curauth.name] = { type: "apiKey", in: curauth.type, @@ -2623,12 +2644,17 @@ const AppCreator = (defaultprops) => { credentials: "include", }) .then((response) => { + setAppBuilding(false) if (response.status === 403) { + + var urlParams = new URLSearchParams(window.location.search) if (urlParams.has("id")) { - toast.error("Please log in to build this app. If this error persists, please contact support@shuffler.io") + toast.error(`Please log in to build this app. If this error persists, please contact ${supportEmail}`) } else { - toast.error("Failed to save the app as you are not the owner. Redirecting you to the forking page. When there, save again.") + toast.error("Failed to save the app as you are not the owner. Redirecting you to the forking page. If this does not load, please download and re-import the app.", { + autoClose: 10000 + }) if (props.match.params.appid !== undefined && props.match.params.appid !== null && props.match.params.appid.length > 0) { setTimeout(() => { window.open(`/apps/new?id=${props.match.params.appid}`, "_blank") @@ -2644,22 +2670,26 @@ const AppCreator = (defaultprops) => { //throw new Error("NOT 200 :O") } - setAppBuilding(false); return response.json(); }) .then((responseJson) => { - if (!responseJson.success) { - if (responseJson.extra !== undefined && responseJson.extra !== null) { + if (responseJson?.success !== true) { + if (responseJson?.extra !== undefined && responseJson?.extra !== null) { toast("Failed building: " + responseJson.extra); - } - - if (responseJson.reason !== undefined) { + } else if (responseJson?.reason !== undefined) { setErrorCode(responseJson.reason); - toast.error("Failed to build: " + responseJson.reason, { - autoClose: 10000 - }) - } + if (responseJson?.details !== undefined && responseJson?.details !== null) { + toast.error("Failed to build - contact support@shuffler.io: " + responseJson.details, { + autoClose: 60000 + }) + } else { + toast.error("Failed to build: " + responseJson.reason, { + autoClose: 10000 + }) + } + } else { + } } else { toast.success("Successfully built openapi app! Added job to rebuild it in your hybrid runtime locations (Orborus)."); if (window.location.pathname.includes("/new")) { @@ -2672,18 +2702,18 @@ const AppCreator = (defaultprops) => { .catch((error) => { setAppBuilding(false); setErrorCode(error.toString()); - toast(error.toString()); + toast.error(error.toString()); }); }; const bearerAuth = authenticationOption === "Bearer auth" ? ( -
+

Bearer auth @@ -2696,12 +2726,12 @@ const AppCreator = (defaultprops) => { // Basicauth const basicAuth = authenticationOption === "Basic auth" ? ( -
+

Basic authentication @@ -2795,7 +2825,7 @@ const AppCreator = (defaultprops) => { flex: 2, marginTop: 0, marginBottom: 0, - backgroundColor: inputColor, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, marginRight: 5, }} fullWidth={true} @@ -2804,16 +2834,28 @@ const AppCreator = (defaultprops) => { margin="normal" variant="outlined" defaultValue={extraAuth[index].name} + helperText={ + + {extraAuth[index]?.name?.toLowerCase() === "url" + || extraAuth[index]?.name?.toLowerCase() === "apikey" + ? `ERROR: Invalid key: ${extraAuth[index].name}. ` : ""} + + } onChange={(e) => { extraAuth[index].name = e.target.value; setExtraAuth(extraAuth); }} + onBlur={(e) => { + // Forcerender + setUpdate(Math.random()); + }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + color: theme.palette.textFieldStyle.color, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, minHeight: 50, marginLeft: 5, maxWidth: "95%", @@ -2828,7 +2870,7 @@ const AppCreator = (defaultprops) => { marginTop: 0, marginBottom: 0, flex: 2, - backgroundColor: inputColor, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, marginRight: 5, }} fullWidth={true} @@ -2843,7 +2885,8 @@ const AppCreator = (defaultprops) => { }} InputProps={{ style: { - color: "white", + color: theme.palette.textFieldStyle.color, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, minHeight: 50, marginLeft: 5, maxWidth: "95%", @@ -2861,9 +2904,9 @@ const AppCreator = (defaultprops) => { value={extraAuth[index].type} style={{ flex: 1, - backgroundColor: inputColor, + backgroundColor: theme.palette.backgroundColor, paddingLeft: "10px", - color: "white", + color: theme.palette.text.primary, height: 50, borderRadius: theme.shape.borderRadius, }} @@ -2874,14 +2917,14 @@ const AppCreator = (defaultprops) => { > Header Query @@ -2929,7 +2972,7 @@ const AppCreator = (defaultprops) => { const jwtAuth = authenticationOption === "JWT" ? ( -
+
JWT authentication { variant="outlined" defaultValue={parameterName} helperText={ - + Must start with / and be a valid path } @@ -2958,7 +3001,7 @@ const AppCreator = (defaultprops) => { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -2979,7 +3022,7 @@ const AppCreator = (defaultprops) => { variant="outlined" defaultValue={parameterName} helperText={ - + Must use 'key=value&key=value' format } @@ -2991,7 +3034,7 @@ const AppCreator = (defaultprops) => { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -3001,7 +3044,7 @@ const AppCreator = (defaultprops) => { const oauth2Auth = authenticationOption === "Oauth2" ? ( -
+
Oauth2 authentication { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -3111,7 +3154,7 @@ const AppCreator = (defaultprops) => { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -3160,7 +3203,7 @@ const AppCreator = (defaultprops) => { }} InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -3177,7 +3220,7 @@ const AppCreator = (defaultprops) => { required InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, maxHeight: 160, }, }} @@ -3202,7 +3245,7 @@ const AppCreator = (defaultprops) => { const apiKey = authenticationOption === "API key" ? ( -
+
API key authentication Do NOT put your actual API-key. Add the name of the field used for authentication, e.g. "X-APIKEY". @@ -3221,7 +3264,7 @@ const AppCreator = (defaultprops) => { variant="outlined" value={parameterName} helperText={ - + Can't be empty or contain any of the following: !#$%&'^"+-._~|]+$:= } @@ -3238,7 +3281,7 @@ const AppCreator = (defaultprops) => { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -3255,7 +3298,7 @@ const AppCreator = (defaultprops) => { borderRadius: theme.shape.borderRadius, backgroundColor: inputColor, paddingLeft: 10, - color: "white", + color: theme.palette.text.primary, height: 57, }} inputProps={{ @@ -3271,7 +3314,7 @@ const AppCreator = (defaultprops) => { return ( {data} @@ -3463,15 +3506,14 @@ const AppCreator = (defaultprops) => { { @@ -3557,7 +3599,7 @@ const AppCreator = (defaultprops) => { placeholder={"Query name (key)"} label={"Query Key"} helperText={ - + Click required to flip } @@ -3569,7 +3611,7 @@ const AppCreator = (defaultprops) => { style={{flex: 3}} InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -3589,7 +3631,7 @@ const AppCreator = (defaultprops) => { style={{flex: 2}} InputProps={{ style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -3662,7 +3704,7 @@ const AppCreator = (defaultprops) => { )} { }} key={currentAction} helperText={ - + Shows an example body to the user. ${} creates variables. } @@ -3687,7 +3729,8 @@ const AppCreator = (defaultprops) => { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + color: theme.palette.textFieldStyle.color, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, }, }} /> @@ -3700,7 +3743,7 @@ const AppCreator = (defaultprops) => { Example success response { defaultValue={currentAction["example_response"]} onChange={(e) => setActionField("example_response", e.target.value)} helperText={ - + Helps with autocompletion and understanding of the endpoint } key={currentAction} InputProps={{ style: { - color: "white", + color: theme.palette.textFieldStyle.color, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, }, }} /> @@ -3794,8 +3838,8 @@ const AppCreator = (defaultprops) => { fullWidth PaperProps={{ style: { - backgroundColor: surfaceColor, - color: "white", + backgroundColor: theme.palette.drawer.backgroundColor, + color: theme.palette.text.primary, minWidth: 700, maxWidth: 700, }, @@ -3812,15 +3856,15 @@ const AppCreator = (defaultprops) => { setFileUploadEnabled(false); }} > - + -
New action
+
New action
Learn more about actions @@ -3832,7 +3876,8 @@ const AppCreator = (defaultprops) => { flex: "1", marginTop: 5, marginRight: 15, - backgroundColor: inputColor, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, }} fullWidth={true} placeholder="Name" @@ -3871,7 +3916,8 @@ const AppCreator = (defaultprops) => { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, }, }} /> @@ -3883,7 +3929,8 @@ const AppCreator = (defaultprops) => { flex: "1", marginTop: 5, marginRight: "15px", - backgroundColor: inputColor, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, }} fullWidth={true} placeholder="Description" @@ -3895,7 +3942,8 @@ const AppCreator = (defaultprops) => { onChange={(e) => setActionField("description", e.target.value)} InputProps={{ style: { - color: "white", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, }, }} /> @@ -3917,14 +3965,18 @@ const AppCreator = (defaultprops) => { }} value={currentActionMethod} style={{ - backgroundColor: inputColor, paddingLeft: "10px", - color: "white", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, height: "50px", }} inputProps={{ name: "Method", id: "method-option", + style: { + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, + }, }} > @@ -3939,7 +3991,7 @@ const AppCreator = (defaultprops) => { > { flex: "1", marginRight: "15px", marginTop: "5px", - backgroundColor: inputColor, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, }} fullWidth={true} placeholder="URL path" @@ -3976,7 +4029,7 @@ const AppCreator = (defaultprops) => { setUrlPath(e.target.value); }} helperText={ - + The path to use. Must start with /. Use {"{variablename}"} to have path variables @@ -3987,7 +4040,8 @@ const AppCreator = (defaultprops) => { input: classes.input, }, style: { - color: "white", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, }, }} onBlur={(event) => { @@ -4270,7 +4324,7 @@ const AppCreator = (defaultprops) => { defaultValue={currentAction["file_field"]} onChange={(e) => setActionField("file_field", e.target.value)} helperText={ - + The File field to interact with } @@ -4279,7 +4333,7 @@ const AppCreator = (defaultprops) => { notchedOutline: classes.notchedOutline, }, style: { - color: "white", + color: theme.palette.text.primary, }, }} /> @@ -4294,7 +4348,8 @@ const AppCreator = (defaultprops) => { flex: "1", marginRight: "15px", marginTop: "5px", - backgroundColor: inputColor, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, }} fullWidth={true} placeholder={ @@ -4308,13 +4363,14 @@ const AppCreator = (defaultprops) => { minRows="2" onChange={(e) => setActionField("headers", e.target.value)} helperText={ - + Headers that are part of the request. Default: EMPTY } InputProps={{ style: { - color: "white", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, }, }} /> @@ -4323,14 +4379,14 @@ const AppCreator = (defaultprops) => { {bodyInfo} {exampleResponse} -
+
@@ -3919,13 +3923,13 @@ const AppExplorer = (props) => { -

+

Apps

@@ -3971,9 +3975,7 @@ const AppExplorer = (props) => { ) : null} {appType === 0 || appType === 2 ? ( { const data = openapi; let exportFileDefaultName = name + ".json"; @@ -3994,7 +3996,7 @@ const AppExplorer = (props) => { if (queryID !== undefined && queryID !== null) { aa("init", { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }); const timestamp = new Date().getTime(); @@ -4020,12 +4022,12 @@ const AppExplorer = (props) => { }} > - + ) : null} - {selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.id !== undefined && userdata.support === true ? + {selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.id !== undefined ? // Iconbutton for authentication with just an icon. Link to /apps/authentication?app_id=app.id { }} > - + : null} @@ -4064,7 +4066,7 @@ const AppExplorer = (props) => { } - {isMobile ? null : ( + {isMobile || app?.reference_org === userdata?.active_org?.id ? null : ( { onChange={handleChange} displayEmpty multiple - style={{ borderRadius: 4, height: 45, fontFamily: theme?.typography?.fontFamily, flex: 1 }} + style={{ borderRadius: 4, height: 45, fontFamily: theme?.typography?.fontFamily, flex: 1, backgroundColor: theme.palette.textFieldStyle.backgroundColor, color: theme.palette.textFieldStyle.color }} renderValue={(selected) => { if (selected.length === 0) return 'All Labels'; return ( @@ -930,6 +940,7 @@ const LabelDropdown = ({ items, currentRefinement, refine }) => {
); }; + const CustomLabelDropdown = connectRefinementList(LabelDropdown); @@ -970,6 +981,8 @@ const filterApps = (apps, searchQuery, selectedCategory, selectedLabel) => { const LoginPrompt = () => { const navigate = useNavigate(); + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); return (
{ // Add this new component for the app skeleton const AppSkeleton = () => { + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); return ( { height={90} style={{ borderRadius: 4, - backgroundColor: "rgba(255, 255, 255, 0.1)" + backgroundColor: theme.palette.loaderColor }} />
{ variant="text" width="40%" height={24} - style={{ backgroundColor: "rgba(255, 255, 255, 0.1)" }} + style={{ backgroundColor: theme.palette.loaderColor }} />
@@ -1106,6 +1121,7 @@ const Apps2 = (props) => { const [defaultSearch, setDefaultSearch] = useState(""); const [apps, setApps] = useState([]); + const [backupApps, setBackupApps] = useState([]); const [filteredApps, setFilteredApps] = useState([]); const [appSearchLoading, setAppSearchLoading] = useState(false); const [creatorProfile, setCreatorProfile] = useState({}); @@ -1117,6 +1133,9 @@ const Apps2 = (props) => { const [validation, setValidation] = useState(null); const [createAppModalOpen, setCreateAppModalOpen] = useState(false); + const {themeMode, brandColor} = useContext(Context); + const theme = getTheme(themeMode, brandColor); + const baseRepository = "https://github.com/frikky/shuffle-apps"; const isCloud = @@ -1179,61 +1198,13 @@ const Apps2 = (props) => { 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(() => { - - // 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]) - + getApps() + }, []) // Find top categories and tags based on the current tab useEffect(() => { - if (currTab === 0 || currTab === 1) { + if (currTab === 0 || currTab === 1 || currTab === 3) { setCategories(findTopCategories()); setLabels(findTopTags()); } @@ -1274,11 +1245,13 @@ const Apps2 = (props) => { }); }; + /* useEffect(() => { if (serverside) { return null; } }, [serverside]); + */ const getApps = () => { // Get apps from localstorage @@ -1289,7 +1262,7 @@ const Apps2 = (props) => { if (storageApps === null || storageApps === undefined || storageApps.length === 0) { storageApps = [] } else { - setAppsToShow(storageApps) + //setAppsToShow(storageApps) setOrgApps(storageApps) setApps(storageApps) // setFilteredApps(storageApps) @@ -1325,18 +1298,25 @@ const Apps2 = (props) => { var privateapps = []; var valid = []; var invalid = []; + + var backups = [] for (var key in responseJson) { 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"] } - if (app.is_valid && !(!app.activated && app.generated)) { + if (app?.is_valid && !(!app?.activated && app?.generated)) { privateapps.push(app); } else if ( - app.private_id !== undefined && - app.private_id.length > 0 + app?.private_id !== undefined && + app?.private_id.length > 0 ) { valid.push(app); } else { @@ -1344,49 +1324,39 @@ const Apps2 = (props) => { } } + if (backups.length > 0) { + setBackupApps(backups) + } + privateapps.push(...valid); privateapps.push(...invalid); - console.log("privateapps: setting apps ", privateapps) + setAppsToShow(privateapps); setOrgApps(privateapps); setApps(privateapps); - // setFilteredApps(privateapps); + //setFilteredApps(privateapps); + const filteredOrgApps = filterApps(privateapps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredOrgApps) + if (privateapps.length > 0) { - if (selectedApp.id === undefined || selectedApp.id === null) { - if (privateapps[0].owner !== undefined && privateapps[0].owner !== null) { - getUserProfile(privateapps[0].owner); + if (selectedApp?.id === undefined || selectedApp?.id === null) { + if (privateapps[0]?.owner !== undefined && privateapps[0]?.owner !== null) { + 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 { localStorage.setItem("apps", JSON.stringify(privateapps)) } catch (e) { console.log("Failed to set apps in localstorage: ", e) } } - - //setTimeout(() => { - // setFirstLoad(false) - //}, 5000) }) .catch((error) => { + console.log("Failed to get apps: ", error.toString()); toast(error.toString()); setIsLoading(false); }); @@ -1762,7 +1732,6 @@ const Apps2 = (props) => { // setOpenModal(true); }; - useEffect(() => { const apps = currTab === 1 ? userApps : orgApps; const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel); @@ -1780,33 +1749,38 @@ const Apps2 = (props) => { } else if (newTab === 1) { const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel); setAppsToShow(filteredUserApps); - } + } else if (newTab === 3) { + const filteredUserApps = filterApps(backupApps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredUserApps); + return + } // Update URL query params based on tab index const tabMapping = { 0: 'org_apps', 1: 'my_apps', - 2: 'all_apps' + 2: 'all_apps', + 3: 'backup_apps', }; - const queryParams = new URLSearchParams(location.search); - queryParams.set('tab', tabMapping[newTab]); + const queryParams = new URLSearchParams(location.search); + queryParams.set('tab', tabMapping[newTab]); - // Maintain search query in URL regardless of tab - if (searchQuery) { - queryParams.set('q', searchQuery); - } else { - queryParams.delete('q'); - } + // Maintain search query in URL regardless of tab + if (searchQuery) { + queryParams.set('q', searchQuery); + } else { + queryParams.delete('q'); + } - navigate(`${location.pathname}?${queryParams.toString()}`); + navigate(`${location.pathname}?${queryParams.toString()}`); }; // Update useEffect for filtering without URL manipulation useEffect(() => { 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); setAppsToShow(filteredApps); }, [searchQuery, selectedCategory, selectedLabel, currTab, userApps, orgApps]); @@ -1869,13 +1843,13 @@ const Apps2 = (props) => { } const tabActive = { - borderBottom: "5px solid #FF8544", + borderBottom: `5px solid ${theme.palette.primary.main}`, borderRadius: "2px", - color: "#FF8544" + color: theme.palette.primary.main, } return ( -
+
{
- {currTab === 0 ? "Org" : currTab === 1 ? "Your" : "Discover"} Apps + {currTab === 0 ? "Org" : currTab === 1 ? "Your" : currTab === 3 ? "Backup" : "Discover"} Apps {isCloud ? null : ( @@ -1922,7 +1896,7 @@ const Apps2 = (props) => { style={{ height: 45, minWidth: 45, - backgroundColor: "#2F2F2F", + backgroundColor: theme.palette.platformColor, borderRadius: 4, padding: "8px 16px", }} @@ -1932,9 +1906,9 @@ const Apps2 = (props) => { }} > {isLoading ? ( - + ) : ( - + )} @@ -1962,7 +1936,7 @@ const Apps2 = (props) => { style={{ height: 45, minWidth: 45, - backgroundColor: "#2F2F2F", + backgroundColor: theme.palette.platformColor, borderRadius: 4, padding: "8px 16px", }} @@ -1974,9 +1948,9 @@ const Apps2 = (props) => { }} > {isLoading ? ( - + ) : ( - + )} @@ -1984,7 +1958,7 @@ const Apps2 = (props) => { )}
-
+
handleTabChange(event, newTab)} @@ -2009,6 +1983,7 @@ const Apps2 = (props) => { ...(currTab === 1 ? tabActive : {}) }} /> + { ...(currTab === 2 ? tabActive : {}) }} /> + + {backupApps.length > 0 && + + }
@@ -2025,7 +2011,7 @@ const Apps2 = (props) => { minWidth: "25%", maxWidth: "25%" }}> - {(currTab === 0 || currTab === 1) ? ( + {(currTab === 0 || currTab === 1 || currTab === 3) ? ( { value={searchQuery} id="shuffle_search_field" onChange={handleSearchChange} + style={{ + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, + }} onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); @@ -2043,7 +2033,10 @@ const Apps2 = (props) => { InputProps={{ style: { borderRadius: 4, - height: 45 + height: 45, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, + fontSize: 18, }, endAdornment: ( @@ -2087,7 +2080,9 @@ const Apps2 = (props) => { style={{ borderRadius: 4, height: 45, - fontFamily: theme?.typography?.fontFamily + fontFamily: theme?.typography?.fontFamily, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + fontSize: 18, }} renderValue={(selected) => { if (selected.length === 0) return 'All Categories'; @@ -2113,6 +2108,7 @@ const Apps2 = (props) => { ))} + {selectedCategory.length > 0 && ( { style={{ borderRadius: 4, height: 45, - fontFamily: theme?.typography?.fontFamily + fontFamily: theme?.typography?.fontFamily, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + fontSize: 18, }} renderValue={(selected) => { if (selected.length === 0) return 'All Labels'; @@ -2211,13 +2209,11 @@ const Apps2 = (props) => { width: '100%', borderRadius: '4px', textTransform: 'none', - backgroundColor: "#FF8544", - color: "#1A1A1A", fontFamily: theme?.typography?.fontFamily, fontSize: 16, fontWeight: 500 }} - startIcon={} + startIcon={} > Create an App @@ -2226,7 +2222,10 @@ const Apps2 = (props) => {
{ - currTab === 0 && ( + currTab !== 0 && currTab !== 3 ? + null + : + (
{isLoading ? ( @@ -2258,7 +2257,7 @@ const Apps2 = (props) => { handleAppClick={handleAppClick} leftSideBarOpenByClick={leftSideBarOpenByClick} userdata={userdata} - fetchApps={fetchApps} + fetchApps={getApps} setUserApps={setUserApps} appsToShow={appsToShow} @@ -2311,7 +2310,7 @@ const Apps2 = (props) => { {appsToShow.map((data, index) => ( { var height = "auto" var width = isArticlePage ? 1000 : isFormPage ? 400: 750 + const { themeMode } = useContext(Context) + const theme = getTheme(themeMode) + const docsImageStyle = { border: isFormPage ? null : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, @@ -215,6 +219,8 @@ export const CodeHandler = (props) => { const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : "" const validate = validateJson(propvalue) + const {themeMode } = useContext(Context) + const theme = getTheme(themeMode) var newprop = propvalue if (validate.valid === false) { @@ -293,6 +299,7 @@ const Docs = (defaultprops) => { let navigate = useNavigate(); const location = useLocation(); + const pathname = location.pathname // Quickfix for react router 5 -> 6 const params = useParams(); //var props = JSON.parse(JSON.stringify(defaultprops)) @@ -301,6 +308,8 @@ const Docs = (defaultprops) => { props.match.params = params //console.log("PARAMS: ", params) + const { themeMode } = useContext(Context) + const theme = getTheme(themeMode) const [mobile, setMobile] = useState(serverMobile === true || isMobile === true ? true : false); const [data, setData] = useState(""); @@ -326,9 +335,37 @@ const Docs = (defaultprops) => { const [sidebarOpen, setSidebarOpen] = useState(false); const [activeSubItem, setActiveSubItem] = useState(false); const headingElementsRef = useRef({}) + const [hasRedirected, setHasRedirected] = useState(false) var isArticlePage = window.location.pathname.includes("/articles/") || window.location.pathname === "/articles" ? true : false; const searchFieldRef = useRef(null); + const handleDocRedirectForPartners = () => { + if (hasRedirected) return; + + if ( + userdata && + userdata?.org_status?.includes("integration_partner") && + userdata?.active_org?.branding?.documentation_link?.length > 0 + ) { + const docLink = userdata?.active_org?.branding?.documentation_link; + if (docLink && docLink !== "") { + setHasRedirected(true); + if (docLink.startsWith('http')) { + window.location.replace(docLink); + } else { + navigate(docLink); + } + } + } + }; + + + useEffect(() => { + if (isLoggedIn && isLoaded) { + handleDocRedirectForPartners() + } + + }, [isLoggedIn, isLoaded]); useEffect(() => { fetchDocList(); @@ -353,6 +390,8 @@ const Docs = (defaultprops) => { navigate('/docs/apps#app-creation-introduction') } } + + }, [location]); useEffect(() => { @@ -501,19 +540,25 @@ const Docs = (defaultprops) => { } }} PaperProps={{ - style: { - color: "white", - minWidth: 750, + sx: { + color: theme.palette.DialogStyle.color, + minWidth: "750px", minHeight: "180px", maxHeight: "85vh", - borderRadius: 16, + borderRadius: theme.palette.DialogStyle.borderRadius, border: "1px solid var(--Container-Stroke, #494949)", - background: "var(--Container, #000000)", + background: theme.palette.DialogStyle.backgroundColor, boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)", position: "fixed", top: "70px", left: "50%", transform: "translateX(-50%)", + '& .MuiDialogContent-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogTitle-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, }, }} sx={{ @@ -526,7 +571,7 @@ const Docs = (defaultprops) => { { } const SidebarPaperStyle = { - backgroundColor: isArticlePage ? "transparent" : "rgb(26,26,26)", + backgroundColor: isArticlePage ? "transparent" : theme.palette.backgroundColor, border: isArticlePage ? "none" : undefined, borderRadius: isArticlePage ? "none" : undefined, boxShadow: isArticlePage ? "none" : undefined, @@ -663,9 +708,9 @@ const Docs = (defaultprops) => { extraInfo = (
{ href={selectedMeta.link} style={{ textDecoration: "none", color: "#f85a3e" }} > - @@ -788,7 +833,7 @@ const Docs = (defaultprops) => { paddingLeft: "0.3em", rotate: "-30deg", paddingTop: "0.9em", display: props.level === 1 ? "none" : "block", }}> - +
{isArticlePage ? (userdata?.support ? extraInfo : "") : extraInfo} @@ -1028,13 +1073,12 @@ const Docs = (defaultprops) => { } const markdownStyle = { - color: "rgba(255, 255, 255, 0.90)", + color: theme.palette.textColor, overflow: "hidden", paddingBottom: 100, margin: "auto", maxWidth: "100%", minWidth: "100%", - overflow: "hidden", fontSize: isMobile ? "1.3rem" : "1.1rem", }; @@ -1188,6 +1232,7 @@ const Docs = (defaultprops) => { const activeHrefStyleToc2 = { ...hrefStyleToc2, color: "#f86a3e", + fontFamily: theme.typography.fontFamily, }; const activeListItemStyle = { @@ -1228,9 +1273,10 @@ const Docs = (defaultprops) => { ), style: { - backgroundColor: "#212121", + backgroundColor: theme.palette.platformColor, borderRadius: 4, - color: "white", + fontSize: 18, + color: theme.palette.textColor, }, }} style={{ @@ -1290,16 +1336,17 @@ const Docs = (defaultprops) => { > {newname} @@ -1371,7 +1418,7 @@ const Docs = (defaultprops) => { )} {tocLines.length > 0 ? ( -

Table Of Content

+

Table Of Content

) : null}
{ paddingLeft: isArticlePage ? "0" : "8px", paddingRight: isArticlePage ? "0" : "8px", lineHeight: "20px", - color: activeId === data.id ? "#f86a3e" : "inherit", }} onClick={(e) => { handleCollapse(index) setActiveId(data.id) }} > - {data.title} + + {data.title} + {data.items.length > 0 ? ( - <>{isopen == index ? : } + <>{isopen === index ? : } ) : null} { @@ -1414,7 +1462,7 @@ const Docs = (defaultprops) => { return ( { // e.preventDefault() @@ -1577,7 +1625,7 @@ const Docs = (defaultprops) => { // Padding and zIndex etc set because of footer in cloud. const loadedCheck = ( - + ); @@ -1589,6 +1637,8 @@ return
{loadedCheck}
; export default Docs; const DocsContent = memo(({postDataBrowser, postDataMobile}) => { + const { themeMode } = useContext(Context); + const theme = getTheme(themeMode); return(
{postDataBrowser} @@ -1596,10 +1646,22 @@ const DocsContent = memo(({postDataBrowser, postDataMobile}) => {
)}) -const DocsWrapper = memo(({isLoggedIn, isLoaded, children })=>{ +const DocsWrapper = memo(({isLoggedIn, isLoaded, children, userdata })=>{ const { leftSideBarOpenByClick, windowWidth } = useContext(Context); + useEffect(() => { + if (isLoaded && isLoggedIn && userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.branding.documentation_link?.length > 0) { + window.location.href = userdata?.active_org?.branding.documentation_link; + } + }, [isLoaded, isLoggedIn, userdata]); + + if (isLoaded && isLoggedIn && userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.branding.documentation_link?.length > 0) { + return ; + } + + + return (
{ const [showPassword, setShowPassword] = useState(false) const [ssoUrl, setSSOUrl] = useState(""); - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "migration.shuffler.io"; + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "migration.shuffler.io" || window.location.host === "sandbox.shuffler.io"; const parsedsearch = serverside === true ? "" : window.location.search useEffect(() => { @@ -314,12 +314,15 @@ const LoginPage = props => { }, }) - if (serverside !== true) { - const tmpMessage = new URLSearchParams(window.location.search).get("message") - if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) { - setMessage(tmpMessage) + useEffect(() => { + if (serverside !== true) { + const tmpMessage = new URLSearchParams(window.location.search).get("message") + if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) { + setMessage(tmpMessage) + toast(tmpMessage) + } } - } + }, []) if (document !== undefined) { if (register) { @@ -361,7 +364,7 @@ const LoginPage = props => { console.log("Should login instead of register!") setRegister(!register) } else { - console.log("Path: " + path, "Register: " + register) + //console.log("Path: " + path, "Register: " + register) } } @@ -444,16 +447,27 @@ const LoginPage = props => { } if (isLoggedIn === true && serverside !== true) { - const tmpView = new URLSearchParams(window.location.search).get("view") - if (tmpView !== undefined && tmpView !== null && tmpView === "pricing") { - window.location.pathname = "/pricing" - return - } else if (tmpView !== undefined && tmpView !== null) { - window.location.pathname = tmpView - return - } + setTimeout(() => { + const tmpView = new URLSearchParams(window.location.search).get("view"); + if (tmpView !== undefined && tmpView !== null) { + let pathOnly = tmpView.split("?")[0]; + if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly; - window.location.pathname = "/workflows" + if (pathOnly === "/pricing" || pathOnly === "admin") { + window.location.replace(pathOnly + window.location.search); + } else { + if(localStorage.getItem("redirectId") !== null && localStorage.getItem("redirectId") !== undefined) { + const redirectId = localStorage.getItem("redirectId") + localStorage.removeItem("redirectId") + pathOnly = pathOnly + "/" + redirectId + } + window.location.replace(pathOnly); + } + return; + } + + window.location.pathname = "/workflows" + }, 2000); } const checkAdmin = () => { @@ -468,6 +482,11 @@ const LoginPage = props => { response.json().then((responseJson) => { if (responseJson["success"] === false) { setLoginInfo(responseJson["reason"]); + + if (responseJson?.reason?.toLowerCase().includes("connection refused")) { + navigate("/loginsetup") + } + } else { if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) { setSSOUrl(responseJson.sso_url); @@ -525,74 +544,75 @@ const LoginPage = props => { 'Content-Type': 'application/json; charset=utf-8', }, }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for login:O!"); + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for login:O!"); + } + + return response.json(); + }) + .then((responseJson) => { + + setLoginLoading(false) + + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + if (responseJson?.region_url !== undefined && responseJson?.region_url !== null && responseJson?.region_url !== "") { + toast.info("Set region to " + responseJson.region_url) + localStorage.setItem("globalUrl", responseJson.region_url) } - return response.json(); - }) - .then((responseJson) => { + if (responseJson["reason"] === "MFA_REDIRECT") { + setLoginInfo("Enter the 6-digit MFA code.") + setMFAField(true) + return - setLoginLoading(false) - - console.log("Resp from backend: ", responseJson) - - if (responseJson["success"] === false) { + } + else if (responseJson["reason"] === "MFA_SETUP") { + window.location.href = `/login/${responseJson.url}/mfa-setup`; + return; + } + else if (responseJson["reason"] === "SSO_REDIRECT") { + //navigate(responseJson["url"]) + window.location.href = responseJson["url"] + return + } + else if (responseJson["reason"] !== undefined && responseJson["reason"] !== null && responseJson["reason"].includes("error")) { setLoginInfo(responseJson["reason"]) + return } - else { - if (responseJson["reason"] === "MFA_REDIRECT") { - setLoginInfo("Enter the 6-digit MFA code.") - setMFAField(true) - return - } - else if (responseJson["reason"] === "MFA_SETUP") { - window.location.href = `/login/${responseJson.url}/mfa-setup`; + setLoginInfo("Successful login! Redirecting you in 3 seconds...") + for (var key in responseJson["cookies"]) { + setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }) + } + + setTimeout(() => { + const tmpView = new URLSearchParams(window.location.search).get("view"); + if (tmpView !== undefined && tmpView !== null) { + let pathOnly = tmpView.split("?")[0]; + if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly; + + if (pathOnly === "/pricing" || pathOnly === "admin") { + window.location.replace(pathOnly + window.location.search); + } else { + if(localStorage.getItem("redirectId") !== null && localStorage.getItem("redirectId") !== undefined) { + const redirectId = localStorage.getItem("redirectId") + localStorage.removeItem("redirectId") + pathOnly = pathOnly + "/" + redirectId + } + window.location.replace(pathOnly); + } return; } - else if (responseJson["reason"] === "SSO_REDIRECT") { - //navigate(responseJson["url"]) - window.location.href = responseJson["url"] - return - } - else if (responseJson["reason"] !== undefined && responseJson["reason"] !== null && responseJson["reason"].includes("error")) { - setLoginInfo(responseJson["reason"]) - return - } - - - setLoginInfo("Successful login! Redirecting you in 3 seconds...") - for (var key in responseJson["cookies"]) { - setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }) - } - - const tmpView = new URLSearchParams(window.location.search).get("view") - if (tmpView !== undefined && tmpView !== null) { - //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}` - // Check if slash in the url - - var newUrl = `/${tmpView}` - if (tmpView.startsWith("/")) { - newUrl = `${tmpView}` - } - - console.log("Found url: ", newUrl) - - window.location.pathname = newUrl - return - } - - console.log("LOGIN DATA: ", responseJson) if (responseJson.tutorials !== undefined && responseJson.tutorials !== null) { // Find welcome in responseJson.tutorials under key name const welcome = responseJson.tutorials.find(function (element) { return element.name === "welcome"; }) - console.log("Welcome: ", welcome) if (welcome === undefined || welcome === null) { console.log("RUN login Welcome!!") @@ -604,12 +624,13 @@ const LoginPage = props => { } window.location.pathname = "/workflows" - } - }) - .catch(error => { - setLoginInfo("Error from login API: " + error) - setLoginLoading(false) - }); + }, 2000); + } + }) + .catch(error => { + setLoginInfo("Error from login API: " + error) + setLoginLoading(false) + }); } else { url = baseurl + '/api/v1/register'; fetch(url, { @@ -632,19 +653,33 @@ const LoginPage = props => { //setLoginInfo("Successful register!") //var newpath = "/login?message=Successfully signed up. You can now sign in." //const tmpMessage = new URLSearchParams(window.location.search).get("message") - setLoginInfo("Successful registration! Redirecting in 3 seconds...") + for (var key in responseJson["cookies"]) { setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }) } + setLoginLoading(false) + setLoginInfo("Successful registration! Redirecting in 3 seconds...") + + setTimeout(() => { console.log("LOGIN DATA: ", responseJson) const tmpView = new URLSearchParams(window.location.search).get("view") if (tmpView !== undefined && tmpView !== null) { - //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}` - const newUrl = `/${tmpView}` - window.location.pathname = newUrl + let pathOnly = tmpView.split("?")[0]; + if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly; + + if (pathOnly === "/pricing" || pathOnly === "admin") { + window.location.replace(pathOnly + window.location.search); + } else { + if(localStorage.getItem("redirectId") !== null && localStorage.getItem("redirectId") !== undefined) { + const redirectId = localStorage.getItem("redirectId") + localStorage.removeItem("redirectId") + pathOnly = pathOnly + "/" + redirectId + } + window.location.replace(pathOnly); + } return } @@ -652,9 +687,8 @@ const LoginPage = props => { console.log("RUN Welcome!!") //window.location.pathname = "/welcome?tab=2" window.location.href = "/welcome" - }, 1500); + }, 2000); } - setLoginLoading(false) }), ) .catch(error => { diff --git a/frontend/src/views/LoginPageOld.jsx b/frontend/src/views/LoginPageOld.jsx index 541cd903..aaee73d6 100755 --- a/frontend/src/views/LoginPageOld.jsx +++ b/frontend/src/views/LoginPageOld.jsx @@ -85,7 +85,13 @@ const LoginDialog = (props) => { .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]); + setLoginInfo(responseJson["reason"]) + + if (responseJson?.reason?.toLowerCase().includes("connection refused")) { + setLoginViewLoading(true) + start() + } + } else { if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) { diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx index e7c617b8..a9a6cf5c 100644 --- a/frontend/src/views/RunWorkflow.jsx +++ b/frontend/src/views/RunWorkflow.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-multi-comp */ -import React, {useState, useEffect} from 'react'; +import React, {useState, useEffect, useContext} from 'react'; import ReactDOM from "react-dom" import ReactJson from "react-json-view-ssr"; @@ -48,6 +48,7 @@ import { Edit as EditIcon, Polyline as PolylineIcon, } from '@mui/icons-material'; +import { Context } from '../context/ContextApi.jsx'; const hrefStyle = { color: "white", @@ -58,6 +59,7 @@ const hrefStyle = { const RunWorkflow = (defaultprops) => { const { globalUrl, userdata, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops; + const { supportEmail } = useContext(Context); let navigate = useNavigate(); const [_, setUpdate] = useState(""); // Used to force rendring, don't remove const [explorerUi, setExplorerUi] = useState(false) @@ -471,7 +473,7 @@ const RunWorkflow = (defaultprops) => { } if (response.status === 401 || response.status === 403) { - toast("This Form is not available for you to run. If you this is an error, contact support@shuffler.io with a link to this form") + toast(`This Form is not available for you to run. If you this is an error, contact ${supportEmail} with a link to this form`) } return response.json() @@ -627,7 +629,7 @@ const RunWorkflow = (defaultprops) => { } if (response.status === 401 || response.status === 403) { - toast("This Form is not available to you. If you think this is an error, please contact support@shuffler.io with the URL.") + toast(`This Form is not available to you. If you think this is an error, please contact ${supportEmail} with the URL.`) } return response.json() diff --git a/frontend/src/views/SetAuthentication.jsx b/frontend/src/views/SetAuthentication.jsx index 63aec23c..2ec3e780 100755 --- a/frontend/src/views/SetAuthentication.jsx +++ b/frontend/src/views/SetAuthentication.jsx @@ -1,13 +1,14 @@ -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { Typography, CircularProgress } from "@mui/material"; import theme from '../theme.jsx'; import { red, } from "../views/AngularWorkflow.jsx" +import { Context } from "../context/ContextApi.jsx"; const SetAuthentication = (props) => { const { globalUrl } = props; - + const { supportEmail } = useContext(Context); var headers = { "Content-Type": "application/json", "Accept": "application/json", @@ -328,7 +329,7 @@ const SetAuthentication = (props) => { {failed ? "Failed auth. Error: " : ""} {response}

- {failed ? "If the error persists, try to use fewer scopes. Contact support@shuffler.io if you need further assistance, and include the current URL and a screenshot. You may now close this window." : ""} + {failed ? `If the error persists, try to use fewer scopes. Contact ${supportEmail} if you need further assistance, and include the current URL and a screenshot. You may now close this window.` : ""}
); diff --git a/frontend/src/views/SettingsPage.jsx b/frontend/src/views/SettingsPage.jsx index c19cd987..ec5ba658 100755 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { useNavigate } from "react-router-dom"; -import theme from '../theme.jsx'; +import {getTheme} from '../theme.jsx'; import { Grid, Typography, @@ -15,6 +15,7 @@ import { //import { useAlert import { ToastContainer, toast } from "react-toastify"; import "../codeeditor-index.css"; +import { Context } from "../context/ContextApi.jsx"; import { FileCopy, Visibility, VisibilityOff } from "@mui/icons-material"; import IconButton from "@mui/material/IconButton"; @@ -40,6 +41,8 @@ const Settings = (props) => { const [MFARequired, setMFARequired] = React.useState(false); const [image2FA, setImage2FA] = React.useState(""); const [value2FA, setValue2FA] = React.useState(""); + const {themeMode, supportEmail} = useContext(Context); + const theme = getTheme(themeMode); // const [file, setFile] = React.useState(""); // const [fileBase64, setFileBase64] = React.useState( @@ -83,7 +86,7 @@ const Settings = (props) => { const boxStyle = { flex: "1", - color: "white", + color: theme.palette.text.primary, position: "relative", marginLeft: "10px", marginRight: "10px", @@ -213,8 +216,8 @@ const Settings = (props) => { left: "50%", transform: "translate(-50%, -50%)", zIndex: "9999", - backgroundColor: "#1a1a1a", - color: "white", + backgroundColor: theme.palette.backgroundColor, + color: theme.palette.text.primary, padding: 20, borderRadius: 5, boxShadow: "0 0 10px rgba(0, 0, 0, 0.3)", @@ -223,7 +226,7 @@ const Settings = (props) => { }; const closeIconButtonStyling = { - color: "white", + color: theme.palette.text.primary, border: "none", backgroundColor: "transparent", marginLeft: "90%", @@ -243,7 +246,7 @@ const Settings = (props) => { width: "100%", fontSize: 16, backgroundColor: disabled ? "gray" : "red", - color: "white", + color: theme.palette.text.primary, cursor: disabled === false && "pointer", }; const checkboxStyle = { @@ -381,7 +384,7 @@ const Settings = (props) => { className="ais-RefinementList-checkbox" onClick={handleCheckBoxEvent} /> -
@@ -410,7 +413,7 @@ const Settings = (props) => { endAdornment: ( {showPassword ? : } @@ -455,7 +458,12 @@ const Settings = (props) => { if (responseJson["success"] === false) { setPasswordFormMessage(responseJson["reason"]); } 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(""); } }) @@ -704,7 +712,7 @@ const Settings = (props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -732,7 +740,7 @@ const Settings = (props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -757,7 +765,7 @@ const Settings = (props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -788,7 +796,7 @@ const Settings = (props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -806,7 +814,7 @@ const Settings = (props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, }, endAdornment: ( <> @@ -857,7 +865,7 @@ const Settings = (props) => { InputProps={{ style:{ height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -877,7 +885,7 @@ const Settings = (props) => { InputProps={{ style:{ height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -899,7 +907,7 @@ const Settings = (props) => { InputProps={{ style:{ height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -919,7 +927,7 @@ const Settings = (props) => { InputProps={{ style:{ height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -954,7 +962,7 @@ const Settings = (props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -979,7 +987,7 @@ const Settings = (props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -1002,7 +1010,7 @@ const Settings = (props) => { InputProps={{ style: { height: "50px", - color: "white", + color: theme.palette.text.primary, }, }} color="primary" @@ -1046,7 +1054,7 @@ const Settings = (props) => { {isCloud ? - By joining the Creator Incentive Program and connecting your Github account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. Support: support@shuffler.io + By joining the Creator Incentive Program and connecting your Github account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. Support: {supportEmail}
+ */}
<>
diff --git a/frontend/src/views/Usecases2.jsx b/frontend/src/views/Usecases2.jsx index 886427b3..036c26a4 100644 --- a/frontend/src/views/Usecases2.jsx +++ b/frontend/src/views/Usecases2.jsx @@ -9,7 +9,7 @@ import { Context } from "../context/ContextApi.jsx" import { ToastContainer, toast } from "react-toastify" import { makeStyles, } from "@mui/styles" import classNames from "classnames" -import theme from '../theme.jsx' +import {getTheme} from '../theme.jsx' import { Autocomplete, @@ -123,8 +123,16 @@ const ParseUsecaseDesc = (priority, appFramework) => { const UsecaseListComponent = (props) => { const { keys, userdata, isCloud, globalUrl, frameworkData, isLoggedIn, workflows, setWorkflows, getFramework, setFrameworkData, } = props + const { themeMode, brandName } = useContext(Context) + const theme = getTheme(themeMode) - + const usecaseLightThemeColor = { + "collect": "#FB47A0", + "enrich": "#F38B14", + "detect": "#0AAD65", + "respond": "#289BDB", + "verify": "#624CE9", + } const [expandedIndex, setExpandedIndex] = useState(-1); const [expandedItem, setExpandedItem] = useState(-1); const [inputUsecase, setInputUsecase] = useState({}); @@ -199,8 +207,8 @@ const UsecaseListComponent = (props) => { const LoadingSkeleton = () => (
{/* Header skeleton */} - - + + {/* Apps selection skeleton */} @@ -220,7 +228,7 @@ const UsecaseListComponent = (props) => { variant="circular" width={40} height={40} - sx={{ bgcolor: 'grey.800' }} + sx={{ bgcolor: theme.palette.loaderColor}} /> ))}
@@ -232,7 +240,7 @@ const UsecaseListComponent = (props) => { sx={{ marginTop: "10px", borderRadius: 20, - bgcolor: 'grey.800' + bgcolor: theme.palette.loaderColor }} /> @@ -275,13 +283,13 @@ const UsecaseListComponent = (props) => { variant="circular" width={30} height={30} - sx={{ bgcolor: 'grey.800' }} + sx={{ bgcolor: theme.palette.loaderColor}} />
{/* Usecase title */} @@ -289,7 +297,7 @@ const UsecaseListComponent = (props) => { variant="text" width="70%" height={24} - sx={{ bgcolor: 'grey.800' }} + sx={{ bgcolor: theme.palette.loaderColor}} /> @@ -596,10 +604,10 @@ const UsecaseListComponent = (props) => { return (
- - Usecases + + Usecases - + Choose a template tailored to your automation requirements, ready for immediate use. @@ -742,7 +750,7 @@ const UsecaseListComponent = (props) => { {keys.map((usecase, index) => { return (
- + {index+1}. {usecase.name.slice(3, 100)} @@ -887,6 +895,9 @@ const Usecases2 = (props) => { const [keys, setKeys] = useState([]) const [treeKeys, setTreeKeys] = useState([]) + const { themeMode, brandName } = useContext(Context) + const theme = getTheme(themeMode) + const [selectedUsecaseCategory, setSelectedUsecaseCategory] = useState(""); const [selectedUsecases, setSelectedUsecases] = useState([]); const [usecases, setUsecases] = useState([]) @@ -987,7 +998,7 @@ const Usecases2 = (props) => { } - document.title = "Shuffle - usecases"; + document.title = brandName?.length > 0 ? `${brandName} - usecases` : "Shuffle - usecases"; var dayGraphLabels = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130]; var dayGraphData = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130]; diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 100eed13..3fe551f4 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -440,6 +440,10 @@ export const collapseField = (field, inputdata) => { return true } + if (field.name === "result") { + return false + } + if (field.type === "array") { return true } @@ -454,6 +458,112 @@ export const collapseField = (field, inputdata) => { return false } +export const HandleJsonCopy = (base, copy, base_node_name) => { + if (typeof copy.name === "string") { + copy.name = copy.name.replaceAll(" ", "_"); + } + + //lol + if (typeof base === 'object' || typeof base === 'dict') { + base = JSON.stringify(base) + } + + if (base_node_name === "execution_argument" || base_node_name === "Runtime Argument") { + base_node_name = "exec" + } + + //console.log("COPY: ", base_node_name, copy); + + //var newitem = JSON.parse(base); + var newitem = validateJson(base).result + + var to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_"); + for (let copykey in copy.namespace) { + if (copy.namespace[copykey].includes("Results for")) { + continue; + } + + if (newitem !== undefined && newitem !== null) { + newitem = newitem[copy.namespace[copykey]]; + if (!isNaN(copy.namespace[copykey])) { + to_be_copied += ".#"; + } else { + to_be_copied += "." + copy.namespace[copykey]; + } + } + } + + if (newitem !== undefined && newitem !== null) { + newitem = newitem[copy.name]; + if (!isNaN(copy.name)) { + to_be_copied += ".#"; + } else { + to_be_copied += "." + copy.name; + } + } + + to_be_copied = to_be_copied.replaceAll(" ", "_"); + console.log("COPY: ", to_be_copied); + const elementName = "copy_element_shuffle"; + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + //console.log("NAVIGATOR: ", navigator); + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(to_be_copied); + copyText.select(); + copyText.setSelectionRange(0, 99999); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + //console.log("COPYING!"); + toast("Copied JSON path to clipboard.") + } else { + console.log("Couldn't find element ", elementName); + } +} + +export const handleReactJsonClipboard = (copy) => { + const elementName = "copy_element_shuffle"; + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + if ( + copy.namespace !== undefined && + copy.name !== undefined && + copy.src !== undefined + ) { + copy = copy.src; + } + + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + var stringified = JSON.stringify(copy); + if (stringified.startsWith('"') && stringified.endsWith('"')) { + stringified = stringified.substring(1, stringified.length - 1); + } + + navigator.clipboard.writeText(stringified); + copyText.select(); + copyText.setSelectionRange(0, 99999); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + console.log("COPYING!"); + toast("Copied value to clipboard, NOT json path.") + } else { + console.log("Failed to copy from " + elementName + ": ", copyText); + } +} + export const validateJson = (showResult) => { if (showResult === undefined || showResult === null) { return { diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx index c4b80b46..d702f24f 100644 --- a/frontend/src/views/Workflows2.jsx +++ b/frontend/src/views/Workflows2.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useContext, memo, useState, useRef } from "react"; import { useLocation, useNavigate, Link } from "react-router-dom"; import ReactDOM from "react-dom" +import { getTheme } from "../theme.jsx"; // Material UI Icons import Add from '@mui/icons-material/Add'; @@ -93,6 +94,11 @@ import { Visibility as VisibilityIcon, EditNote as EditNoteIcon, ErrorOutline as ErrorOutlineIcon, + Coronavirus as CoronavirusIcon, + Fingerprint as FingerprintIcon, + Psychology as PsychologyIcon, + Wifi as WifiIcon, + Devices as DevicesIcon, } from "@mui/icons-material"; // Additional Components @@ -103,7 +109,7 @@ import Dropzone from "../components/Dropzone.jsx"; import { ToastContainer, toast } from "react-toastify" import { MuiChipsInput } from "mui-chips-input"; import { v4 as uuidv4 } from "uuid"; -import theme from "../theme.jsx"; +// import theme from "./theme.jsx"; import algoliasearch from 'algoliasearch/lite'; import { InstantSearch, Configure, connectHits, connectSearchBox, connectRefinementList } from 'react-instantsearch-dom'; import { debounce } from "lodash"; @@ -112,43 +118,12 @@ import { removeQuery } from "../components/ScrollToTop.jsx"; import {green, yellow, red, grey } from "../views/AngularWorkflow.jsx" -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240"); +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e"); const svgSize = 24; const imagesize = 22; -const useStyles = makeStyles(() => { - return { - datagrid: { - border: 0, - "& .MuiDataGrid-columnsContainer": { - backgroundColor: - theme?.palette?.type === "light" ? "#fafafa" : theme?.palette?.inputColor, - }, - "& .MuiDataGrid-iconSeparator": { - display: "none", - }, - "& .MuiDataGrid-colCell, .MuiDataGrid-cell": { - borderRight: `1px solid ${theme?.palette?.type === "light" ? "white" : "#303030" - }`, - }, - "& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": { - borderBottom: `1px solid ${theme?.palette?.type === "light" ? "#f0f0f0" : "#303030" - }`, - }, - "& .MuiDataGrid-cell": { - color: - theme?.palette?.type === "light" ? "white" : "rgba(255,255,255,0.65)", - }, - "& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption": - { - borderRadius: 0, - color: "white", - }, - }, - } -}) @@ -160,19 +135,20 @@ const useStyles = makeStyles(() => { export const GetIconInfo = (action) => { // Finds the icon based on the action. Should be verbs. const iconList = [ - { key: "cases", values: ["cases"] }, + { key: "cases", values: ["cases", "ticket", "alert"] }, { key: "cache_add", values: ["set_cache"] }, { key: "cache_get", values: ["get_cache"] }, { key: "filter", values: ["filter"] }, { key: "merge", values: ["join", "merge", "route", "router"] }, { key: "search", - values: ["search", "find", "locate", "index", "analyze", "anal", "match", "check cache", "check", "verify", "validate"], + values: ["search", "find", "locate", "index", "analyze", "anal", "match", "check cache", "check", "verify", "validate", "siem", ], }, { key: "list", values: ["list", "head", "options"] }, { key: "download", values: [ + "ingest", "capture", "get", "download", @@ -186,20 +162,6 @@ export const GetIconInfo = (action) => { }, { key: "add", values: ["add", "accept",] }, { key: "delete", values: ["delete", "remove", "clear", "clean", "dismiss",] }, - { - key: "send", - values: [ - "send", - "dispatch", - "mail", - "forward", - "post", - "submit", - "mark", - "set", - "release", - ], - }, { key: "repeat", values: ["repeat", "retry", "pause", "skip", "copy", "replicat", "demo",], @@ -229,8 +191,29 @@ export const GetIconInfo = (action) => { key: "compare", values: ["compare", "convert", "to", "filter", "translate", "parse"], }, + { key: "assets", values: ["cmdb", "assets", "asset", "cmdb", "inventory", "host", "hosts", "device", "devices"] }, { key: "close", values: ["close", "stop", "cancel", "block"] }, { key: "communication", values: ["communication", "comms", "email", "mail",] }, + { key: "eradication", values: ["eradication", "edr", "xdr"] }, + { key: "iam", values: ["iam", "identity", "access", "auth", "authentication", "authorization", "oauth", "sso", "openid"] }, + { key: "intel", values: ["intel", "feed", "threat intel", "threat intelligence", "ti", "t.i.", "t.i", "ti.", "rule", "technique", "tactic", "techniques", "tactics", "ioc", "indicator",] }, + { key: "network", values: ["network", "net", "networking", "firewall", "proxy", "vpn", "sdwan", "sd-wan"] }, + { + key: "send", + values: [ + "send", + "dispatch", + "mail", + "forward", + "post", + "submit", + "mark", + "set", + "release", + ], + }, + + ]; var selectedKey = "" @@ -389,7 +372,45 @@ export const GetIconInfo = (action) => { iconBackgroundColor: "green", originalIcon: , }, - }; + eradication: { + icon: "", + iconColor: "white", + iconBackgroundColor: "green", + originalIcon: , + }, + iam: { + icon: "", + iconColor: "white", + iconBackgroundColor: "green", + originalIcon: , + }, + intel: { + icon: "", + iconColor: "white", + iconBackgroundColor: "green", + originalIcon: , + }, + network: { + icon: "", + iconColor: "white", + iconBackgroundColor: "green", + originalIcon: , + }, + assets: { + icon: "", + iconColor: "white", + iconBackgroundColor: "green", + originalIcon: , + }, + } + + /* + { key: "eradication", values: ["eradication", "edr", "xdr"] }, + { key: "iam", values: ["iam", "identity", "access", "auth", "authentication", "authorization", "oauth", "sso", "openid"] }, + { key: "intel", values: ["intel", "threat intel", "threat intelligence", "ti", "t.i.", "t.i", "ti."] }, + { key: "network", values: ["network", "net", "networking", "firewall", "proxy", "vpn", "sdwan", "sd-wan"] }, + { key: "siem", values: ["siem", "security information and event management", "security information and event management", "security information and event management"] }, + */ var selectedItem = parsedIcons[selectedKey]; if (selectedItem === undefined || selectedItem === null) { @@ -406,11 +427,8 @@ export const GetIconInfo = (action) => { selectedItem.iconBackgroundColor = defaultColor; } - if (selectedItem.icon === "" || selectedItem.icon === undefined) { - console.log( - `MISSING PATH FOR ${selectedKey} (find in scope): `, - selectedItem.originalIcon.type.type - ); + if ((selectedItem.icon === "" || selectedItem.icon === undefined) && (selectedItem.originalIcon === undefined || selectedItem.originalIcon === "")) { + console.log(`MISSING PATH FOR ${selectedKey} (find in scope): `, selectedItem.originalIcon.type.type) } if ( @@ -436,19 +454,7 @@ export const GetIconInfo = (action) => { return selectedItem; }; -const chipStyle = { - backgroundColor: "#2F2F2F", - marginRight: 5, - paddingLeft: 5, - paddingRight: 5, - height: 35, - cursor: "pointer", - borderColor: "#2F2F2F", - color: "#C8C8C8", - fontSize: "14px", - fontFamily: theme?.typography?.fontFamily, - borderRadius: "17.5px" -}; + export const collapseField = (field) => { if (field === undefined || field === null) { @@ -626,12 +632,14 @@ export const validateJson = (showResult) => { //Custom hook for handling styling of the dropzone const useDropzoneStyles = () => { const { leftSideBarOpenByClick } = useContext(Context); + const { themeMode, brandColor } = useContext(Context); + const theme = getTheme(themeMode, brandColor); return { paddingTop: 70, // minHeight: 1000, - backgroundColor: "#1A1A1A", - fontFamily: theme?.typography?.fontFamily, + backgroundColor: theme.palette.backgroundColor, + fontFamily: theme.typography?.fontFamily, // maxWidth: window.innerWidth > 1366 ? 1366 : isMobile ? "100%" : 1200, paddingLeft: leftSideBarOpenByClick ? 200 : 0, transition: "padding-left 0.3s ease", @@ -662,14 +670,65 @@ const Workflows2 = (props) => { const [isLoadingWorkflow, setIsLoadingWorkflow] = useState(false); const [isLoadingPublicWorkflow, setIsLoadingPublicWorkflow] = useState(false); const [view, setView] = useState(localStorage?.getItem("workflowView") || "grid"); - const classes = useStyles(theme) const imgSize = 60; + const { themeMode, brandColor, brandName } = useContext(Context); + const theme = getTheme(themeMode, brandColor); + const chipStyle = { + backgroundColor: theme.palette.chipStyle.backgroundColor, + marginRight: 5, + paddingLeft: 5, + paddingRight: 5, + height: 35, + cursor: "pointer", + borderColor: theme.palette.chipStyle.borderColor, + color: theme.palette.chipStyle.color, + fontSize: "14px", + fontFamily: theme.typography?.fontFamily, + borderRadius: "17.5px" + }; + + const newStyles = makeStyles(() => { + + return { + datagrid: { + border: 0, + "& .MuiDataGrid-columnsContainer": { + backgroundColor: + theme.palette?.type === "light" ? "#fafafa" : theme.palette?.inputColor, + }, + "& .MuiDataGrid-iconSeparator": { + display: "none", + }, + "& .MuiDataGrid-colCell, .MuiDataGrid-cell": { + borderRight: `1px solid ${theme.palette?.type === "light" ? "white" : "#303030" + }`, + }, + "& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": { + borderBottom: `1px solid ${theme.palette?.type === "light" ? "#f0f0f0" : "#303030" + }`, + }, + "& .MuiDataGrid-cell": { + color: + theme.palette?.type === "light" ? "white" : "rgba(255,255,255,0.65)", + }, + "& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption": + { + borderRadius: 0, + color: "white", + }, + }, + } + }) + + const classes = newStyles(theme); + const referenceUrl = globalUrl + "/api/v1/hooks/"; var upload = ""; const [workflows, setWorkflows] = React.useState([]); + const [backupWorkflows, setBackupWorkflows] = React.useState([]); const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove const [selectedUsecases, setSelectedUsecases] = React.useState([]); const [filteredWorkflows, setFilteredWorkflows] = React.useState([]); @@ -726,7 +785,7 @@ const Workflows2 = (props) => { const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); - document.title = "Shuffle - Workflows"; + document.title = brandName?.length > 0 ? `${brandName} - Workflows` : "Shuffle - Workflows"; useEffect(() => { const queryParams = new URLSearchParams(location.search); @@ -744,11 +803,18 @@ const Workflows2 = (props) => { const handleTabChange = (event, newValue) => { setCurrTab(newValue); + + //if (view === "list" && currTab > 0) { + // setView("grid") + // setUpdate(Math.random()) + //} + // Update URL query params based on tab index const tabMapping = { 0: 'org_workflows', 1: 'my_workflows', - 2: 'all_workflows' + 2: 'all_workflows', + 3: 'backup_apps', }; const queryParams = new URLSearchParams(location.search); queryParams.set('tab', tabMapping[newValue]); @@ -757,9 +823,6 @@ const Workflows2 = (props) => { navigate(`${location.pathname}?${queryParams.toString()}`); }; - - - const handleCreateWorkflow = () => { setModalOpen(true) setIsEditing(false) @@ -1099,23 +1162,31 @@ const Workflows2 = (props) => { setSelectedWorkflowId(""); }} PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: 500, - padding: 50, - }, - }} + sx: { + borderRadius: theme?.palette?.DialogStyle?.borderRadius, + border: theme?.palette?.DialogStyle?.border, + minWidth: '440px', + 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, + }, + } + }} > -
+
Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId)?.name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}?
Other workflows relying on {selectedWorkflowIndexes.length > 0 ? "them" : "it"} one will stop working
@@ -2240,6 +2336,11 @@ const Workflows2 = (props) => { setOpen(false); setAnchorEl(null); }} + MenuListProps={{ + sx: { + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + } + }} > {isDistributed ? { : null} { event.stopPropagation() @@ -2274,7 +2375,7 @@ const Workflows2 = (props) => { { window.open(`/forms/${data.id}`, "_blank") }} @@ -2287,7 +2388,7 @@ const Workflows2 = (props) => { { sideloadWorkflow(data.id, "publish") @@ -2301,8 +2402,8 @@ const Workflows2 = (props) => { { sideloadWorkflow(data.id, "export", setOpen) @@ -2317,7 +2418,7 @@ const Workflows2 = (props) => { { duplicateWorkflow(data) @@ -2330,7 +2431,7 @@ const Workflows2 = (props) => { { setDeleteModalOpen(true); setSelectedWorkflowId(data.id); @@ -2447,7 +2548,7 @@ const Workflows2 = (props) => { return ( -
+
{selectedCategory !== "" ? @@ -2461,7 +2562,7 @@ const Workflows2 = (props) => { width: 3, backgroundColor: boxColor, borderRadius: "0 100px 0 0", - fontFamily: theme?.typography?.fontFamily, + fontFamily: theme.typography?.fontFamily, }} onClick={() => { addFilter(selectedCategory) @@ -2472,7 +2573,7 @@ const Workflows2 = (props) => { {currTab === 2 ? null : @@ -2526,7 +2627,7 @@ const Workflows2 = (props) => { Edit: {data.name} @@ -2553,13 +2654,13 @@ const Workflows2 = (props) => { } placement="right"> @@ -2594,7 +2695,7 @@ const Workflows2 = (props) => { style={{ height: 24, width: 24, - filter: "brightness(0.6)", + filter: themeMode === "dark" ? "brightness(0.6)" : "brightness(0.9)", cursor: "pointer", }} onClick={() => { @@ -2751,7 +2852,7 @@ const Workflows2 = (props) => { overflow: "hidden", marginTop: 8, maxHeight: 35, - fontFamily: theme?.typography?.fontFamily, + fontFamily: theme.typography?.fontFamily, }} > {data.tags !== undefined && data.tags !== null @@ -3014,7 +3115,7 @@ const Workflows2 = (props) => { data.status, ).then((response) => { if (response !== undefined) { - toast("Successfully imported " + data.name); + toast.success("Imported " + data.name); } }); } @@ -3366,6 +3467,8 @@ const Workflows2 = (props) => { return obj; }) + console.log("ROWS: ", rows) + workflowData = ( { ); } return ( -
+
{ onClick={() => { localStorage.setItem("view", "list"); setView("list"); + + setCurrTab(0) }} > @@ -3957,20 +4062,30 @@ const Workflows2 = (props) => { // } useEffect(() => { - if (currTab === 2) return; - if (userdata !== undefined && userdata !== null && filteredWorkflows.length > 0) { + if (filteredWorkflows.length > 0) { + var categoryWorkflows = [] if (currTab === 0) { categoryWorkflows = filteredWorkflows.filter(workflow => workflow?.org_id === userdata?.active_org?.id) - setOrgWorkflows(categoryWorkflows) + + if (categoryWorkflows.length === 0) { + setOrgWorkflows(filteredWorkflows) + } else { + setOrgWorkflows(categoryWorkflows) + } } else if (currTab === 1) { categoryWorkflows = filteredWorkflows.filter(workflow => workflow?.org_id === userdata?.active_org?.id && workflow?.owner === userdata?.id) setMyWorkflows(categoryWorkflows) } + setIsLoadingWorkflow(false); - } + } else { + if (currTab === 0 && workflows?.length > 0) { + setOrgWorkflows(workflows) + } + } }, [currTab, workflows, userdata, filteredWorkflows, filters]) @@ -3984,8 +4099,8 @@ const Workflows2 = (props) => { const iconButtonStyle = { - color: 'white', - backgroundColor: '#212121', + color: theme.palette.text.primary, + backgroundColor: theme.palette.platformColor, borderRadius: '4px', padding: "12px 16px", cursor: 'pointer', @@ -4085,20 +4200,20 @@ const Workflows2 = (props) => { maxWidth: "25%", minWidth: "25%", height: 47, - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, }} InputProps={{ style: { - color: "white", + color: theme.palette.textFieldStyle.color, height: "100%", - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, }, placeholder: "Search Workflows", }} sx={{ '& .MuiOutlinedInput-root': { borderRadius: '4px', - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, }, }} value={localQuery} @@ -4140,9 +4255,9 @@ const Workflows2 = (props) => { maxWidth: "25%", height: 47, borderRadius: 4, - backgroundColor: "#212121", - fontFamily: theme?.typography?.fontFamily, - color: "#FFFFFF", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + fontFamily: theme.typography?.fontFamily, + color: theme.palette.textFieldStyle.color, }} MenuProps={{ anchorOrigin: { @@ -4155,9 +4270,9 @@ const Workflows2 = (props) => { }, PaperProps: { style: { - backgroundColor: '#212121', - color: '#FFFFFF', - fontFamily: theme?.typography?.fontFamily, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, + fontFamily: theme.typography?.fontFamily, } } }} @@ -4193,7 +4308,7 @@ const Workflows2 = (props) => { ) } > - + All Usecases {items.map((usecase, index) => ( @@ -4206,13 +4321,13 @@ const Workflows2 = (props) => { '&:hover': { backgroundColor: '#3A3A3A', // Darker background on hover }, - fontFamily: theme?.typography?.fontFamily, + fontFamily: theme.typography?.fontFamily, fontSize: 16 }} > {usecase.label} ({usecase.count}) @@ -4225,7 +4340,7 @@ const Workflows2 = (props) => { const tabStyle = { textTransform: 'none', marginRight: 20, - fontFamily: theme?.typography?.fontFamily, + fontFamily: theme.typography?.fontFamily, fontSize: 16, borderBottom: "5px solid transparent", minHeight: "48px", @@ -4233,9 +4348,9 @@ const Workflows2 = (props) => { } const tabActive = { - borderBottom: "5px solid #FF8544", + borderBottom: `5px solid ${theme.palette.primary.main}`, borderRadius: "2px", - color: "#FF8544" + color: theme.palette.primary.main } @@ -4258,16 +4373,16 @@ const Workflows2 = (props) => { maxWidth: isSafari ? "100%" : "70%", margin: "auto", }}> - + {currTab === 0 ? "Org" : currTab === 1 ? "Your" : "Discover"} Workflows -
+
handleTabChange(event, newTab)} style={{ - fontFamily: theme?.typography?.fontFamily, + fontFamily: theme.typography?.fontFamily, fontSize: 16, marginBottom: "-2px" }} @@ -4296,6 +4411,17 @@ const Workflows2 = (props) => { }} /> + {backupWorkflows.length > 0 && + + } + { @@ -4305,7 +4431,7 @@ const Workflows2 = (props) => { ...tabStyle, marginRight: 0, marginLeft: 25, - ...(currTab === 3 ? tabActive : {}) + ...(currTab === 4 ? tabActive : {}) }} /> @@ -4327,16 +4453,17 @@ const Workflows2 = (props) => { minWidth: "25%", height: 43, maxHeight: "fit-content", - backgroundColor: "#212121", - zIndex: 1000, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + zIndex: 1000, + color: theme.palette.textFieldStyle.color }} disabled={currTab === 2} InputProps={{ style: { - color: "white", height: "fit-content", maxHeight: "fit-content", - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color }, placeholder: "Filter Workflows", // endAdornment: ( @@ -4359,7 +4486,8 @@ const Workflows2 = (props) => { '& .MuiOutlinedInput-root': { height: "fit-content", borderRadius: '4px', - backgroundColor: '#212121', + color: theme.palette.textFieldStyle.color, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, '& fieldset': { borderColor: 'rgba(255, 255, 255, 0.23)', }, @@ -4373,10 +4501,12 @@ const Workflows2 = (props) => { display: 'flex', flexWrap: 'wrap', gap: '4px', + fontSize: 18, padding: '4px 8px', alignItems: 'center', height: "fit-content", // Match height - backgroundColor: "#212121", + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color }, // Rest of the styling remains the same... @@ -4424,8 +4554,9 @@ const Workflows2 = (props) => { maxWidth: "25%", height: 47, borderRadius: 4, - backgroundColor: "#212121", - fontFamily: theme?.typography?.fontFamily + fontSize: 18, + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + fontFamily: theme.typography?.fontFamily, }} sx={{ '& .MuiOutlinedInput-root': { @@ -4460,12 +4591,12 @@ const Workflows2 = (props) => { removeFilter(filters.indexOf(usecase?.name.toLowerCase())) } }} - style={{ + sx={{ padding: "12px 16px", borderBottom: index === usecases.length - 2 ? "none" : "1px solid rgba(255,255,255,0.05)", "&:hover": { backgroundColor: "rgba(255,255,255,0.1)" - } + }, }} >
{ style={{ padding: 0, marginRight: 8, - color: "rgba(255,255,255,0.7)" + color: theme.palette.textFieldStyle.color, }} />
{ @@ -4500,8 +4631,8 @@ const Workflows2 = (props) => { { onClick={() => navigate("/workflows/debug")} disabled={currTab === 2} > - + - + { const newView = view === "grid" ? "list" : "grid"; localStorage.setItem("workflowView", newView); setView(newView); + + if (view === "grid") { + setCurrTab(0) + } }} disabled={currTab === 2} > {view === "grid" ? - : - + : + } @@ -4568,7 +4703,7 @@ const Workflows2 = (props) => { > {submitLoading ? : - + } @@ -4587,7 +4722,7 @@ const Workflows2 = (props) => { disabled={isCloud || currTab === 2} onClick={() => exportAllWorkflows(workflows)} > - +
@@ -4600,13 +4735,11 @@ const Workflows2 = (props) => { borderRadius: 4, flex: 0.8, textTransform: 'none', - backgroundColor: "#FF8544", - color: "#1A1A1A", - fontFamily: theme?.typography?.fontFamily, + fontFamily: theme.typography?.fontFamily, fontSize: 16, fontWeight: 500 }} - startIcon={} + startIcon={} > Create Workflow @@ -4637,9 +4770,33 @@ const Workflows2 = (props) => { paddingBottom: 40 }}> + {currTab !== 0 ? null : + orgWorkflows.length === 0 ? + + No workflows found in this org with the Org ID filter. If this is an error, please click the "List View" button at the top to see ALL available workflows ({workflows.length}). + : + orgWorkflows.map((data, index) => { + // Shouldn't be a part of this list + if (data.public === true) { + return null + } + // if (firstLoad) { + // workflowDelay += 75 + // } else { + // return + // } - {currTab === 0 && orgWorkflows.map((data, index) => { + return ( + + {/**/} + + {/**/} + + ) + })} + + {currTab === 3 && backupWorkflows.map((data, index) => { // Shouldn't be a part of this list if (data.public === true) { return null @@ -4661,24 +4818,29 @@ const Workflows2 = (props) => { })} { - currTab === 1 && myWorkflows.map((data, index) => { - if (data.public === true) { - return null - } + currTab !== 1 ? null : + myWorkflows.length === 0 ? + + No workflows found in this org for your user. + : + myWorkflows.map((data, index) => { + if (data.public === true) { + return null + } - // if (firstLoad) { - // workflowDelay += 75 - // } else { - // return - // } + // if (firstLoad) { + // workflowDelay += 75 + // } else { + // return + // } - return ( - - {/**/} - - {/**/} - - ) + return ( + + {/**/} + + {/**/} + + ) }) }
@@ -5042,7 +5204,7 @@ const Workflows2 = (props) => { //isLoaded && isLoggedIn && workflowDone ? ( const loadedCheck = - workflowDone ? ( + workflowDone && isLoaded ? (
{/* @@ -5105,7 +5267,9 @@ const Workflows2 = (props) => { }} > - Loading Workflows + + Loading Workflows and Apps +
); diff --git a/functions/kubernetes/charts/shuffle/README.md b/functions/kubernetes/charts/shuffle/README.md index ecc8c265..6c3a4ed2 100644 --- a/functions/kubernetes/charts/shuffle/README.md +++ b/functions/kubernetes/charts/shuffle/README.md @@ -217,7 +217,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia | `backend.affinity` | Affinity for backend pods assignment | `{}` | | `backend.nodeSelector` | Node labels for backend pods assignment | `{}` | | `backend.tolerations` | Tolerations for backend pods assignment | `[]` | -| `backend.updateStrategy.type` | backend deployment strategy type | `RollingUpdate` | +| `backend.updateStrategy.type` | backend deployment strategy type | `Recreate` | | `backend.priorityClassName` | backend pods' priorityClassName | `""` | | `backend.topologySpreadConstraints` | Topology Spread Constraints for backend pod assignment spread across your cluster among failure-domains | `[]` | | `backend.schedulerName` | Name of the k8s scheduler (other than default) for backend pods | `""` | @@ -244,6 +244,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia | `backend.autoscaling.hpa.maxReplicas` | Maximum number of replicas | `""` | | `backend.autoscaling.hpa.targetCPU` | Target CPU utilization percentage | `""` | | `backend.autoscaling.hpa.targetMemory` | Target Memory utilization percentage | `""` | +| `backend.service.labels` | Extra labels for backend service | `{}` | | `backend.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | | `backend.serviceAccount.name` | The name of the ServiceAccount to use. | `""` | | `backend.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` | @@ -359,6 +360,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia | `frontend.autoscaling.hpa.maxReplicas` | Maximum number of replicas | `""` | | `frontend.autoscaling.hpa.targetCPU` | Target CPU utilization percentage | `""` | | `frontend.autoscaling.hpa.targetMemory` | Target Memory utilization percentage | `""` | +| `frontend.service.labels` | Extra labels for frontend service | `{}` | | `frontend.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | | `frontend.serviceAccount.name` | The name of the ServiceAccount to use. | `""` | | `frontend.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` | @@ -476,23 +478,38 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia ### worker Parameters -| Name | Description | Value | -| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `worker.image.registry` | worker image registry | `ghcr.io` | -| `worker.image.repository` | worker image repository | `shuffle/shuffle-worker` | -| `worker.image.tag` | worker image tag (immutable tags are recommended, defaults to appVersion) | `""` | -| `worker.image.digest` | worker image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` | -| `worker.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | -| `worker.serviceAccount.name` | The name of the ServiceAccount to use. | `""` | -| `worker.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` | -| `worker.serviceAccount.automountServiceAccountToken` | Automount service account token for the worker service account | `true` | -| `worker.serviceAccount.imagePullSecrets` | Add image pull secrets to the worker service account | `[]` | -| `worker.rbac.create` | Specifies whether RBAC resources should be created | `true` | -| `worker.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` | -| `worker.networkPolicy.allowExternal` | Don't require server label for connections | `true` | -| `worker.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` | -| `worker.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` | -| `worker.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` | +| Name | Description | Value | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `worker.image.registry` | worker image registry | `ghcr.io` | +| `worker.image.repository` | worker image repository | `shuffle/shuffle-worker` | +| `worker.image.tag` | worker image tag (immutable tags are recommended, defaults to appVersion) | `""` | +| `worker.image.digest` | worker image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag image tag (immutable tags are recommended) | `""` | +| `worker.podSecurityContext.enabled` | Enable worker pods' Security Context | `true` | +| `worker.podSecurityContext.fsGroupChangePolicy` | Set filesystem group change policy for worker pods | `Always` | +| `worker.podSecurityContext.sysctls` | Set kernel settings using the sysctl interface for worker pods | `[]` | +| `worker.podSecurityContext.supplementalGroups` | Set filesystem extra groups for worker pods | `[]` | +| `worker.podSecurityContext.fsGroup` | Set fsGroup in worker pods' Security Context | `1001` | +| `worker.containerSecurityContext.enabled` | Enabled worker container' Security Context | `true` | +| `worker.containerSecurityContext.seLinuxOptions` | Set SELinux options in worker container | `{}` | +| `worker.containerSecurityContext.runAsUser` | Set runAsUser in worker container' Security Context | `1001` | +| `worker.containerSecurityContext.runAsGroup` | Set runAsGroup in worker container' Security Context | `1001` | +| `worker.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in worker container' Security Context | `true` | +| `worker.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in worker container' Security Context | `true` | +| `worker.containerSecurityContext.privileged` | Set privileged in worker container' Security Context | `false` | +| `worker.containerSecurityContext.allowPrivilegeEscalation` | Set allowPrivilegeEscalation in worker container' Security Context | `false` | +| `worker.containerSecurityContext.capabilities.drop` | List of capabilities to be dropped in worker container | `["ALL"]` | +| `worker.containerSecurityContext.seccompProfile.type` | Set seccomp profile in worker container | `RuntimeDefault` | +| `worker.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | +| `worker.serviceAccount.name` | The name of the ServiceAccount to use. | `""` | +| `worker.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` | +| `worker.serviceAccount.automountServiceAccountToken` | Automount service account token for the worker service account | `true` | +| `worker.serviceAccount.imagePullSecrets` | Add image pull secrets to the worker service account | `[]` | +| `worker.rbac.create` | Specifies whether RBAC resources should be created | `true` | +| `worker.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` | +| `worker.networkPolicy.allowExternal` | Don't require server label for connections | `true` | +| `worker.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` | +| `worker.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` | +| `worker.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` | ### app Parameters @@ -509,6 +526,33 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia | `app.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` | | `app.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` | | `app.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` | +| `app.exposedContainerPort` | The port that shuffle app containers will listen on for new requests. | `80` | +| `app.podSecurityContext.enabled` | Enable app pods' Security Context | `true` | +| `app.podSecurityContext.fsGroupChangePolicy` | Set filesystem group change policy for app pods | `Always` | +| `app.podSecurityContext.sysctls` | Set kernel settings using the sysctl interface for app pods | `[]` | +| `app.podSecurityContext.supplementalGroups` | Set filesystem extra groups for app pods | `[]` | +| `app.podSecurityContext.fsGroup` | Set fsGroup in app pods' Security Context | `1001` | +| `app.containerSecurityContext.enabled` | Enabled app container' Security Context | `true` | +| `app.containerSecurityContext.seLinuxOptions` | Set SELinux options in app container | `{}` | +| `app.containerSecurityContext.runAsUser` | Set runAsUser in app container' Security Context | `1001` | +| `app.containerSecurityContext.runAsGroup` | Set runAsGroup in app container' Security Context | `1001` | +| `app.containerSecurityContext.runAsNonRoot` | Set runAsNonRoot in app container' Security Context | `true` | +| `app.containerSecurityContext.readOnlyRootFilesystem` | Set readOnlyRootFilesystem in app container' Security Context | `true` | +| `app.containerSecurityContext.privileged` | Set privileged in app container' Security Context | `false` | +| `app.containerSecurityContext.allowPrivilegeEscalation` | Set allowPrivilegeEscalation in app container' Security Context | `false` | +| `app.containerSecurityContext.capabilities.drop` | List of capabilities to be dropped in app container | `["ALL"]` | +| `app.containerSecurityContext.seccompProfile.type` | Set seccomp profile in app container | `RuntimeDefault` | +| `app.serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | +| `app.serviceAccount.name` | The name of the ServiceAccount to use. | `""` | +| `app.serviceAccount.annotations` | Additional Service Account annotations (evaluated as a template) | `{}` | +| `app.serviceAccount.automountServiceAccountToken` | Automount service account token for the app service account | `true` | +| `app.serviceAccount.imagePullSecrets` | Add image pull secrets to the app service account | `[]` | +| `app.rbac.create` | Specifies whether RBAC resources should be created | `true` | +| `app.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` | +| `app.networkPolicy.allowExternal` | Don't require server label for connections | `true` | +| `app.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` | +| `app.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` | +| `app.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` | ### Traffic Exposure Parameters diff --git a/functions/kubernetes/charts/shuffle/templates/backend/backend-cm-env.yaml b/functions/kubernetes/charts/shuffle/templates/backend/backend-cm-env.yaml index e7138dc0..dbbdff94 100644 --- a/functions/kubernetes/charts/shuffle/templates/backend/backend-cm-env.yaml +++ b/functions/kubernetes/charts/shuffle/templates/backend/backend-cm-env.yaml @@ -8,18 +8,20 @@ metadata: annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} data: - BACKEND_PORT: "5001" + BACKEND_PORT: "{{ .Values.backend.containerPorts.http }}" {{- if .Values.shuffle.baseUrl }} BASE_URL: "{{ .Values.shuffle.baseUrl }}" SSO_REDIRECT_URL: "{{ .Values.shuffle.baseUrl }}" {{- else }} - BASE_URL: "http://{{ include "shuffle.backend.name" . }}:5001" + BASE_URL: "http://{{ include "shuffle.backend.name" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.backend.containerPorts.http }}" {{- end }} ORG_ID: "{{ .Values.shuffle.org }}" SHUFFLE_APP_DOWNLOAD_LOCATION: "{{ .Values.backend.apps.downloadLocation }}" SHUFFLE_DOWNLOAD_AUTH_BRANCH: "{{ .Values.backend.apps.downloadBranch }}" SHUFFLE_APP_FORCE_UPDATE: "{{ .Values.backend.apps.forceUpdate }}" SHUFFLE_CHAT_DISABLED: "true" + # Sets backend_url parameter for workflow execution to the cluster-internal shuffle-backend address + SHUFFLE_CLOUDRUN_URL: "http://{{ include "shuffle.backend.name" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.backend.containerPorts.http }}" SHUFFLE_OPENSEARCH_URL: {{ include "common.tplvalues.render" (dict "value" .Values.backend.openSearch.url "context" $) }} SHUFFLE_OPENSEARCH_USERNAME: "{{ .Values.backend.openSearch.username }}" SHUFFLE_OPENSEARCH_CERTIFICATE_FILE: "{{ .Values.backend.openSearch.certificateFile }}" diff --git a/functions/kubernetes/charts/shuffle/templates/backend/backend-svc.yaml b/functions/kubernetes/charts/shuffle/templates/backend/backend-svc.yaml index 18328899..990f9410 100644 --- a/functions/kubernetes/charts/shuffle/templates/backend/backend-svc.yaml +++ b/functions/kubernetes/charts/shuffle/templates/backend/backend-svc.yaml @@ -3,7 +3,8 @@ kind: Service metadata: name: {{ template "shuffle.backend.name" . }} namespace: {{ include "common.names.namespace" . | quote }} - labels: {{- include "shuffle.backend.labels" (dict "customLabels" .Values.commonLabels "context" $) | nindent 4 }} + {{- $serviceLabels := include "common.tplvalues.merge" (dict "values" (list .Values.backend.service.labels .Values.commonLabels) "context" .) }} + labels: {{- include "shuffle.backend.labels" (dict "customLabels" $serviceLabels "context" $) | nindent 4 }} {{- if .Values.commonAnnotations }} annotations: {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} {{- end }} @@ -14,5 +15,6 @@ spec: port: {{ .Values.backend.containerPorts.http }} targetPort: http protocol: TCP + appProtocol: http {{- $podLabels := include "common.tplvalues.merge" (dict "values" (list .Values.backend.podLabels .Values.commonLabels) "context" .) }} selector: {{- include "shuffle.backend.matchLabels" (dict "customLabels" $podLabels "context" $) | nindent 4 }} diff --git a/functions/kubernetes/charts/shuffle/templates/frontend/frontend-svc.yaml b/functions/kubernetes/charts/shuffle/templates/frontend/frontend-svc.yaml index 76851c0a..de2db9fb 100644 --- a/functions/kubernetes/charts/shuffle/templates/frontend/frontend-svc.yaml +++ b/functions/kubernetes/charts/shuffle/templates/frontend/frontend-svc.yaml @@ -3,7 +3,8 @@ kind: Service metadata: name: {{ template "shuffle.frontend.name" . }} namespace: {{ include "common.names.namespace" . | quote }} - labels: {{- include "shuffle.frontend.labels" (dict "customLabels" .Values.commonLabels "context" $) | nindent 4 }} + {{- $serviceLabels := include "common.tplvalues.merge" (dict "values" (list .Values.frontend.service.labels .Values.commonLabels) "context" .) }} + labels: {{- include "shuffle.frontend.labels" (dict "customLabels" $serviceLabels "context" $) | nindent 4 }} {{- if .Values.commonAnnotations }} annotations: {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} {{- end }} @@ -14,11 +15,13 @@ spec: port: {{ .Values.frontend.containerPorts.http }} targetPort: http protocol: TCP + appProtocol: http {{- if .Values.frontend.containerPorts.https }} - name: https port: {{ .Values.frontend.containerPorts.https }} targetPort: https protocol: TCP + appProtocol: https {{- end }} {{- $podLabels := include "common.tplvalues.merge" (dict "values" (list .Values.frontend.podLabels .Values.commonLabels) "context" .) }} selector: {{- include "shuffle.frontend.matchLabels" (dict "customLabels" $podLabels "context" $) | nindent 4 }} diff --git a/functions/kubernetes/charts/shuffle/templates/orborus/orborus-cm-env.yaml b/functions/kubernetes/charts/shuffle/templates/orborus/orborus-cm-env.yaml index 57b020a7..504b674d 100644 --- a/functions/kubernetes/charts/shuffle/templates/orborus/orborus-cm-env.yaml +++ b/functions/kubernetes/charts/shuffle/templates/orborus/orborus-cm-env.yaml @@ -11,7 +11,7 @@ data: ENVIRONMENT_NAME: "{{ .Values.shuffle.org }}" ORG_ID: "{{ .Values.shuffle.org }}" TZ: "{{ .Values.shuffle.timezone }}" - BASE_URL: "http://{{ include "shuffle.backend.name" . }}.{{ .Release.Namespace }}.svc.cluster.local:5001" + BASE_URL: "http://{{ include "shuffle.backend.name" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.backend.containerPorts.http }}" KUBERNETES_NAMESPACE: "{{ .Release.Namespace }}" KUBERNETES_SERVICE_ACCOUNT: {{ include "shuffle.orborus.serviceAccount.name" . }} SHUFFLE_WORKER_IMAGE: "{{ include "shuffle.worker.image" . }}" diff --git a/functions/kubernetes/charts/shuffle/templates/orborus/orborus-dpl.yaml b/functions/kubernetes/charts/shuffle/templates/orborus/orborus-dpl.yaml index 62a23242..fd73dd15 100644 --- a/functions/kubernetes/charts/shuffle/templates/orborus/orborus-dpl.yaml +++ b/functions/kubernetes/charts/shuffle/templates/orborus/orborus-dpl.yaml @@ -88,8 +88,26 @@ spec: value: "true" - name: SHUFFLE_WORKER_SERVICE_ACCOUNT_NAME value: {{ include "shuffle.worker.serviceAccount.name" . }} + - name: SHUFFLE_APP_EXPOSED_PORT + value: {{ .Values.app.exposedContainerPort | quote }} + {{- if .Values.worker.podSecurityContext.enabled }} + - name: SHUFFLE_WORKER_POD_SECURITY_CONTEXT + value: {{ omit .Values.worker.podSecurityContext "enabled" | mustToJson | quote }} + {{- end }} + {{- if .Values.worker.containerSecurityContext.enabled }} + - name: SHUFFLE_WORKER_CONTAINER_SECURITY_CONTEXT + value: {{ include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.worker.containerSecurityContext "context" $) | fromYaml | mustToJson | quote }} + {{- end }} - name: SHUFFLE_APP_SERVICE_ACCOUNT_NAME value: {{ include "shuffle.app.serviceAccount.name" . }} + {{- if .Values.app.podSecurityContext.enabled }} + - name: SHUFFLE_APP_POD_SECURITY_CONTEXT + value: {{ omit .Values.app.podSecurityContext "enabled" | mustToJson | quote }} + {{- end }} + {{- if .Values.app.containerSecurityContext.enabled }} + - name: SHUFFLE_APP_CONTAINER_SECURITY_CONTEXT + value: {{ include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.app.containerSecurityContext "context" $) | fromYaml | mustToJson | quote }} + {{- end }} {{- if .Values.orborus.extraEnvVars }} {{- include "common.tplvalues.render" (dict "value" .Values.orborus.extraEnvVars "context" $) | nindent 12 }} {{- end }} diff --git a/functions/kubernetes/charts/shuffle/templates/shuffle-app/shuffle-app-network-policy.yaml b/functions/kubernetes/charts/shuffle/templates/shuffle-app/shuffle-app-network-policy.yaml index d4a24fe6..a8493002 100644 --- a/functions/kubernetes/charts/shuffle/templates/shuffle-app/shuffle-app-network-policy.yaml +++ b/functions/kubernetes/charts/shuffle/templates/shuffle-app/shuffle-app-network-policy.yaml @@ -29,6 +29,16 @@ spec: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system + # Allow access to backend + - ports: + - port: {{ .Values.backend.containerPorts.http }} + protocol: TCP + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Release.Namespace }} + podSelector: + matchLabels: {{ include "shuffle.backend.matchLabels" . | nindent 14 }} # Allow access to workers - ports: - port: 33333 @@ -44,17 +54,18 @@ spec: {{- end }} {{- end }} ingress: - {{- if .Values.app.networkPolicy.allowExternal }} - - {} - {{- else }} - # Allow access from workers. Apps will typicaly use port 80/TCP, but this is not enforced. - - from: + - ports: + - port: {{ .Values.app.exposedContainerPort }} + protocol: TCP + {{- if not .Values.app.networkPolicy.allowExternal }} + # Allow traffic from workers + from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ .Release.Namespace }} podSelector: matchLabels: {{ include "shuffle.worker.matchLabels" . | nindent 14 }} - {{- end }} + {{- end }} {{- if .Values.app.networkPolicy.extraIngress }} {{- include "common.tplvalues.render" ( dict "value" .Values.app.networkPolicy.extraIngress "context" $ ) | nindent 4 }} {{- end }} diff --git a/functions/kubernetes/charts/shuffle/values.schema.json b/functions/kubernetes/charts/shuffle/values.schema.json index c4687857..661eefc6 100644 --- a/functions/kubernetes/charts/shuffle/values.schema.json +++ b/functions/kubernetes/charts/shuffle/values.schema.json @@ -517,7 +517,7 @@ "type": { "type": "string", "description": "backend deployment strategy type", - "default": "RollingUpdate" + "default": "Recreate" } } }, @@ -683,6 +683,16 @@ } } }, + "service": { + "type": "object", + "properties": { + "labels": { + "type": "object", + "description": "Extra labels for backend service", + "default": {} + } + } + }, "serviceAccount": { "type": "object", "properties": { @@ -1362,6 +1372,16 @@ } } }, + "service": { + "type": "object", + "properties": { + "labels": { + "type": "object", + "description": "Extra labels for frontend service", + "default": {} + } + } + }, "serviceAccount": { "type": "object", "properties": { @@ -2064,6 +2084,103 @@ } } }, + "podSecurityContext": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable worker pods' Security Context", + "default": true + }, + "fsGroupChangePolicy": { + "type": "string", + "description": "Set filesystem group change policy for worker pods", + "default": "Always" + }, + "sysctls": { + "type": "array", + "description": "Set kernel settings using the sysctl interface for worker pods", + "default": [], + "items": {} + }, + "supplementalGroups": { + "type": "array", + "description": "Set filesystem extra groups for worker pods", + "default": [], + "items": {} + }, + "fsGroup": { + "type": "number", + "description": "Set fsGroup in worker pods' Security Context", + "default": 1001 + } + } + }, + "containerSecurityContext": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled worker container' Security Context", + "default": true + }, + "runAsUser": { + "type": "number", + "description": "Set runAsUser in worker container' Security Context", + "default": 1001 + }, + "runAsGroup": { + "type": "number", + "description": "Set runAsGroup in worker container' Security Context", + "default": 1001 + }, + "runAsNonRoot": { + "type": "boolean", + "description": "Set runAsNonRoot in worker container' Security Context", + "default": true + }, + "readOnlyRootFilesystem": { + "type": "boolean", + "description": "Set readOnlyRootFilesystem in worker container' Security Context", + "default": true + }, + "privileged": { + "type": "boolean", + "description": "Set privileged in worker container' Security Context", + "default": false + }, + "allowPrivilegeEscalation": { + "type": "boolean", + "description": "Set allowPrivilegeEscalation in worker container' Security Context", + "default": false + }, + "capabilities": { + "type": "object", + "properties": { + "drop": { + "type": "array", + "description": "List of capabilities to be dropped in worker container", + "default": [ + "ALL" + ], + "items": { + "type": "string" + } + } + } + }, + "seccompProfile": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Set seccomp profile in worker container", + "default": "RuntimeDefault" + } + } + } + } + }, "serviceAccount": { "type": "object", "properties": { @@ -2142,6 +2259,103 @@ "app": { "type": "object", "properties": { + "podSecurityContext": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable app pods' Security Context", + "default": true + }, + "fsGroupChangePolicy": { + "type": "string", + "description": "Set filesystem group change policy for app pods", + "default": "Always" + }, + "sysctls": { + "type": "array", + "description": "Set kernel settings using the sysctl interface for app pods", + "default": [], + "items": {} + }, + "supplementalGroups": { + "type": "array", + "description": "Set filesystem extra groups for app pods", + "default": [], + "items": {} + }, + "fsGroup": { + "type": "number", + "description": "Set fsGroup in app pods' Security Context", + "default": 1001 + } + } + }, + "containerSecurityContext": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled app container' Security Context", + "default": true + }, + "runAsUser": { + "type": "number", + "description": "Set runAsUser in app container' Security Context", + "default": 1001 + }, + "runAsGroup": { + "type": "number", + "description": "Set runAsGroup in app container' Security Context", + "default": 1001 + }, + "runAsNonRoot": { + "type": "boolean", + "description": "Set runAsNonRoot in app container' Security Context", + "default": true + }, + "readOnlyRootFilesystem": { + "type": "boolean", + "description": "Set readOnlyRootFilesystem in app container' Security Context", + "default": true + }, + "privileged": { + "type": "boolean", + "description": "Set privileged in app container' Security Context", + "default": false + }, + "allowPrivilegeEscalation": { + "type": "boolean", + "description": "Set allowPrivilegeEscalation in app container' Security Context", + "default": false + }, + "capabilities": { + "type": "object", + "properties": { + "drop": { + "type": "array", + "description": "List of capabilities to be dropped in app container", + "default": [ + "ALL" + ], + "items": { + "type": "string" + } + } + } + }, + "seccompProfile": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Set seccomp profile in app container", + "default": "RuntimeDefault" + } + } + } + } + }, "serviceAccount": { "type": "object", "properties": { @@ -2214,6 +2428,11 @@ "items": {} } } + }, + "exposedContainerPort": { + "type": "number", + "description": "The port that shuffle app containers will listen on for new requests. ", + "default": 80 } } }, diff --git a/functions/kubernetes/charts/shuffle/values.yaml b/functions/kubernetes/charts/shuffle/values.yaml index 7b2feaa5..08763cb6 100644 --- a/functions/kubernetes/charts/shuffle/values.yaml +++ b/functions/kubernetes/charts/shuffle/values.yaml @@ -310,14 +310,14 @@ backend: ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ ## tolerations: [] - ## ONLY FOR DEPLOYMENTS: ## @param backend.updateStrategy.type backend deployment strategy type ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy ## updateStrategy: ## Can be set to RollingUpdate or Recreate + ## Backend uses ReadWriteOnce volumes by default, which is incompatible with RollingUpdate ## - type: RollingUpdate + type: Recreate ## @param backend.priorityClassName backend pods' priorityClassName ## priorityClassName: "" @@ -421,6 +421,14 @@ backend: targetCPU: "" targetMemory: "" + ## Service configuration + ## + service: + ## @param backend.service.labels Extra labels for backend service + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + labels: {} + ## ServiceAccount configuration ## serviceAccount: @@ -870,6 +878,14 @@ frontend: targetCPU: "" targetMemory: "" + ## Service configuration + ## + service: + ## @param frontend.service.labels Extra labels for frontend service + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + labels: {} + ## ServiceAccount configuration ## serviceAccount: @@ -1324,6 +1340,48 @@ worker: tag: "" digest: "" + ## Configure Pods Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param worker.podSecurityContext.enabled Enable worker pods' Security Context + ## @param worker.podSecurityContext.fsGroupChangePolicy Set filesystem group change policy for worker pods + ## @param worker.podSecurityContext.sysctls Set kernel settings using the sysctl interface for worker pods + ## @param worker.podSecurityContext.supplementalGroups Set filesystem extra groups for worker pods + ## @param worker.podSecurityContext.fsGroup Set fsGroup in worker pods' Security Context + ## + podSecurityContext: + enabled: true + fsGroupChangePolicy: Always + sysctls: [] + supplementalGroups: [] + fsGroup: 1001 + + ## Configure Container Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param worker.containerSecurityContext.enabled Enabled worker container' Security Context + ## @param worker.containerSecurityContext.seLinuxOptions [object,nullable] Set SELinux options in worker container + ## @param worker.containerSecurityContext.runAsUser Set runAsUser in worker container' Security Context + ## @param worker.containerSecurityContext.runAsGroup Set runAsGroup in worker container' Security Context + ## @param worker.containerSecurityContext.runAsNonRoot Set runAsNonRoot in worker container' Security Context + ## @param worker.containerSecurityContext.readOnlyRootFilesystem Set readOnlyRootFilesystem in worker container' Security Context + ## @param worker.containerSecurityContext.privileged Set privileged in worker container' Security Context + ## @param worker.containerSecurityContext.allowPrivilegeEscalation Set allowPrivilegeEscalation in worker container' Security Context + ## @param worker.containerSecurityContext.capabilities.drop List of capabilities to be dropped in worker container + ## @param worker.containerSecurityContext.seccompProfile.type Set seccomp profile in worker container + ## + containerSecurityContext: + enabled: true + seLinuxOptions: {} + runAsUser: 1001 + runAsGroup: 1001 + runAsNonRoot: true + readOnlyRootFilesystem: true + privileged: false + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: "RuntimeDefault" + ## ServiceAccount configuration ## serviceAccount: @@ -1386,6 +1444,48 @@ worker: ## @section app Parameters ## app: + ## Configure Pods Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param app.podSecurityContext.enabled Enable app pods' Security Context + ## @param app.podSecurityContext.fsGroupChangePolicy Set filesystem group change policy for app pods + ## @param app.podSecurityContext.sysctls Set kernel settings using the sysctl interface for app pods + ## @param app.podSecurityContext.supplementalGroups Set filesystem extra groups for app pods + ## @param app.podSecurityContext.fsGroup Set fsGroup in app pods' Security Context + ## + podSecurityContext: + enabled: true + fsGroupChangePolicy: Always + sysctls: [] + supplementalGroups: [] + fsGroup: 1001 + + ## Configure Container Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param app.containerSecurityContext.enabled Enabled app container' Security Context + ## @param app.containerSecurityContext.seLinuxOptions [object,nullable] Set SELinux options in app container + ## @param app.containerSecurityContext.runAsUser Set runAsUser in app container' Security Context + ## @param app.containerSecurityContext.runAsGroup Set runAsGroup in app container' Security Context + ## @param app.containerSecurityContext.runAsNonRoot Set runAsNonRoot in app container' Security Context + ## @param app.containerSecurityContext.readOnlyRootFilesystem Set readOnlyRootFilesystem in app container' Security Context + ## @param app.containerSecurityContext.privileged Set privileged in app container' Security Context + ## @param app.containerSecurityContext.allowPrivilegeEscalation Set allowPrivilegeEscalation in app container' Security Context + ## @param app.containerSecurityContext.capabilities.drop List of capabilities to be dropped in app container + ## @param app.containerSecurityContext.seccompProfile.type Set seccomp profile in app container + ## + containerSecurityContext: + enabled: true + seLinuxOptions: {} + runAsUser: 1001 + runAsGroup: 1001 + runAsNonRoot: true + readOnlyRootFilesystem: true + privileged: false + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: "RuntimeDefault" + ## ServiceAccount configuration ## serviceAccount: @@ -1445,6 +1545,10 @@ app: ## extraEgress: [] + ## @param app.exposedContainerPort The port that shuffle app containers will listen on for new requests. + ## + exposedContainerPort: 80 + ## @section Traffic Exposure Parameters ## diff --git a/functions/onprem/orborus/Dockerfile b/functions/onprem/orborus/Dockerfile index 921514b0..3121e93a 100644 --- a/functions/onprem/orborus/Dockerfile +++ b/functions/onprem/orborus/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23 as builder +FROM golang:1.24 as builder WORKDIR /app diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod index fd65af94..5d3ee295 100644 --- a/functions/onprem/orborus/go.mod +++ b/functions/onprem/orborus/go.mod @@ -1,137 +1,152 @@ module orborus -go 1.23.0 +go 1.24.0 -toolchain go1.23.1 +toolchain go1.24.4 //replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared require ( - github.com/docker/docker v28.0.4+incompatible + github.com/docker/docker v28.2.2+incompatible github.com/docker/go-connections v0.5.0 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.8.54 - k8s.io/api v0.32.3 - k8s.io/apimachinery v0.32.3 + github.com/shuffle/shuffle-shared v0.8.84 + k8s.io/api v0.33.1 + k8s.io/apimachinery v0.33.1 ) require ( - cloud.google.com/go/auth v0.15.0 // indirect + cloud.google.com/go/auth v0.16.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect ) require ( - cloud.google.com/go v0.112.2 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/datastore v1.15.0 // indirect - cloud.google.com/go/iam v1.1.6 // indirect - cloud.google.com/go/scheduler v1.10.6 // indirect - cloud.google.com/go/storage v1.39.1 // indirect + cel.dev/expr v0.20.0 // indirect + cloud.google.com/go v0.121.1 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/datastore v1.20.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect + cloud.google.com/go/scheduler v1.11.7 // indirect + cloud.google.com/go/storage v1.55.0 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.1.3 // indirect - github.com/adrg/strutil v0.2.3 // indirect - github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/strutil v0.3.1 // indirect + github.com/algolia/algoliasearch-client-go/v3 v3.31.4 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/cyphar/filepath-securejoin v0.2.5 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/frikky/kin-openapi v0.41.0 // indirect - github.com/frikky/schemaless v0.0.13 // indirect + github.com/frikky/kin-openapi v0.42.0 // indirect + github.com/frikky/schemaless v0.0.16 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.0 // indirect - github.com/go-git/go-git/v5 v5.13.0 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.16.1 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-github/v28 v28.1.1 // indirect - github.com/google/go-querystring v1.0.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/googleapis/gax-go/v2 v2.14.2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opensearch-project/opensearch-go v1.1.0 // indirect github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/sashabaranov/go-openai v1.19.2 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/sashabaranov/go-openai v1.40.1 // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect - github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect + github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - go.opencensus.io v0.24.0 // indirect + github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect - go4.org v0.0.0-20201209231011-d4a079459e60 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.26.0 // indirect - google.golang.org/api v0.228.0 // indirect + google.golang.org/api v0.236.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect - google.golang.org/grpc v1.71.0 // indirect + google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/grpc v1.72.2 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.32.3 // indirect + k8s.io/client-go v0.33.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum index 3b9256ab..13ae08b5 100644 --- a/functions/onprem/orborus/go.sum +++ b/functions/onprem/orborus/go.sum @@ -1,3 +1,5 @@ +cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= +cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -7,29 +9,37 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= -cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= -cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= -cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw= +cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= +cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.15.0 h1:0P9WcsQeTWjuD1H14JIY7XQscIPQ4Laje8ti96IC5vg= -cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM= +cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/scheduler v1.10.6 h1:5U8iXLoQ03qOB+ZXlAecU7fiE33+u3QiM9nh4cd0eTE= -cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE= +cloud.google.com/go/scheduler v1.11.7 h1:zkMEJ0UbEJ3O7NwEUlKLIp6eXYv1L7wHjbxyxznajKM= +cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= -cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= +cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= +cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= +cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -37,17 +47,25 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= -github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= -github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ= -github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4= +github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA= +github.com/algolia/algoliasearch-client-go/v3 v3.31.4 h1:UJhx6AhZCYf0qZygDz2c1x1+1q2q2sfzsRaQM6yswWk= +github.com/algolia/algoliasearch-client-go/v3 v3.31.4/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -66,53 +84,66 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3w github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= -github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= -github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= +github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= -github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frikky/kin-openapi v0.41.0 h1:oMmjo+ekGS971lb3KLeZZOqRDZOwWi3+g/OiSWP08+s= -github.com/frikky/kin-openapi v0.41.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= -github.com/frikky/schemaless v0.0.13 h1:ARiN9V7wr2VZXAr9JK5wvTbyPgpGrgeiL1VhR5MlgaQ= -github.com/frikky/schemaless v0.0.13/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= +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.16 h1:4d2ZktB9xGsAusbbKliOI8TuriSrdIMzD/6ToY3wkz8= +github.com/frikky/schemaless v0.0.16/go.mod h1:jT48kTcmr1q3o8i+8qe7g+eCsbwaz2Q9CjOJevQQzQs= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -121,14 +152,16 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= -github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= -github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= +github.com/go-git/go-git/v5 v5.16.1 h1:TuxMBWNL7R05tXsUGi0kh1vi4tq0WfXNLlIrAkXG1k8= +github.com/go-git/go-git/v5 v5.16.1/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -152,8 +185,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -162,27 +195,19 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -190,15 +215,14 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -207,15 +231,14 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAx github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -249,6 +272,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -266,50 +293,55 @@ github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M= github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ= github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/sashabaranov/go-openai v1.19.2 h1:+dkuCADSnwXV02YVJkdphY8XD9AyHLUWwk6V7LB6EL8= -github.com/sashabaranov/go-openai v1.19.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sashabaranov/go-openai v1.40.1 h1:bJ08Iwct5mHBVkuvG6FEcb9MDTfsXdTYPGjYLRdeTEU= +github.com/sashabaranov/go-openai v1.40.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= -github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA= -github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= +github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs= +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.8.38 h1:/V3jqOXT1IEn4XIvZP+Pa+rDvQfN7HY/wF68Ct1P74s= -github.com/shuffle/shuffle-shared v0.8.38/go.mod h1:KFMepkCunjhYmzbyEW6CRP85yH/li8ogNGpc1qjRbm4= +github.com/shuffle/shuffle-shared v0.8.84 h1:ElIMQYjKBVOiadbiGkSzt/lPU5xqaQwRxQvk9wx/xYM= +github.com/shuffle/shuffle-shared v0.8.84/go.mod h1:RdfNxqCPI+zU4jQKy3E/p4Io2injm7LpSKQUCDHNtLk= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -319,6 +351,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -328,36 +361,40 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= -go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -365,8 +402,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -394,8 +431,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -409,25 +444,23 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -436,8 +469,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -451,7 +484,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -463,14 +495,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -481,8 +513,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= @@ -520,8 +552,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -530,8 +560,8 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs= -google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= +google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0= +google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -552,33 +582,21 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= @@ -602,23 +620,23 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= +k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= +k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= +k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= +k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -626,7 +644,10 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 1f37da85..9b5c87da 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -76,7 +76,11 @@ var maxCPUPercent = 90 var isKubernetes = os.Getenv("IS_KUBERNETES") var kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") var workerServiceAccountName = os.Getenv("SHUFFLE_WORKER_SERVICE_ACCOUNT_NAME") +var workerPodSecurityContext = os.Getenv("SHUFFLE_WORKER_POD_SECURITY_CONTEXT") +var workerContainerSecurityContext = os.Getenv("SHUFFLE_WORKER_CONTAINER_SECURITY_CONTEXT") var appServiceAccountName = os.Getenv("SHUFFLE_APP_SERVICE_ACCOUNT_NAME") +var appPodSecurityContext = os.Getenv("SHUFFLE_APP_POD_SECURITY_CONTEXT") +var appContainerSecurityContext = os.Getenv("SHUFFLE_APP_CONTAINER_SECURITY_CONTEXT") // var baseimagename = "docker.pkg.github.com/shuffle/shuffle" // var baseimagename = "ghcr.io/frikky" @@ -119,6 +123,7 @@ var tenzirDisabled = false var dockercli *dockerclient.Client var containerId string var executionCount = 0 +var orborusUuid = os.Getenv("SHUFFLE_ORBORUS_UUID") var imagedownloadTimeout = time.Second * 300 var window = shuffle.NewTimeWindow(1 * time.Minute) @@ -606,13 +611,31 @@ func deployServiceWorkers(image string) { if defaultNetworkAttach == true || strings.ToLower(os.Getenv("SHUFFLE_DEFAULT_NETWORK_ATTACH")) == "true" { targetName := "shuffle_shuffle" - log.Printf("[DEBUG] Adding network attach for network %s to worker in swarm", targetName) - serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{ - Target: targetName, - }) + isAttachable := false + networks, err := dockercli.NetworkList(ctx, network.ListOptions{}) + 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? - serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=%s", targetName)) + if isAttachable { + 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 != "" { @@ -705,6 +728,34 @@ func deployServiceWorkers(image string) { } else { 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) + 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 { log.Printf("[WARNING] Failed deploying workers: %s", err) if len(serviceSpec.Networks) > 1 { @@ -747,7 +798,7 @@ func handleBackendImageDownload(ctx context.Context, images string) error { //log.Printf("[DEBUG] Removing existing image (s): %s", images) newImages := []string{} - successful := []string{} + successful := []string{} for _, curimage := range strings.Split(images, ",") { curimage = strings.TrimSpace(curimage) if shuffle.ArrayContains(handled, curimage) { @@ -835,11 +886,12 @@ func handleBackendImageDownload(ctx context.Context, images string) error { log.Printf("[ERROR] Failed updating service %s with the new image %s: %s. Resp: %#v", service.Spec.Annotations.Name, image, err, resp) } else { log.Printf("[DEBUG] Updated service %s with the new image %s. Resp: %#v", service.Spec.Annotations.Name, image, resp) + + found = true if !strings.Contains(fmt.Sprintf("%s", resp), "error") { break } else { - found = true log.Printf("[ERROR] Failed updating service %s with the new image %s: %s. Resp: %#v", service.Spec.Annotations.Name, image, err, resp) } } @@ -996,10 +1048,22 @@ func deployK8sWorker(image string, identifier string, env []string) error { env = append(env, fmt.Sprintf("SHUFFLE_USE_GHCR_OVERRIDE_FOR_AUTODEPLOY=%s", os.Getenv("SHUFFLE_USE_GHCR_OVERRIDE_FOR_AUTODEPLOY"))) } + if len(os.Getenv("SHUFFLE_APP_EXPOSED_PORT")) > 0 { + env = append(env, fmt.Sprintf("SHUFFLE_APP_EXPOSED_PORT=%s", os.Getenv("SHUFFLE_APP_EXPOSED_PORT"))) + } + if len(appServiceAccountName) > 0 { env = append(env, fmt.Sprintf("SHUFFLE_APP_SERVICE_ACCOUNT_NAME=%s", appServiceAccountName)) } + if len(appPodSecurityContext) > 0 { + env = append(env, fmt.Sprintf("SHUFFLE_APP_POD_SECURITY_CONTEXT=%s", appPodSecurityContext)) + } + + if len(appContainerSecurityContext) > 0 { + env = append(env, fmt.Sprintf("SHUFFLE_APP_CONTAINER_SECURITY_CONTEXT=%s", appContainerSecurityContext)) + } + clientset, _, err := shuffle.GetKubernetesClient() if err != nil { log.Printf("[ERROR] Error getting kubernetes client:", err) @@ -1067,9 +1131,9 @@ func deployK8sWorker(image string, identifier string, env []string) error { } labels := map[string]string{ - "app.kubernetes.io/name": "shuffle-worker", - "app.kubernetes.io/instance": identifier, - // "app.kubernetes.io/version": "", + // Well-known Kubernetes labels + "app.kubernetes.io/name": "shuffle-worker", + "app.kubernetes.io/instance": identifier, "app.kubernetes.io/part-of": "shuffle", "app.kubernetes.io/managed-by": "shuffle-orborus", // Keep legacy labels for backward compatibility @@ -1081,10 +1145,33 @@ func deployK8sWorker(image string, identifier string, env []string) error { "app.kubernetes.io/instance": identifier, } + // Parse security contexts from env + var podSecurityContext *corev1.PodSecurityContext + var containerSecurityContext *corev1.SecurityContext + + if len(workerPodSecurityContext) > 0 { + podSecurityContext = &corev1.PodSecurityContext{} + err = json.Unmarshal([]byte(workerPodSecurityContext), podSecurityContext) + if err != nil { + log.Printf("[ERROR] Failed to unmarshal worker pod security context: %v", err) + return fmt.Errorf("failed to unmarshal worker pod security context: %v", err) + } + } + + if len(workerContainerSecurityContext) > 0 { + containerSecurityContext = &corev1.SecurityContext{} + err = json.Unmarshal([]byte(workerContainerSecurityContext), containerSecurityContext) + if err != nil { + log.Printf("[ERROR] Failed to unmarshal worker container security context: %v", err) + return fmt.Errorf("failed to unmarshal worker container security context: %v", err) + } + } + containerAttachment := corev1.Container{ - Name: identifier, - Image: kubernetesImage, - Env: buildEnvVars(envMap), + Name: identifier, + Image: kubernetesImage, + Env: buildEnvVars(envMap), + SecurityContext: containerSecurityContext, //ImagePullPolicy: "Never", ImagePullPolicy: corev1.PullIfNotPresent, @@ -1201,6 +1288,7 @@ func deployK8sWorker(image string, identifier string, env []string) error { }, DNSPolicy: corev1.DNSClusterFirst, ServiceAccountName: workerServiceAccountName, + SecurityContext: podSecurityContext, }, }, }, @@ -1212,7 +1300,6 @@ func deployK8sWorker(image string, identifier string, env []string) error { return err } - // kubectl expose deployment shuffle-workers --type=NodePort --port=33333 --target-port=33333 service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: identifier, @@ -1227,7 +1314,7 @@ func deployK8sWorker(image string, identifier string, env []string) error { TargetPort: intstr.FromInt(33333), }, }, - Type: corev1.ServiceTypeNodePort, + Type: corev1.ServiceTypeClusterIP, }, } @@ -1271,7 +1358,6 @@ func deployWorker(image string, identifier string, env []string, executionReques Resources: container.Resources{}, } - // This is just to test the mounting locally so // I can control from what source I'm mounting // the certs to. Default behaviour is: @@ -1662,6 +1748,8 @@ func getOrborusStats(ctx context.Context) shuffle.OrborusStats { Environment: environment, OrborusLabel: orborusLabel, Timestamp: time.Now().Unix(), + + Uuid: orborusUuid, } if (swarmConfig == "run" || swarmConfig == "swarm") && strings.Contains(newWorkerImage, "scale") { @@ -1918,6 +2006,10 @@ func main() { //baseUrl = "http://localhost:5001" } + if len(orborusUuid) == 0 { + orborusUuid = uuid.NewV4().String() + } + //if orgId == "" { // log.Printf("[ERROR] Org not defined. Set variable ORG_ID based on your org") // os.Exit(3) @@ -2120,6 +2212,8 @@ func main() { } log.Printf("[INFO] Waiting for executions at %s with Environment %#v", fullUrl, environment) + + hasStarted := false for { if req.Method == "POST" { @@ -2190,8 +2284,12 @@ func main() { continue } - // FIXME - add check for StatusCode - if newresp.StatusCode != 200 { + // Controls Leader/Follower mode + if newresp.StatusCode == 409 { + log.Printf("[INFO] Another Orborus is already handling jobs. Polling every 30 seconds in case Leader stops. Resp: %s", string(body)) + time.Sleep(time.Duration(30) * time.Second) + continue + } else if newresp.StatusCode != 200 { log.Printf("[ERROR] Backend connection failed for url '%s', or is missing (%d): %s", fullUrl, newresp.StatusCode, string(body)) } else { if !hasStarted { diff --git a/functions/onprem/worker/Dockerfile b/functions/onprem/worker/Dockerfile index a2fb13b3..e25112d0 100755 --- a/functions/onprem/worker/Dockerfile +++ b/functions/onprem/worker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23 as builder +FROM golang:1.24 as builder WORKDIR /app #RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod index 165889e7..1df6cb59 100644 --- a/functions/onprem/worker/go.mod +++ b/functions/onprem/worker/go.mod @@ -1,128 +1,142 @@ module worker -go 1.23.0 +go 1.24.0 -toolchain go1.23.1 +toolchain go1.24.4 //replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared require ( - github.com/docker/docker v28.0.4+incompatible + github.com/docker/docker v28.2.2+incompatible github.com/gorilla/mux v1.8.1 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.8.54 - k8s.io/api v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/client-go v0.32.3 + github.com/shuffle/shuffle-shared v0.8.84 + k8s.io/api v0.33.1 + k8s.io/apimachinery v0.33.1 + k8s.io/client-go v0.33.1 ) require ( - cloud.google.com/go/auth v0.15.0 // indirect + cloud.google.com/go/auth v0.16.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect ) require ( - cloud.google.com/go v0.112.2 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/datastore v1.15.0 // indirect - cloud.google.com/go/iam v1.1.6 // indirect - cloud.google.com/go/scheduler v1.10.6 // indirect - cloud.google.com/go/storage v1.39.1 // indirect + cel.dev/expr v0.20.0 // indirect + cloud.google.com/go v0.121.1 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/datastore v1.20.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect + cloud.google.com/go/scheduler v1.11.7 // indirect + cloud.google.com/go/storage v1.55.0 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.1.3 // indirect - github.com/adrg/strutil v0.2.3 // indirect - github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/strutil v0.3.1 // indirect + github.com/algolia/algoliasearch-client-go/v3 v3.31.4 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/cyphar/filepath-securejoin v0.2.5 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/frikky/kin-openapi v0.41.0 // indirect - github.com/frikky/schemaless v0.0.13 // indirect + github.com/frikky/kin-openapi v0.42.0 // indirect + github.com/frikky/schemaless v0.0.16 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.0 // indirect - github.com/go-git/go-git/v5 v5.13.0 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.16.1 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-github/v28 v28.1.1 // indirect - github.com/google/go-querystring v1.0.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/googleapis/gax-go/v2 v2.14.2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opensearch-project/opensearch-go v1.1.0 // indirect github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/sashabaranov/go-openai v1.19.2 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/sashabaranov/go-openai v1.40.1 // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect - github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect + github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - go.opencensus.io v0.24.0 // indirect + github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect - go4.org v0.0.0-20201209231011-d4a079459e60 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.26.0 // indirect - google.golang.org/api v0.228.0 // indirect + google.golang.org/api v0.236.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect - google.golang.org/grpc v1.71.0 // indirect + google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/grpc v1.72.2 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -130,9 +144,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/functions/onprem/worker/go.sum b/functions/onprem/worker/go.sum index 60ad1824..0ef2d3a6 100644 --- a/functions/onprem/worker/go.sum +++ b/functions/onprem/worker/go.sum @@ -1,3 +1,5 @@ +cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= +cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -7,29 +9,37 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= -cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= -cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= -cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw= +cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= +cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.15.0 h1:0P9WcsQeTWjuD1H14JIY7XQscIPQ4Laje8ti96IC5vg= -cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM= +cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/scheduler v1.10.6 h1:5U8iXLoQ03qOB+ZXlAecU7fiE33+u3QiM9nh4cd0eTE= -cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE= +cloud.google.com/go/scheduler v1.11.7 h1:zkMEJ0UbEJ3O7NwEUlKLIp6eXYv1L7wHjbxyxznajKM= +cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= -cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= +cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= +cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= +cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -37,17 +47,25 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= -github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= -github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ= -github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4= +github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA= +github.com/algolia/algoliasearch-client-go/v3 v3.31.4 h1:UJhx6AhZCYf0qZygDz2c1x1+1q2q2sfzsRaQM6yswWk= +github.com/algolia/algoliasearch-client-go/v3 v3.31.4/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -66,53 +84,66 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3w github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= -github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= -github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= +github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= -github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frikky/kin-openapi v0.41.0 h1:oMmjo+ekGS971lb3KLeZZOqRDZOwWi3+g/OiSWP08+s= -github.com/frikky/kin-openapi v0.41.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= -github.com/frikky/schemaless v0.0.13 h1:ARiN9V7wr2VZXAr9JK5wvTbyPgpGrgeiL1VhR5MlgaQ= -github.com/frikky/schemaless v0.0.13/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= +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.16 h1:4d2ZktB9xGsAusbbKliOI8TuriSrdIMzD/6ToY3wkz8= +github.com/frikky/schemaless v0.0.16/go.mod h1:jT48kTcmr1q3o8i+8qe7g+eCsbwaz2Q9CjOJevQQzQs= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -121,14 +152,16 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= -github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= -github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= +github.com/go-git/go-git/v5 v5.16.1 h1:TuxMBWNL7R05tXsUGi0kh1vi4tq0WfXNLlIrAkXG1k8= +github.com/go-git/go-git/v5 v5.16.1/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -152,8 +185,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -162,27 +195,19 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -190,15 +215,14 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -207,15 +231,14 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAx github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= @@ -251,6 +274,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -268,50 +295,55 @@ github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M= github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ= github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/sashabaranov/go-openai v1.19.2 h1:+dkuCADSnwXV02YVJkdphY8XD9AyHLUWwk6V7LB6EL8= -github.com/sashabaranov/go-openai v1.19.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sashabaranov/go-openai v1.40.1 h1:bJ08Iwct5mHBVkuvG6FEcb9MDTfsXdTYPGjYLRdeTEU= +github.com/sashabaranov/go-openai v1.40.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= -github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA= -github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= +github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs= +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.8.38 h1:/V3jqOXT1IEn4XIvZP+Pa+rDvQfN7HY/wF68Ct1P74s= -github.com/shuffle/shuffle-shared v0.8.38/go.mod h1:KFMepkCunjhYmzbyEW6CRP85yH/li8ogNGpc1qjRbm4= +github.com/shuffle/shuffle-shared v0.8.84 h1:ElIMQYjKBVOiadbiGkSzt/lPU5xqaQwRxQvk9wx/xYM= +github.com/shuffle/shuffle-shared v0.8.84/go.mod h1:RdfNxqCPI+zU4jQKy3E/p4Io2injm7LpSKQUCDHNtLk= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -321,6 +353,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -330,36 +363,40 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= -go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -367,8 +404,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -396,8 +433,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -411,25 +446,23 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -438,8 +471,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -453,7 +486,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -465,14 +497,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -483,8 +515,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= @@ -522,8 +554,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -532,8 +562,8 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs= -google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= +google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0= +google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -554,33 +584,21 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= @@ -604,23 +622,23 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= +k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= +k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= +k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= +k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -628,7 +646,10 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index cde2484c..2fbf51e3 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -57,9 +57,13 @@ var logsDisabled = os.Getenv("SHUFFLE_LOGS_DISABLED") var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP")) var swarmNetworkName = os.Getenv("SHUFFLE_SWARM_NETWORK_NAME") var dockerApiVersion = strings.ToLower(os.Getenv("DOCKER_API_VERSION")) -var appServiceAccountName = os.Getenv("SHUFFLE_APP_SERVICE_ACCOUNT_NAME") +// Kubernetes settings +var appServiceAccountName = os.Getenv("SHUFFLE_APP_SERVICE_ACCOUNT_NAME") +var appPodSecurityContext = os.Getenv("SHUFFLE_APP_POD_SECURITY_CONTEXT") +var appContainerSecurityContext = os.Getenv("SHUFFLE_APP_CONTAINER_SECURITY_CONTEXT") var kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE") + var executionCount int64 var baseimagename = os.Getenv("SHUFFLE_BASE_IMAGE_NAME") @@ -74,6 +78,7 @@ var appsInitialized = false var hostname string var maxReplicas = uint64(12) +var debug bool /* var environments []string @@ -399,6 +404,11 @@ func deployk8sApp(image string, identifier string, env []string) error { kubernetesNamespace = "default" } + deployport, err := strconv.Atoi(os.Getenv("SHUFFLE_APP_EXPOSED_PORT")) + if err != nil { + deployport = 80 + } + envMap := make(map[string]string) for _, envStr := range env { parts := strings.SplitN(envStr, "=", 2) @@ -408,9 +418,7 @@ func deployk8sApp(image string, identifier string, env []string) error { } // add to env - // fmt.Sprintf("SHUFFLE_APP_EXPOSED_PORT=%d", deployport), - // fmt.Sprintf("SHUFFLE_SWARM_CONFIG=%s", os.Getenv("SHUFFLE_SWARM_CONFIG")), - envMap["SHUFFLE_APP_EXPOSED_PORT"] = "80" + envMap["SHUFFLE_APP_EXPOSED_PORT"] = strconv.Itoa(deployport) envMap["SHUFFLE_SWARM_CONFIG"] = os.Getenv("SHUFFLE_SWARM_CONFIG") envMap["BASE_URL"] = "http://shuffle-workers:33333" @@ -489,13 +497,16 @@ func deployk8sApp(image string, identifier string, env []string) error { name := strings.ReplaceAll(identifier, "_", "-") labels := map[string]string{ - "app.kubernetes.io/name": "shuffle-app", - "app.kubernetes.io/instance": name, - // "app.kubernetes.io/version": "", + // Well-known Kubernetes labels + "app.kubernetes.io/name": "shuffle-app", + "app.kubernetes.io/instance": name, "app.kubernetes.io/part-of": "shuffle", "app.kubernetes.io/managed-by": "shuffle-worker", // Keep legacy labels for backward compatibility "app": name, + // TODO: Add Shuffle specific labels + // "app.shuffler.io/name": "APP_NAME", + // "app.shuffler.io/version": "APP_VERSION", } matchLabels := map[string]string{ @@ -503,6 +514,28 @@ func deployk8sApp(image string, identifier string, env []string) error { "app.kubernetes.io/instance": name, } + // Parse security contexts from env + var podSecurityContext *corev1.PodSecurityContext + var containerSecurityContext *corev1.SecurityContext + + if len(appPodSecurityContext) > 0 { + podSecurityContext = &corev1.PodSecurityContext{} + err = json.Unmarshal([]byte(appPodSecurityContext), podSecurityContext) + if err != nil { + log.Printf("[ERROR] Failed to unmarshal app pod security context: %v", err) + return fmt.Errorf("failed to unmarshal app pod security context: %v", err) + } + } + + if len(appContainerSecurityContext) > 0 { + containerSecurityContext = &corev1.SecurityContext{} + err = json.Unmarshal([]byte(appContainerSecurityContext), containerSecurityContext) + if err != nil { + log.Printf("[ERROR] Failed to unmarshal app container security context: %v", err) + return fmt.Errorf("failed to unmarshal app container security context: %v", err) + } + } + // pod := &corev1.Pod{ // ObjectMeta: metav1.ObjectMeta{ // Name: podName, @@ -599,10 +632,18 @@ func deployk8sApp(image string, identifier string, env []string) error { Name: value, Image: image, Env: buildEnvVars(envMap), + Ports: []corev1.ContainerPort{ + { + Protocol: "TCP", + ContainerPort: int32(deployport), + }, + }, + SecurityContext: containerSecurityContext, }, }, DNSPolicy: corev1.DNSClusterFirst, ServiceAccountName: appServiceAccountName, + SecurityContext: podSecurityContext, }, }, }, @@ -614,7 +655,6 @@ func deployk8sApp(image string, identifier string, env []string) error { return err } - // kubectl expose deployment {podName} --type=NodePort --port=80 --target-port=80 service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -626,10 +666,10 @@ func deployk8sApp(image string, identifier string, env []string) error { { Protocol: "TCP", Port: 80, - TargetPort: intstr.FromInt(80), + TargetPort: intstr.FromInt(deployport), }, }, - Type: corev1.ServiceTypeNodePort, + Type: corev1.ServiceTypeClusterIP, }, } @@ -917,7 +957,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] // Add more volume binds if possible if len(volumeBinds) > 0 { - // Only use mounts, not direct binds + // Only use mounts, not direct binds hostConfig.Binds = []string{} hostConfig.Mounts = []mount.Mount{} for _, bind := range volumeBinds { @@ -931,7 +971,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] sourceFolder := bindSplit[0] destinationFolder := bindSplit[1] - readOnly := false + readOnly := false if len(bindSplit) > 2 { mode := bindSplit[2] if mode == "ro" { @@ -940,9 +980,9 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] } builtMount := mount.Mount{ - Type: mount.TypeBind, - Source: sourceFolder, - Target: destinationFolder, + Type: mount.TypeBind, + Source: sourceFolder, + Target: destinationFolder, ReadOnly: readOnly, } @@ -1853,18 +1893,18 @@ func executionInit(workflowExecution shuffle.WorkflowExecution) error { } } - // Validates RERUN of single actions - // Identified by: + // Validates RERUN of single actions + // Identified by: // 1. Predefined result from previous exec // 2. Only ONE action // 3. Every predefined result having result.Action.Category == "rerun" /* - if len(workflowExecution.Workflow.Actions) == 1 && len(workflowExecution.Results) > 0 { - finished := shuffle.ValidateFinished(ctx, extra, workflowExecution) - if finished { - return nil + if len(workflowExecution.Workflow.Actions) == 1 && len(workflowExecution.Results) > 0 { + finished := shuffle.ValidateFinished(ctx, extra, workflowExecution) + if finished { + return nil + } } - } */ nextActions = append(nextActions, startAction) @@ -1954,7 +1994,6 @@ func executionInit(workflowExecution shuffle.WorkflowExecution) error { //log.Printf("Successfully downloaded and built %s", image) } - visited := []string{} executed := []string{} environments := []string{} @@ -2608,11 +2647,16 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl } if setExecution || workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { - log.Printf("[DEBUG][%s] Running setexec with status %s and %d/%d results", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) + if debug { + log.Printf("[DEBUG][%s] Running setexec with status %s and %d/%d results", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) + } + //result(s)", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results)) err = setWorkflowExecution(ctx, *workflowExecution, dbSave) if err != nil { - resp.WriteHeader(401) + log.Printf("[ERROR][%s] Failed setting execution: %s", workflowExecution.ExecutionId, err) + + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult: %s"}`, err))) return } @@ -2621,7 +2665,10 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { finished := shuffle.ValidateFinished(ctx, -1, *workflowExecution) if !finished { - log.Printf("[DEBUG][%s] Handling next node since it's not finished!", workflowExecution.ExecutionId) + if debug { + log.Printf("[DEBUG][%s] Handling next node since it's not finished!", workflowExecution.ExecutionId) + } + handleExecutionResult(*workflowExecution) } else { shutdownData, err := json.Marshal(workflowExecution) @@ -3583,7 +3630,9 @@ func sendAppRequest(ctx context.Context, incomingUrl, appName string, port int, log.Printf("[ERROR] Failed reading app request body body: %s", err) return err } else { - log.Printf("[DEBUG][%s] NEWRESP (from app %s with label %s): %s", workflowExecution.ExecutionId, action.AppName, action.Label, string(body)) + if debug { + log.Printf("[DEBUG][%s] NEWRESP (from app): %s", workflowExecution.ExecutionId, string(body)) + } } return nil @@ -3841,7 +3890,7 @@ func checkStandaloneRun() { if !strings.Contains(backendUrl, "http") { log.Printf("[ERROR] Backend URL should start with http:// or https://") return - + } // Format: @@ -3915,7 +3964,7 @@ func checkStandaloneRun() { continue } - // This is to handle reruns of SINGLE actions + // This is to handle reruns of SINGLE actions if result.Action.Category == "rerun" { newResults = append(newResults, result) continue @@ -3969,13 +4018,16 @@ func checkStandaloneRun() { log.Printf("\n\n\n[DEBUG] Finished resetting execution %s. Body: %s. Starting execution.\n\n\n", newresp.Status, string(body)) - } // Initial loop etc func main() { checkStandaloneRun() + if os.Getenv("DEBUG") == "true" { + debug = true + } + /*** STARTREMOVE ***/ if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" || os.Getenv("SHUFFLE_SWARM_CONFIG") == "swarm" { logsDisabled = "true"