diff --git a/CHANGELOG.md b/CHANGELOG.md
index 63861827f4..ac5ce42a09 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,6 @@
- Include system CA roots and a writable `/tmp` in scratch container images
for HTTPS requests and large multipart uploads, while retaining the non-root
runtime and explicit private-CA configuration (PR #84).
-
- Upgrade the Swagger generator to v0.36.7 and runtime/server-middleware to
v0.33.4. Regenerate array context validators so zero elements do not stop
validation of later elements. Preserve the customized server and configuration
@@ -36,6 +35,36 @@
settings. Keep the maintained SILO, PostgreSQL, browser, artifact and
reproducibility checks. Exercise observer renewal with production lease
defaults and synchronization against the database deadline.
+- Start folder-size calculations and decryption-key copying when Console is
+ opened over plain HTTP, where browsers omit `crypto.randomUUID` and the async
+ Clipboard API. Previously both buttons silently did nothing.
+- Let `console usage-worker` trust the same roots as `console server`: it
+ accepts `--certs-dir`, trusts `certs/CAs` and Console's public certificate,
+ and applies `CONSOLE_MINIO_SERVER_TLS_SKIP_VERIFY` to the SILO endpoint only.
+ The worker only reads that directory; it neither creates it nor loads
+ `private.key`.
+- Charge `CONSOLE_USAGE_SCAN_TIMEOUT` for listing time summed across scan
+ slices, not for time a bucket spends queued behind others. Many large buckets
+ no longer all end `limited` together. Buckets left limited by the earlier
+ accounting resume with a full budget on their next retry; stop older usage
+ workers before starting 2.7.0 workers so the budget is accumulated
+ consistently.
+- Return scans interrupted by worker shutdown, including during a database
+ write, to the queue with their checkpoint instead of showing them as failed.
+- Forget a deleted bucket's saved totals on `s3:BucketRemoved`, including after
+ a force delete that emits no object events, so a bucket re-created under the
+ same name never shows them. Deletions that cannot be persisted at once are
+ retried.
+- Without the usage database, retry a busy scanner (HTTP 429) with backoff for
+ about 30 seconds before reporting a bucket as failed.
+- Reject malformed or truncated multipart uploads with an error instead of a
+ request-handler panic.
+- Persist bucket changes received by the usage worker's observer when the
+ worker stops, so a deletion received during reconciliation is not lost.
+- Keep the reveal button and unit selectors of password, URL, e-mail and number
+ fields beside the text in Arabic, and restore the task-count badge position.
+- Never treat a canceled bucket-usage authorization probe as authorized, and
+ build the share-link expiry from Luxon's locale-independent ISO output.
## Release v2.6.0
diff --git a/README.md b/README.md
index d03512ba9e..a8d434c0ba 100644
--- a/README.md
+++ b/README.md
@@ -36,11 +36,11 @@
## SILO Console 2.7.0
-This source prepares **2.7.0**: ten UI languages and Arabic RTL, independently
-cached language assets, container HTTPS and temporary-file fixes, and updated
-Go dependencies and API documentation. Release artifacts are prepared as a
-GitHub draft; [v2.6.0](https://github.com/pgsty/silo-console/releases/tag/v2.6.0)
-remains the latest published stable release until that draft is published.
+This source is **2.7.0**: ten UI languages and Arabic RTL, independently cached
+language assets, container HTTPS and temporary-file fixes, usage-worker TLS and
+scheduling fixes, folder sizes over plain HTTP, and updated Go dependencies and
+API documentation. See [GitHub Releases](https://github.com/pgsty/silo-console/releases)
+for publication status.
[English](README.md) · [简体中文](README_ZH.md)
diff --git a/README_ZH.md b/README_ZH.md
index df5498d587..faa564ac54 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -10,9 +10,9 @@ SILO 对象存储的 Web 管理控制台,由 Pigsty 社区维护。前端使
## SILO Console 2.7.0
-当前源码准备 **2.7.0**:十种语言与阿拉伯语 RTL、独立缓存的语言资源、容器 HTTPS 与临时文件修复,以及 Go 依赖和 API 文档更新。
-制品通过标签流水线准备为 GitHub Draft Release;草稿公开发布前,最新正式版仍为
-[v2.6.0](https://github.com/pgsty/silo-console/releases/tag/v2.6.0)。
+本源码为 **2.7.0**:十种语言与阿拉伯语 RTL、独立缓存的语言资源、容器 HTTPS 与临时文件修复、用量后台任务的 TLS 与调度修复、
+普通 HTTP 下的目录大小计算,以及 Go 依赖和 API 文档更新。
+发布状态见 [GitHub Releases](https://github.com/pgsty/silo-console/releases)。
**2.7.0** 使用 **Go 1.27.3**、**silo-pkg v3.16.0** 与 **mcli 20261109 正式版**。
MC 的 `RELEASE.2026-11-09T00-00-00Z` 是日历标签,Go 用伪版本
diff --git a/api/bucket_usage.go b/api/bucket_usage.go
index c0030928da..0b0bd1963c 100644
--- a/api/bucket_usage.go
+++ b/api/bucket_usage.go
@@ -108,7 +108,9 @@ func authorizeBucketUsage(ctx context.Context, client usage.ListingClient, bucke
if open {
return first.Err
}
- return nil
+ // minio-go closes the channel without an error when ctx is canceled;
+ // that must not read as an authorized, empty listing.
+ return ctx.Err()
}
}
diff --git a/api/bucket_usage_authorize_test.go b/api/bucket_usage_authorize_test.go
new file mode 100644
index 0000000000..a22d2a0651
--- /dev/null
+++ b/api/bucket_usage_authorize_test.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2026 Pigsty
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+package api
+
+import (
+ "context"
+ "testing"
+
+ "github.com/minio/minio-go/v7"
+)
+
+// closedListing models minio-go on a canceled context: the channel closes
+// without an error object.
+type closedListing struct{}
+
+func (closedListing) ListObjects(context.Context, string, minio.ListObjectsOptions) <-chan minio.ObjectInfo {
+ objects := make(chan minio.ObjectInfo)
+ close(objects)
+ return objects
+}
+
+func TestAuthorizeBucketUsageDoesNotAuthorizeACanceledProbe(t *testing.T) {
+ if err := authorizeBucketUsage(context.Background(), closedListing{}, "empty", false); err != nil {
+ t.Fatalf("an empty, readable bucket must be authorized: %v", err)
+ }
+ canceled, cancel := context.WithCancel(context.Background())
+ cancel()
+ // Either select branch may win; both must refuse.
+ for range 100 {
+ if err := authorizeBucketUsage(canceled, closedListing{}, "bucket", true); err == nil {
+ t.Fatal("a canceled probe was treated as authorized")
+ }
+ }
+}
diff --git a/api/tls.go b/api/tls.go
index 8ef31ab6c3..b5e3864cfe 100644
--- a/api/tls.go
+++ b/api/tls.go
@@ -122,6 +122,13 @@ var (
insecureMinIOTransport *http.Transport
)
+// MinIOEndpointTransport exposes the SILO endpoint policy of
+// PrepareSTSClientTransport, without the per-request client address, to
+// processes that act on their own behalf, such as the usage worker.
+func MinIOEndpointTransport() http.RoundTripper {
+ return minioEndpointTransport()
+}
+
// minioEndpointTransport returns GlobalTransport unless the operator explicitly
// enabled the SILO endpoint compatibility switch for an HTTPS endpoint.
func minioEndpointTransport() http.RoundTripper {
diff --git a/api/user_objects.go b/api/user_objects.go
index 05aac0ba94..d2f8e13372 100644
--- a/api/user_objects.go
+++ b/api/user_objects.go
@@ -1014,9 +1014,15 @@ func uploadFiles(ctx context.Context, client MinioClient, params objectApi.PostB
for {
p, err := mr.NextPart()
+ // Only an unwrapped io.EOF marks the closing boundary. A truncated
+ // body yields a wrapped EOF and malformed headers other errors; none
+ // of them return a part.
if err == io.EOF {
break
}
+ if err != nil {
+ return err
+ }
size, err := strconv.ParseInt(p.FormName(), 10, 64)
if err != nil {
diff --git a/api/user_objects_upload_test.go b/api/user_objects_upload_test.go
new file mode 100644
index 0000000000..bfef53a130
--- /dev/null
+++ b/api/user_objects_upload_test.go
@@ -0,0 +1,63 @@
+// Copyright (c) 2026 Pigsty
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+package api
+
+import (
+ "context"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ objectApi "github.com/minio/console/api/operations/object"
+ "github.com/minio/minio-go/v7"
+)
+
+func TestUploadFilesRejectsMalformedMultipartWithoutPanic(t *testing.T) {
+ var uploaded []string
+ minioPutObjectMock = func(_ context.Context, bucket, object string, reader io.Reader, size int64, _ minio.PutObjectOptions) (minio.UploadInfo, error) {
+ data, err := io.ReadAll(reader)
+ if err != nil {
+ return minio.UploadInfo{}, err
+ }
+ uploaded = append(uploaded, bucket+"/"+object+"="+string(data))
+ return minio.UploadInfo{Size: size}, nil
+ }
+ t.Cleanup(func() { minioPutObjectMock = nil })
+
+ params := func(body string) objectApi.PostBucketsBucketNameObjectsUploadParams {
+ r := httptest.NewRequest(http.MethodPost, "/api/v1/buckets/b/objects/upload", strings.NewReader(body))
+ r.Header.Set("Content-Type", "multipart/form-data; boundary=X")
+ prefix := "/dir/a.txt"
+ return objectApi.PostBucketsBucketNameObjectsUploadParams{HTTPRequest: r, BucketName: "b", Prefix: &prefix}
+ }
+ const part = "--X\r\nContent-Disposition: form-data; name=\"5\"; filename=\"a.txt\"\r\n\r\nhello\r\n"
+
+ if err := uploadFiles(context.Background(), minioClientMock{}, params(part+"--X--\r\n")); err != nil {
+ t.Fatalf("valid upload: %v", err)
+ }
+ if len(uploaded) != 1 || uploaded[0] != "b/dir/a.txt=hello" {
+ t.Fatalf("valid upload stored %q", uploaded)
+ }
+
+ for name, body := range map[string]string{
+ // Each of these used to return a nil part and panic.
+ "malformed part header": "--X\r\nnot a header line\r\n\r\nhello\r\n--X--\r\n",
+ "truncated after boundary": part + "--X",
+ "empty body": "",
+ "no boundary": "garbage",
+ } {
+ t.Run(name, func(t *testing.T) {
+ uploaded = nil
+ if err := uploadFiles(context.Background(), minioClientMock{}, params(body)); err == nil {
+ t.Fatalf("expected an error, stored %q", uploaded)
+ }
+ })
+ }
+}
diff --git a/cmd/console/usage_worker.go b/cmd/console/usage_worker.go
index b51f72e210..59f565ffdc 100644
--- a/cmd/console/usage_worker.go
+++ b/cmd/console/usage_worker.go
@@ -5,24 +5,58 @@ package main
import (
"context"
+ "fmt"
"os"
"os/signal"
+ "path/filepath"
"syscall"
"github.com/minio/cli"
+ "github.com/minio/console/api"
+ "github.com/minio/console/pkg/certs"
"github.com/minio/console/pkg/usage"
)
var usageWorkerCmd = cli.Command{
Name: "usage-worker",
Usage: "Observe storage changes and refresh durable bucket-usage metadata",
- Action: func(_ *cli.Context) error {
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "certs-dir",
+ Value: certs.GlobalCertsCADir.Get(),
+ Usage: "path to certs directory; certificates in its CAs directory are trusted for the storage endpoint",
+ },
+ },
+ Action: func(cliCtx *cli.Context) error {
config, err := usage.LoadConfig(true)
if err != nil {
return err
}
+ if err = loadUsageWorkerTLS(cliCtx, &config); err != nil {
+ return err
+ }
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
return usage.RunWorker(ctx, config)
},
}
+
+// loadUsageWorkerTLS gives the worker the server's outbound trust: system
+// roots, certs/CAs and Console's public certificate, with the endpoint-scoped
+// CONSOLE_MINIO_SERVER_TLS_SKIP_VERIFY policy applied to the SILO endpoint.
+// The worker serves no TLS, so it neither creates the certs directory nor
+// loads a private key; a read-only or key-protected certs directory works.
+func loadUsageWorkerTLS(ctx *cli.Context, config *usage.Config) error {
+ dir, _, err := certs.ResolveConfigDirFromCtx(ctx, "certs-dir", certs.DefaultCertsDir.Get)
+ if err != nil {
+ return err
+ }
+ certs.GlobalCertsDir = dir
+ certs.GlobalCertsCADir = &certs.ConfigDir{Path: filepath.Join(dir.Get(), certs.CertsCADir)}
+ if api.GlobalRootCAs, err = certs.GetTrustedRootCAs(); err != nil {
+ return fmt.Errorf("unable to load trusted certificates at %s: failed with %w", dir.Get(), err)
+ }
+ api.ApplyGlobalRootCAs()
+ config.Transport = api.MinIOEndpointTransport()
+ return nil
+}
diff --git a/cmd/console/usage_worker_tls_test.go b/cmd/console/usage_worker_tls_test.go
new file mode 100644
index 0000000000..d1d9f80fa6
--- /dev/null
+++ b/cmd/console/usage_worker_tls_test.go
@@ -0,0 +1,111 @@
+// Copyright (c) 2026 Pigsty
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+package main
+
+import (
+ "context"
+ "crypto/x509"
+ "errors"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/minio/console/api"
+ "github.com/minio/console/pkg/certs"
+ "github.com/minio/console/pkg/usage"
+)
+
+func storageServer(t *testing.T) *httptest.Server {
+ t.Helper()
+ server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.Header().Set("Content-Type", "application/xml")
+ _, _ = w.Write([]byte(``))
+ }))
+ t.Cleanup(server.Close)
+ return server
+}
+
+// listBuckets makes one request with the worker's S3 client, built exactly as
+// RunWorker builds it from the loaded configuration.
+func listBuckets(t *testing.T, endpoint string, args ...string) error {
+ t.Helper()
+ // A pooled connection keeps the trust it was verified with.
+ api.GlobalTransport.CloseIdleConnections()
+ config := usage.Config{Endpoint: endpoint, AccessKey: "worker", SecretKey: "worker-secret"}
+ if err := loadUsageWorkerTLS(serverCLIContext(t, args...), &config); err != nil {
+ t.Fatalf("loadUsageWorkerTLS: %v", err)
+ }
+ client, err := config.NewS3Client()
+ if err != nil {
+ t.Fatalf("NewS3Client: %v", err)
+ }
+ _, err = client.ListBuckets(context.Background())
+ return err
+}
+
+func isUnknownAuthority(err error) bool {
+ var unknownAuthority x509.UnknownAuthorityError
+ return errors.As(err, &unknownAuthority)
+}
+
+func TestUsageWorkerTrustsConsoleCAsAndEndpointPolicy(t *testing.T) {
+ preserveStartupTLSGlobals(t)
+ // Tests dial their own servers; a proxy would turn TLS into CONNECT.
+ api.GlobalTransport.Proxy = nil
+ silo, other := storageServer(t), storageServer(t)
+
+ t.Run("CAs directory", func(t *testing.T) {
+ certsDir := t.TempDir()
+ writeCertificatePEM(t, filepath.Join(certsDir, certs.CertsCADir, "silo.crt"), silo.Certificate())
+ if err := listBuckets(t, silo.URL, "--certs-dir", certsDir); err != nil {
+ t.Fatalf("worker did not trust the configured CA: %v", err)
+ }
+ })
+
+ t.Run("without the CA", func(t *testing.T) {
+ if err := listBuckets(t, silo.URL, "--certs-dir", t.TempDir()); !isUnknownAuthority(err) {
+ t.Fatalf("expected x509.UnknownAuthorityError, got %v", err)
+ }
+ })
+
+ t.Run("missing certs directory is not created", func(t *testing.T) {
+ certsDir := filepath.Join(t.TempDir(), "absent")
+ if err := listBuckets(t, other.URL, "--certs-dir", certsDir); !isUnknownAuthority(err) {
+ t.Fatalf("expected x509.UnknownAuthorityError, got %v", err)
+ }
+ if _, err := os.Stat(certsDir); !errors.Is(err, os.ErrNotExist) {
+ t.Fatalf("the worker created its certs directory: %v", err)
+ }
+ })
+
+ t.Run("an unreadable server key does not block the worker", func(t *testing.T) {
+ certsDir := t.TempDir()
+ writeCertificatePEM(t, filepath.Join(certsDir, certs.CertsCADir, "silo.crt"), silo.Certificate())
+ writeCertificatePEM(t, filepath.Join(certsDir, certs.PublicCertFile), other.Certificate())
+ key := filepath.Join(certsDir, certs.PrivateKeyFile)
+ if err := os.WriteFile(key, []byte("not readable"), 0o000); err != nil {
+ t.Fatal(err)
+ }
+ if err := listBuckets(t, silo.URL, "--certs-dir", certsDir); err != nil {
+ t.Fatalf("worker failed with a key it does not need: %v", err)
+ }
+ })
+
+ t.Run("skip-verify exempts only the configured endpoint", func(t *testing.T) {
+ t.Setenv(api.ConsoleMinIOServer, silo.URL)
+ t.Setenv(api.ConsoleMinIOServerTLSSkipVerify, "on")
+ if err := listBuckets(t, silo.URL, "--certs-dir", t.TempDir()); err != nil {
+ t.Fatalf("skip-verify was not applied to the SILO endpoint: %v", err)
+ }
+ if err := listBuckets(t, other.URL, "--certs-dir", t.TempDir()); !isUnknownAuthority(err) {
+ t.Fatalf("skip-verify leaked to another origin: %v", err)
+ }
+ })
+}
diff --git a/docs/Embedding.md b/docs/Embedding.md
index 2875968e6d..778a814007 100644
--- a/docs/Embedding.md
+++ b/docs/Embedding.md
@@ -4,7 +4,7 @@ Console 2.7.0 source uses silo-pkg v3.16.0 and the published
mcli 20261109 source, recorded in `hack/deps-release.json`, with Go 1.27.3 and upstream
minio-go `v7.4.2-0.20261025165926-320af050c445`. This SDK revision includes
post-v7.4.1 fixes; it is not a tagged v7.4.2 release.
-The candidate retains the fixed `golang.org/x/net v0.62.0` and
+It retains the fixed `golang.org/x/net v0.62.0` and
`golang.org/x/crypto v0.58.1-0.20261009181718-2f1f834330b2` revisions already used
by Console v2.6.0. Use the README replacement block from the Console revision
being embedded in a SILO server.
@@ -13,13 +13,13 @@ MC resolves to `v0.0.0-20261109021353-e1d4cc5abfe0`, the source commit
`e1d4cc5abfe00b674eb36347399e7398990be467` tagged
`RELEASE.2026-11-09T00-00-00Z`. The calendar tag is not Go SemVer.
silo-pkg v3.16.0 resolves to `b7aeea85e84e8888d6ab424c7123a1297711bc3f`.
-These are released dependencies. Console 2.7.0 artifacts are prepared as a
-GitHub draft before public release; check the release's publication status.
+These are released dependencies; see GitHub Releases for Console's own
+publication status.
Published Console v2.6.0 used silo-pkg v3.15.2 and mcli 20261026; its
[dependency record](https://github.com/pgsty/silo-console/blob/v2.6.0/hack/deps-release.json)
remains attached to that release.
-The candidate's generator is go-swagger v0.36.7; its linked runtime and
+The generator is go-swagger v0.36.7; its linked runtime and
server-middleware are v0.33.4. Generated array context validators now continue
past zero elements. The current schema has no `readOnly` constraints, so this
does not introduce new contextual request restrictions. The default generated
diff --git a/docs/FolderSizes.md b/docs/FolderSizes.md
index 85a2738227..81d1c1cf81 100644
--- a/docs/FolderSizes.md
+++ b/docs/FolderSizes.md
@@ -6,7 +6,7 @@ The operator guide lives on the SILO documentation site:
- [中文:用量扫描、权限、限制与可选后台任务](https://silo.pgsty.com/zh/administration/console/managing-objects/#usage-scans)
Usage scans shipped in [Console 2.6.0](https://github.com/pgsty/silo-console/releases/tag/v2.6.0)
-and remain available in the 2.7.0 candidate. The optional worker and the matching
+and remain available in 2.7.0. The optional worker and the matching
server APIs require coordinated SILO support; Server 20261026 does not acquire
the feature merely because this source tree contains it. The implementation and
original guide were contributed by Urbanize in
diff --git a/docs/README.md b/docs/README.md
index 568b44662c..e7466852c0 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,7 +1,6 @@
# Docs
-These pages describe the Console v2.7.0 candidate source. Console v2.6.0 remains
-the latest published release while the v2.7.0 GitHub draft is being prepared.
+These pages describe the Console v2.7.0 source.
Published binaries, packages and release status are available on
[GitHub Releases](https://github.com/pgsty/silo-console/releases).
See [CHANGELOG.md](../CHANGELOG.md) and the
diff --git a/docs/TLS.md b/docs/TLS.md
index 747e6fef46..3162c4ebe3 100644
--- a/docs/TLS.md
+++ b/docs/TLS.md
@@ -47,6 +47,12 @@ trust store is the system root store plus every certificate under
`--tls-ca` file when it is set. Console's own serving certificates are added to
that pool as well, so a Console that talks to itself trusts itself.
+The optional `console usage-worker` uses the same trust store and accepts the
+same `--certs-dir` flag. It only reads that directory: unlike `console server`
+it does not create it and never loads `private.key`, so a read-only directory
+or a key the worker cannot read is fine. Its storage requests follow the SILO endpoint policy
+described below, including `CONSOLE_MINIO_SERVER_TLS_SKIP_VERIFY`.
+
Embedded in SILO, Console uses the server's pool (`certs/CAs` plus the server's
own public certificates) and dials the address the server advertises to it:
`MINIO_SERVER_URL` when set, otherwise `https://127.0.0.1:`. The server
diff --git a/pkg/certs/certs.go b/pkg/certs/certs.go
index f9b0c4d4bb..119dfb9616 100644
--- a/pkg/certs/certs.go
+++ b/pkg/certs/certs.go
@@ -128,6 +128,19 @@ func MkdirAllIgnorePerm(path string) error {
}
func NewConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool, error) {
+ configDir, dirSet, err := ResolveConfigDirFromCtx(ctx, option, getDefaultDir)
+ if err != nil {
+ return nil, false, err
+ }
+ if err = MkdirAllIgnorePerm(configDir.Path); err != nil {
+ return nil, false, fmt.Errorf("%w: Unable to create directory specified %s=%s", err, option, configDir.Path)
+ }
+ return configDir, dirSet, nil
+}
+
+// ResolveConfigDirFromCtx resolves a directory option like NewConfigDirFromCtx
+// without creating it, for processes that only read the directory.
+func ResolveConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool, error) {
var dir string
var dirSet bool
@@ -164,9 +177,6 @@ func NewConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() s
if err != nil {
return nil, false, fmt.Errorf("%w: Unable to fetch absolute path for %s=%s", err, option, dir)
}
- if err = MkdirAllIgnorePerm(dirAbs); err != nil {
- return nil, false, fmt.Errorf("%w: Unable to create directory specified %s=%s", err, option, dir)
- }
return &ConfigDir{Path: dirAbs}, dirSet, nil
}
@@ -315,6 +325,32 @@ func GetAllCertificatesAndCAs() (*x509.CertPool, []*x509.Certificate, *xcerts.Ma
return rootCAs, publicCerts, certsManager, nil
}
+// GetTrustedRootCAs returns the pool Console verifies peers with: the system
+// roots, every certificate under certs/CAs and Console's own public.crt when
+// present. Unlike GetAllCertificatesAndCAs it never loads a private key, so a
+// process that only makes outbound connections, such as the usage worker,
+// needs neither a readable key nor its password. An unreadable public.crt is
+// skipped like unreadable files under certs/CAs.
+func GetTrustedRootCAs() (*x509.CertPool, error) {
+ rootCAs, err := xcerts.GetRootCAs(GlobalCertsCADir.Get())
+ if err != nil {
+ return nil, err
+ }
+ if rootCAs == nil {
+ rootCAs = &x509.CertPool{}
+ }
+ if isFile(getPublicCertFile()) {
+ publicCerts, err := ParsePublicCertFile(getPublicCertFile())
+ if err != nil && !errors.Is(err, os.ErrPermission) {
+ return nil, err
+ }
+ for _, publicCert := range publicCerts {
+ rootCAs.AddCert(publicCert)
+ }
+ }
+ return rootCAs, nil
+}
+
// EnsureCertAndKey checks if both client certificate and key paths are provided
func EnsureCertAndKey(clientCert, clientKey string) error {
if (clientCert != "" && clientKey == "") ||
diff --git a/pkg/usage/bucket_events_integration_test.go b/pkg/usage/bucket_events_integration_test.go
new file mode 100644
index 0000000000..f2190ca69e
--- /dev/null
+++ b/pkg/usage/bucket_events_integration_test.go
@@ -0,0 +1,174 @@
+//go:build integration
+
+// Copyright (c) 2026 Pigsty
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+package usage
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/jackc/pgx/v5/pgxpool"
+ "github.com/minio/minio-go/v7"
+)
+
+func TestRemovedBucketForgetsTotalsAndFencesTheActiveScan(t *testing.T) {
+ s := integrationStore(t)
+ ctx := context.Background()
+ if err := s.Queue(ctx, "recreated", true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ first, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err = s.Complete(ctx, first, observation(5)); err != nil {
+ t.Fatal(err)
+ }
+ if err = s.Queue(ctx, "recreated", true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ active, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err = s.Removed(ctx, "recreated", 0); err != nil {
+ t.Fatal(err)
+ }
+ record, err := s.Get(ctx, "recreated")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.State != "queued" || record.LastGood != nil || record.LastCompletedAt != nil || record.Progress != nil || record.Checkpoint != nil {
+ t.Fatalf("a deleted bucket kept its totals: %+v", record)
+ }
+ if err = s.Complete(ctx, active, observation(9)); !errors.Is(err, ErrLeaseLost) {
+ t.Fatalf("a scan of the deleted bucket published its totals: %v", err)
+ }
+ // The re-created bucket is measured afresh.
+ rescan, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ c := scanBudgetConfig(t)
+ source := listingFunc(func(ctx context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo {
+ objects := make(chan minio.ObjectInfo, 1)
+ objects <- minio.ObjectInfo{Key: "new", Size: 2, IsLatest: true}
+ close(objects)
+ return objects
+ })
+ if err = s.RunSlice(ctx, source, rescan, c); err != nil {
+ t.Fatal(err)
+ }
+ record, err = s.Get(ctx, "recreated")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.State != "complete" || record.LastGood == nil || record.LastGood.Totals.CurrentBytes != "2" {
+ t.Fatalf("the re-created bucket was not measured afresh: %+v", record)
+ }
+ // Removing a bucket that was never seen is a no-op.
+ if err = s.Removed(ctx, "never-seen", 0); err != nil {
+ t.Fatal(err)
+ }
+ if _, err = s.Get(ctx, "never-seen"); err == nil {
+ t.Fatal("removal created a row for an unknown bucket")
+ }
+}
+
+// A bucket left limited keeps its totals and an exhausted checkpoint, and
+// automatic re-queueing keeps both. If the deletion cannot be persisted at
+// first, it must survive for a later flush instead of being dropped when the
+// observer restarts.
+func TestUnpersistedBucketRemovalIsRetried(t *testing.T) {
+ s := integrationStore(t)
+ ctx := context.Background()
+ if err := s.Queue(ctx, "removed-limited", true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ job, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err = s.Complete(ctx, job, observation(5)); err != nil {
+ t.Fatal(err)
+ }
+ if err = s.Queue(ctx, "removed-limited", true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ if job, err = s.Claim(ctx, time.Minute); err != nil {
+ t.Fatal(err)
+ }
+ spent := observation(1)
+ if err = s.SaveCheckpoint(ctx, job, Checkpoint{Generation: job.Generation, LastKey: "a", ListingTime: time.Hour, Snapshot: spent}, spent, time.Minute); err != nil {
+ t.Fatal(err)
+ }
+ if err = s.Fail(ctx, job, "duration", true, time.Hour); err != nil {
+ t.Fatal(err)
+ }
+
+ c := scanBudgetConfig(t)
+ pending := &pendingChanges{buckets: map[string]pendingChange{}}
+ pending.add("removed-limited", pendingChange{at: time.Now().UTC(), removed: true})
+ unavailable, err := pgxpool.New(ctx, os.Getenv("TEST_USAGE_DATABASE_URL"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ unavailable.Close()
+ if flushPending(ctx, &Store{pool: unavailable, scope: s.scope}, pending, c) {
+ t.Fatal("a closed database accepted the deletion")
+ }
+ // Reconnect reconciliation alone keeps the limited row and its totals.
+ if err = s.Queue(ctx, "removed-limited", false, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ if !flushPending(ctx, s, pending, c) {
+ t.Fatal("the retained deletion was not persisted")
+ }
+ record, err := s.Get(ctx, "removed-limited")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.State != "queued" || record.LastGood != nil || record.Checkpoint != nil {
+ t.Fatalf("the deleted bucket's results survived: %+v", record)
+ }
+}
+
+// A shutdown can interrupt a reconciliation after the event consumer has
+// received a deletion but before the event loop flushed it. The worker must
+// persist it on the way out; reconnect reconciliation alone keeps a limited
+// row's totals.
+func TestShutdownPersistsPendingBucketRemoval(t *testing.T) {
+ s := integrationStore(t)
+ ctx := context.Background()
+ if err := s.Queue(ctx, "removed-at-shutdown", true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ job, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err = s.Complete(ctx, job, observation(5)); err != nil {
+ t.Fatal(err)
+ }
+ pending := &pendingChanges{buckets: map[string]pendingChange{}}
+ pending.add("removed-at-shutdown", pendingChange{at: time.Now().UTC(), removed: true})
+ stopped, stop := context.WithCancel(ctx)
+ stop()
+ observeChanges(stopped, s, scanBudgetConfig(t), "stopping-observer", pending)
+ record, err := s.Get(ctx, "removed-at-shutdown")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.LastGood != nil || record.State != "queued" {
+ t.Fatalf("a deletion received before shutdown was lost: %+v", record)
+ }
+}
diff --git a/pkg/usage/checkpoint.go b/pkg/usage/checkpoint.go
index e8881eebfa..b2d7d34ac3 100644
--- a/pkg/usage/checkpoint.go
+++ b/pkg/usage/checkpoint.go
@@ -15,16 +15,24 @@ import (
// StartAfter key for version listings but no public version-ID resume marker.
// Saving an intermediate version as the cursor would silently skip its peers.
// This object is private worker state and never returned to a browser.
+//
+// ListingTime is the scan budget already used: listing time summed over the
+// scan's slices, excluding time spent queued behind other buckets.
+// Checkpoints written before this field existed decode it as zero.
type Checkpoint struct {
- Generation int64 `json:"generation"`
- LastKey string `json:"last_key"`
- Snapshot Snapshot `json:"snapshot"`
+ Generation int64 `json:"generation"`
+ LastKey string `json:"last_key"`
+ ListingTime time.Duration `json:"listing_time_ns,omitempty"`
+ Snapshot Snapshot `json:"snapshot"`
}
func (c Checkpoint) Validate() error {
if c.Generation < 1 {
return errors.New("invalid usage checkpoint generation")
}
+ if c.ListingTime < 0 {
+ return errors.New("invalid usage checkpoint listing time")
+ }
if err := c.Snapshot.Validate(); err != nil {
return err
}
diff --git a/pkg/usage/config.go b/pkg/usage/config.go
index c972d4629d..3b6fb8e32a 100644
--- a/pkg/usage/config.go
+++ b/pkg/usage/config.go
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"net"
+ "net/http"
"net/url"
"os"
"strconv"
@@ -38,6 +39,10 @@ type Config struct {
FullReconcileInterval time.Duration
RetryInterval time.Duration
MaxRetryInterval time.Duration
+ // Transport carries the caller's TLS trust and endpoint policy to every
+ // storage request. It is never read from the environment; nil keeps the
+ // minio-go default, which trusts only the system roots.
+ Transport http.RoundTripper
}
func LoadConfig(worker bool) (Config, error) {
diff --git a/pkg/usage/event_transport_test.go b/pkg/usage/event_transport_test.go
index ef5bd12224..ca0085291d 100644
--- a/pkg/usage/event_transport_test.go
+++ b/pkg/usage/event_transport_test.go
@@ -9,6 +9,7 @@ import (
"net/http"
"strings"
"testing"
+ "time"
"github.com/minio/minio-go/v7/pkg/notification"
)
@@ -76,6 +77,30 @@ func TestChangeEventsQueueBucketsWithoutCollectingRequestOrObjectData(t *testing
if _, at, ok = changedBucket(event); !ok || at.IsZero() {
t.Fatal("bad source timestamp silently dropped a real deletion")
}
+ // A force delete emits only the bucket event.
+ for _, name := range []string{"s3:BucketCreated:*", "s3:BucketRemoved:*"} {
+ event.EventName = name
+ if bucket, _, ok := changedBucket(event); !ok || bucket != "changed" {
+ t.Fatalf("%s did not invalidate the bucket", name)
+ }
+ }
+}
+
+func TestPendingChangesKeepADeletionFollowedByRecreation(t *testing.T) {
+ pending := &pendingChanges{buckets: map[string]pendingChange{}}
+ removed := time.Date(2026, 11, 9, 0, 0, 0, 0, time.UTC)
+ pending.add("b", pendingChange{at: removed, removed: true})
+ pending.add("b", pendingChange{at: removed.Add(time.Second)})
+ pending.add("b", pendingChange{at: removed.Add(-time.Second)})
+ batch := pending.take()
+ if change := batch["b"]; !change.removed || !change.at.Equal(removed.Add(time.Second)) {
+ t.Fatalf("coalescing lost the deletion or the newest time: %+v", change)
+ }
+ pending.add("b", pendingChange{at: removed.Add(2 * time.Second)})
+ pending.restore(batch)
+ if change := pending.take()["b"]; !change.removed || !change.at.Equal(removed.Add(2*time.Second)) {
+ t.Fatalf("restore lost the deletion or a newer arrival: %+v", change)
+ }
}
func TestRetryBackoffIsBounded(t *testing.T) {
diff --git a/pkg/usage/scan.go b/pkg/usage/scan.go
index 2bfc9d125b..685e6d39ae 100644
--- a/pkg/usage/scan.go
+++ b/pkg/usage/scan.go
@@ -28,7 +28,7 @@ func (c Config) NewS3Client() (*minio.Client, error) {
if err != nil {
return nil, errors.New("invalid usage storage endpoint")
}
- return minio.New(endpoint.Host, &minio.Options{Secure: endpoint.Scheme == "https", Creds: credentials.NewStaticV4(c.AccessKey, c.SecretKey, c.SessionToken)})
+ return minio.New(endpoint.Host, &minio.Options{Secure: endpoint.Scheme == "https", Creds: credentials.NewStaticV4(c.AccessKey, c.SecretKey, c.SessionToken), Transport: c.Transport})
}
func (s Stats) counts() (Counts, error) {
@@ -54,9 +54,11 @@ func scanFailure(err error) *ScanFailure {
// groups advance the checkpoint. A complete snapshot is published only when
// the SDK reaches the end without a listing error or canceled context.
func (s *Store) RunSlice(ctx context.Context, client ListingClient, job Record, c Config) error {
- started := time.Now().UTC()
+ sliceStarted := time.Now()
+ started := sliceStarted.UTC()
counts := Counts{}
lastKey := ""
+ spent := time.Duration(0)
if job.Checkpoint != nil {
var err error
counts, err = job.Checkpoint.Snapshot.Totals.counts()
@@ -65,6 +67,7 @@ func (s *Store) RunSlice(ctx context.Context, client ListingClient, job Record,
}
started = job.Checkpoint.Snapshot.StartedAt
lastKey = job.Checkpoint.LastKey
+ spent = job.Checkpoint.ListingTime
}
snapshot := func() Snapshot {
return Snapshot{Totals: counts.Stats(), ScannedEntries: counts.CurrentObjects + counts.RetainedVersions + counts.DeleteMarkers, StartedAt: started, UpdatedAt: time.Now().UTC()}
@@ -75,18 +78,21 @@ func (s *Store) RunSlice(ctx context.Context, client ListingClient, job Record,
generation = job.Checkpoint.Generation
}
safe := func() Checkpoint {
- return Checkpoint{Generation: generation, LastKey: safeKey, Snapshot: Snapshot{Totals: safeCounts.Stats(), ScannedEntries: safeCounts.CurrentObjects + safeCounts.RetainedVersions + safeCounts.DeleteMarkers, StartedAt: started, UpdatedAt: time.Now().UTC()}}
+ return Checkpoint{Generation: generation, LastKey: safeKey, ListingTime: spent + time.Since(sliceStarted), Snapshot: Snapshot{Totals: safeCounts.Stats(), ScannedEntries: safeCounts.CurrentObjects + safeCounts.RetainedVersions + safeCounts.DeleteMarkers, StartedAt: started, UpdatedAt: time.Now().UTC()}}
}
- // A fair slice resumes the same scan, including its original wall-clock
- // budget. Starting a fresh timeout here lets large buckets run indefinitely.
- deadline := started.Add(c.ScanTimeout)
+ // A fair slice resumes the same scan with the budget it has left. The
+ // budget counts listing time summed over slices, not the wall clock since
+ // the scan started: time spent queued while other buckets take their
+ // turns must not exhaust it, but a fresh timeout per slice would let a
+ // large bucket run indefinitely.
+ remaining := c.ScanTimeout - spent
if ctx.Err() != nil {
return &ScanFailure{Code: "worker_shutdown"}
}
- if !time.Now().Before(deadline) {
+ if remaining <= 0 {
return &ScanFailure{Code: "duration", Limited: true}
}
- scanCtx, cancel := context.WithDeadline(ctx, deadline)
+ scanCtx, cancel := context.WithTimeout(ctx, remaining)
objects := client.ListObjects(scanCtx, job.Bucket, minio.ListObjectsOptions{Recursive: true, WithVersions: true, StartAfter: lastKey, MaxKeys: 1000})
defer func() {
cancel()
@@ -99,7 +105,6 @@ func (s *Store) RunSlice(ctx context.Context, client ListingClient, job Record,
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
- sliceStarted := time.Now()
currentKey := ""
persist := func() error {
operation, cancel := context.WithTimeout(ctx, c.Heartbeat)
@@ -126,6 +131,11 @@ func (s *Store) RunSlice(ctx context.Context, client ListingClient, job Record,
if ctx.Err() != nil {
return &ScanFailure{Code: "worker_shutdown"}
}
+ // Record the exhausted budget, as the other duration
+ // exits do, so a retry ends limited without listing.
+ if err := persist(); err != nil {
+ return err
+ }
return &ScanFailure{Code: "duration", Limited: true}
}
return s.Complete(ctx, job, snapshot(), c.MinScanInterval)
diff --git a/pkg/usage/scan_budget_integration_test.go b/pkg/usage/scan_budget_integration_test.go
new file mode 100644
index 0000000000..4e9eed09a7
--- /dev/null
+++ b/pkg/usage/scan_budget_integration_test.go
@@ -0,0 +1,297 @@
+//go:build integration
+
+// Copyright (c) 2026 Pigsty
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+package usage
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/minio/minio-go/v7"
+)
+
+// slowListing returns 30 keys per bucket, each after delay, resuming after
+// StartAfter like the SDK.
+func slowListing(delay time.Duration) ListingClient {
+ return listingFunc(func(ctx context.Context, _ string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
+ objects := make(chan minio.ObjectInfo)
+ go func() {
+ defer close(objects)
+ for i := range 30 {
+ key := fmt.Sprintf("k%03d", i)
+ if key <= opts.StartAfter {
+ continue
+ }
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(delay):
+ }
+ select {
+ case <-ctx.Done():
+ return
+ case objects <- minio.ObjectInfo{Key: key, Size: 1, IsLatest: true}:
+ }
+ }
+ }()
+ return objects
+ })
+}
+
+func scanBudgetConfig(t *testing.T) Config {
+ t.Helper()
+ c, err := ParseConfig(nil, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.ScanTimeout = 3 * time.Second
+ c.ScanSlice = 100 * time.Millisecond
+ c.PollInterval = 20 * time.Millisecond
+ c.MinScanInterval = 0
+ return c
+}
+
+// Each bucket needs about 1–1.5 s of listing, well within its 3 s budget, but
+// the four together need over 4 s of a single scan worker's time. Charging
+// the wall clock since each scan started, as earlier releases did, ended all
+// four limited because each bucket also waited for the others' slices.
+func TestScanBudgetExcludesTimeQueuedBehindOtherBuckets(t *testing.T) {
+ s := integrationStore(t)
+ c := scanBudgetConfig(t)
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ buckets := []string{"budget-a", "budget-b", "budget-c", "budget-d"}
+ for _, bucket := range buckets {
+ if err := s.Queue(ctx, bucket, true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ }
+ done := make(chan struct{})
+ go func() { defer close(done); runScanWorker(ctx, s, slowListing(30*time.Millisecond), c) }()
+ defer func() { cancel(); <-done }()
+ deadline := time.Now().Add(30 * time.Second)
+ for {
+ finished := 0
+ for _, bucket := range buckets {
+ record, err := s.Get(ctx, bucket)
+ if err != nil {
+ t.Fatal(err)
+ }
+ switch record.State {
+ case "complete":
+ if record.LastGood == nil || record.LastGood.Totals.CurrentObjects != 30 {
+ t.Fatalf("%s completed with wrong totals: %+v", bucket, record.LastGood)
+ }
+ finished++
+ case "limited", "error":
+ t.Fatalf("%s ended %s/%s although its own listing fits the budget", bucket, record.State, record.ErrorCode)
+ }
+ }
+ if finished == len(buckets) {
+ return
+ }
+ if time.Now().After(deadline) {
+ t.Fatal("scans did not finish")
+ }
+ time.Sleep(50 * time.Millisecond)
+ }
+}
+
+// The budget still bounds a single bucket whose own listing is too long.
+func TestScanBudgetLimitsABucketWhoseListingExceedsIt(t *testing.T) {
+ s := integrationStore(t)
+ c := scanBudgetConfig(t)
+ c.ScanTimeout = 500 * time.Millisecond
+ ctx := context.Background()
+ if err := s.Queue(ctx, "budget-long", true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ source := slowListing(50 * time.Millisecond) // 1.5 s of listing
+ for slices := 0; ; slices++ {
+ if slices > 50 {
+ t.Fatal("scan neither completed nor reached its budget")
+ }
+ job, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = s.RunSlice(ctx, source, job, c)
+ if err == nil {
+ record, _ := s.Get(ctx, "budget-long")
+ if record.State == "complete" {
+ t.Fatal("a listing longer than the budget completed")
+ }
+ continue
+ }
+ var failure *ScanFailure
+ if !errors.As(err, &failure) || failure.Code != "duration" || !failure.Limited {
+ t.Fatalf("expected limited/duration, got %v", err)
+ }
+ if err = s.Fail(ctx, job, failure.Code, true, 0); err != nil {
+ t.Fatal(err)
+ }
+ record, err := s.Get(ctx, "budget-long")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.Checkpoint == nil || record.Checkpoint.ListingTime < c.ScanTimeout {
+ t.Fatalf("limited checkpoint must record the exhausted budget: %+v", record.Checkpoint)
+ }
+ return
+ }
+}
+
+// A worker stopping during a scan (a rolling restart) must not show the
+// bucket as failed or delay it by a retry backoff.
+func TestWorkerShutdownRequeuesScanWithItsCheckpoint(t *testing.T) {
+ s := integrationStore(t)
+ c := scanBudgetConfig(t)
+ c.ProgressInterval = 50 * time.Millisecond
+ c.ScanSlice = time.Minute
+ c.RetryInterval = time.Hour
+ c.MaxRetryInterval = time.Hour
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ if err := s.Queue(ctx, "budget-restart", true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ done := make(chan struct{})
+ go func() { defer close(done); runScanWorker(ctx, s, slowListing(100*time.Millisecond), c) }()
+ deadline := time.Now().Add(10 * time.Second)
+ for {
+ record, err := s.Get(context.Background(), "budget-restart")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.State == "running" && record.Checkpoint != nil && record.Checkpoint.LastKey != "" {
+ break
+ }
+ if time.Now().After(deadline) {
+ t.Fatalf("scan never saved a checkpoint: %+v", record)
+ }
+ time.Sleep(20 * time.Millisecond)
+ }
+ cancel()
+ <-done
+ record, err := s.Get(context.Background(), "budget-restart")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.State != "queued" || record.ErrorCode != "" || record.Checkpoint == nil {
+ t.Fatalf("shutdown must requeue the scan with its checkpoint: %+v", record)
+ }
+ resumed, err := s.Claim(context.Background(), time.Minute)
+ if err != nil {
+ t.Fatalf("the next worker cannot resume at once: %v", err)
+ }
+ if resumed.Bucket != "budget-restart" || resumed.Checkpoint == nil || resumed.Checkpoint.LastKey != record.Checkpoint.LastKey {
+ t.Fatalf("resumed without the saved cursor: %+v", resumed)
+ }
+}
+
+// When the budget runs out, the SDK closes the listing channel at the same
+// time as the scan context expires, and select may take either. Both exits
+// must record the exhausted budget, or a retry lists again.
+func TestBudgetExhaustionIsRecordedOnEveryExit(t *testing.T) {
+ s := integrationStore(t)
+ c := scanBudgetConfig(t)
+ c.ScanTimeout = 20 * time.Millisecond
+ c.ScanSlice = time.Minute
+ ctx := context.Background()
+ // A steady stream keeps the scan loop busy until the budget expires; the
+ // lister then closes its channel like the SDK does, so both the closed
+ // channel and the expired context are ready. No progress tick persists
+ // the budget earlier.
+ busy := listingFunc(func(ctx context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo {
+ objects := make(chan minio.ObjectInfo)
+ go func() {
+ defer close(objects)
+ for i := 0; ; i++ {
+ select {
+ case <-ctx.Done():
+ return
+ case objects <- minio.ObjectInfo{Key: fmt.Sprintf("k%09d", i), Size: 1, IsLatest: true}:
+ }
+ }
+ }()
+ return objects
+ })
+ for i := range 40 {
+ bucket := fmt.Sprintf("budget-exit-%02d", i)
+ if err := s.Queue(ctx, bucket, true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ job, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = s.RunSlice(ctx, busy, job, c)
+ var failure *ScanFailure
+ if job.Bucket != bucket || !errors.As(err, &failure) || failure.Code != "duration" {
+ t.Fatalf("expected limited/duration for %s, got %s: %v", bucket, job.Bucket, err)
+ }
+ // Keep earlier limited rows out of later claims.
+ if err = s.Fail(ctx, job, failure.Code, true, time.Hour); err != nil {
+ t.Fatal(err)
+ }
+ record, err := s.Get(ctx, bucket)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.Checkpoint == nil || record.Checkpoint.ListingTime < c.ScanTimeout {
+ t.Fatalf("attempt %d left the spent budget unrecorded: %+v", i, record.Checkpoint)
+ }
+ }
+}
+
+// A shutdown that cancels a checkpoint, yield or completion write surfaces as
+// a database error rather than worker_shutdown; the job must still be handed
+// back at once instead of waiting for its lease to expire.
+func TestShutdownDuringADatabaseWriteStillRequeues(t *testing.T) {
+ s := integrationStore(t)
+ c := scanBudgetConfig(t)
+ ctx := context.Background()
+ for _, bucket := range []string{"budget-canceled-write", "budget-upstream"} {
+ if err := s.Queue(ctx, bucket, true, 0, 0, nil); err != nil {
+ t.Fatal(err)
+ }
+ }
+ job, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ stopping, stop := context.WithCancel(ctx)
+ stop()
+ settleScan(stopping, s, job, fmt.Errorf("save checkpoint: %w", context.Canceled), c)
+ record, err := s.Get(ctx, job.Bucket)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if record.State != "queued" || record.ErrorCode != "" {
+ t.Fatalf("an interrupted write left the job %s/%s", record.State, record.ErrorCode)
+ }
+ // A failure while the worker keeps running is still recorded.
+ other, err := s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if other.Bucket == job.Bucket {
+ other, err = s.Claim(ctx, time.Minute)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ settleScan(ctx, s, other, &ScanFailure{Code: "upstream"}, c)
+ if record, err = s.Get(ctx, other.Bucket); err != nil || record.State != "error" || record.ErrorCode != "upstream" {
+ t.Fatalf("a scan failure was not recorded: %+v %v", record, err)
+ }
+}
diff --git a/pkg/usage/store.go b/pkg/usage/store.go
index 0665dd825b..eb9dafa7b5 100644
--- a/pkg/usage/store.go
+++ b/pkg/usage/store.go
@@ -148,10 +148,10 @@ func (s *Store) Migrate(ctx context.Context) error {
// Repeated manual clicks join an existing job rather than creating a backlog.
//
// A failed or limited job keeps its checkpoint so automatic retries never
-// restart an over-budget listing. That checkpoint also carries the original
-// scan deadline, so resuming it after a limit can only end limited again. An
-// explicit refresh therefore discards it and starts a new scan with a new
-// budget; automatic events and reconciliation leave it in place.
+// restart an over-budget listing. That checkpoint also carries the listing
+// time already spent, so resuming it after a limit can only end limited
+// again. An explicit refresh therefore discards it and starts a new scan with
+// a new budget; automatic events and reconciliation leave it in place.
func (s *Store) Queue(ctx context.Context, bucket string, manual bool, debounce, spacing time.Duration, eventAt *time.Time) error {
if debounce < 0 || spacing < 0 {
return errors.New("usage queue delays must not be negative")
@@ -324,6 +324,40 @@ func (s *Store) Fail(ctx context.Context, job Record, code string, limited bool,
return nil
}
+// Release hands a scan interrupted by worker shutdown back to the queue. It
+// keeps the checkpoint and visible progress so the next worker resumes at
+// once, and it is neither recorded as an error nor counted as an attempt,
+// so a rolling restart does not show buckets as failed.
+func (s *Store) Release(ctx context.Context, job Record) error {
+ command, err := s.pool.Exec(ctx, `UPDATE console_usage_buckets SET state='queued',error_code='',lease_token=NULL,lease_until=NULL,
+ next_run_at=clock_timestamp(),attempts=GREATEST(attempts-1,0)
+ WHERE scope=$1 AND bucket=$2 AND lease_token=$3 AND state='running' AND lease_until>clock_timestamp()`, s.scope, job.Bucket, job.LeaseToken)
+ if err != nil {
+ return err
+ }
+ if command.RowsAffected() != 1 {
+ return ErrLeaseLost
+ }
+ return nil
+}
+
+// Removed forgets a deleted bucket's results at once. A force delete removes
+// every object but emits only s3:BucketRemoved, so without this a bucket
+// re-created under the same name, possibly by another owner, would show the
+// old bucket's totals until the next full reconciliation. A scan in progress
+// loses its lease. The queued rescan then either retires the row through
+// MissingBucket or measures the re-created bucket.
+func (s *Store) Removed(ctx context.Context, bucket string, debounce time.Duration) error {
+ if bucket == "" || len(bucket) > 63 || debounce < 0 {
+ return errors.New("invalid usage bucket")
+ }
+ _, err := s.pool.Exec(ctx, `UPDATE console_usage_buckets SET state='queued',generation=generation+1,
+ last_good=NULL,progress=NULL,checkpoint=NULL,last_completed_at=NULL,lease_token=NULL,lease_until=NULL,
+ attempts=0,error_code='',priority=GREATEST(priority,10),next_run_at=clock_timestamp()+$3::bigint*interval '1 millisecond'
+ WHERE scope=$1 AND bucket=$2`, s.scope, bucket, debounce.Milliseconds())
+ return err
+}
+
// Discover queues new buckets without invalidating healthy existing results.
// Frequent inventory checks are cheap; full safety-net scans are independent.
func (s *Store) Discover(ctx context.Context, bucket string, debounce time.Duration) error {
diff --git a/pkg/usage/worker.go b/pkg/usage/worker.go
index 4c45e0bce7..d3c5d98c5a 100644
--- a/pkg/usage/worker.go
+++ b/pkg/usage/worker.go
@@ -99,34 +99,47 @@ func runScanWorker(ctx context.Context, s *Store, client ListingClient, c Config
}
continue
}
- err = s.RunSlice(ctx, client, job, c)
- if err == nil {
- continue
- }
- if errors.Is(err, ErrLeaseLost) {
- continue
- }
- var failure *ScanFailure
- if errors.As(err, &failure) {
- if failure.Code == "not_found" {
- operation, stop := context.WithTimeout(ctx, c.Heartbeat)
- _ = s.MissingBucket(operation, job)
- stop()
- continue
- }
- operation, cancel := context.WithTimeout(context.Background(), c.Heartbeat)
- persistErr := s.Fail(operation, job, failure.Code, failure.Limited, c.retry(job.Attempts))
- cancel()
- if persistErr != nil && !errors.Is(persistErr, ErrLeaseLost) {
- log.Print("usage worker: state store unavailable while recording scan failure")
- }
- if failure.Code != "worker_shutdown" {
- log.Printf("usage worker: bucket=%q scan=%s", job.Bucket, failure.Code)
- }
- } else if ctx.Err() == nil {
- log.Print("usage worker: scan interrupted by state store failure; lease recovery will resume it")
+ settleScan(ctx, s, job, s.RunSlice(ctx, client, job, c), c)
+ }
+}
+
+// settleScan records how a claimed slice ended. A stopping worker hands its
+// job back with Release whatever interrupted it: the shutdown check inside
+// RunSlice, or a database write canceled with the worker's context. The
+// lease guard makes Release a no-op for a job that was already completed,
+// yielded or taken over.
+func settleScan(ctx context.Context, s *Store, job Record, err error, c Config) {
+ if err == nil || errors.Is(err, ErrLeaseLost) {
+ return
+ }
+ var failure *ScanFailure
+ isFailure := errors.As(err, &failure)
+ if ctx.Err() != nil || (isFailure && failure.Code == "worker_shutdown") {
+ operation, cancel := context.WithTimeout(context.Background(), c.Heartbeat)
+ releaseErr := s.Release(operation, job)
+ cancel()
+ if releaseErr != nil && !errors.Is(releaseErr, ErrLeaseLost) {
+ log.Print("usage worker: state store unavailable while releasing an interrupted scan; lease recovery will resume it")
}
+ return
+ }
+ if !isFailure {
+ log.Print("usage worker: scan interrupted by state store failure; lease recovery will resume it")
+ return
+ }
+ if failure.Code == "not_found" {
+ operation, stop := context.WithTimeout(ctx, c.Heartbeat)
+ _ = s.MissingBucket(operation, job)
+ stop()
+ return
+ }
+ operation, cancel := context.WithTimeout(context.Background(), c.Heartbeat)
+ persistErr := s.Fail(operation, job, failure.Code, failure.Limited, c.retry(job.Attempts))
+ cancel()
+ if persistErr != nil && !errors.Is(persistErr, ErrLeaseLost) {
+ log.Print("usage worker: state store unavailable while recording scan failure")
}
+ log.Printf("usage worker: bucket=%q scan=%s", job.Bucket, failure.Code)
}
func reconcile(ctx context.Context, s *Store, client *minio.Client, c Config, full bool) error {
@@ -150,8 +163,21 @@ func reconcile(ctx context.Context, s *Store, client *minio.Client, c Config, fu
}
func runObserver(ctx context.Context, s *Store, c Config) {
- owner := uuid.NewString()
+ // Received changes that could not be persisted outlive an observer lease.
+ // Reconnect reconciliation re-queues every bucket, but it cannot tell that
+ // a bucket was deleted and re-created, so a lost deletion would leave the
+ // old bucket's totals in place.
+ observeChanges(ctx, s, c, uuid.NewString(), &pendingChanges{buckets: map[string]pendingChange{}})
+}
+
+// observeChanges runs observer leases until ctx ends, then persists whatever
+// is still pending, such as a deletion received while a reconciliation kept
+// the event loop from flushing. observeLease returns only after its event
+// consumer has stopped, so nothing is added after the final flush.
+func observeChanges(ctx context.Context, s *Store, c Config, owner string, pending *pendingChanges) {
+ defer flushPending(ctx, s, pending, c)
for ctx.Err() == nil {
+ flushPending(ctx, s, pending, c)
operation, cancel := context.WithTimeout(ctx, c.Heartbeat)
acquired, err := s.AcquireObserver(operation, owner, c.Lease)
cancel()
@@ -161,14 +187,14 @@ func runObserver(ctx context.Context, s *Store, c Config) {
}
continue
}
- observeLease(ctx, s, c, owner)
+ observeLease(ctx, s, c, owner, pending)
if !wait(ctx, c.RetryInterval) {
return
}
}
}
-func observeLease(ctx context.Context, s *Store, c Config, owner string) {
+func observeLease(ctx context.Context, s *Store, c Config, owner string, pending *pendingChanges) {
streamCtx, cancel := context.WithCancel(ctx)
ctx = streamCtx
// Inventory may take longer than a lease. Renew independently of the event
@@ -212,10 +238,14 @@ func observeLease(ctx context.Context, s *Store, c Config, owner string) {
<-streamDone
}
}()
- base, err := minio.DefaultTransport(strings.HasPrefix(c.Endpoint, "https://"))
- if err != nil {
- log.Print("usage observer: transport configuration invalid")
- return
+ base := c.Transport
+ if base == nil {
+ defaultTransport, err := minio.DefaultTransport(strings.HasPrefix(c.Endpoint, "https://"))
+ if err != nil {
+ log.Print("usage observer: transport configuration invalid")
+ return
+ }
+ base = defaultTransport
}
transport := &eventTransport{base: base, wake: make(chan struct{}, 1)}
endpoint, _ := url.Parse(c.Endpoint)
@@ -223,13 +253,14 @@ func observeLease(ctx context.Context, s *Store, c Config, owner string) {
if err != nil {
return
}
- events = client.ListenNotification(streamCtx, "", "", []string{"s3:ObjectCreated:*", "s3:ObjectRemoved:*"})
+ // Bucket events matter too: a force delete removes every object but
+ // emits only s3:BucketRemoved.
+ events = client.ListenNotification(streamCtx, "", "", []string{"s3:ObjectCreated:*", "s3:ObjectRemoved:*", "s3:BucketCreated:*", "s3:BucketRemoved:*"})
// The storage server drops notifications for a subscriber that stops
// reading, without closing the stream. Consume the stream continuously, so
// reconciliation and database writes on the loop below cannot lose changes
// while coverage still reports a reconciled connection. Pending work is one
// entry per bucket, bounded by the inventory rather than the event rate.
- pending := &pendingChanges{buckets: map[string]time.Time{}}
streamDone = make(chan struct{})
go func() {
defer close(streamDone)
@@ -242,7 +273,7 @@ func observeLease(ctx context.Context, s *Store, c Config, owner string) {
}
for _, event := range info.Records {
if bucket, at, ok := changedBucket(event); ok {
- pending.add(bucket, at)
+ pending.add(bucket, pendingChange{at: at, removed: strings.HasPrefix(event.EventName, "s3:BucketRemoved:")})
}
}
}
@@ -257,27 +288,7 @@ func observeLease(ctx context.Context, s *Store, c Config, owner string) {
defer flush.Stop()
connected := false
var observedEpoch uint64
- flushEvents := func() bool {
- batch := pending.take()
- for bucket, at := range batch {
- parent := ctx
- if ctx.Err() != nil {
- parent = context.Background()
- }
- operation, stop := context.WithTimeout(parent, c.Heartbeat)
- err := s.Queue(operation, bucket, false, c.EventDebounce, c.MinScanInterval, &at)
- stop()
- if err != nil {
- pending.restore(batch)
- if ctx.Err() == nil {
- log.Print("usage observer: failed to persist changes; reconnect reconciliation required")
- }
- return false
- }
- delete(batch, bucket)
- }
- return true
- }
+ flushEvents := func() bool { return flushPending(ctx, s, pending, c) }
doReconcile := func(full bool) bool {
operation, stop := context.WithTimeout(ctx, c.ScanTimeout)
defer stop()
@@ -353,33 +364,77 @@ func observeLease(ctx context.Context, s *Store, c Config, owner string) {
}
}
+// flushPending persists received changes: a deletion first clears the
+// bucket's results, then the bucket is queued like any other change. What
+// cannot be persisted is restored for the next attempt. During shutdown the
+// writes use a fresh context so received changes are not dropped.
+func flushPending(ctx context.Context, s *Store, pending *pendingChanges, c Config) bool {
+ batch := pending.take()
+ for bucket, change := range batch {
+ parent := ctx
+ if ctx.Err() != nil {
+ parent = context.Background()
+ }
+ operation, stop := context.WithTimeout(parent, c.Heartbeat)
+ var err error
+ if change.removed {
+ err = s.Removed(operation, bucket, c.EventDebounce)
+ }
+ if err == nil {
+ err = s.Queue(operation, bucket, false, c.EventDebounce, c.MinScanInterval, &change.at)
+ }
+ stop()
+ if err != nil {
+ pending.restore(batch)
+ if ctx.Err() == nil {
+ log.Print("usage observer: failed to persist changes; they are kept for the next attempt")
+ }
+ return false
+ }
+ delete(batch, bucket)
+ }
+ return true
+}
+
+// pendingChange is the newest event time for a bucket and whether the
+// bucket was deleted since the last flush.
+type pendingChange struct {
+ at time.Time
+ removed bool
+}
+
// pendingChanges coalesces received notifications into one invalidation per
-// bucket until the observer loop persists them.
+// bucket until the observer loop persists them. A deletion is kept even when
+// a later event, such as re-creating the bucket, arrives in the same batch.
type pendingChanges struct {
sync.Mutex
- buckets map[string]time.Time
+ buckets map[string]pendingChange
}
-func (p *pendingChanges) add(bucket string, at time.Time) {
+func (p *pendingChanges) add(bucket string, change pendingChange) {
p.Lock()
defer p.Unlock()
- if previous, exists := p.buckets[bucket]; !exists || at.After(previous) {
- p.buckets[bucket] = at
+ if previous, exists := p.buckets[bucket]; exists {
+ if previous.at.After(change.at) {
+ change.at = previous.at
+ }
+ change.removed = change.removed || previous.removed
}
+ p.buckets[bucket] = change
}
-func (p *pendingChanges) take() map[string]time.Time {
+func (p *pendingChanges) take() map[string]pendingChange {
p.Lock()
defer p.Unlock()
batch := p.buckets
- p.buckets = map[string]time.Time{}
+ p.buckets = map[string]pendingChange{}
return batch
}
// restore returns unpersisted changes without discarding newer arrivals.
-func (p *pendingChanges) restore(batch map[string]time.Time) {
- for bucket, at := range batch {
- p.add(bucket, at)
+func (p *pendingChanges) restore(batch map[string]pendingChange) {
+ for bucket, change := range batch {
+ p.add(bucket, change)
}
}
@@ -390,6 +445,8 @@ func changedBucket(event notification.Event) (string, time.Time, bool) {
switch {
case strings.HasPrefix(event.EventName, "s3:ObjectCreated:"):
case strings.HasPrefix(event.EventName, "s3:ObjectRemoved:"):
+ case strings.HasPrefix(event.EventName, "s3:BucketCreated:"):
+ case strings.HasPrefix(event.EventName, "s3:BucketRemoved:"):
default:
return "", time.Time{}, false
}
diff --git a/pkg/usage/worker_regression_integration_test.go b/pkg/usage/worker_regression_integration_test.go
index f5ad3e0294..7a668af482 100644
--- a/pkg/usage/worker_regression_integration_test.go
+++ b/pkg/usage/worker_regression_integration_test.go
@@ -18,7 +18,7 @@ import (
"github.com/minio/minio-go/v7"
)
-func TestScanDeadlineSurvivesCheckpoint(t *testing.T) {
+func TestScanBudgetSurvivesCheckpoint(t *testing.T) {
s := integrationStore(t)
ctx := context.Background()
if err := s.Queue(ctx, "deadline-bucket", true, 0, 0, nil); err != nil {
@@ -30,7 +30,7 @@ func TestScanDeadlineSurvivesCheckpoint(t *testing.T) {
}
snapshot := observation(10)
snapshot.StartedAt = time.Now().Add(-time.Hour)
- checkpoint := Checkpoint{Generation: job.Generation, LastKey: "a", Snapshot: snapshot}
+ checkpoint := Checkpoint{Generation: job.Generation, LastKey: "a", ListingTime: time.Hour, Snapshot: snapshot}
if err := s.Yield(ctx, job, checkpoint, snapshot); err != nil {
t.Fatal(err)
}
@@ -65,7 +65,7 @@ func TestScanDeadlineSurvivesCheckpoint(t *testing.T) {
var failure *ScanFailure
if !errors.As(err, &failure) || failure.Code != "duration" || !failure.Limited {
record, _ := s.Get(ctx, "deadline-bucket")
- t.Fatalf("expired full-scan deadline must remain limited after resume; err=%v state=%s", err, record.State)
+ t.Fatalf("an exhausted listing budget must remain limited after resume; err=%v state=%s", err, record.State)
}
if listed {
t.Fatal("an expired checkpoint must not start another S3 listing")
@@ -102,7 +102,7 @@ func TestManualRefreshRestartsLimitedScanWithFreshBudget(t *testing.T) {
}
stale := observation(10)
stale.StartedAt = time.Now().Add(-time.Hour)
- if err = s.Yield(ctx, job, Checkpoint{Generation: job.Generation, LastKey: "a", Snapshot: stale}, stale); err != nil {
+ if err = s.Yield(ctx, job, Checkpoint{Generation: job.Generation, LastKey: "a", ListingTime: time.Hour, Snapshot: stale}, stale); err != nil {
t.Fatal(err)
}
var starts []string
@@ -267,7 +267,10 @@ func TestObserverRenewsLeaseDuringInventory(t *testing.T) {
}
ctx, cancel := context.WithCancel(t.Context())
done := make(chan struct{})
- go func() { defer close(done); observeLease(ctx, s, c, "review-observer") }()
+ go func() {
+ defer close(done)
+ observeLease(ctx, s, c, "review-observer", &pendingChanges{buckets: map[string]pendingChange{}})
+ }()
defer func() { cancel(); <-done }()
select {
case <-inventoryStarted:
@@ -372,7 +375,10 @@ func TestObserverKeepsReadingEventsDuringSlowReconciliation(t *testing.T) {
}
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
- go func() { defer close(done); observeLease(ctx, s, c, "draining-observer") }()
+ go func() {
+ defer close(done)
+ observeLease(ctx, s, c, "draining-observer", &pendingChanges{buckets: map[string]pendingChange{}})
+ }()
defer func() { cancel(); <-done }()
deadline := time.Now().Add(30 * time.Second)
for {
diff --git a/web-app/build/.vite/manifest.json.gz b/web-app/build/.vite/manifest.json.gz
index f1a2d67575..4ad4a987ac 100644
Binary files a/web-app/build/.vite/manifest.json.gz and b/web-app/build/.vite/manifest.json.gz differ
diff --git a/web-app/build/assets/AccessDetailsPanel-Bn8j2mPU.js.gz b/web-app/build/assets/AccessDetailsPanel-Bn8j2mPU.js.gz
deleted file mode 100644
index 802d543c20..0000000000
Binary files a/web-app/build/assets/AccessDetailsPanel-Bn8j2mPU.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AccessDetailsPanel-CglkAzCT.js.gz b/web-app/build/assets/AccessDetailsPanel-CglkAzCT.js.gz
new file mode 100644
index 0000000000..24a948742b
Binary files /dev/null and b/web-app/build/assets/AccessDetailsPanel-CglkAzCT.js.gz differ
diff --git a/web-app/build/assets/AccessRulePanel-C7paZ2C0.js.gz b/web-app/build/assets/AccessRulePanel-C7paZ2C0.js.gz
new file mode 100644
index 0000000000..1e179e2295
Binary files /dev/null and b/web-app/build/assets/AccessRulePanel-C7paZ2C0.js.gz differ
diff --git a/web-app/build/assets/AccessRulePanel-DNOBSphi.js.gz b/web-app/build/assets/AccessRulePanel-DNOBSphi.js.gz
deleted file mode 100644
index e1bf158757..0000000000
Binary files a/web-app/build/assets/AccessRulePanel-DNOBSphi.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Accordion-BO3et2aS.js.gz b/web-app/build/assets/Accordion-BO3et2aS.js.gz
deleted file mode 100644
index dc027c3fd9..0000000000
Binary files a/web-app/build/assets/Accordion-BO3et2aS.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Accordion-JbA7mLqa.js.gz b/web-app/build/assets/Accordion-JbA7mLqa.js.gz
new file mode 100644
index 0000000000..0c0220fdfc
Binary files /dev/null and b/web-app/build/assets/Accordion-JbA7mLqa.js.gz differ
diff --git a/web-app/build/assets/Account-CfAelSJk.js.gz b/web-app/build/assets/Account-CfAelSJk.js.gz
deleted file mode 100644
index 02bfc07a25..0000000000
Binary files a/web-app/build/assets/Account-CfAelSJk.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Account-D0jdEEpT.js.gz b/web-app/build/assets/Account-D0jdEEpT.js.gz
new file mode 100644
index 0000000000..70a300c0ea
Binary files /dev/null and b/web-app/build/assets/Account-D0jdEEpT.js.gz differ
diff --git a/web-app/build/assets/AccountUtils-DkxPNVNt.js.gz b/web-app/build/assets/AccountUtils-DkxPNVNt.js.gz
new file mode 100644
index 0000000000..66c2b891b6
Binary files /dev/null and b/web-app/build/assets/AccountUtils-DkxPNVNt.js.gz differ
diff --git a/web-app/build/assets/AccountUtils-DpUrvAVT.js.gz b/web-app/build/assets/AccountUtils-DpUrvAVT.js.gz
deleted file mode 100644
index 98938240ae..0000000000
Binary files a/web-app/build/assets/AccountUtils-DpUrvAVT.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddAccessRule-DG00FLVg.js.gz b/web-app/build/assets/AddAccessRule-DG00FLVg.js.gz
new file mode 100644
index 0000000000..b22abee9b3
Binary files /dev/null and b/web-app/build/assets/AddAccessRule-DG00FLVg.js.gz differ
diff --git a/web-app/build/assets/AddAccessRule-s8c4ASmD.js.gz b/web-app/build/assets/AddAccessRule-s8c4ASmD.js.gz
deleted file mode 100644
index d3847d7cd7..0000000000
Binary files a/web-app/build/assets/AddAccessRule-s8c4ASmD.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddBucket-CY7SsPzq.js.gz b/web-app/build/assets/AddBucket-CY7SsPzq.js.gz
deleted file mode 100644
index 6c7ef0a4ea..0000000000
Binary files a/web-app/build/assets/AddBucket-CY7SsPzq.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddBucket-llLnfIpq.js.gz b/web-app/build/assets/AddBucket-llLnfIpq.js.gz
new file mode 100644
index 0000000000..01dc426c68
Binary files /dev/null and b/web-app/build/assets/AddBucket-llLnfIpq.js.gz differ
diff --git a/web-app/build/assets/AddBucketReplication-BhXAwPjC.js.gz b/web-app/build/assets/AddBucketReplication-BhXAwPjC.js.gz
new file mode 100644
index 0000000000..57202d2ee0
Binary files /dev/null and b/web-app/build/assets/AddBucketReplication-BhXAwPjC.js.gz differ
diff --git a/web-app/build/assets/AddBucketReplication-D25Z67xS.js.gz b/web-app/build/assets/AddBucketReplication-D25Z67xS.js.gz
deleted file mode 100644
index 21d6e2c615..0000000000
Binary files a/web-app/build/assets/AddBucketReplication-D25Z67xS.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddBucketTagModal-DF8nWd2m.js.gz b/web-app/build/assets/AddBucketTagModal-DF8nWd2m.js.gz
new file mode 100644
index 0000000000..d575da414e
Binary files /dev/null and b/web-app/build/assets/AddBucketTagModal-DF8nWd2m.js.gz differ
diff --git a/web-app/build/assets/AddBucketTagModal-DT0z1KwZ.js.gz b/web-app/build/assets/AddBucketTagModal-DT0z1KwZ.js.gz
deleted file mode 100644
index 179d37258b..0000000000
Binary files a/web-app/build/assets/AddBucketTagModal-DT0z1KwZ.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddEvent-J3AWxLjP.js.gz b/web-app/build/assets/AddEvent-J3AWxLjP.js.gz
new file mode 100644
index 0000000000..3c97731427
Binary files /dev/null and b/web-app/build/assets/AddEvent-J3AWxLjP.js.gz differ
diff --git a/web-app/build/assets/AddEvent-b07MOIzL.js.gz b/web-app/build/assets/AddEvent-b07MOIzL.js.gz
deleted file mode 100644
index b65f769eea..0000000000
Binary files a/web-app/build/assets/AddEvent-b07MOIzL.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddEventDestination-BPA3Nc90.js.gz b/web-app/build/assets/AddEventDestination-BPA3Nc90.js.gz
new file mode 100644
index 0000000000..a7497aeb5d
Binary files /dev/null and b/web-app/build/assets/AddEventDestination-BPA3Nc90.js.gz differ
diff --git a/web-app/build/assets/AddEventDestination-DijCTKmw.js.gz b/web-app/build/assets/AddEventDestination-DijCTKmw.js.gz
deleted file mode 100644
index 661e040dfb..0000000000
Binary files a/web-app/build/assets/AddEventDestination-DijCTKmw.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddGroupScreen-BdrBeXvz.js.gz b/web-app/build/assets/AddGroupScreen-BdrBeXvz.js.gz
new file mode 100644
index 0000000000..0194064a2d
Binary files /dev/null and b/web-app/build/assets/AddGroupScreen-BdrBeXvz.js.gz differ
diff --git a/web-app/build/assets/AddGroupScreen-DxeJ5wMF.js.gz b/web-app/build/assets/AddGroupScreen-DxeJ5wMF.js.gz
deleted file mode 100644
index 7634ca8140..0000000000
Binary files a/web-app/build/assets/AddGroupScreen-DxeJ5wMF.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddIDPConfigurationHelpbox-C6RSKlFk.js.gz b/web-app/build/assets/AddIDPConfigurationHelpbox-C6RSKlFk.js.gz
new file mode 100644
index 0000000000..d78cdf0bb5
Binary files /dev/null and b/web-app/build/assets/AddIDPConfigurationHelpbox-C6RSKlFk.js.gz differ
diff --git a/web-app/build/assets/AddIDPConfigurationHelpbox-Q9TkQb59.js.gz b/web-app/build/assets/AddIDPConfigurationHelpbox-Q9TkQb59.js.gz
deleted file mode 100644
index daf0d87d43..0000000000
Binary files a/web-app/build/assets/AddIDPConfigurationHelpbox-Q9TkQb59.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddIDPOpenIDConfiguration-DM7ZXGNP.js.gz b/web-app/build/assets/AddIDPOpenIDConfiguration-DM7ZXGNP.js.gz
new file mode 100644
index 0000000000..2944efd2b9
Binary files /dev/null and b/web-app/build/assets/AddIDPOpenIDConfiguration-DM7ZXGNP.js.gz differ
diff --git a/web-app/build/assets/AddIDPOpenIDConfiguration-uI_VPwkS.js.gz b/web-app/build/assets/AddIDPOpenIDConfiguration-uI_VPwkS.js.gz
deleted file mode 100644
index 125cd282c1..0000000000
Binary files a/web-app/build/assets/AddIDPOpenIDConfiguration-uI_VPwkS.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddKey-C3kOUl26.js.gz b/web-app/build/assets/AddKey-C3kOUl26.js.gz
deleted file mode 100644
index 345ab5cab1..0000000000
Binary files a/web-app/build/assets/AddKey-C3kOUl26.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddKey-DABy8cLt.js.gz b/web-app/build/assets/AddKey-DABy8cLt.js.gz
new file mode 100644
index 0000000000..a1afe2481e
Binary files /dev/null and b/web-app/build/assets/AddKey-DABy8cLt.js.gz differ
diff --git a/web-app/build/assets/AddPolicyScreen-62ej-MFP.js.gz b/web-app/build/assets/AddPolicyScreen-62ej-MFP.js.gz
new file mode 100644
index 0000000000..83940538a3
Binary files /dev/null and b/web-app/build/assets/AddPolicyScreen-62ej-MFP.js.gz differ
diff --git a/web-app/build/assets/AddPolicyScreen-e2sAHz1i.js.gz b/web-app/build/assets/AddPolicyScreen-e2sAHz1i.js.gz
deleted file mode 100644
index 61067b539a..0000000000
Binary files a/web-app/build/assets/AddPolicyScreen-e2sAHz1i.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddReplicationModal-CM6xKQt9.js.gz b/web-app/build/assets/AddReplicationModal-CM6xKQt9.js.gz
new file mode 100644
index 0000000000..802c10282f
Binary files /dev/null and b/web-app/build/assets/AddReplicationModal-CM6xKQt9.js.gz differ
diff --git a/web-app/build/assets/AddReplicationModal-DG2Uf1yQ.js.gz b/web-app/build/assets/AddReplicationModal-DG2Uf1yQ.js.gz
deleted file mode 100644
index be70664e09..0000000000
Binary files a/web-app/build/assets/AddReplicationModal-DG2Uf1yQ.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddReplicationSites-BPrvVXkH.js.gz b/web-app/build/assets/AddReplicationSites-BPrvVXkH.js.gz
deleted file mode 100644
index 3630d93b47..0000000000
Binary files a/web-app/build/assets/AddReplicationSites-BPrvVXkH.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddReplicationSites-VVs6l229.js.gz b/web-app/build/assets/AddReplicationSites-VVs6l229.js.gz
new file mode 100644
index 0000000000..9ec06968d4
Binary files /dev/null and b/web-app/build/assets/AddReplicationSites-VVs6l229.js.gz differ
diff --git a/web-app/build/assets/AddServiceAccountScreen-CxpFruYj.js.gz b/web-app/build/assets/AddServiceAccountScreen-CxpFruYj.js.gz
new file mode 100644
index 0000000000..d9ca91d6c1
Binary files /dev/null and b/web-app/build/assets/AddServiceAccountScreen-CxpFruYj.js.gz differ
diff --git a/web-app/build/assets/AddServiceAccountScreen-VzBqUeb_.js.gz b/web-app/build/assets/AddServiceAccountScreen-VzBqUeb_.js.gz
deleted file mode 100644
index 5f88be52bf..0000000000
Binary files a/web-app/build/assets/AddServiceAccountScreen-VzBqUeb_.js.gz and /dev/null differ
diff --git a/web-app/build/assets/AddTierConfiguration-B1hK4pFM.js.gz b/web-app/build/assets/AddTierConfiguration-B1hK4pFM.js.gz
new file mode 100644
index 0000000000..22d1d2d865
Binary files /dev/null and b/web-app/build/assets/AddTierConfiguration-B1hK4pFM.js.gz differ
diff --git a/web-app/build/assets/AddTierConfiguration-fjMFtCvJ.js.gz b/web-app/build/assets/AddTierConfiguration-fjMFtCvJ.js.gz
deleted file mode 100644
index ba0906a718..0000000000
Binary files a/web-app/build/assets/AddTierConfiguration-fjMFtCvJ.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Autocomplete-2BzeuN5q.js.gz b/web-app/build/assets/Autocomplete-2BzeuN5q.js.gz
new file mode 100644
index 0000000000..ad6c450853
Binary files /dev/null and b/web-app/build/assets/Autocomplete-2BzeuN5q.js.gz differ
diff --git a/web-app/build/assets/Autocomplete-CV9PhuoR.js.gz b/web-app/build/assets/Autocomplete-CV9PhuoR.js.gz
deleted file mode 100644
index 988db51153..0000000000
Binary files a/web-app/build/assets/Autocomplete-CV9PhuoR.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BackLink-Cq3rpmwR.js.gz b/web-app/build/assets/BackLink-Cq3rpmwR.js.gz
new file mode 100644
index 0000000000..722804fa28
Binary files /dev/null and b/web-app/build/assets/BackLink-Cq3rpmwR.js.gz differ
diff --git a/web-app/build/assets/BackLink-bj3aQO2v.js.gz b/web-app/build/assets/BackLink-bj3aQO2v.js.gz
deleted file mode 100644
index fd8f441f2d..0000000000
Binary files a/web-app/build/assets/BackLink-bj3aQO2v.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BrowserHandler-BlI-I8M9.js.gz b/web-app/build/assets/BrowserHandler-BlI-I8M9.js.gz
deleted file mode 100644
index 59e1107955..0000000000
Binary files a/web-app/build/assets/BrowserHandler-BlI-I8M9.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BrowserHandler-F6PQ9PxU.js.gz b/web-app/build/assets/BrowserHandler-F6PQ9PxU.js.gz
new file mode 100644
index 0000000000..202a92261e
Binary files /dev/null and b/web-app/build/assets/BrowserHandler-F6PQ9PxU.js.gz differ
diff --git a/web-app/build/assets/BucketDetails-BfxHA_Dt.js.gz b/web-app/build/assets/BucketDetails-BfxHA_Dt.js.gz
deleted file mode 100644
index 6c8e8e7316..0000000000
Binary files a/web-app/build/assets/BucketDetails-BfxHA_Dt.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BucketDetails-ClD83hKK.js.gz b/web-app/build/assets/BucketDetails-ClD83hKK.js.gz
new file mode 100644
index 0000000000..eaf0d92b8b
Binary files /dev/null and b/web-app/build/assets/BucketDetails-ClD83hKK.js.gz differ
diff --git a/web-app/build/assets/BucketEventsPanel-DPgRVNCi.js.gz b/web-app/build/assets/BucketEventsPanel-DPgRVNCi.js.gz
deleted file mode 100644
index 2f42787fff..0000000000
Binary files a/web-app/build/assets/BucketEventsPanel-DPgRVNCi.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BucketEventsPanel-oLg5NOX5.js.gz b/web-app/build/assets/BucketEventsPanel-oLg5NOX5.js.gz
new file mode 100644
index 0000000000..a03ddffd29
Binary files /dev/null and b/web-app/build/assets/BucketEventsPanel-oLg5NOX5.js.gz differ
diff --git a/web-app/build/assets/BucketLifecyclePanel-BW7g5UMk.js.gz b/web-app/build/assets/BucketLifecyclePanel-BW7g5UMk.js.gz
deleted file mode 100644
index fa9bb1ad57..0000000000
Binary files a/web-app/build/assets/BucketLifecyclePanel-BW7g5UMk.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BucketLifecyclePanel-DJMrXvMv.js.gz b/web-app/build/assets/BucketLifecyclePanel-DJMrXvMv.js.gz
new file mode 100644
index 0000000000..d227e00432
Binary files /dev/null and b/web-app/build/assets/BucketLifecyclePanel-DJMrXvMv.js.gz differ
diff --git a/web-app/build/assets/BucketPageHeader-Bh8e8CwZ.js.gz b/web-app/build/assets/BucketPageHeader-Bh8e8CwZ.js.gz
new file mode 100644
index 0000000000..1fc44d6bab
Binary files /dev/null and b/web-app/build/assets/BucketPageHeader-Bh8e8CwZ.js.gz differ
diff --git a/web-app/build/assets/BucketPageHeader-DaqfKzZx.js.gz b/web-app/build/assets/BucketPageHeader-DaqfKzZx.js.gz
deleted file mode 100644
index 4cac120612..0000000000
Binary files a/web-app/build/assets/BucketPageHeader-DaqfKzZx.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BucketReplicationPanel-8GXBlZvl.js.gz b/web-app/build/assets/BucketReplicationPanel-8GXBlZvl.js.gz
new file mode 100644
index 0000000000..1f7a149605
Binary files /dev/null and b/web-app/build/assets/BucketReplicationPanel-8GXBlZvl.js.gz differ
diff --git a/web-app/build/assets/BucketReplicationPanel-Cd-nPY7F.js.gz b/web-app/build/assets/BucketReplicationPanel-Cd-nPY7F.js.gz
deleted file mode 100644
index 0cec9d9463..0000000000
Binary files a/web-app/build/assets/BucketReplicationPanel-Cd-nPY7F.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BucketSummaryPanel-BDC-8bzA.js.gz b/web-app/build/assets/BucketSummaryPanel-BDC-8bzA.js.gz
deleted file mode 100644
index a71e24143d..0000000000
Binary files a/web-app/build/assets/BucketSummaryPanel-BDC-8bzA.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BucketSummaryPanel-CAD3yFBM.js.gz b/web-app/build/assets/BucketSummaryPanel-CAD3yFBM.js.gz
new file mode 100644
index 0000000000..89bc78cce1
Binary files /dev/null and b/web-app/build/assets/BucketSummaryPanel-CAD3yFBM.js.gz differ
diff --git a/web-app/build/assets/BucketTags-DL6gJEPI.js.gz b/web-app/build/assets/BucketTags-DL6gJEPI.js.gz
new file mode 100644
index 0000000000..a5d01c5e10
Binary files /dev/null and b/web-app/build/assets/BucketTags-DL6gJEPI.js.gz differ
diff --git a/web-app/build/assets/BucketTags-DWhZx8TH.js.gz b/web-app/build/assets/BucketTags-DWhZx8TH.js.gz
deleted file mode 100644
index d95790359c..0000000000
Binary files a/web-app/build/assets/BucketTags-DWhZx8TH.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Buckets-CBcDZdYt.js.gz b/web-app/build/assets/Buckets-CBcDZdYt.js.gz
new file mode 100644
index 0000000000..5190e6e751
Binary files /dev/null and b/web-app/build/assets/Buckets-CBcDZdYt.js.gz differ
diff --git a/web-app/build/assets/Buckets-D1vHd4Fp.js.gz b/web-app/build/assets/Buckets-D1vHd4Fp.js.gz
deleted file mode 100644
index 7b8f42d271..0000000000
Binary files a/web-app/build/assets/Buckets-D1vHd4Fp.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BulkAddToGroup-BauZUPB4.js.gz b/web-app/build/assets/BulkAddToGroup-BauZUPB4.js.gz
new file mode 100644
index 0000000000..f19a8e575e
Binary files /dev/null and b/web-app/build/assets/BulkAddToGroup-BauZUPB4.js.gz differ
diff --git a/web-app/build/assets/BulkAddToGroup-DthRbVer.js.gz b/web-app/build/assets/BulkAddToGroup-DthRbVer.js.gz
deleted file mode 100644
index 0dfff3b225..0000000000
Binary files a/web-app/build/assets/BulkAddToGroup-DthRbVer.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BulkLifecycleModal-mvDxY-1l.js.gz b/web-app/build/assets/BulkLifecycleModal-mvDxY-1l.js.gz
new file mode 100644
index 0000000000..ced9c9de6a
Binary files /dev/null and b/web-app/build/assets/BulkLifecycleModal-mvDxY-1l.js.gz differ
diff --git a/web-app/build/assets/BulkLifecycleModal-znOgNya7.js.gz b/web-app/build/assets/BulkLifecycleModal-znOgNya7.js.gz
deleted file mode 100644
index e3f92dbc09..0000000000
Binary files a/web-app/build/assets/BulkLifecycleModal-znOgNya7.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BulkReplicationModal-5F5shPWS.js.gz b/web-app/build/assets/BulkReplicationModal-5F5shPWS.js.gz
deleted file mode 100644
index d661084a1e..0000000000
Binary files a/web-app/build/assets/BulkReplicationModal-5F5shPWS.js.gz and /dev/null differ
diff --git a/web-app/build/assets/BulkReplicationModal-BylN4pjO.js.gz b/web-app/build/assets/BulkReplicationModal-BylN4pjO.js.gz
new file mode 100644
index 0000000000..08e66208ac
Binary files /dev/null and b/web-app/build/assets/BulkReplicationModal-BylN4pjO.js.gz differ
diff --git a/web-app/build/assets/Checkbox-AyDyii8V.js.gz b/web-app/build/assets/Checkbox-AyDyii8V.js.gz
deleted file mode 100644
index 52e9233102..0000000000
Binary files a/web-app/build/assets/Checkbox-AyDyii8V.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Checkbox-Cjwn4LDX.js.gz b/web-app/build/assets/Checkbox-Cjwn4LDX.js.gz
new file mode 100644
index 0000000000..ef1fda9c5e
Binary files /dev/null and b/web-app/build/assets/Checkbox-Cjwn4LDX.js.gz differ
diff --git a/web-app/build/assets/CliEquivalent-BAzZiD40.js.gz b/web-app/build/assets/CliEquivalent-BAzZiD40.js.gz
deleted file mode 100644
index 89cd886bde..0000000000
Binary files a/web-app/build/assets/CliEquivalent-BAzZiD40.js.gz and /dev/null differ
diff --git a/web-app/build/assets/CliEquivalent-KNk977Nb.js.gz b/web-app/build/assets/CliEquivalent-KNk977Nb.js.gz
new file mode 100644
index 0000000000..60572dfa1d
Binary files /dev/null and b/web-app/build/assets/CliEquivalent-KNk977Nb.js.gz differ
diff --git a/web-app/build/assets/CodeMirrorWrapper-DaTvaTa3.js.gz b/web-app/build/assets/CodeMirrorWrapper-DaTvaTa3.js.gz
new file mode 100644
index 0000000000..81adef9554
Binary files /dev/null and b/web-app/build/assets/CodeMirrorWrapper-DaTvaTa3.js.gz differ
diff --git a/web-app/build/assets/CodeMirrorWrapper-MZFrzgs3.js.gz b/web-app/build/assets/CodeMirrorWrapper-MZFrzgs3.js.gz
deleted file mode 100644
index e1dd9beade..0000000000
Binary files a/web-app/build/assets/CodeMirrorWrapper-MZFrzgs3.js.gz and /dev/null differ
diff --git a/web-app/build/assets/CommentBox-B0SvN_qu.js.gz b/web-app/build/assets/CommentBox-B0SvN_qu.js.gz
deleted file mode 100644
index b63b9aa074..0000000000
Binary files a/web-app/build/assets/CommentBox-B0SvN_qu.js.gz and /dev/null differ
diff --git a/web-app/build/assets/CommentBox-BtbISKQE.js.gz b/web-app/build/assets/CommentBox-BtbISKQE.js.gz
new file mode 100644
index 0000000000..6e006d541a
Binary files /dev/null and b/web-app/build/assets/CommentBox-BtbISKQE.js.gz differ
diff --git a/web-app/build/assets/ComponentsScreen-Ax46Fm5-.js.gz b/web-app/build/assets/ComponentsScreen-Ax46Fm5-.js.gz
new file mode 100644
index 0000000000..45431dd8cf
Binary files /dev/null and b/web-app/build/assets/ComponentsScreen-Ax46Fm5-.js.gz differ
diff --git a/web-app/build/assets/ComponentsScreen-DrpMPK77.js.gz b/web-app/build/assets/ComponentsScreen-DrpMPK77.js.gz
deleted file mode 100644
index 4d32e60f59..0000000000
Binary files a/web-app/build/assets/ComponentsScreen-DrpMPK77.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ConfMySql-BjS9bDJw.js.gz b/web-app/build/assets/ConfMySql-BjS9bDJw.js.gz
deleted file mode 100644
index e7ebab1c8b..0000000000
Binary files a/web-app/build/assets/ConfMySql-BjS9bDJw.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ConfMySql-CY1hYBBy.js.gz b/web-app/build/assets/ConfMySql-CY1hYBBy.js.gz
new file mode 100644
index 0000000000..301130bf12
Binary files /dev/null and b/web-app/build/assets/ConfMySql-CY1hYBBy.js.gz differ
diff --git a/web-app/build/assets/ConfPostgres-B829u_Wz.js.gz b/web-app/build/assets/ConfPostgres-B829u_Wz.js.gz
new file mode 100644
index 0000000000..aaa739f4eb
Binary files /dev/null and b/web-app/build/assets/ConfPostgres-B829u_Wz.js.gz differ
diff --git a/web-app/build/assets/ConfPostgres-CZ_EZjFY.js.gz b/web-app/build/assets/ConfPostgres-CZ_EZjFY.js.gz
deleted file mode 100644
index 9a44c5e489..0000000000
Binary files a/web-app/build/assets/ConfPostgres-CZ_EZjFY.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ConfTargetGeneric-BIAzGNvb.js.gz b/web-app/build/assets/ConfTargetGeneric-BIAzGNvb.js.gz
deleted file mode 100644
index ebc89d2e3f..0000000000
Binary files a/web-app/build/assets/ConfTargetGeneric-BIAzGNvb.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ConfTargetGeneric-DvdZ0SfW.js.gz b/web-app/build/assets/ConfTargetGeneric-DvdZ0SfW.js.gz
new file mode 100644
index 0000000000..284e1d28c9
Binary files /dev/null and b/web-app/build/assets/ConfTargetGeneric-DvdZ0SfW.js.gz differ
diff --git a/web-app/build/assets/ConfigurationOptions-B_Y5Lk1b.js.gz b/web-app/build/assets/ConfigurationOptions-B_Y5Lk1b.js.gz
new file mode 100644
index 0000000000..b2c4e34fde
Binary files /dev/null and b/web-app/build/assets/ConfigurationOptions-B_Y5Lk1b.js.gz differ
diff --git a/web-app/build/assets/ConfigurationOptions-Dt_EusE4.js.gz b/web-app/build/assets/ConfigurationOptions-Dt_EusE4.js.gz
deleted file mode 100644
index ff53093546..0000000000
Binary files a/web-app/build/assets/ConfigurationOptions-Dt_EusE4.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ConfirmDialog-DJ4gAy9E.js.gz b/web-app/build/assets/ConfirmDialog-DJ4gAy9E.js.gz
new file mode 100644
index 0000000000..cde0bb9850
Binary files /dev/null and b/web-app/build/assets/ConfirmDialog-DJ4gAy9E.js.gz differ
diff --git a/web-app/build/assets/ConfirmDialog-rMIwsAIt.js.gz b/web-app/build/assets/ConfirmDialog-rMIwsAIt.js.gz
deleted file mode 100644
index 88adf1b382..0000000000
Binary files a/web-app/build/assets/ConfirmDialog-rMIwsAIt.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ConsoleKBar-137PskFm.js.gz b/web-app/build/assets/ConsoleKBar-137PskFm.js.gz
new file mode 100644
index 0000000000..0c08cb5477
Binary files /dev/null and b/web-app/build/assets/ConsoleKBar-137PskFm.js.gz differ
diff --git a/web-app/build/assets/ConsoleKBar-BpSg_38F.js.gz b/web-app/build/assets/ConsoleKBar-BpSg_38F.js.gz
deleted file mode 100644
index 8b62f51430..0000000000
Binary files a/web-app/build/assets/ConsoleKBar-BpSg_38F.js.gz and /dev/null differ
diff --git a/web-app/build/assets/CreatePathModal-BYofPhn9.js.gz b/web-app/build/assets/CreatePathModal-BYofPhn9.js.gz
new file mode 100644
index 0000000000..8014b4caf5
Binary files /dev/null and b/web-app/build/assets/CreatePathModal-BYofPhn9.js.gz differ
diff --git a/web-app/build/assets/CreatePathModal-Bzo7-YGc.js.gz b/web-app/build/assets/CreatePathModal-Bzo7-YGc.js.gz
deleted file mode 100644
index fffa186ffe..0000000000
Binary files a/web-app/build/assets/CreatePathModal-Bzo7-YGc.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Dashboard-4TdJlcCa.js.gz b/web-app/build/assets/Dashboard-4TdJlcCa.js.gz
deleted file mode 100644
index 6450e2e680..0000000000
Binary files a/web-app/build/assets/Dashboard-4TdJlcCa.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Dashboard-C0D1T1jM.js.gz b/web-app/build/assets/Dashboard-C0D1T1jM.js.gz
new file mode 100644
index 0000000000..b6030dda97
Binary files /dev/null and b/web-app/build/assets/Dashboard-C0D1T1jM.js.gz differ
diff --git a/web-app/build/assets/DataTable-CsSYCw1i.js.gz b/web-app/build/assets/DataTable-CsSYCw1i.js.gz
new file mode 100644
index 0000000000..d74df771d8
Binary files /dev/null and b/web-app/build/assets/DataTable-CsSYCw1i.js.gz differ
diff --git a/web-app/build/assets/DataTable-DZ2kzmOs.js.gz b/web-app/build/assets/DataTable-DZ2kzmOs.js.gz
deleted file mode 100644
index f14292ffd4..0000000000
Binary files a/web-app/build/assets/DataTable-DZ2kzmOs.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DateRangeSelector-BHtHiA0n.js.gz b/web-app/build/assets/DateRangeSelector-BHtHiA0n.js.gz
new file mode 100644
index 0000000000..9f8ca243c2
Binary files /dev/null and b/web-app/build/assets/DateRangeSelector-BHtHiA0n.js.gz differ
diff --git a/web-app/build/assets/DateRangeSelector-BWmbbGdi.js.gz b/web-app/build/assets/DateRangeSelector-BWmbbGdi.js.gz
deleted file mode 100644
index f7c7866363..0000000000
Binary files a/web-app/build/assets/DateRangeSelector-BWmbbGdi.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DateTimeInput-C6CyFdUy.js.gz b/web-app/build/assets/DateTimeInput-C6CyFdUy.js.gz
new file mode 100644
index 0000000000..65af0946d5
Binary files /dev/null and b/web-app/build/assets/DateTimeInput-C6CyFdUy.js.gz differ
diff --git a/web-app/build/assets/DateTimeInput-kvVq7NU8.js.gz b/web-app/build/assets/DateTimeInput-kvVq7NU8.js.gz
deleted file mode 100644
index 56b58d4e9a..0000000000
Binary files a/web-app/build/assets/DateTimeInput-kvVq7NU8.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeleteAccessRule-B4BMZ3Ld.js.gz b/web-app/build/assets/DeleteAccessRule-B4BMZ3Ld.js.gz
new file mode 100644
index 0000000000..14575b0b36
Binary files /dev/null and b/web-app/build/assets/DeleteAccessRule-B4BMZ3Ld.js.gz differ
diff --git a/web-app/build/assets/DeleteAccessRule-Dk1qHPjW.js.gz b/web-app/build/assets/DeleteAccessRule-Dk1qHPjW.js.gz
deleted file mode 100644
index 8cf22ea988..0000000000
Binary files a/web-app/build/assets/DeleteAccessRule-Dk1qHPjW.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeleteBucket-BiPPnlG-.js.gz b/web-app/build/assets/DeleteBucket-BiPPnlG-.js.gz
deleted file mode 100644
index 9b4d584113..0000000000
Binary files a/web-app/build/assets/DeleteBucket-BiPPnlG-.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeleteBucket-DgSB7Nf_.js.gz b/web-app/build/assets/DeleteBucket-DgSB7Nf_.js.gz
new file mode 100644
index 0000000000..877d31f7f4
Binary files /dev/null and b/web-app/build/assets/DeleteBucket-DgSB7Nf_.js.gz differ
diff --git a/web-app/build/assets/DeleteBucketTagModal-BBNCIFOJ.js.gz b/web-app/build/assets/DeleteBucketTagModal-BBNCIFOJ.js.gz
deleted file mode 100644
index 94845fcee4..0000000000
Binary files a/web-app/build/assets/DeleteBucketTagModal-BBNCIFOJ.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeleteBucketTagModal-CKPJZX__.js.gz b/web-app/build/assets/DeleteBucketTagModal-CKPJZX__.js.gz
new file mode 100644
index 0000000000..f0dcf246aa
Binary files /dev/null and b/web-app/build/assets/DeleteBucketTagModal-CKPJZX__.js.gz differ
diff --git a/web-app/build/assets/DeleteEvent-CW1f-8Ur.js.gz b/web-app/build/assets/DeleteEvent-CW1f-8Ur.js.gz
new file mode 100644
index 0000000000..22d20a1d52
Binary files /dev/null and b/web-app/build/assets/DeleteEvent-CW1f-8Ur.js.gz differ
diff --git a/web-app/build/assets/DeleteEvent-ptoTnZ3P.js.gz b/web-app/build/assets/DeleteEvent-ptoTnZ3P.js.gz
deleted file mode 100644
index aee4f41167..0000000000
Binary files a/web-app/build/assets/DeleteEvent-ptoTnZ3P.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeleteGroup-B_syd0uy.js.gz b/web-app/build/assets/DeleteGroup-B_syd0uy.js.gz
new file mode 100644
index 0000000000..22587a7f55
Binary files /dev/null and b/web-app/build/assets/DeleteGroup-B_syd0uy.js.gz differ
diff --git a/web-app/build/assets/DeleteGroup-DNaTe5MU.js.gz b/web-app/build/assets/DeleteGroup-DNaTe5MU.js.gz
deleted file mode 100644
index feb73deb45..0000000000
Binary files a/web-app/build/assets/DeleteGroup-DNaTe5MU.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeleteIDPConfigurationModal-CndDBICH.js.gz b/web-app/build/assets/DeleteIDPConfigurationModal-CndDBICH.js.gz
new file mode 100644
index 0000000000..dbbefe14fa
Binary files /dev/null and b/web-app/build/assets/DeleteIDPConfigurationModal-CndDBICH.js.gz differ
diff --git a/web-app/build/assets/DeleteIDPConfigurationModal-b-9nFHyk.js.gz b/web-app/build/assets/DeleteIDPConfigurationModal-b-9nFHyk.js.gz
deleted file mode 100644
index 24d4be7f6e..0000000000
Binary files a/web-app/build/assets/DeleteIDPConfigurationModal-b-9nFHyk.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeleteMultipleObjects-BL7dpPtx.js.gz b/web-app/build/assets/DeleteMultipleObjects-BL7dpPtx.js.gz
new file mode 100644
index 0000000000..659b9a8f81
Binary files /dev/null and b/web-app/build/assets/DeleteMultipleObjects-BL7dpPtx.js.gz differ
diff --git a/web-app/build/assets/DeleteMultipleObjects-ejVvqVGb.js.gz b/web-app/build/assets/DeleteMultipleObjects-ejVvqVGb.js.gz
deleted file mode 100644
index d2456efbff..0000000000
Binary files a/web-app/build/assets/DeleteMultipleObjects-ejVvqVGb.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeletePolicy-BViq5HcV.js.gz b/web-app/build/assets/DeletePolicy-BViq5HcV.js.gz
deleted file mode 100644
index 57969fad63..0000000000
Binary files a/web-app/build/assets/DeletePolicy-BViq5HcV.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeletePolicy-C4iaJQ85.js.gz b/web-app/build/assets/DeletePolicy-C4iaJQ85.js.gz
new file mode 100644
index 0000000000..ecd3f841d5
Binary files /dev/null and b/web-app/build/assets/DeletePolicy-C4iaJQ85.js.gz differ
diff --git a/web-app/build/assets/DeleteReplicationRule-BJBLsxmj.js.gz b/web-app/build/assets/DeleteReplicationRule-BJBLsxmj.js.gz
new file mode 100644
index 0000000000..c91eec4043
Binary files /dev/null and b/web-app/build/assets/DeleteReplicationRule-BJBLsxmj.js.gz differ
diff --git a/web-app/build/assets/DeleteReplicationRule-BnCfZVbM.js.gz b/web-app/build/assets/DeleteReplicationRule-BnCfZVbM.js.gz
deleted file mode 100644
index ff78ea3295..0000000000
Binary files a/web-app/build/assets/DeleteReplicationRule-BnCfZVbM.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DeleteServiceAccount-CAmTwX4x.js.gz b/web-app/build/assets/DeleteServiceAccount-CAmTwX4x.js.gz
new file mode 100644
index 0000000000..a74ddeacc3
Binary files /dev/null and b/web-app/build/assets/DeleteServiceAccount-CAmTwX4x.js.gz differ
diff --git a/web-app/build/assets/DeleteServiceAccount-CwXrmhAF.js.gz b/web-app/build/assets/DeleteServiceAccount-CwXrmhAF.js.gz
deleted file mode 100644
index dd0ed01013..0000000000
Binary files a/web-app/build/assets/DeleteServiceAccount-CwXrmhAF.js.gz and /dev/null differ
diff --git a/web-app/build/assets/DistributedOnly-BZl1_dJi.js.gz b/web-app/build/assets/DistributedOnly-BZl1_dJi.js.gz
new file mode 100644
index 0000000000..16efa891ee
Binary files /dev/null and b/web-app/build/assets/DistributedOnly-BZl1_dJi.js.gz differ
diff --git a/web-app/build/assets/DistributedOnly-BpLT-l5g.js.gz b/web-app/build/assets/DistributedOnly-BpLT-l5g.js.gz
deleted file mode 100644
index d9a8d18a6f..0000000000
Binary files a/web-app/build/assets/DistributedOnly-BpLT-l5g.js.gz and /dev/null differ
diff --git a/web-app/build/assets/EditAccessRule-CMRDoH-o.js.gz b/web-app/build/assets/EditAccessRule-CMRDoH-o.js.gz
deleted file mode 100644
index f19e0287d7..0000000000
Binary files a/web-app/build/assets/EditAccessRule-CMRDoH-o.js.gz and /dev/null differ
diff --git a/web-app/build/assets/EditAccessRule-Ydox2HZT.js.gz b/web-app/build/assets/EditAccessRule-Ydox2HZT.js.gz
new file mode 100644
index 0000000000..69aa5ed861
Binary files /dev/null and b/web-app/build/assets/EditAccessRule-Ydox2HZT.js.gz differ
diff --git a/web-app/build/assets/EditBucketReplication-CrooLCml.js.gz b/web-app/build/assets/EditBucketReplication-CrooLCml.js.gz
deleted file mode 100644
index 109952118d..0000000000
Binary files a/web-app/build/assets/EditBucketReplication-CrooLCml.js.gz and /dev/null differ
diff --git a/web-app/build/assets/EditBucketReplication-D7dmxTPZ.js.gz b/web-app/build/assets/EditBucketReplication-D7dmxTPZ.js.gz
new file mode 100644
index 0000000000..d1dac04338
Binary files /dev/null and b/web-app/build/assets/EditBucketReplication-D7dmxTPZ.js.gz differ
diff --git a/web-app/build/assets/EditReplicationModal-CJK4K4B4.js.gz b/web-app/build/assets/EditReplicationModal-CJK4K4B4.js.gz
new file mode 100644
index 0000000000..cea41c3e43
Binary files /dev/null and b/web-app/build/assets/EditReplicationModal-CJK4K4B4.js.gz differ
diff --git a/web-app/build/assets/EditReplicationModal-J0Meb1GJ.js.gz b/web-app/build/assets/EditReplicationModal-J0Meb1GJ.js.gz
deleted file mode 100644
index 1e49dda517..0000000000
Binary files a/web-app/build/assets/EditReplicationModal-J0Meb1GJ.js.gz and /dev/null differ
diff --git a/web-app/build/assets/EnableBucketEncryption-BYzTi60M.js.gz b/web-app/build/assets/EnableBucketEncryption-BYzTi60M.js.gz
deleted file mode 100644
index 8d41d66df0..0000000000
Binary files a/web-app/build/assets/EnableBucketEncryption-BYzTi60M.js.gz and /dev/null differ
diff --git a/web-app/build/assets/EnableBucketEncryption-Da2aLjfc.js.gz b/web-app/build/assets/EnableBucketEncryption-Da2aLjfc.js.gz
new file mode 100644
index 0000000000..0f39a3be08
Binary files /dev/null and b/web-app/build/assets/EnableBucketEncryption-Da2aLjfc.js.gz differ
diff --git a/web-app/build/assets/EnableQuota-BO-8nPWn.js.gz b/web-app/build/assets/EnableQuota-BO-8nPWn.js.gz
new file mode 100644
index 0000000000..d6d387a26a
Binary files /dev/null and b/web-app/build/assets/EnableQuota-BO-8nPWn.js.gz differ
diff --git a/web-app/build/assets/EnableQuota-Ct3y5N_E.js.gz b/web-app/build/assets/EnableQuota-Ct3y5N_E.js.gz
deleted file mode 100644
index b49d7cfadc..0000000000
Binary files a/web-app/build/assets/EnableQuota-Ct3y5N_E.js.gz and /dev/null differ
diff --git a/web-app/build/assets/EnableVersioningModal-DPKT_EPA.js.gz b/web-app/build/assets/EnableVersioningModal-DPKT_EPA.js.gz
new file mode 100644
index 0000000000..d5523942d7
Binary files /dev/null and b/web-app/build/assets/EnableVersioningModal-DPKT_EPA.js.gz differ
diff --git a/web-app/build/assets/EnableVersioningModal-fGUP2_sE.js.gz b/web-app/build/assets/EnableVersioningModal-fGUP2_sE.js.gz
deleted file mode 100644
index 1551bd369b..0000000000
Binary files a/web-app/build/assets/EnableVersioningModal-fGUP2_sE.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ErrorLogs-CwDLNem4.js.gz b/web-app/build/assets/ErrorLogs-CwDLNem4.js.gz
deleted file mode 100644
index 7e7bf30935..0000000000
Binary files a/web-app/build/assets/ErrorLogs-CwDLNem4.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ErrorLogs-CygbXnVL.js.gz b/web-app/build/assets/ErrorLogs-CygbXnVL.js.gz
new file mode 100644
index 0000000000..5e692c9587
Binary files /dev/null and b/web-app/build/assets/ErrorLogs-CygbXnVL.js.gz differ
diff --git a/web-app/build/assets/EventDestinations-BmZOCES6.js.gz b/web-app/build/assets/EventDestinations-BmZOCES6.js.gz
deleted file mode 100644
index 5e294d4305..0000000000
Binary files a/web-app/build/assets/EventDestinations-BmZOCES6.js.gz and /dev/null differ
diff --git a/web-app/build/assets/EventDestinations-CVsOUx6c.js.gz b/web-app/build/assets/EventDestinations-CVsOUx6c.js.gz
new file mode 100644
index 0000000000..36e5c0612d
Binary files /dev/null and b/web-app/build/assets/EventDestinations-CVsOUx6c.js.gz differ
diff --git a/web-app/build/assets/EventTypeSelector-Bkz-dhjU.js.gz b/web-app/build/assets/EventTypeSelector-Bkz-dhjU.js.gz
new file mode 100644
index 0000000000..75050ce840
Binary files /dev/null and b/web-app/build/assets/EventTypeSelector-Bkz-dhjU.js.gz differ
diff --git a/web-app/build/assets/EventTypeSelector-DJuRZbLN.js.gz b/web-app/build/assets/EventTypeSelector-DJuRZbLN.js.gz
deleted file mode 100644
index 6ae283ea51..0000000000
Binary files a/web-app/build/assets/EventTypeSelector-DJuRZbLN.js.gz and /dev/null differ
diff --git a/web-app/build/assets/FileSelector-BrLXUnNd.js.gz b/web-app/build/assets/FileSelector-BrLXUnNd.js.gz
deleted file mode 100644
index 7fa6d5eacf..0000000000
Binary files a/web-app/build/assets/FileSelector-BrLXUnNd.js.gz and /dev/null differ
diff --git a/web-app/build/assets/FileSelector-C25tYyJ0.js.gz b/web-app/build/assets/FileSelector-C25tYyJ0.js.gz
new file mode 100644
index 0000000000..d5fd50dcff
Binary files /dev/null and b/web-app/build/assets/FileSelector-C25tYyJ0.js.gz differ
diff --git a/web-app/build/assets/FilteredEmptyState-DiI6qBuU.js.gz b/web-app/build/assets/FilteredEmptyState-DiI6qBuU.js.gz
new file mode 100644
index 0000000000..f1d5fc5b1d
Binary files /dev/null and b/web-app/build/assets/FilteredEmptyState-DiI6qBuU.js.gz differ
diff --git a/web-app/build/assets/FilteredEmptyState-pjAuzuwb.js.gz b/web-app/build/assets/FilteredEmptyState-pjAuzuwb.js.gz
deleted file mode 100644
index 37d416cf21..0000000000
Binary files a/web-app/build/assets/FilteredEmptyState-pjAuzuwb.js.gz and /dev/null differ
diff --git a/web-app/build/assets/FluidHoverShowcase-AC9ua2Bm.js.gz b/web-app/build/assets/FluidHoverShowcase-AC9ua2Bm.js.gz
deleted file mode 100644
index 1ea59a9df5..0000000000
Binary files a/web-app/build/assets/FluidHoverShowcase-AC9ua2Bm.js.gz and /dev/null differ
diff --git a/web-app/build/assets/FluidHoverShowcase-D_894eDt.js.gz b/web-app/build/assets/FluidHoverShowcase-D_894eDt.js.gz
new file mode 100644
index 0000000000..8633b97eab
Binary files /dev/null and b/web-app/build/assets/FluidHoverShowcase-D_894eDt.js.gz differ
diff --git a/web-app/build/assets/Groups-H8W897oI.js.gz b/web-app/build/assets/Groups-H8W897oI.js.gz
deleted file mode 100644
index 39b60a3bf0..0000000000
Binary files a/web-app/build/assets/Groups-H8W897oI.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Groups-OdjZIQqU.js.gz b/web-app/build/assets/Groups-OdjZIQqU.js.gz
new file mode 100644
index 0000000000..3108da203c
Binary files /dev/null and b/web-app/build/assets/Groups-OdjZIQqU.js.gz differ
diff --git a/web-app/build/assets/GroupsDetails-CDpyDinR.js.gz b/web-app/build/assets/GroupsDetails-CDpyDinR.js.gz
deleted file mode 100644
index e7552eaf15..0000000000
Binary files a/web-app/build/assets/GroupsDetails-CDpyDinR.js.gz and /dev/null differ
diff --git a/web-app/build/assets/GroupsDetails-DvLWDYyn.js.gz b/web-app/build/assets/GroupsDetails-DvLWDYyn.js.gz
new file mode 100644
index 0000000000..1bca3e0124
Binary files /dev/null and b/web-app/build/assets/GroupsDetails-DvLWDYyn.js.gz differ
diff --git a/web-app/build/assets/HealthInfo-Cf7YiffP.js.gz b/web-app/build/assets/HealthInfo-Cf7YiffP.js.gz
deleted file mode 100644
index 7283728e81..0000000000
Binary files a/web-app/build/assets/HealthInfo-Cf7YiffP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/HealthInfo-zpXjh3I5.js.gz b/web-app/build/assets/HealthInfo-zpXjh3I5.js.gz
new file mode 100644
index 0000000000..7649ed291a
Binary files /dev/null and b/web-app/build/assets/HealthInfo-zpXjh3I5.js.gz differ
diff --git a/web-app/build/assets/HelpBox-CfLqs82v.js.gz b/web-app/build/assets/HelpBox-CfLqs82v.js.gz
new file mode 100644
index 0000000000..7369e0a810
Binary files /dev/null and b/web-app/build/assets/HelpBox-CfLqs82v.js.gz differ
diff --git a/web-app/build/assets/HelpBox-DJt8t65o.js.gz b/web-app/build/assets/HelpBox-DJt8t65o.js.gz
deleted file mode 100644
index aa09bfe5d1..0000000000
Binary files a/web-app/build/assets/HelpBox-DJt8t65o.js.gz and /dev/null differ
diff --git a/web-app/build/assets/IDPLDAPConfigurationDetails-Bcm4jYPD.js.gz b/web-app/build/assets/IDPLDAPConfigurationDetails-Bcm4jYPD.js.gz
new file mode 100644
index 0000000000..3e85ee2901
Binary files /dev/null and b/web-app/build/assets/IDPLDAPConfigurationDetails-Bcm4jYPD.js.gz differ
diff --git a/web-app/build/assets/IDPLDAPConfigurationDetails-DNWD-1xe.js.gz b/web-app/build/assets/IDPLDAPConfigurationDetails-DNWD-1xe.js.gz
deleted file mode 100644
index db2523d4eb..0000000000
Binary files a/web-app/build/assets/IDPLDAPConfigurationDetails-DNWD-1xe.js.gz and /dev/null differ
diff --git a/web-app/build/assets/IDPOpenIDConfigurationDetails-DqM4-T83.js.gz b/web-app/build/assets/IDPOpenIDConfigurationDetails-DqM4-T83.js.gz
new file mode 100644
index 0000000000..3d118a9af8
Binary files /dev/null and b/web-app/build/assets/IDPOpenIDConfigurationDetails-DqM4-T83.js.gz differ
diff --git a/web-app/build/assets/IDPOpenIDConfigurationDetails-WA3qBXFP.js.gz b/web-app/build/assets/IDPOpenIDConfigurationDetails-WA3qBXFP.js.gz
deleted file mode 100644
index b0bc40553e..0000000000
Binary files a/web-app/build/assets/IDPOpenIDConfigurationDetails-WA3qBXFP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/IDPOpenIDConfigurations-BkEDdb0g.js.gz b/web-app/build/assets/IDPOpenIDConfigurations-BkEDdb0g.js.gz
deleted file mode 100644
index 484ca8dd51..0000000000
Binary files a/web-app/build/assets/IDPOpenIDConfigurations-BkEDdb0g.js.gz and /dev/null differ
diff --git a/web-app/build/assets/IDPOpenIDConfigurations-lXd2Xe5-.js.gz b/web-app/build/assets/IDPOpenIDConfigurations-lXd2Xe5-.js.gz
new file mode 100644
index 0000000000..559e9d4fd6
Binary files /dev/null and b/web-app/build/assets/IDPOpenIDConfigurations-lXd2Xe5-.js.gz differ
diff --git a/web-app/build/assets/IconsScreen-B9jjA3-L.js.gz b/web-app/build/assets/IconsScreen-B9jjA3-L.js.gz
deleted file mode 100644
index d63cdaa3d0..0000000000
Binary files a/web-app/build/assets/IconsScreen-B9jjA3-L.js.gz and /dev/null differ
diff --git a/web-app/build/assets/IconsScreen-C6zQG5M2.js.gz b/web-app/build/assets/IconsScreen-C6zQG5M2.js.gz
new file mode 100644
index 0000000000..76cf512f37
Binary files /dev/null and b/web-app/build/assets/IconsScreen-C6zQG5M2.js.gz differ
diff --git a/web-app/build/assets/InformativeMessage-BOqHj7-M.js.gz b/web-app/build/assets/InformativeMessage-BOqHj7-M.js.gz
deleted file mode 100644
index 809d539ea2..0000000000
Binary files a/web-app/build/assets/InformativeMessage-BOqHj7-M.js.gz and /dev/null differ
diff --git a/web-app/build/assets/InformativeMessage-BPz8HrdR.js.gz b/web-app/build/assets/InformativeMessage-BPz8HrdR.js.gz
new file mode 100644
index 0000000000..ffbfb27da5
Binary files /dev/null and b/web-app/build/assets/InformativeMessage-BPz8HrdR.js.gz differ
diff --git a/web-app/build/assets/Inspect-DWz71seB.js.gz b/web-app/build/assets/Inspect-DWz71seB.js.gz
new file mode 100644
index 0000000000..7f80dc4617
Binary files /dev/null and b/web-app/build/assets/Inspect-DWz71seB.js.gz differ
diff --git a/web-app/build/assets/Inspect-z11H0H5H.js.gz b/web-app/build/assets/Inspect-z11H0H5H.js.gz
deleted file mode 100644
index 2c5b931163..0000000000
Binary files a/web-app/build/assets/Inspect-z11H0H5H.js.gz and /dev/null differ
diff --git a/web-app/build/assets/KMSHelpbox-5Nvt4pRH.js.gz b/web-app/build/assets/KMSHelpbox-5Nvt4pRH.js.gz
deleted file mode 100644
index 9d9229285c..0000000000
Binary files a/web-app/build/assets/KMSHelpbox-5Nvt4pRH.js.gz and /dev/null differ
diff --git a/web-app/build/assets/KMSHelpbox-DXH7oQL3.js.gz b/web-app/build/assets/KMSHelpbox-DXH7oQL3.js.gz
new file mode 100644
index 0000000000..21465d12d0
Binary files /dev/null and b/web-app/build/assets/KMSHelpbox-DXH7oQL3.js.gz differ
diff --git a/web-app/build/assets/KMSRoutes-BdCTFM5Z.js.gz b/web-app/build/assets/KMSRoutes-BdCTFM5Z.js.gz
new file mode 100644
index 0000000000..7c09372fdf
Binary files /dev/null and b/web-app/build/assets/KMSRoutes-BdCTFM5Z.js.gz differ
diff --git a/web-app/build/assets/KMSRoutes-Bsyqi2te.js.gz b/web-app/build/assets/KMSRoutes-Bsyqi2te.js.gz
deleted file mode 100644
index 313501493e..0000000000
Binary files a/web-app/build/assets/KMSRoutes-Bsyqi2te.js.gz and /dev/null differ
diff --git a/web-app/build/assets/LabelWithIcon-BQY_t5HX.js.gz b/web-app/build/assets/LabelWithIcon-BQY_t5HX.js.gz
deleted file mode 100644
index 0a8f14aa58..0000000000
Binary files a/web-app/build/assets/LabelWithIcon-BQY_t5HX.js.gz and /dev/null differ
diff --git a/web-app/build/assets/LabelWithIcon-DCvEhNlP.js.gz b/web-app/build/assets/LabelWithIcon-DCvEhNlP.js.gz
new file mode 100644
index 0000000000..980d53e078
Binary files /dev/null and b/web-app/build/assets/LabelWithIcon-DCvEhNlP.js.gz differ
diff --git a/web-app/build/assets/License-BG5OgRZc.js.gz b/web-app/build/assets/License-BG5OgRZc.js.gz
new file mode 100644
index 0000000000..8911f2ed51
Binary files /dev/null and b/web-app/build/assets/License-BG5OgRZc.js.gz differ
diff --git a/web-app/build/assets/License-CZV8gIh9.js.gz b/web-app/build/assets/License-CZV8gIh9.js.gz
deleted file mode 100644
index c235af0b6a..0000000000
Binary files a/web-app/build/assets/License-CZV8gIh9.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ListBuckets-DyHS8Q8M.js.gz b/web-app/build/assets/ListBuckets-DyHS8Q8M.js.gz
deleted file mode 100644
index be90309e8c..0000000000
Binary files a/web-app/build/assets/ListBuckets-DyHS8Q8M.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ListBuckets-vcI4saRJ.js.gz b/web-app/build/assets/ListBuckets-vcI4saRJ.js.gz
new file mode 100644
index 0000000000..e3ef1a04e4
Binary files /dev/null and b/web-app/build/assets/ListBuckets-vcI4saRJ.js.gz differ
diff --git a/web-app/build/assets/ListEventDestinations-D69olgOT.js.gz b/web-app/build/assets/ListEventDestinations-D69olgOT.js.gz
deleted file mode 100644
index 90899b0ca6..0000000000
Binary files a/web-app/build/assets/ListEventDestinations-D69olgOT.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ListEventDestinations-Zr--h893.js.gz b/web-app/build/assets/ListEventDestinations-Zr--h893.js.gz
new file mode 100644
index 0000000000..a14c70537d
Binary files /dev/null and b/web-app/build/assets/ListEventDestinations-Zr--h893.js.gz differ
diff --git a/web-app/build/assets/ListKeys-B6GgOMKr.js.gz b/web-app/build/assets/ListKeys-B6GgOMKr.js.gz
new file mode 100644
index 0000000000..5bc365e522
Binary files /dev/null and b/web-app/build/assets/ListKeys-B6GgOMKr.js.gz differ
diff --git a/web-app/build/assets/ListKeys-C_7WyENF.js.gz b/web-app/build/assets/ListKeys-C_7WyENF.js.gz
deleted file mode 100644
index 664baca15e..0000000000
Binary files a/web-app/build/assets/ListKeys-C_7WyENF.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ListPolicies-Ckz-Oh0p.js.gz b/web-app/build/assets/ListPolicies-Ckz-Oh0p.js.gz
deleted file mode 100644
index efe704518e..0000000000
Binary files a/web-app/build/assets/ListPolicies-Ckz-Oh0p.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ListPolicies-DSi0Cvb_.js.gz b/web-app/build/assets/ListPolicies-DSi0Cvb_.js.gz
new file mode 100644
index 0000000000..e4c9dfb493
Binary files /dev/null and b/web-app/build/assets/ListPolicies-DSi0Cvb_.js.gz differ
diff --git a/web-app/build/assets/ListTiersConfiguration-CGN9AYl7.js.gz b/web-app/build/assets/ListTiersConfiguration-CGN9AYl7.js.gz
deleted file mode 100644
index 407ff44d6b..0000000000
Binary files a/web-app/build/assets/ListTiersConfiguration-CGN9AYl7.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ListTiersConfiguration-DLCpiGeQ.js.gz b/web-app/build/assets/ListTiersConfiguration-DLCpiGeQ.js.gz
new file mode 100644
index 0000000000..6144014565
Binary files /dev/null and b/web-app/build/assets/ListTiersConfiguration-DLCpiGeQ.js.gz differ
diff --git a/web-app/build/assets/LoginCallback-Bl9QsJ0f.js.gz b/web-app/build/assets/LoginCallback-Bl9QsJ0f.js.gz
deleted file mode 100644
index 6e4db66526..0000000000
Binary files a/web-app/build/assets/LoginCallback-Bl9QsJ0f.js.gz and /dev/null differ
diff --git a/web-app/build/assets/LoginCallback-D6LJDvTG.js.gz b/web-app/build/assets/LoginCallback-D6LJDvTG.js.gz
new file mode 100644
index 0000000000..24f35aa47d
Binary files /dev/null and b/web-app/build/assets/LoginCallback-D6LJDvTG.js.gz differ
diff --git a/web-app/build/assets/LogoutPage-1td9pWU_.js.gz b/web-app/build/assets/LogoutPage-1td9pWU_.js.gz
deleted file mode 100644
index 1ec09173e7..0000000000
Binary files a/web-app/build/assets/LogoutPage-1td9pWU_.js.gz and /dev/null differ
diff --git a/web-app/build/assets/LogoutPage-D7SHg-4g.js.gz b/web-app/build/assets/LogoutPage-D7SHg-4g.js.gz
new file mode 100644
index 0000000000..61a7c1e2c3
Binary files /dev/null and b/web-app/build/assets/LogoutPage-D7SHg-4g.js.gz differ
diff --git a/web-app/build/assets/LogsSearchMain-CXvGW3tv.js.gz b/web-app/build/assets/LogsSearchMain-CXvGW3tv.js.gz
new file mode 100644
index 0000000000..7aaa8228d4
Binary files /dev/null and b/web-app/build/assets/LogsSearchMain-CXvGW3tv.js.gz differ
diff --git a/web-app/build/assets/LogsSearchMain-_PwRPdYH.js.gz b/web-app/build/assets/LogsSearchMain-_PwRPdYH.js.gz
deleted file mode 100644
index fa44fde48a..0000000000
Binary files a/web-app/build/assets/LogsSearchMain-_PwRPdYH.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ModalBox-CNftDkHF.js.gz b/web-app/build/assets/ModalBox-CNftDkHF.js.gz
new file mode 100644
index 0000000000..63d0903e0e
Binary files /dev/null and b/web-app/build/assets/ModalBox-CNftDkHF.js.gz differ
diff --git a/web-app/build/assets/ModalBox-CSre1z64.js.gz b/web-app/build/assets/ModalBox-CSre1z64.js.gz
deleted file mode 100644
index bd00ee880d..0000000000
Binary files a/web-app/build/assets/ModalBox-CSre1z64.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ModalPresence-CwKZcWyv.js.gz b/web-app/build/assets/ModalPresence-CwKZcWyv.js.gz
deleted file mode 100644
index 3661f8641e..0000000000
Binary files a/web-app/build/assets/ModalPresence-CwKZcWyv.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ModalPresence-DQ-4YLcL.js.gz b/web-app/build/assets/ModalPresence-DQ-4YLcL.js.gz
new file mode 100644
index 0000000000..a51d0d79c6
Binary files /dev/null and b/web-app/build/assets/ModalPresence-DQ-4YLcL.js.gz differ
diff --git a/web-app/build/assets/ObjectBrowser-ChHgFNxH.js.gz b/web-app/build/assets/ObjectBrowser-ChHgFNxH.js.gz
new file mode 100644
index 0000000000..c840f37ca8
Binary files /dev/null and b/web-app/build/assets/ObjectBrowser-ChHgFNxH.js.gz differ
diff --git a/web-app/build/assets/ObjectBrowser-D_Ka6YFA.js.gz b/web-app/build/assets/ObjectBrowser-D_Ka6YFA.js.gz
deleted file mode 100644
index d9bdb858a6..0000000000
Binary files a/web-app/build/assets/ObjectBrowser-D_Ka6YFA.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ObjectManager-BkpjHysq.js.gz b/web-app/build/assets/ObjectManager-BkpjHysq.js.gz
deleted file mode 100644
index 65bbcae42a..0000000000
Binary files a/web-app/build/assets/ObjectManager-BkpjHysq.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ObjectManager-DFEQQOpM.js.gz b/web-app/build/assets/ObjectManager-DFEQQOpM.js.gz
new file mode 100644
index 0000000000..8025600b08
Binary files /dev/null and b/web-app/build/assets/ObjectManager-DFEQQOpM.js.gz differ
diff --git a/web-app/build/assets/OverflowMenu-CrJ5B24e.js.gz b/web-app/build/assets/OverflowMenu-CrJ5B24e.js.gz
new file mode 100644
index 0000000000..76636d61ed
Binary files /dev/null and b/web-app/build/assets/OverflowMenu-CrJ5B24e.js.gz differ
diff --git a/web-app/build/assets/OverflowMenu-Dm5NOjiR.js.gz b/web-app/build/assets/OverflowMenu-Dm5NOjiR.js.gz
deleted file mode 100644
index ffbdb9fe7b..0000000000
Binary files a/web-app/build/assets/OverflowMenu-Dm5NOjiR.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Overview-C7ToLKe-.js.gz b/web-app/build/assets/Overview-C7ToLKe-.js.gz
deleted file mode 100644
index be62cb3606..0000000000
Binary files a/web-app/build/assets/Overview-C7ToLKe-.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Overview-Sp_lKojk.js.gz b/web-app/build/assets/Overview-Sp_lKojk.js.gz
new file mode 100644
index 0000000000..a8647ad95f
Binary files /dev/null and b/web-app/build/assets/Overview-Sp_lKojk.js.gz differ
diff --git a/web-app/build/assets/PanelTitle-BawI_bpb.js.gz b/web-app/build/assets/PanelTitle-BawI_bpb.js.gz
deleted file mode 100644
index a2f00308e8..0000000000
Binary files a/web-app/build/assets/PanelTitle-BawI_bpb.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PanelTitle-D0ybrcoL.js.gz b/web-app/build/assets/PanelTitle-D0ybrcoL.js.gz
new file mode 100644
index 0000000000..7b5722bdcd
Binary files /dev/null and b/web-app/build/assets/PanelTitle-D0ybrcoL.js.gz differ
diff --git a/web-app/build/assets/Policies-B4Cz6-gI.js.gz b/web-app/build/assets/Policies-B4Cz6-gI.js.gz
deleted file mode 100644
index ac59a44cdc..0000000000
Binary files a/web-app/build/assets/Policies-B4Cz6-gI.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Policies-FkqPdsEx.js.gz b/web-app/build/assets/Policies-FkqPdsEx.js.gz
new file mode 100644
index 0000000000..f348216b09
Binary files /dev/null and b/web-app/build/assets/Policies-FkqPdsEx.js.gz differ
diff --git a/web-app/build/assets/PolicyDetails-D2SeP52U.js.gz b/web-app/build/assets/PolicyDetails-D2SeP52U.js.gz
new file mode 100644
index 0000000000..a3ccda7a03
Binary files /dev/null and b/web-app/build/assets/PolicyDetails-D2SeP52U.js.gz differ
diff --git a/web-app/build/assets/PolicyDetails-HQKzhLE9.js.gz b/web-app/build/assets/PolicyDetails-HQKzhLE9.js.gz
deleted file mode 100644
index fa9cf55a44..0000000000
Binary files a/web-app/build/assets/PolicyDetails-HQKzhLE9.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PolicySelectors-CMwuNlN0.js.gz b/web-app/build/assets/PolicySelectors-CMwuNlN0.js.gz
deleted file mode 100644
index 14d1768b79..0000000000
Binary files a/web-app/build/assets/PolicySelectors-CMwuNlN0.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PolicySelectors-FpJyHmc6.js.gz b/web-app/build/assets/PolicySelectors-FpJyHmc6.js.gz
new file mode 100644
index 0000000000..61cb38e8c6
Binary files /dev/null and b/web-app/build/assets/PolicySelectors-FpJyHmc6.js.gz differ
diff --git a/web-app/build/assets/PolicyTypeBadge-Bu7nQYAg.js.gz b/web-app/build/assets/PolicyTypeBadge-Bu7nQYAg.js.gz
new file mode 100644
index 0000000000..74b87ed40f
Binary files /dev/null and b/web-app/build/assets/PolicyTypeBadge-Bu7nQYAg.js.gz differ
diff --git a/web-app/build/assets/PolicyTypeBadge-DtbEVljG.js.gz b/web-app/build/assets/PolicyTypeBadge-DtbEVljG.js.gz
deleted file mode 100644
index 5b22d65978..0000000000
Binary files a/web-app/build/assets/PolicyTypeBadge-DtbEVljG.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PreviewCsv-BgNfVZ7k.js.gz b/web-app/build/assets/PreviewCsv-BgNfVZ7k.js.gz
new file mode 100644
index 0000000000..b17a9d3935
Binary files /dev/null and b/web-app/build/assets/PreviewCsv-BgNfVZ7k.js.gz differ
diff --git a/web-app/build/assets/PreviewCsv-BkgABCcK.js.gz b/web-app/build/assets/PreviewCsv-BkgABCcK.js.gz
deleted file mode 100644
index 30ccf5fcdd..0000000000
Binary files a/web-app/build/assets/PreviewCsv-BkgABCcK.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PreviewFileContent-BKZf9BV-.js.gz b/web-app/build/assets/PreviewFileContent-BKZf9BV-.js.gz
deleted file mode 100644
index 73c298ee57..0000000000
Binary files a/web-app/build/assets/PreviewFileContent-BKZf9BV-.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PreviewFileContent-CVnChZUu.js.gz b/web-app/build/assets/PreviewFileContent-CVnChZUu.js.gz
new file mode 100644
index 0000000000..455e5e1645
Binary files /dev/null and b/web-app/build/assets/PreviewFileContent-CVnChZUu.js.gz differ
diff --git a/web-app/build/assets/PreviewMarkdown--7E280QN.js.gz b/web-app/build/assets/PreviewMarkdown--7E280QN.js.gz
new file mode 100644
index 0000000000..9c63c59f17
Binary files /dev/null and b/web-app/build/assets/PreviewMarkdown--7E280QN.js.gz differ
diff --git a/web-app/build/assets/PreviewMarkdown-D0fF9zkT.js.gz b/web-app/build/assets/PreviewMarkdown-D0fF9zkT.js.gz
deleted file mode 100644
index 35d8737ae5..0000000000
Binary files a/web-app/build/assets/PreviewMarkdown-D0fF9zkT.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PreviewPDF-BdaoSJnP.js.gz b/web-app/build/assets/PreviewPDF-BdaoSJnP.js.gz
deleted file mode 100644
index 2e7ea9e56b..0000000000
Binary files a/web-app/build/assets/PreviewPDF-BdaoSJnP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PreviewPDF-VoPLOhJR.js.gz b/web-app/build/assets/PreviewPDF-VoPLOhJR.js.gz
new file mode 100644
index 0000000000..62fea62a79
Binary files /dev/null and b/web-app/build/assets/PreviewPDF-VoPLOhJR.js.gz differ
diff --git a/web-app/build/assets/PreviewText-CYNIEP0a.js.gz b/web-app/build/assets/PreviewText-CYNIEP0a.js.gz
new file mode 100644
index 0000000000..58d8be69df
Binary files /dev/null and b/web-app/build/assets/PreviewText-CYNIEP0a.js.gz differ
diff --git a/web-app/build/assets/PreviewText-DCO3K_36.js.gz b/web-app/build/assets/PreviewText-DCO3K_36.js.gz
deleted file mode 100644
index ff47cdc818..0000000000
Binary files a/web-app/build/assets/PreviewText-DCO3K_36.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Profile-BZY7169c.js.gz b/web-app/build/assets/Profile-BZY7169c.js.gz
deleted file mode 100644
index 05761ea8a0..0000000000
Binary files a/web-app/build/assets/Profile-BZY7169c.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Profile-DHunq15N.js.gz b/web-app/build/assets/Profile-DHunq15N.js.gz
new file mode 100644
index 0000000000..6b0e78d696
Binary files /dev/null and b/web-app/build/assets/Profile-DHunq15N.js.gz differ
diff --git a/web-app/build/assets/PrometheusErrorIcon--etZYZlt.js.gz b/web-app/build/assets/PrometheusErrorIcon--etZYZlt.js.gz
deleted file mode 100644
index ceae9ff949..0000000000
Binary files a/web-app/build/assets/PrometheusErrorIcon--etZYZlt.js.gz and /dev/null differ
diff --git a/web-app/build/assets/PrometheusErrorIcon-DCz33H7o.js.gz b/web-app/build/assets/PrometheusErrorIcon-DCz33H7o.js.gz
new file mode 100644
index 0000000000..45ed92f8da
Binary files /dev/null and b/web-app/build/assets/PrometheusErrorIcon-DCz33H7o.js.gz differ
diff --git a/web-app/build/assets/QueryMultiSelector-8CIgcbzp.js.gz b/web-app/build/assets/QueryMultiSelector-8CIgcbzp.js.gz
deleted file mode 100644
index f1eea9f7b1..0000000000
Binary files a/web-app/build/assets/QueryMultiSelector-8CIgcbzp.js.gz and /dev/null differ
diff --git a/web-app/build/assets/QueryMultiSelector-BsxB07AL.js.gz b/web-app/build/assets/QueryMultiSelector-BsxB07AL.js.gz
new file mode 100644
index 0000000000..22c20304f0
Binary files /dev/null and b/web-app/build/assets/QueryMultiSelector-BsxB07AL.js.gz differ
diff --git a/web-app/build/assets/ReadBox-CKh3JaXl.js.gz b/web-app/build/assets/ReadBox-CKh3JaXl.js.gz
deleted file mode 100644
index 26bfdfeafc..0000000000
Binary files a/web-app/build/assets/ReadBox-CKh3JaXl.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ReadBox-DgnnSHte.js.gz b/web-app/build/assets/ReadBox-DgnnSHte.js.gz
new file mode 100644
index 0000000000..2a2baeb074
Binary files /dev/null and b/web-app/build/assets/ReadBox-DgnnSHte.js.gz differ
diff --git a/web-app/build/assets/ResetConfigurationModal-BiiYokD3.js.gz b/web-app/build/assets/ResetConfigurationModal-BiiYokD3.js.gz
new file mode 100644
index 0000000000..2c1539ae9d
Binary files /dev/null and b/web-app/build/assets/ResetConfigurationModal-BiiYokD3.js.gz differ
diff --git a/web-app/build/assets/ResetConfigurationModal-uN-gjHPq.js.gz b/web-app/build/assets/ResetConfigurationModal-uN-gjHPq.js.gz
deleted file mode 100644
index 66e5a1066c..0000000000
Binary files a/web-app/build/assets/ResetConfigurationModal-uN-gjHPq.js.gz and /dev/null differ
diff --git a/web-app/build/assets/RewindEnable-CZPx75CP.js.gz b/web-app/build/assets/RewindEnable-CZPx75CP.js.gz
deleted file mode 100644
index 40c4f900eb..0000000000
Binary files a/web-app/build/assets/RewindEnable-CZPx75CP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/RewindEnable-rxWnXwTq.js.gz b/web-app/build/assets/RewindEnable-rxWnXwTq.js.gz
new file mode 100644
index 0000000000..813b292350
Binary files /dev/null and b/web-app/build/assets/RewindEnable-rxWnXwTq.js.gz differ
diff --git a/web-app/build/assets/SSOLogin-C3O68KPV.js.gz b/web-app/build/assets/SSOLogin-C3O68KPV.js.gz
new file mode 100644
index 0000000000..16195ce91a
Binary files /dev/null and b/web-app/build/assets/SSOLogin-C3O68KPV.js.gz differ
diff --git a/web-app/build/assets/SSOLogin-DTfLb_eT.js.gz b/web-app/build/assets/SSOLogin-DTfLb_eT.js.gz
deleted file mode 100644
index 807a4c2015..0000000000
Binary files a/web-app/build/assets/SSOLogin-DTfLb_eT.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ScreenTitle-COd2DzNi.js.gz b/web-app/build/assets/ScreenTitle-COd2DzNi.js.gz
new file mode 100644
index 0000000000..8f35e742a4
Binary files /dev/null and b/web-app/build/assets/ScreenTitle-COd2DzNi.js.gz differ
diff --git a/web-app/build/assets/ScreenTitle-rIgLl4La.js.gz b/web-app/build/assets/ScreenTitle-rIgLl4La.js.gz
deleted file mode 100644
index 9faef5f16c..0000000000
Binary files a/web-app/build/assets/ScreenTitle-rIgLl4La.js.gz and /dev/null differ
diff --git a/web-app/build/assets/SearchBox-CRjdLFPE.js.gz b/web-app/build/assets/SearchBox-CRjdLFPE.js.gz
new file mode 100644
index 0000000000..f8e11f7dd5
Binary files /dev/null and b/web-app/build/assets/SearchBox-CRjdLFPE.js.gz differ
diff --git a/web-app/build/assets/SearchBox-Dn4Z6XDs.js.gz b/web-app/build/assets/SearchBox-Dn4Z6XDs.js.gz
deleted file mode 100644
index 76b3ac36a9..0000000000
Binary files a/web-app/build/assets/SearchBox-Dn4Z6XDs.js.gz and /dev/null differ
diff --git a/web-app/build/assets/SectionTitle-C1UrSNqk.js.gz b/web-app/build/assets/SectionTitle-C1UrSNqk.js.gz
deleted file mode 100644
index d6dc670a71..0000000000
Binary files a/web-app/build/assets/SectionTitle-C1UrSNqk.js.gz and /dev/null differ
diff --git a/web-app/build/assets/SectionTitle-DKflitFp.js.gz b/web-app/build/assets/SectionTitle-DKflitFp.js.gz
new file mode 100644
index 0000000000..406e97965f
Binary files /dev/null and b/web-app/build/assets/SectionTitle-DKflitFp.js.gz differ
diff --git a/web-app/build/assets/SecureComponent-C2COUZhB.js.gz b/web-app/build/assets/SecureComponent-C2COUZhB.js.gz
new file mode 100644
index 0000000000..9e59b032cd
Binary files /dev/null and b/web-app/build/assets/SecureComponent-C2COUZhB.js.gz differ
diff --git a/web-app/build/assets/SecureComponent-Cyii73MM.js.gz b/web-app/build/assets/SecureComponent-Cyii73MM.js.gz
deleted file mode 100644
index f2a2795083..0000000000
Binary files a/web-app/build/assets/SecureComponent-Cyii73MM.js.gz and /dev/null differ
diff --git a/web-app/build/assets/SetAccessPolicy-B86PZTlV.js.gz b/web-app/build/assets/SetAccessPolicy-B86PZTlV.js.gz
deleted file mode 100644
index 1aabec1979..0000000000
Binary files a/web-app/build/assets/SetAccessPolicy-B86PZTlV.js.gz and /dev/null differ
diff --git a/web-app/build/assets/SetAccessPolicy-YmUeCkiY.js.gz b/web-app/build/assets/SetAccessPolicy-YmUeCkiY.js.gz
new file mode 100644
index 0000000000..5d6cb948e0
Binary files /dev/null and b/web-app/build/assets/SetAccessPolicy-YmUeCkiY.js.gz differ
diff --git a/web-app/build/assets/SetPolicy-BczA6MsQ.js.gz b/web-app/build/assets/SetPolicy-BczA6MsQ.js.gz
new file mode 100644
index 0000000000..2af0b386a5
Binary files /dev/null and b/web-app/build/assets/SetPolicy-BczA6MsQ.js.gz differ
diff --git a/web-app/build/assets/SetPolicy-DCoTTphk.js.gz b/web-app/build/assets/SetPolicy-DCoTTphk.js.gz
deleted file mode 100644
index b686c7b595..0000000000
Binary files a/web-app/build/assets/SetPolicy-DCoTTphk.js.gz and /dev/null differ
diff --git a/web-app/build/assets/SetRetentionConfig-BmSXh8ml.js.gz b/web-app/build/assets/SetRetentionConfig-BmSXh8ml.js.gz
new file mode 100644
index 0000000000..57c081274b
Binary files /dev/null and b/web-app/build/assets/SetRetentionConfig-BmSXh8ml.js.gz differ
diff --git a/web-app/build/assets/SetRetentionConfig-Ci54N2Ws.js.gz b/web-app/build/assets/SetRetentionConfig-Ci54N2Ws.js.gz
deleted file mode 100644
index 3457177ff5..0000000000
Binary files a/web-app/build/assets/SetRetentionConfig-Ci54N2Ws.js.gz and /dev/null differ
diff --git a/web-app/build/assets/ShortcutHelpDialog-Bd35KGn1.js.gz b/web-app/build/assets/ShortcutHelpDialog-Bd35KGn1.js.gz
new file mode 100644
index 0000000000..7711040683
Binary files /dev/null and b/web-app/build/assets/ShortcutHelpDialog-Bd35KGn1.js.gz differ
diff --git a/web-app/build/assets/ShortcutHelpDialog-CpEE4tc1.js.gz b/web-app/build/assets/ShortcutHelpDialog-CpEE4tc1.js.gz
deleted file mode 100644
index f297a876df..0000000000
Binary files a/web-app/build/assets/ShortcutHelpDialog-CpEE4tc1.js.gz and /dev/null differ
diff --git a/web-app/build/assets/SiteReplication-CBgP2eM4.js.gz b/web-app/build/assets/SiteReplication-CBgP2eM4.js.gz
deleted file mode 100644
index b1beb912ae..0000000000
Binary files a/web-app/build/assets/SiteReplication-CBgP2eM4.js.gz and /dev/null differ
diff --git a/web-app/build/assets/SiteReplication-GP5zCH6q.js.gz b/web-app/build/assets/SiteReplication-GP5zCH6q.js.gz
new file mode 100644
index 0000000000..9dc2618ddb
Binary files /dev/null and b/web-app/build/assets/SiteReplication-GP5zCH6q.js.gz differ
diff --git a/web-app/build/assets/SiteReplicationStatus-BhRivYqf.js.gz b/web-app/build/assets/SiteReplicationStatus-BhRivYqf.js.gz
new file mode 100644
index 0000000000..0ae50c64bc
Binary files /dev/null and b/web-app/build/assets/SiteReplicationStatus-BhRivYqf.js.gz differ
diff --git a/web-app/build/assets/SiteReplicationStatus-Bo7v1PoP.js.gz b/web-app/build/assets/SiteReplicationStatus-Bo7v1PoP.js.gz
deleted file mode 100644
index e5997ee9ac..0000000000
Binary files a/web-app/build/assets/SiteReplicationStatus-Bo7v1PoP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Speedtest-BjLHZr8A.js.gz b/web-app/build/assets/Speedtest-BjLHZr8A.js.gz
deleted file mode 100644
index 18305f9093..0000000000
Binary files a/web-app/build/assets/Speedtest-BjLHZr8A.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Speedtest-C3cI_LHI.js.gz b/web-app/build/assets/Speedtest-C3cI_LHI.js.gz
new file mode 100644
index 0000000000..447eebea9c
Binary files /dev/null and b/web-app/build/assets/Speedtest-C3cI_LHI.js.gz differ
diff --git a/web-app/build/assets/Status-CK4HHGK3.js.gz b/web-app/build/assets/Status-CK4HHGK3.js.gz
new file mode 100644
index 0000000000..d1d607c6b3
Binary files /dev/null and b/web-app/build/assets/Status-CK4HHGK3.js.gz differ
diff --git a/web-app/build/assets/Status-D9P-yHBD.js.gz b/web-app/build/assets/Status-D9P-yHBD.js.gz
deleted file mode 100644
index d6e8a7eba9..0000000000
Binary files a/web-app/build/assets/Status-D9P-yHBD.js.gz and /dev/null differ
diff --git a/web-app/build/assets/StatusBadge-BTgl2fcD.js.gz b/web-app/build/assets/StatusBadge-BTgl2fcD.js.gz
deleted file mode 100644
index 715db5407b..0000000000
Binary files a/web-app/build/assets/StatusBadge-BTgl2fcD.js.gz and /dev/null differ
diff --git a/web-app/build/assets/StatusBadge-BbgadeTl.js.gz b/web-app/build/assets/StatusBadge-BbgadeTl.js.gz
new file mode 100644
index 0000000000..0082678da1
Binary files /dev/null and b/web-app/build/assets/StatusBadge-BbgadeTl.js.gz differ
diff --git a/web-app/build/assets/StatusCountCard-BhGJqMs7.js.gz b/web-app/build/assets/StatusCountCard-BhGJqMs7.js.gz
deleted file mode 100644
index 14417bca7c..0000000000
Binary files a/web-app/build/assets/StatusCountCard-BhGJqMs7.js.gz and /dev/null differ
diff --git a/web-app/build/assets/StatusCountCard-CEd5RndI.js.gz b/web-app/build/assets/StatusCountCard-CEd5RndI.js.gz
new file mode 100644
index 0000000000..d26ce71fe0
Binary files /dev/null and b/web-app/build/assets/StatusCountCard-CEd5RndI.js.gz differ
diff --git a/web-app/build/assets/Tabs-YOliqYd-.js.gz b/web-app/build/assets/Tabs-YOliqYd-.js.gz
new file mode 100644
index 0000000000..896a7b2850
Binary files /dev/null and b/web-app/build/assets/Tabs-YOliqYd-.js.gz differ
diff --git a/web-app/build/assets/Tabs-vSudT0qV.js.gz b/web-app/build/assets/Tabs-vSudT0qV.js.gz
deleted file mode 100644
index 6907e0f852..0000000000
Binary files a/web-app/build/assets/Tabs-vSudT0qV.js.gz and /dev/null differ
diff --git a/web-app/build/assets/TierTypeSelector-BkyHChrR.js.gz b/web-app/build/assets/TierTypeSelector-BkyHChrR.js.gz
deleted file mode 100644
index c8649b797e..0000000000
Binary files a/web-app/build/assets/TierTypeSelector-BkyHChrR.js.gz and /dev/null differ
diff --git a/web-app/build/assets/TierTypeSelector-pa4Ypgu_.js.gz b/web-app/build/assets/TierTypeSelector-pa4Ypgu_.js.gz
new file mode 100644
index 0000000000..e4e67625b3
Binary files /dev/null and b/web-app/build/assets/TierTypeSelector-pa4Ypgu_.js.gz differ
diff --git a/web-app/build/assets/Tools-CNJwXv1q.js.gz b/web-app/build/assets/Tools-CNJwXv1q.js.gz
deleted file mode 100644
index ad67acd4bf..0000000000
Binary files a/web-app/build/assets/Tools-CNJwXv1q.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Tools-CyHILfHH.js.gz b/web-app/build/assets/Tools-CyHILfHH.js.gz
new file mode 100644
index 0000000000..1a33596403
Binary files /dev/null and b/web-app/build/assets/Tools-CyHILfHH.js.gz differ
diff --git a/web-app/build/assets/Trace-ChzeHata.js.gz b/web-app/build/assets/Trace-ChzeHata.js.gz
deleted file mode 100644
index 5862f8fe3f..0000000000
Binary files a/web-app/build/assets/Trace-ChzeHata.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Trace-ws-ClIX2.js.gz b/web-app/build/assets/Trace-ws-ClIX2.js.gz
new file mode 100644
index 0000000000..d5b85b4b15
Binary files /dev/null and b/web-app/build/assets/Trace-ws-ClIX2.js.gz differ
diff --git a/web-app/build/assets/UpdateTierCredentialsModal-DAMXw9I7.js.gz b/web-app/build/assets/UpdateTierCredentialsModal-DAMXw9I7.js.gz
deleted file mode 100644
index f356e47f29..0000000000
Binary files a/web-app/build/assets/UpdateTierCredentialsModal-DAMXw9I7.js.gz and /dev/null differ
diff --git a/web-app/build/assets/UpdateTierCredentialsModal-DSBnKVli.js.gz b/web-app/build/assets/UpdateTierCredentialsModal-DSBnKVli.js.gz
new file mode 100644
index 0000000000..de72a7c555
Binary files /dev/null and b/web-app/build/assets/UpdateTierCredentialsModal-DSBnKVli.js.gz differ
diff --git a/web-app/build/assets/Users-CKok5nEq.js.gz b/web-app/build/assets/Users-CKok5nEq.js.gz
deleted file mode 100644
index 0bbd47ff7f..0000000000
Binary files a/web-app/build/assets/Users-CKok5nEq.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Users-yui36wyu.js.gz b/web-app/build/assets/Users-yui36wyu.js.gz
new file mode 100644
index 0000000000..4237daff24
Binary files /dev/null and b/web-app/build/assets/Users-yui36wyu.js.gz differ
diff --git a/web-app/build/assets/UsersSelectors-Dctx0pAf.js.gz b/web-app/build/assets/UsersSelectors-Dctx0pAf.js.gz
deleted file mode 100644
index 7a177dadee..0000000000
Binary files a/web-app/build/assets/UsersSelectors-Dctx0pAf.js.gz and /dev/null differ
diff --git a/web-app/build/assets/UsersSelectors-zpUzBGdf.js.gz b/web-app/build/assets/UsersSelectors-zpUzBGdf.js.gz
new file mode 100644
index 0000000000..122e374201
Binary files /dev/null and b/web-app/build/assets/UsersSelectors-zpUzBGdf.js.gz differ
diff --git a/web-app/build/assets/ValuePair-BDO-NOc0.js.gz b/web-app/build/assets/ValuePair-BDO-NOc0.js.gz
new file mode 100644
index 0000000000..af6c708485
Binary files /dev/null and b/web-app/build/assets/ValuePair-BDO-NOc0.js.gz differ
diff --git a/web-app/build/assets/ValuePair-i-UbUu01.js.gz b/web-app/build/assets/ValuePair-i-UbUu01.js.gz
deleted file mode 100644
index bc15e7572a..0000000000
Binary files a/web-app/build/assets/ValuePair-i-UbUu01.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Watch-BCM98jI6.js.gz b/web-app/build/assets/Watch-BCM98jI6.js.gz
deleted file mode 100644
index 95ac1bee54..0000000000
Binary files a/web-app/build/assets/Watch-BCM98jI6.js.gz and /dev/null differ
diff --git a/web-app/build/assets/Watch-BOctG4kN.js.gz b/web-app/build/assets/Watch-BOctG4kN.js.gz
new file mode 100644
index 0000000000..1a8c6414e6
Binary files /dev/null and b/web-app/build/assets/Watch-BOctG4kN.js.gz differ
diff --git a/web-app/build/assets/Wizard-BQxltdCk.js.gz b/web-app/build/assets/Wizard-BQxltdCk.js.gz
new file mode 100644
index 0000000000..f1373be0d1
Binary files /dev/null and b/web-app/build/assets/Wizard-BQxltdCk.js.gz differ
diff --git a/web-app/build/assets/Wizard-NMksYTZ5.js.gz b/web-app/build/assets/Wizard-NMksYTZ5.js.gz
deleted file mode 100644
index 1998f51a93..0000000000
Binary files a/web-app/build/assets/Wizard-NMksYTZ5.js.gz and /dev/null differ
diff --git a/web-app/build/assets/activity-CQUf9_bL.js.gz b/web-app/build/assets/activity-CQUf9_bL.js.gz
new file mode 100644
index 0000000000..d6e655ce9f
Binary files /dev/null and b/web-app/build/assets/activity-CQUf9_bL.js.gz differ
diff --git a/web-app/build/assets/activity-CtmEFmut.js.gz b/web-app/build/assets/activity-CtmEFmut.js.gz
deleted file mode 100644
index c302f9e39e..0000000000
Binary files a/web-app/build/assets/activity-CtmEFmut.js.gz and /dev/null differ
diff --git a/web-app/build/assets/archive-Cu6uIv3W.js.gz b/web-app/build/assets/archive-Cu6uIv3W.js.gz
deleted file mode 100644
index 95452dcc5c..0000000000
Binary files a/web-app/build/assets/archive-Cu6uIv3W.js.gz and /dev/null differ
diff --git a/web-app/build/assets/archive-g0MAZgFy.js.gz b/web-app/build/assets/archive-g0MAZgFy.js.gz
new file mode 100644
index 0000000000..63733ccb33
Binary files /dev/null and b/web-app/build/assets/archive-g0MAZgFy.js.gz differ
diff --git a/web-app/build/assets/arrow-left-LkIWgayt.js.gz b/web-app/build/assets/arrow-left-LkIWgayt.js.gz
new file mode 100644
index 0000000000..1afdf1b35f
Binary files /dev/null and b/web-app/build/assets/arrow-left-LkIWgayt.js.gz differ
diff --git a/web-app/build/assets/arrow-left-m7yJ5axC.js.gz b/web-app/build/assets/arrow-left-m7yJ5axC.js.gz
deleted file mode 100644
index f807fb6eea..0000000000
Binary files a/web-app/build/assets/arrow-left-m7yJ5axC.js.gz and /dev/null differ
diff --git a/web-app/build/assets/bell-CaA1zjAK.js.gz b/web-app/build/assets/bell-CaA1zjAK.js.gz
new file mode 100644
index 0000000000..c75ec0252c
Binary files /dev/null and b/web-app/build/assets/bell-CaA1zjAK.js.gz differ
diff --git a/web-app/build/assets/bell-DP27mYke.js.gz b/web-app/build/assets/bell-DP27mYke.js.gz
deleted file mode 100644
index 5ad3557ae1..0000000000
Binary files a/web-app/build/assets/bell-DP27mYke.js.gz and /dev/null differ
diff --git a/web-app/build/assets/boxes-BkxIJ6W2.js.gz b/web-app/build/assets/boxes-BkxIJ6W2.js.gz
new file mode 100644
index 0000000000..ba47a9ae64
Binary files /dev/null and b/web-app/build/assets/boxes-BkxIJ6W2.js.gz differ
diff --git a/web-app/build/assets/boxes-_8PXvP4Q.js.gz b/web-app/build/assets/boxes-_8PXvP4Q.js.gz
deleted file mode 100644
index 78321de34b..0000000000
Binary files a/web-app/build/assets/boxes-_8PXvP4Q.js.gz and /dev/null differ
diff --git a/web-app/build/assets/calendar-clock-BBAKYgvi.js.gz b/web-app/build/assets/calendar-clock-BBAKYgvi.js.gz
new file mode 100644
index 0000000000..d658d5da8a
Binary files /dev/null and b/web-app/build/assets/calendar-clock-BBAKYgvi.js.gz differ
diff --git a/web-app/build/assets/calendar-clock-BraTdpwh.js.gz b/web-app/build/assets/calendar-clock-BraTdpwh.js.gz
deleted file mode 100644
index 18aaf9f4f7..0000000000
Binary files a/web-app/build/assets/calendar-clock-BraTdpwh.js.gz and /dev/null differ
diff --git a/web-app/build/assets/calendar-days-CFoMttJy.js.gz b/web-app/build/assets/calendar-days-CFoMttJy.js.gz
deleted file mode 100644
index 6dbc6b0ffa..0000000000
Binary files a/web-app/build/assets/calendar-days-CFoMttJy.js.gz and /dev/null differ
diff --git a/web-app/build/assets/calendar-days-De4UWm9X.js.gz b/web-app/build/assets/calendar-days-De4UWm9X.js.gz
new file mode 100644
index 0000000000..2d8ccb71a5
Binary files /dev/null and b/web-app/build/assets/calendar-days-De4UWm9X.js.gz differ
diff --git a/web-app/build/assets/chart-no-axes-column-Bc7SfxIT.js.gz b/web-app/build/assets/chart-no-axes-column-Bc7SfxIT.js.gz
deleted file mode 100644
index 35e6d5d5e2..0000000000
Binary files a/web-app/build/assets/chart-no-axes-column-Bc7SfxIT.js.gz and /dev/null differ
diff --git a/web-app/build/assets/chart-no-axes-column-BrqSLc7S.js.gz b/web-app/build/assets/chart-no-axes-column-BrqSLc7S.js.gz
new file mode 100644
index 0000000000..d84b44ae24
Binary files /dev/null and b/web-app/build/assets/chart-no-axes-column-BrqSLc7S.js.gz differ
diff --git a/web-app/build/assets/chevron-left-DaM0XWlS.js.gz b/web-app/build/assets/chevron-left-DaM0XWlS.js.gz
new file mode 100644
index 0000000000..2c628d9517
Binary files /dev/null and b/web-app/build/assets/chevron-left-DaM0XWlS.js.gz differ
diff --git a/web-app/build/assets/chevron-left-sLIP0mqG.js.gz b/web-app/build/assets/chevron-left-sLIP0mqG.js.gz
deleted file mode 100644
index 756a0dfdd4..0000000000
Binary files a/web-app/build/assets/chevron-left-sLIP0mqG.js.gz and /dev/null differ
diff --git a/web-app/build/assets/chevron-right-DFIPYKMk.js.gz b/web-app/build/assets/chevron-right-DFIPYKMk.js.gz
deleted file mode 100644
index de4e98533d..0000000000
Binary files a/web-app/build/assets/chevron-right-DFIPYKMk.js.gz and /dev/null differ
diff --git a/web-app/build/assets/chevron-right-DcskDAfg.js.gz b/web-app/build/assets/chevron-right-DcskDAfg.js.gz
new file mode 100644
index 0000000000..e46f41ec36
Binary files /dev/null and b/web-app/build/assets/chevron-right-DcskDAfg.js.gz differ
diff --git a/web-app/build/assets/circle-check--s-qzPFr.js.gz b/web-app/build/assets/circle-check--s-qzPFr.js.gz
deleted file mode 100644
index eea15cc254..0000000000
Binary files a/web-app/build/assets/circle-check--s-qzPFr.js.gz and /dev/null differ
diff --git a/web-app/build/assets/circle-check-DW0UgEZG.js.gz b/web-app/build/assets/circle-check-DW0UgEZG.js.gz
new file mode 100644
index 0000000000..d81165d8e0
Binary files /dev/null and b/web-app/build/assets/circle-check-DW0UgEZG.js.gz differ
diff --git a/web-app/build/assets/circle-slash-BygNhiUP.js.gz b/web-app/build/assets/circle-slash-BygNhiUP.js.gz
deleted file mode 100644
index a1614d58ee..0000000000
Binary files a/web-app/build/assets/circle-slash-BygNhiUP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/circle-slash-COtvDZQg.js.gz b/web-app/build/assets/circle-slash-COtvDZQg.js.gz
new file mode 100644
index 0000000000..f86a824ace
Binary files /dev/null and b/web-app/build/assets/circle-slash-COtvDZQg.js.gz differ
diff --git a/web-app/build/assets/clock-BKuZCgWf.js.gz b/web-app/build/assets/clock-BKuZCgWf.js.gz
new file mode 100644
index 0000000000..d397d64c7f
Binary files /dev/null and b/web-app/build/assets/clock-BKuZCgWf.js.gz differ
diff --git a/web-app/build/assets/clock-BTaMdW1J.js.gz b/web-app/build/assets/clock-BTaMdW1J.js.gz
deleted file mode 100644
index b49a2fa878..0000000000
Binary files a/web-app/build/assets/clock-BTaMdW1J.js.gz and /dev/null differ
diff --git a/web-app/build/assets/code-B8Qpp6xY.js.gz b/web-app/build/assets/code-B8Qpp6xY.js.gz
new file mode 100644
index 0000000000..ed4f44b3be
Binary files /dev/null and b/web-app/build/assets/code-B8Qpp6xY.js.gz differ
diff --git a/web-app/build/assets/code-CkhtDBGc.js.gz b/web-app/build/assets/code-CkhtDBGc.js.gz
deleted file mode 100644
index 01599f7619..0000000000
Binary files a/web-app/build/assets/code-CkhtDBGc.js.gz and /dev/null differ
diff --git a/web-app/build/assets/copy-CYo4Zsxo.js.gz b/web-app/build/assets/copy-CYo4Zsxo.js.gz
new file mode 100644
index 0000000000..174b73d944
Binary files /dev/null and b/web-app/build/assets/copy-CYo4Zsxo.js.gz differ
diff --git a/web-app/build/assets/copy-PhpXQRIk.js.gz b/web-app/build/assets/copy-PhpXQRIk.js.gz
deleted file mode 100644
index ebab247d72..0000000000
Binary files a/web-app/build/assets/copy-PhpXQRIk.js.gz and /dev/null differ
diff --git a/web-app/build/assets/copy-to-clipboard-SfaOv4Kn.js.gz b/web-app/build/assets/copy-to-clipboard-SfaOv4Kn.js.gz
new file mode 100644
index 0000000000..8f678e4d60
Binary files /dev/null and b/web-app/build/assets/copy-to-clipboard-SfaOv4Kn.js.gz differ
diff --git a/web-app/build/assets/database-BlKx18pM.js.gz b/web-app/build/assets/database-BlKx18pM.js.gz
deleted file mode 100644
index ab0c306bb2..0000000000
Binary files a/web-app/build/assets/database-BlKx18pM.js.gz and /dev/null differ
diff --git a/web-app/build/assets/database-CkZBfZx9.js.gz b/web-app/build/assets/database-CkZBfZx9.js.gz
new file mode 100644
index 0000000000..c783425478
Binary files /dev/null and b/web-app/build/assets/database-CkZBfZx9.js.gz differ
diff --git a/web-app/build/assets/download-Cnuw4xd9.js.gz b/web-app/build/assets/download-Cnuw4xd9.js.gz
new file mode 100644
index 0000000000..5e791ab5a8
Binary files /dev/null and b/web-app/build/assets/download-Cnuw4xd9.js.gz differ
diff --git a/web-app/build/assets/download-m6916hdM.js.gz b/web-app/build/assets/download-m6916hdM.js.gz
deleted file mode 100644
index 5e413f2e19..0000000000
Binary files a/web-app/build/assets/download-m6916hdM.js.gz and /dev/null differ
diff --git a/web-app/build/assets/editor-CqOxti4I.js.gz b/web-app/build/assets/editor-CqOxti4I.js.gz
deleted file mode 100644
index fdd5f760a8..0000000000
Binary files a/web-app/build/assets/editor-CqOxti4I.js.gz and /dev/null differ
diff --git a/web-app/build/assets/editor-DcV35snB.js.gz b/web-app/build/assets/editor-DcV35snB.js.gz
new file mode 100644
index 0000000000..caaf559e15
Binary files /dev/null and b/web-app/build/assets/editor-DcV35snB.js.gz differ
diff --git a/web-app/build/assets/file-music-B8UgsG52.js.gz b/web-app/build/assets/file-music-B8UgsG52.js.gz
deleted file mode 100644
index 57866e98fa..0000000000
Binary files a/web-app/build/assets/file-music-B8UgsG52.js.gz and /dev/null differ
diff --git a/web-app/build/assets/file-music-DYbMqB5l.js.gz b/web-app/build/assets/file-music-DYbMqB5l.js.gz
new file mode 100644
index 0000000000..4bb30d09a4
Binary files /dev/null and b/web-app/build/assets/file-music-DYbMqB5l.js.gz differ
diff --git a/web-app/build/assets/folder-D6KPo1XY.js.gz b/web-app/build/assets/folder-D6KPo1XY.js.gz
deleted file mode 100644
index 9559681821..0000000000
Binary files a/web-app/build/assets/folder-D6KPo1XY.js.gz and /dev/null differ
diff --git a/web-app/build/assets/folder-DJKhePq4.js.gz b/web-app/build/assets/folder-DJKhePq4.js.gz
new file mode 100644
index 0000000000..2a49a63085
Binary files /dev/null and b/web-app/build/assets/folder-DJKhePq4.js.gz differ
diff --git a/web-app/build/assets/gauge-CSaLtwlg.js.gz b/web-app/build/assets/gauge-CSaLtwlg.js.gz
deleted file mode 100644
index 3ebec7c5f6..0000000000
Binary files a/web-app/build/assets/gauge-CSaLtwlg.js.gz and /dev/null differ
diff --git a/web-app/build/assets/gauge-Cn7oQsz2.js.gz b/web-app/build/assets/gauge-Cn7oQsz2.js.gz
new file mode 100644
index 0000000000..c911bc2e52
Binary files /dev/null and b/web-app/build/assets/gauge-Cn7oQsz2.js.gz differ
diff --git a/web-app/build/assets/hard-drive-B4CIRsJz.js.gz b/web-app/build/assets/hard-drive-B4CIRsJz.js.gz
deleted file mode 100644
index 7da9759122..0000000000
Binary files a/web-app/build/assets/hard-drive-B4CIRsJz.js.gz and /dev/null differ
diff --git a/web-app/build/assets/hard-drive-DQpBL1Nu.js.gz b/web-app/build/assets/hard-drive-DQpBL1Nu.js.gz
new file mode 100644
index 0000000000..bdf0848fa9
Binary files /dev/null and b/web-app/build/assets/hard-drive-DQpBL1Nu.js.gz differ
diff --git a/web-app/build/assets/heart-pulse-TYfDkV8P.js.gz b/web-app/build/assets/heart-pulse-TYfDkV8P.js.gz
deleted file mode 100644
index 6e23ca08c4..0000000000
Binary files a/web-app/build/assets/heart-pulse-TYfDkV8P.js.gz and /dev/null differ
diff --git a/web-app/build/assets/heart-pulse-o5YbDPlf.js.gz b/web-app/build/assets/heart-pulse-o5YbDPlf.js.gz
new file mode 100644
index 0000000000..86d0b96736
Binary files /dev/null and b/web-app/build/assets/heart-pulse-o5YbDPlf.js.gz differ
diff --git a/web-app/build/assets/index-CC_YHHcs.js.gz b/web-app/build/assets/index-CC_YHHcs.js.gz
new file mode 100644
index 0000000000..b93b59cdcf
Binary files /dev/null and b/web-app/build/assets/index-CC_YHHcs.js.gz differ
diff --git a/web-app/build/assets/index-D3Bfae5V.js.gz b/web-app/build/assets/index-D3Bfae5V.js.gz
deleted file mode 100644
index ae67b7399e..0000000000
Binary files a/web-app/build/assets/index-D3Bfae5V.js.gz and /dev/null differ
diff --git a/web-app/build/assets/info-Cg15zJKq.js.gz b/web-app/build/assets/info-Cg15zJKq.js.gz
deleted file mode 100644
index ce7f6458d5..0000000000
Binary files a/web-app/build/assets/info-Cg15zJKq.js.gz and /dev/null differ
diff --git a/web-app/build/assets/info-DRrY2H15.js.gz b/web-app/build/assets/info-DRrY2H15.js.gz
new file mode 100644
index 0000000000..3bbb6a99a3
Binary files /dev/null and b/web-app/build/assets/info-DRrY2H15.js.gz differ
diff --git a/web-app/build/assets/inspectDownload-BJjcjJaC.js.gz b/web-app/build/assets/inspectDownload-BJjcjJaC.js.gz
new file mode 100644
index 0000000000..3a897b126d
Binary files /dev/null and b/web-app/build/assets/inspectDownload-BJjcjJaC.js.gz differ
diff --git a/web-app/build/assets/inspectDownload-BMNtgwSJ.js.gz b/web-app/build/assets/inspectDownload-BMNtgwSJ.js.gz
deleted file mode 100644
index 94f40323b2..0000000000
Binary files a/web-app/build/assets/inspectDownload-BMNtgwSJ.js.gz and /dev/null differ
diff --git a/web-app/build/assets/key-square-6iDCmm_E.js.gz b/web-app/build/assets/key-square-6iDCmm_E.js.gz
deleted file mode 100644
index 06b2d12e0a..0000000000
Binary files a/web-app/build/assets/key-square-6iDCmm_E.js.gz and /dev/null differ
diff --git a/web-app/build/assets/key-square-D6pCsZi4.js.gz b/web-app/build/assets/key-square-D6pCsZi4.js.gz
new file mode 100644
index 0000000000..e7e8a98126
Binary files /dev/null and b/web-app/build/assets/key-square-D6pCsZi4.js.gz differ
diff --git a/web-app/build/assets/layers-2-BZBBcd10.js.gz b/web-app/build/assets/layers-2-BZBBcd10.js.gz
deleted file mode 100644
index c4b234263e..0000000000
Binary files a/web-app/build/assets/layers-2-BZBBcd10.js.gz and /dev/null differ
diff --git a/web-app/build/assets/layers-2-CuQsqw1I.js.gz b/web-app/build/assets/layers-2-CuQsqw1I.js.gz
new file mode 100644
index 0000000000..5634a54c86
Binary files /dev/null and b/web-app/build/assets/layers-2-CuQsqw1I.js.gz differ
diff --git a/web-app/build/assets/layers-BAlxGcwP.js.gz b/web-app/build/assets/layers-BAlxGcwP.js.gz
deleted file mode 100644
index 9e5a461837..0000000000
Binary files a/web-app/build/assets/layers-BAlxGcwP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/layers-DlkGWby6.js.gz b/web-app/build/assets/layers-DlkGWby6.js.gz
new file mode 100644
index 0000000000..ec54f2e87a
Binary files /dev/null and b/web-app/build/assets/layers-DlkGWby6.js.gz differ
diff --git a/web-app/build/assets/lib-BVN4wEer.js.gz b/web-app/build/assets/lib-BVN4wEer.js.gz
deleted file mode 100644
index e9b414d12e..0000000000
Binary files a/web-app/build/assets/lib-BVN4wEer.js.gz and /dev/null differ
diff --git a/web-app/build/assets/lib-BtWwCU9q.js.gz b/web-app/build/assets/lib-BtWwCU9q.js.gz
new file mode 100644
index 0000000000..0898d27364
Binary files /dev/null and b/web-app/build/assets/lib-BtWwCU9q.js.gz differ
diff --git a/web-app/build/assets/list-filter-CftUyU8Z.js.gz b/web-app/build/assets/list-filter-CftUyU8Z.js.gz
deleted file mode 100644
index 4862dbb045..0000000000
Binary files a/web-app/build/assets/list-filter-CftUyU8Z.js.gz and /dev/null differ
diff --git a/web-app/build/assets/list-filter-i6z8n5_Q.js.gz b/web-app/build/assets/list-filter-i6z8n5_Q.js.gz
new file mode 100644
index 0000000000..897e9577ed
Binary files /dev/null and b/web-app/build/assets/list-filter-i6z8n5_Q.js.gz differ
diff --git a/web-app/build/assets/logs-BCcYvuQU.js.gz b/web-app/build/assets/logs-BCcYvuQU.js.gz
new file mode 100644
index 0000000000..d62d881f18
Binary files /dev/null and b/web-app/build/assets/logs-BCcYvuQU.js.gz differ
diff --git a/web-app/build/assets/logs-zOC3x8DY.js.gz b/web-app/build/assets/logs-zOC3x8DY.js.gz
deleted file mode 100644
index 833ed56a06..0000000000
Binary files a/web-app/build/assets/logs-zOC3x8DY.js.gz and /dev/null differ
diff --git a/web-app/build/assets/mds-B7pDlMNU.js.gz b/web-app/build/assets/mds-B28KNzw0.js.gz
similarity index 67%
rename from web-app/build/assets/mds-B7pDlMNU.js.gz
rename to web-app/build/assets/mds-B28KNzw0.js.gz
index a069f08786..dc7e6e074d 100644
Binary files a/web-app/build/assets/mds-B7pDlMNU.js.gz and b/web-app/build/assets/mds-B28KNzw0.js.gz differ
diff --git a/web-app/build/assets/minus-C7mL7rc_.js.gz b/web-app/build/assets/minus-C7mL7rc_.js.gz
deleted file mode 100644
index e9f588d7a3..0000000000
Binary files a/web-app/build/assets/minus-C7mL7rc_.js.gz and /dev/null differ
diff --git a/web-app/build/assets/minus-CxCOHWeS.js.gz b/web-app/build/assets/minus-CxCOHWeS.js.gz
new file mode 100644
index 0000000000..88f4b93834
Binary files /dev/null and b/web-app/build/assets/minus-CxCOHWeS.js.gz differ
diff --git a/web-app/build/assets/monitor-BgDUlzZJ.js.gz b/web-app/build/assets/monitor-BgDUlzZJ.js.gz
new file mode 100644
index 0000000000..c50093e83e
Binary files /dev/null and b/web-app/build/assets/monitor-BgDUlzZJ.js.gz differ
diff --git a/web-app/build/assets/monitor-TgkeIoFw.js.gz b/web-app/build/assets/monitor-TgkeIoFw.js.gz
deleted file mode 100644
index 57011d2632..0000000000
Binary files a/web-app/build/assets/monitor-TgkeIoFw.js.gz and /dev/null differ
diff --git a/web-app/build/assets/mutationTasks-DCfDMxJ7.js.gz b/web-app/build/assets/mutationTasks-DCfDMxJ7.js.gz
new file mode 100644
index 0000000000..3994bb3354
Binary files /dev/null and b/web-app/build/assets/mutationTasks-DCfDMxJ7.js.gz differ
diff --git a/web-app/build/assets/mutationTasks-DlFXm8JK.js.gz b/web-app/build/assets/mutationTasks-DlFXm8JK.js.gz
deleted file mode 100644
index c16f10ffbf..0000000000
Binary files a/web-app/build/assets/mutationTasks-DlFXm8JK.js.gz and /dev/null differ
diff --git a/web-app/build/assets/network-AXiDKLCe.js.gz b/web-app/build/assets/network-AXiDKLCe.js.gz
deleted file mode 100644
index bab3f312b9..0000000000
Binary files a/web-app/build/assets/network-AXiDKLCe.js.gz and /dev/null differ
diff --git a/web-app/build/assets/network-BKvXcB_6.js.gz b/web-app/build/assets/network-BKvXcB_6.js.gz
new file mode 100644
index 0000000000..435b72644a
Binary files /dev/null and b/web-app/build/assets/network-BKvXcB_6.js.gz differ
diff --git a/web-app/build/assets/pencil-BBECVVzN.js.gz b/web-app/build/assets/pencil-BBECVVzN.js.gz
new file mode 100644
index 0000000000..1062b5349a
Binary files /dev/null and b/web-app/build/assets/pencil-BBECVVzN.js.gz differ
diff --git a/web-app/build/assets/pencil-Cg37lTQM.js.gz b/web-app/build/assets/pencil-Cg37lTQM.js.gz
deleted file mode 100644
index 3e5a65137b..0000000000
Binary files a/web-app/build/assets/pencil-Cg37lTQM.js.gz and /dev/null differ
diff --git a/web-app/build/assets/plus-DdpTuvy_.js.gz b/web-app/build/assets/plus-DdpTuvy_.js.gz
deleted file mode 100644
index f4384788e2..0000000000
Binary files a/web-app/build/assets/plus-DdpTuvy_.js.gz and /dev/null differ
diff --git a/web-app/build/assets/plus-WkqQYxIA.js.gz b/web-app/build/assets/plus-WkqQYxIA.js.gz
new file mode 100644
index 0000000000..5cf934fb8a
Binary files /dev/null and b/web-app/build/assets/plus-WkqQYxIA.js.gz differ
diff --git a/web-app/build/assets/prefixUsage-ByVKkjxv.js.gz b/web-app/build/assets/prefixUsage-ByVKkjxv.js.gz
new file mode 100644
index 0000000000..2f6e6f12e1
Binary files /dev/null and b/web-app/build/assets/prefixUsage-ByVKkjxv.js.gz differ
diff --git a/web-app/build/assets/prefixUsage-DPvRtJe9.js.gz b/web-app/build/assets/prefixUsage-DPvRtJe9.js.gz
deleted file mode 100644
index d143568aab..0000000000
Binary files a/web-app/build/assets/prefixUsage-DPvRtJe9.js.gz and /dev/null differ
diff --git a/web-app/build/assets/repeat-2-BpmqwLH1.js.gz b/web-app/build/assets/repeat-2-BpmqwLH1.js.gz
new file mode 100644
index 0000000000..6abfab6d37
Binary files /dev/null and b/web-app/build/assets/repeat-2-BpmqwLH1.js.gz differ
diff --git a/web-app/build/assets/repeat-2-DNmbbZSR.js.gz b/web-app/build/assets/repeat-2-DNmbbZSR.js.gz
deleted file mode 100644
index cd1d95c9b3..0000000000
Binary files a/web-app/build/assets/repeat-2-DNmbbZSR.js.gz and /dev/null differ
diff --git a/web-app/build/assets/replicationValidation-Be7R_T7d.js.gz b/web-app/build/assets/replicationValidation-Be7R_T7d.js.gz
new file mode 100644
index 0000000000..f0fbe5ac75
Binary files /dev/null and b/web-app/build/assets/replicationValidation-Be7R_T7d.js.gz differ
diff --git a/web-app/build/assets/replicationValidation-C3UwIcP0.js.gz b/web-app/build/assets/replicationValidation-C3UwIcP0.js.gz
deleted file mode 100644
index 3eca1f43e6..0000000000
Binary files a/web-app/build/assets/replicationValidation-C3UwIcP0.js.gz and /dev/null differ
diff --git a/web-app/build/assets/rotate-ccw-BLAa--Dl.js.gz b/web-app/build/assets/rotate-ccw-BLAa--Dl.js.gz
deleted file mode 100644
index c3f99a5b20..0000000000
Binary files a/web-app/build/assets/rotate-ccw-BLAa--Dl.js.gz and /dev/null differ
diff --git a/web-app/build/assets/rotate-ccw-D-xvAMq8.js.gz b/web-app/build/assets/rotate-ccw-D-xvAMq8.js.gz
new file mode 100644
index 0000000000..99c463febf
Binary files /dev/null and b/web-app/build/assets/rotate-ccw-D-xvAMq8.js.gz differ
diff --git a/web-app/build/assets/scan-search-BlI8QzCl.js.gz b/web-app/build/assets/scan-search-BlI8QzCl.js.gz
deleted file mode 100644
index ea9bb012e1..0000000000
Binary files a/web-app/build/assets/scan-search-BlI8QzCl.js.gz and /dev/null differ
diff --git a/web-app/build/assets/scan-search-Dqsy2un3.js.gz b/web-app/build/assets/scan-search-Dqsy2un3.js.gz
new file mode 100644
index 0000000000..2526e5e612
Binary files /dev/null and b/web-app/build/assets/scan-search-Dqsy2un3.js.gz differ
diff --git a/web-app/build/assets/search-C5Hl7-Uo.js.gz b/web-app/build/assets/search-C5Hl7-Uo.js.gz
deleted file mode 100644
index 7c2bb076eb..0000000000
Binary files a/web-app/build/assets/search-C5Hl7-Uo.js.gz and /dev/null differ
diff --git a/web-app/build/assets/search-CUAtdbbb.js.gz b/web-app/build/assets/search-CUAtdbbb.js.gz
new file mode 100644
index 0000000000..c69d1b42f5
Binary files /dev/null and b/web-app/build/assets/search-CUAtdbbb.js.gz differ
diff --git a/web-app/build/assets/server-C66T6PLQ.js.gz b/web-app/build/assets/server-C66T6PLQ.js.gz
new file mode 100644
index 0000000000..e30b4b89e0
Binary files /dev/null and b/web-app/build/assets/server-C66T6PLQ.js.gz differ
diff --git a/web-app/build/assets/server-CGq9PK5-.js.gz b/web-app/build/assets/server-CGq9PK5-.js.gz
deleted file mode 100644
index b7fe4e1e24..0000000000
Binary files a/web-app/build/assets/server-CGq9PK5-.js.gz and /dev/null differ
diff --git a/web-app/build/assets/settings-8EmlkJM3.js.gz b/web-app/build/assets/settings-8EmlkJM3.js.gz
deleted file mode 100644
index ec0e9f0894..0000000000
Binary files a/web-app/build/assets/settings-8EmlkJM3.js.gz and /dev/null differ
diff --git a/web-app/build/assets/settings-Dp2xMr6a.js.gz b/web-app/build/assets/settings-Dp2xMr6a.js.gz
new file mode 100644
index 0000000000..8a93994fb5
Binary files /dev/null and b/web-app/build/assets/settings-Dp2xMr6a.js.gz differ
diff --git a/web-app/build/assets/share-2-BG4atnJL.js.gz b/web-app/build/assets/share-2-BG4atnJL.js.gz
new file mode 100644
index 0000000000..85836246a2
Binary files /dev/null and b/web-app/build/assets/share-2-BG4atnJL.js.gz differ
diff --git a/web-app/build/assets/share-2-CjqMO8e6.js.gz b/web-app/build/assets/share-2-CjqMO8e6.js.gz
deleted file mode 100644
index a4a8c68ce7..0000000000
Binary files a/web-app/build/assets/share-2-CjqMO8e6.js.gz and /dev/null differ
diff --git a/web-app/build/assets/shared-BY06TgMc.js.gz b/web-app/build/assets/shared-BY06TgMc.js.gz
deleted file mode 100644
index 359f472c51..0000000000
Binary files a/web-app/build/assets/shared-BY06TgMc.js.gz and /dev/null differ
diff --git a/web-app/build/assets/shared-C9BQDZGO.js.gz b/web-app/build/assets/shared-C9BQDZGO.js.gz
new file mode 100644
index 0000000000..71b6565cca
Binary files /dev/null and b/web-app/build/assets/shared-C9BQDZGO.js.gz differ
diff --git a/web-app/build/assets/shield-DLyEUeHC.js.gz b/web-app/build/assets/shield-DLyEUeHC.js.gz
deleted file mode 100644
index 74e2ae4ccd..0000000000
Binary files a/web-app/build/assets/shield-DLyEUeHC.js.gz and /dev/null differ
diff --git a/web-app/build/assets/shield-DawbDyz0.js.gz b/web-app/build/assets/shield-DawbDyz0.js.gz
new file mode 100644
index 0000000000..38f57faf40
Binary files /dev/null and b/web-app/build/assets/shield-DawbDyz0.js.gz differ
diff --git a/web-app/build/assets/shield-check-CZJz-Uv1.js.gz b/web-app/build/assets/shield-check-CZJz-Uv1.js.gz
new file mode 100644
index 0000000000..848fb94d11
Binary files /dev/null and b/web-app/build/assets/shield-check-CZJz-Uv1.js.gz differ
diff --git a/web-app/build/assets/shield-check-q7jetnYS.js.gz b/web-app/build/assets/shield-check-q7jetnYS.js.gz
deleted file mode 100644
index be697fa734..0000000000
Binary files a/web-app/build/assets/shield-check-q7jetnYS.js.gz and /dev/null differ
diff --git a/web-app/build/assets/shield-plus-7VZ0om8z.js.gz b/web-app/build/assets/shield-plus-7VZ0om8z.js.gz
new file mode 100644
index 0000000000..fe9c98de85
Binary files /dev/null and b/web-app/build/assets/shield-plus-7VZ0om8z.js.gz differ
diff --git a/web-app/build/assets/shield-plus-CTaCwosP.js.gz b/web-app/build/assets/shield-plus-CTaCwosP.js.gz
deleted file mode 100644
index a37fb33239..0000000000
Binary files a/web-app/build/assets/shield-plus-CTaCwosP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/store-DiSrkf12.js.gz b/web-app/build/assets/store-DiSrkf12.js.gz
new file mode 100644
index 0000000000..f408b53d69
Binary files /dev/null and b/web-app/build/assets/store-DiSrkf12.js.gz differ
diff --git a/web-app/build/assets/store-HGwDj7kv.js.gz b/web-app/build/assets/store-HGwDj7kv.js.gz
deleted file mode 100644
index 77a2aee418..0000000000
Binary files a/web-app/build/assets/store-HGwDj7kv.js.gz and /dev/null differ
diff --git a/web-app/build/assets/tag-BfZcm9gO.js.gz b/web-app/build/assets/tag-BfZcm9gO.js.gz
deleted file mode 100644
index 1c0306450a..0000000000
Binary files a/web-app/build/assets/tag-BfZcm9gO.js.gz and /dev/null differ
diff --git a/web-app/build/assets/tag-CxgI2jXv.js.gz b/web-app/build/assets/tag-CxgI2jXv.js.gz
new file mode 100644
index 0000000000..af53da5580
Binary files /dev/null and b/web-app/build/assets/tag-CxgI2jXv.js.gz differ
diff --git a/web-app/build/assets/terminal-A1FFEXev.js.gz b/web-app/build/assets/terminal-A1FFEXev.js.gz
new file mode 100644
index 0000000000..1b12ec0aa7
Binary files /dev/null and b/web-app/build/assets/terminal-A1FFEXev.js.gz differ
diff --git a/web-app/build/assets/terminal-D4eplTGJ.js.gz b/web-app/build/assets/terminal-D4eplTGJ.js.gz
deleted file mode 100644
index 1f8f1698a9..0000000000
Binary files a/web-app/build/assets/terminal-D4eplTGJ.js.gz and /dev/null differ
diff --git a/web-app/build/assets/trash-CiPD8hS_.js.gz b/web-app/build/assets/trash-CiPD8hS_.js.gz
deleted file mode 100644
index 478ce7d0ce..0000000000
Binary files a/web-app/build/assets/trash-CiPD8hS_.js.gz and /dev/null differ
diff --git a/web-app/build/assets/trash-DadWYzUS.js.gz b/web-app/build/assets/trash-DadWYzUS.js.gz
new file mode 100644
index 0000000000..4caa25d3f5
Binary files /dev/null and b/web-app/build/assets/trash-DadWYzUS.js.gz differ
diff --git a/web-app/build/assets/triangle-alert-B18VOJ_y.js.gz b/web-app/build/assets/triangle-alert-B18VOJ_y.js.gz
new file mode 100644
index 0000000000..b6101f9bf4
Binary files /dev/null and b/web-app/build/assets/triangle-alert-B18VOJ_y.js.gz differ
diff --git a/web-app/build/assets/triangle-alert-BYoxbxy1.js.gz b/web-app/build/assets/triangle-alert-BYoxbxy1.js.gz
deleted file mode 100644
index b1a0f2770a..0000000000
Binary files a/web-app/build/assets/triangle-alert-BYoxbxy1.js.gz and /dev/null differ
diff --git a/web-app/build/assets/typography-JApMsY3F.js.gz b/web-app/build/assets/typography-JApMsY3F.js.gz
deleted file mode 100644
index 157577ebd1..0000000000
Binary files a/web-app/build/assets/typography-JApMsY3F.js.gz and /dev/null differ
diff --git a/web-app/build/assets/typography-v-iIUha0.js.gz b/web-app/build/assets/typography-v-iIUha0.js.gz
new file mode 100644
index 0000000000..f1686c1a06
Binary files /dev/null and b/web-app/build/assets/typography-v-iIUha0.js.gz differ
diff --git a/web-app/build/assets/upload-CYTC0OUz.js.gz b/web-app/build/assets/upload-CYTC0OUz.js.gz
deleted file mode 100644
index b50fa1f863..0000000000
Binary files a/web-app/build/assets/upload-CYTC0OUz.js.gz and /dev/null differ
diff --git a/web-app/build/assets/upload-CgL_Y_yW.js.gz b/web-app/build/assets/upload-CgL_Y_yW.js.gz
new file mode 100644
index 0000000000..1754a4e87e
Binary files /dev/null and b/web-app/build/assets/upload-CgL_Y_yW.js.gz differ
diff --git a/web-app/build/assets/useBoundedTable-HR1wP3Wp.js.gz b/web-app/build/assets/useBoundedTable-HR1wP3Wp.js.gz
new file mode 100644
index 0000000000..ef73480a5b
Binary files /dev/null and b/web-app/build/assets/useBoundedTable-HR1wP3Wp.js.gz differ
diff --git a/web-app/build/assets/useBoundedTable-d1TyWCiV.js.gz b/web-app/build/assets/useBoundedTable-d1TyWCiV.js.gz
deleted file mode 100644
index e1281c932d..0000000000
Binary files a/web-app/build/assets/useBoundedTable-d1TyWCiV.js.gz and /dev/null differ
diff --git a/web-app/build/assets/useTableLayout-DvZh3nze.js.gz b/web-app/build/assets/useTableLayout-DvZh3nze.js.gz
new file mode 100644
index 0000000000..6a6ba237a1
Binary files /dev/null and b/web-app/build/assets/useTableLayout-DvZh3nze.js.gz differ
diff --git a/web-app/build/assets/useTableLayout-ToMNXs38.js.gz b/web-app/build/assets/useTableLayout-ToMNXs38.js.gz
deleted file mode 100644
index 3fef0f164d..0000000000
Binary files a/web-app/build/assets/useTableLayout-ToMNXs38.js.gz and /dev/null differ
diff --git a/web-app/build/assets/user-round-plus-BlabC7d5.js.gz b/web-app/build/assets/user-round-plus-BlabC7d5.js.gz
new file mode 100644
index 0000000000..ce7a9f2da2
Binary files /dev/null and b/web-app/build/assets/user-round-plus-BlabC7d5.js.gz differ
diff --git a/web-app/build/assets/user-round-plus-DcTzuK_1.js.gz b/web-app/build/assets/user-round-plus-DcTzuK_1.js.gz
deleted file mode 100644
index 50a73b3d06..0000000000
Binary files a/web-app/build/assets/user-round-plus-DcTzuK_1.js.gz and /dev/null differ
diff --git a/web-app/build/assets/users-CXn-jf3L.js.gz b/web-app/build/assets/users-CXn-jf3L.js.gz
new file mode 100644
index 0000000000..6f7e508b9f
Binary files /dev/null and b/web-app/build/assets/users-CXn-jf3L.js.gz differ
diff --git a/web-app/build/assets/users-DqJf2nYi.js.gz b/web-app/build/assets/users-DqJf2nYi.js.gz
deleted file mode 100644
index 8c1e20007c..0000000000
Binary files a/web-app/build/assets/users-DqJf2nYi.js.gz and /dev/null differ
diff --git a/web-app/build/assets/users-round-B0CKMX6y.js.gz b/web-app/build/assets/users-round-B0CKMX6y.js.gz
deleted file mode 100644
index 6bf54370fd..0000000000
Binary files a/web-app/build/assets/users-round-B0CKMX6y.js.gz and /dev/null differ
diff --git a/web-app/build/assets/users-round-DK3tUyL5.js.gz b/web-app/build/assets/users-round-DK3tUyL5.js.gz
new file mode 100644
index 0000000000..88ad480956
Binary files /dev/null and b/web-app/build/assets/users-round-DK3tUyL5.js.gz differ
diff --git a/web-app/build/assets/utils--e3qTLdP.js.gz b/web-app/build/assets/utils--e3qTLdP.js.gz
deleted file mode 100644
index 081e27bd27..0000000000
Binary files a/web-app/build/assets/utils--e3qTLdP.js.gz and /dev/null differ
diff --git a/web-app/build/assets/utils-Bu1hCuTq.js.gz b/web-app/build/assets/utils-Bu1hCuTq.js.gz
new file mode 100644
index 0000000000..73502e3de4
Binary files /dev/null and b/web-app/build/assets/utils-Bu1hCuTq.js.gz differ
diff --git a/web-app/build/assets/utils-CylzT_qV.js.gz b/web-app/build/assets/utils-CylzT_qV.js.gz
deleted file mode 100644
index 3f80500e0b..0000000000
Binary files a/web-app/build/assets/utils-CylzT_qV.js.gz and /dev/null differ
diff --git a/web-app/build/assets/utils-DH-n9Rq7.js.gz b/web-app/build/assets/utils-DH-n9Rq7.js.gz
new file mode 100644
index 0000000000..cbdcff90bd
Binary files /dev/null and b/web-app/build/assets/utils-DH-n9Rq7.js.gz differ
diff --git a/web-app/build/assets/webhook-DmZDdniN.js.gz b/web-app/build/assets/webhook-DmZDdniN.js.gz
deleted file mode 100644
index d2f63d47f3..0000000000
Binary files a/web-app/build/assets/webhook-DmZDdniN.js.gz and /dev/null differ
diff --git a/web-app/build/assets/webhook-NLLBNkNW.js.gz b/web-app/build/assets/webhook-NLLBNkNW.js.gz
new file mode 100644
index 0000000000..9bed2cc6cc
Binary files /dev/null and b/web-app/build/assets/webhook-NLLBNkNW.js.gz differ
diff --git a/web-app/build/index.html b/web-app/build/index.html
index 0c1cebf5b9..aebed06bca 100644
--- a/web-app/build/index.html
+++ b/web-app/build/index.html
@@ -29,7 +29,7 @@
SILO Console
-
+
@@ -43,10 +43,10 @@
-
-
+
+
-
+
diff --git a/web-app/e2e/bucket-usage-refresh.unit.ts b/web-app/e2e/bucket-usage-refresh.unit.ts
index 4a1eab6ecd..2e240ed37e 100644
--- a/web-app/e2e/bucket-usage-refresh.unit.ts
+++ b/web-app/e2e/bucket-usage-refresh.unit.ts
@@ -235,6 +235,51 @@ test("without the persistent service, Refresh calculates current totals and neve
expect(scans).toBe(1);
});
+test("without the persistent service, a busy scanner (429) is retried instead of failing the bucket", async () => {
+ let scans = 0;
+ await page.route("**/api/v1/bucket-usage/*", (route) =>
+ route.fulfill({
+ json: { enabled: false, poll_after_ms: 250, buckets: [] },
+ }),
+ );
+ await page.route(
+ "**/api/v1/buckets/fixture-bucket/prefix-usage*",
+ (route) => {
+ scans++;
+ if (scans === 1)
+ return route.fulfill({
+ status: 429,
+ json: {
+ code: 429,
+ message: "Other size calculations are running. Try again shortly.",
+ },
+ });
+ return route.fulfill({
+ contentType: "application/x-ndjson",
+ body:
+ JSON.stringify({
+ ...snapshot,
+ bucket: "fixture-bucket",
+ prefix: "",
+ mode: "versions",
+ status: "complete",
+ folders: [],
+ }) + "\n",
+ });
+ },
+ );
+ await page.goto(base);
+ const row = page.getByRole("region", { name: "fixture-bucket" });
+ await expect(page.getByRole("status")).toContainText(
+ "Automatic updates are not configured",
+ );
+ await page.getByRole("button", { name: "Refresh", exact: true }).click();
+ await expect(row).toContainText("1.5 KiB");
+ await expect(row).toContainText("Updated");
+ await expect(row).not.toContainText("Update failed");
+ expect(scans).toBe(2);
+});
+
for (const outcome of [
"outage",
"limited",
diff --git a/web-app/e2e/css-logical-properties.unit.ts b/web-app/e2e/css-logical-properties.unit.ts
new file mode 100644
index 0000000000..a39cd28c21
--- /dev/null
+++ b/web-app/e2e/css-logical-properties.unit.ts
@@ -0,0 +1,38 @@
+// Copyright (c) 2026 Pigsty
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// RTL support uses logical CSS properties. In styled-components template
+// literals they must be hyphenated (inset-inline-end); a run-together name
+// such as "insetinlineend" is an unknown property that browsers drop.
+
+import { expect, test } from "@playwright/test";
+import { readdirSync, readFileSync } from "node:fs";
+import { join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const roots = ["../src/", "../packages/mds/src/"].map((relative) =>
+ fileURLToPath(new URL(relative, import.meta.url)),
+);
+const sources = (dir: string): string[] =>
+ readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
+ const path = join(dir, entry.name);
+ if (entry.isDirectory()) return sources(path);
+ return /\.(ts|tsx)$/.test(entry.name) ? [path] : [];
+ });
+
+test("logical CSS properties in template literals are hyphenated", () => {
+ const malformed =
+ /\b(?:inset|margin|padding|border)(?:inline|block)(?:start|end)?\s*:/;
+ const offenders = roots.flatMap(sources).flatMap((path) =>
+ readFileSync(path, "utf8")
+ .split("\n")
+ .map((line, index) => [path, index + 1, line] as const)
+ .filter(([, , line]) => malformed.test(line))
+ .map(([path, line]) => `${path}:${line}`),
+ );
+ expect(offenders).toEqual([]);
+});
diff --git a/web-app/e2e/languages-browser.unit.ts b/web-app/e2e/languages-browser.unit.ts
index a9c4dd4749..041c8a31d2 100644
--- a/web-app/e2e/languages-browser.unit.ts
+++ b/web-app/e2e/languages-browser.unit.ts
@@ -222,6 +222,39 @@ test("all languages switch offline without discarding form input", async () => {
await page.close();
}
});
+test("Arabic lays out protocol fields' icons and overlay button LTR", async () => {
+ const page = await browser.newPage({
+ viewport: { width: 1280, height: 720 },
+ });
+ try {
+ await setup(page, "ar");
+ await page.goto(`${base}/login`);
+ const input = page.locator("#secretKey");
+ await input.fill("secret-value");
+ const layout = await input.evaluate((el) => {
+ const field = el.getBoundingClientRect();
+ const style = getComputedStyle(el);
+ const button = el
+ .closest(".textBoxContainer")!
+ .querySelector(".overlayAction")!
+ .getBoundingClientRect();
+ return {
+ direction: style.direction,
+ textEnd: field.right - parseFloat(style.paddingRight),
+ buttonLeft: button.left,
+ buttonRight: button.right,
+ fieldRight: field.right,
+ };
+ });
+ expect(layout.direction).toBe("ltr");
+ // The reveal button stays inside the padding reserved after the text.
+ expect(layout.buttonLeft).toBeGreaterThanOrEqual(layout.textEnd);
+ expect(layout.buttonRight).toBeLessThanOrEqual(layout.fieldRight);
+ } finally {
+ await page.close();
+ }
+});
+
test("Arabic keeps protocol payloads LTR and does not translate log messages", async () => {
const page = await browser.newPage({ viewport: { width: 350, height: 900 } });
try {
diff --git a/web-app/e2e/secure-context.unit.ts b/web-app/e2e/secure-context.unit.ts
new file mode 100644
index 0000000000..5de917baff
--- /dev/null
+++ b/web-app/e2e/secure-context.unit.ts
@@ -0,0 +1,53 @@
+// Copyright (c) 2026 Pigsty
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Embedded Console is commonly served over plain HTTP (http://:9001), where
+// browsers omit secure-context APIs. A call to one of them throws before the
+// surrounding request starts, so features silently do nothing. The share-link
+// expiry must also survive browsers whose locale formats digits natively.
+
+import { expect, test } from "@playwright/test";
+import { readdirSync, readFileSync } from "node:fs";
+import { join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { Settings } from "luxon";
+import { shareExpiryTimestamp } from "../src/screens/Console/Common/FormComponents/DaysSelector/shareExpiry";
+
+const roots = ["../src/", "../packages/mds/src/"].map((relative) =>
+ fileURLToPath(new URL(relative, import.meta.url)),
+);
+const sources = (dir: string): string[] =>
+ readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
+ const path = join(dir, entry.name);
+ if (entry.isDirectory()) return sources(path);
+ return /\.(ts|tsx)$/.test(entry.name) ? [path] : [];
+ });
+
+test("source avoids APIs that plain-HTTP pages do not expose", () => {
+ const offenders = roots
+ .flatMap(sources)
+ .filter((path) =>
+ /crypto\.randomUUID|crypto\.subtle|navigator\.clipboard/.test(
+ readFileSync(path, "utf8"),
+ ),
+ );
+ expect(offenders).toEqual([]);
+});
+
+test("share expiry stays parseable under native-digit locales", () => {
+ const previous = Settings.defaultLocale;
+ try {
+ for (const locale of ["en-US", "ar", "ar-EG", "ar-SA", "fa", "zh-CN"]) {
+ Settings.defaultLocale = locale;
+ const value = shareExpiryTimestamp("2026-11-16T10:20:30.456+08:00");
+ expect(value, locale).toMatch(/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d$/);
+ expect(Number.isNaN(new Date(`${value}`).getTime()), locale).toBe(false);
+ }
+ } finally {
+ Settings.defaultLocale = previous;
+ }
+});
diff --git a/web-app/e2e/visual-polish.unit.ts b/web-app/e2e/visual-polish.unit.ts
index de56c3435b..5e8ea9b02c 100644
--- a/web-app/e2e/visual-polish.unit.ts
+++ b/web-app/e2e/visual-polish.unit.ts
@@ -192,6 +192,23 @@ test("actual Inspect guards pristine submit and preserves encoded download and d
page.getByRole("dialog", { name: "Inspect Decryption Key" }),
).toBeVisible();
await expect(page.locator("#inspect-dec-key")).toHaveValue("test-key");
+ // polish.test is plain HTTP, where the async Clipboard API is absent. The
+ // copy action must still work, and stay the field's only overlay button.
+ const copyKey = page.locator(
+ ".textBoxContainer:has(#inspect-dec-key) .overlayAction button",
+ );
+ await expect(copyKey).toHaveCount(1);
+ const pageErrors: string[] = [];
+ page.on("pageerror", (error) => pageErrors.push(error.message));
+ await copyKey.click();
+ expect(pageErrors).toEqual([]);
+ await expect(page.locator("#inspect-dec-key")).toHaveAttribute(
+ "type",
+ "password",
+ );
+ // Leave the button so its tooltip does not take the first Escape.
+ await page.mouse.move(0, 0);
+ await page.locator("#inspect-dec-key").focus();
await page.keyboard.press("Escape");
await expect(page.locator("#inspect_volume")).toHaveValue("");
await expect(page.locator('[aria-invalid="true"]')).toHaveCount(0);
diff --git a/web-app/package.json b/web-app/package.json
index c0c2a35d35..249c3d134d 100644
--- a/web-app/package.json
+++ b/web-app/package.json
@@ -7,6 +7,7 @@
"type": "module",
"dependencies": {
"@reduxjs/toolkit": "^1.9.7",
+ "copy-to-clipboard": "^3.3.3",
"kbar": "^0.1.0-beta.48",
"lodash": "^4.18.1",
"luxon": "^3.7.2",
diff --git a/web-app/packages/mds/src/components/InputBox/InputBox.tsx b/web-app/packages/mds/src/components/InputBox/InputBox.tsx
index ff066eff3b..57d40eba8a 100644
--- a/web-app/packages/mds/src/components/InputBox/InputBox.tsx
+++ b/web-app/packages/mds/src/components/InputBox/InputBox.tsx
@@ -27,6 +27,9 @@ import VisibilityOffIcon from "../Icons/VisibilityOffIcon";
import VisibilityOnIcon from "../Icons/VisibilityOnIcon";
import Box from "../Box/Box";
+// Matches the forced-LTR input types in web-app/src/index.css.
+const ltrFieldTypes = ["password", "url", "email", "number"];
+
const InputBase = styled.input`
${fieldChrome}
padding-inline-start: ${({ startIcon }) =>
@@ -137,7 +140,13 @@ const InputBox = React.forwardRef(
)}
-
+ {/* Protocol fields (passwords, URLs, e-mail, numbers) stay LTR in RTL
+ layouts. Lay out their icons and overlay button in the same
+ direction, or the button sits over the start of the text. */}
+
{startIcon && {startIcon}}
(
{overlayObject}
)}
{error && (
-
+
{error}
)}
diff --git a/web-app/src/screens/Console/Buckets/ListBuckets/useBucketUsage.tsx b/web-app/src/screens/Console/Buckets/ListBuckets/useBucketUsage.tsx
index 25f20561ae..68f0963c4a 100644
--- a/web-app/src/screens/Console/Buckets/ListBuckets/useBucketUsage.tsx
+++ b/web-app/src/screens/Console/Buckets/ListBuckets/useBucketUsage.tsx
@@ -37,6 +37,24 @@ export const BucketUsageProvider = UsageContext.Provider;
export const useBucketUsageView = () => useContext(UsageContext);
const usagePending = (row: BucketUsageRecord) =>
row.state === "queued" || row.state === "running";
+// A busy server (429) is retried with jittered, capped backoff for about
+// half a minute before the bucket is reported as failed.
+const usageBusyAttempts = 8;
+const usageBusyWait = (attempt: number, signal: AbortSignal) =>
+ new Promise((resolve) => {
+ if (signal.aborted) return resolve(false);
+ const cap = Math.min(8000, 1000 * 2 ** (attempt - 1));
+ const timer = setTimeout(finish, cap / 2 + (Math.random() * cap) / 2);
+ function finish() {
+ signal.removeEventListener("abort", abort);
+ resolve(!signal.aborted);
+ }
+ function abort() {
+ clearTimeout(timer);
+ finish();
+ }
+ signal.addEventListener("abort", abort, { once: true });
+ });
const usageStates: readonly string[] = [
"not_scanned",
"queued",
@@ -186,70 +204,83 @@ export function useBucketUsage(names: string[]): UsageView {
};
});
publish();
- try {
- let completed: BucketUsageSnapshot | undefined;
- const response = await api.buckets.prefixUsage(
- bucket,
- { prefix: "", versions, folders: false },
- { signal: controller.signal },
- );
- await readPrefixUsageStream(
- response,
- { bucket, prefix: "", mode: versions ? "versions" : "current" },
- (frame) => {
- const totals = {
- ...frame.totals,
- ...(versions
- ? {}
- : {
- retained_bytes: undefined,
- retained_versions: undefined,
- delete_markers: undefined,
- }),
- };
- const snapshot = {
- totals,
- scanned_entries: frame.scanned_entries,
- started_at: frame.started_at,
- updated_at: frame.updated_at,
- };
+ for (let attempt = 1; ; attempt++) {
+ try {
+ let completed: BucketUsageSnapshot | undefined;
+ const response = await api.buckets.prefixUsage(
+ bucket,
+ { prefix: "", versions, folders: false },
+ { signal: controller.signal },
+ );
+ await readPrefixUsageStream(
+ response,
+ { bucket, prefix: "", mode: versions ? "versions" : "current" },
+ (frame) => {
+ const totals = {
+ ...frame.totals,
+ ...(versions
+ ? {}
+ : {
+ retained_bytes: undefined,
+ retained_versions: undefined,
+ delete_markers: undefined,
+ }),
+ };
+ const snapshot = {
+ totals,
+ scanned_entries: frame.scanned_entries,
+ started_at: frame.started_at,
+ updated_at: frame.updated_at,
+ };
+ row = {
+ ...row,
+ state:
+ frame.status === "scanning" || frame.status === "complete"
+ ? "running"
+ : frame.status === "failed"
+ ? "error"
+ : frame.status,
+ progress: snapshot,
+ };
+ if (frame.status === "complete") completed = snapshot;
+ publish();
+ },
+ );
+ // Only replace last-good after the entire stream validates. A
+ // malformed trailer or disconnect must not publish new totals.
+ if (completed) {
row = {
...row,
- state:
- frame.status === "scanning" || frame.status === "complete"
- ? "running"
- : frame.status === "failed"
- ? "error"
- : frame.status,
- progress: snapshot,
+ state: "complete",
+ last_good: completed,
+ last_completed_at: completed.updated_at,
};
- if (frame.status === "complete") completed = snapshot;
publish();
- },
- );
- // Only replace last-good after the entire stream validates. A
- // malformed trailer or disconnect must not publish new totals.
- if (completed) {
+ }
+ break;
+ } catch (error) {
+ const status = error instanceof Response ? error.status : 0;
+ // Prefix-usage slots are shared by every Console session, so a
+ // busy server answers 429. Wait and retry the same bucket
+ // instead of reporting it as failed.
+ if (
+ status === 429 &&
+ attempt < usageBusyAttempts &&
+ (await usageBusyWait(attempt, controller.signal))
+ )
+ continue;
row = {
...row,
- state: "complete",
- last_good: completed,
- last_completed_at: completed.updated_at,
+ state:
+ status === 401 || status === 403
+ ? "forbidden"
+ : status === 404
+ ? "missing"
+ : "error",
};
publish();
+ break;
}
- } catch (error) {
- const status = error instanceof Response ? error.status : 0;
- row = {
- ...row,
- state:
- status === 401 || status === 403
- ? "forbidden"
- : status === 404
- ? "missing"
- : "error",
- };
- publish();
}
}
};
diff --git a/web-app/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx b/web-app/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx
index dbd32a6dae..3be6102842 100644
--- a/web-app/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx
+++ b/web-app/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx
@@ -16,6 +16,7 @@
import React, { useEffect, useState } from "react";
import { DateTime } from "luxon";
+import { shareExpiryTimestamp } from "./shareExpiry";
import { Box, InputBox, InputLabel, LinkIcon } from "mds";
import { formatText, useT } from "i18n";
@@ -78,11 +79,10 @@ const DaysSelector = ({
}, [selectedDays, selectedHours, selectedMinutes]);
useEffect(() => {
- if (validDate && dateSelected) {
- const formattedDate = DateTime.fromISO(dateSelected).toFormat(
- "yyyy-MM-dd HH:mm:ss",
- );
- onChange(formattedDate.split(" ").join("T"), true);
+ const formattedDate =
+ validDate && dateSelected ? shareExpiryTimestamp(dateSelected) : null;
+ if (formattedDate) {
+ onChange(formattedDate, true);
} else {
onChange("0000-00-00", false);
}
diff --git a/web-app/src/screens/Console/Common/FormComponents/DaysSelector/shareExpiry.ts b/web-app/src/screens/Console/Common/FormComponents/DaysSelector/shareExpiry.ts
new file mode 100644
index 0000000000..af885c7516
--- /dev/null
+++ b/web-app/src/screens/Console/Common/FormComponents/DaysSelector/shareExpiry.ts
@@ -0,0 +1,16 @@
+// Copyright (c) 2026 Pigsty
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+import { DateTime } from "luxon";
+
+// Returns a local timestamp ("yyyy-MM-ddTHH:mm:ss") that `new Date()` parses.
+// Luxon's toFormat follows the browser locale and emits Arabic-Indic or
+// Persian digits for ar-EG, ar-SA and fa, so use its technical ISO output.
+export const shareExpiryTimestamp = (iso: string): string | null =>
+ DateTime.fromISO(iso)
+ .startOf("second")
+ .toISO({ includeOffset: false, suppressMilliseconds: true });
diff --git a/web-app/src/screens/Console/Common/ObjectManager/ObjectManagerButton.tsx b/web-app/src/screens/Console/Common/ObjectManager/ObjectManagerButton.tsx
index f29cf7e455..18f3623972 100644
--- a/web-app/src/screens/Console/Common/ObjectManager/ObjectManagerButton.tsx
+++ b/web-app/src/screens/Console/Common/ObjectManager/ObjectManagerButton.tsx
@@ -26,7 +26,7 @@ import { activeTask } from "./taskModel";
const Badge = styled.span`
position: absolute;
- insetinlineend: -1px;
+ inset-inline-end: -1px;
top: -1px;
border-radius: 10px;
padding: 0 4px;
diff --git a/web-app/src/screens/Console/ObjectBrowser/usePrefixUsage.ts b/web-app/src/screens/Console/ObjectBrowser/usePrefixUsage.ts
index 898f7592c7..cb72b0f89c 100644
--- a/web-app/src/screens/Console/ObjectBrowser/usePrefixUsage.ts
+++ b/web-app/src/screens/Console/ObjectBrowser/usePrefixUsage.ts
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { api } from "api";
import type { PrefixUsageFrame } from "api/consoleApi";
import { readPrefixUsageStream } from "./prefixUsage";
-import { registerTaskControl, removeTrace } from "./transferManager";
+import { makeid, registerTaskControl, removeTrace } from "./transferManager";
import { useTaskSession } from "../Common/ObjectManager/useTaskSession";
type Scan = {
@@ -40,7 +40,9 @@ export function usePrefixUsage(
const request = ++serial.current;
const controller = new AbortController();
active.current = controller;
- const task = `prefix-usage-${crypto.randomUUID()}`;
+ // randomUUID exists only in secure contexts; embedded Console is
+ // often served over plain HTTP, so use the shared task-ID helper.
+ const task = `prefix-usage-${makeid(16)}`;
registerTaskControl(task, { cancel: () => controller.abort() });
const current = () => serial.current === request && isCurrentSession();
setScan({ owner, prefix: target, running: true });
diff --git a/web-app/src/screens/Console/Tools/KeyRevealer.tsx b/web-app/src/screens/Console/Tools/KeyRevealer.tsx
index 8bae2413c3..37bc4f2ae2 100644
--- a/web-app/src/screens/Console/Tools/KeyRevealer.tsx
+++ b/web-app/src/screens/Console/Tools/KeyRevealer.tsx
@@ -15,6 +15,7 @@
// along with this program. If not, see .
import React, { useState } from "react";
+import copy from "copy-to-clipboard";
import { Button, CopyIcon, InputBox, Box, breakPoints } from "mds";
import { useT } from "i18n";
@@ -44,7 +45,9 @@ const KeyRevealer = ({ value }: { value: string }) => {
value={value}
overlayIcon={}
readOnly={true}
- overlayAction={() => navigator.clipboard.writeText(value)}
+ // The async Clipboard API exists only in secure contexts; Console is
+ // often served over plain HTTP, so use the execCommand fallback.
+ overlayAction={() => copy(value)}
/>