From 6eb81141eee49e322c9331ab5e263fecd3cbe938 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de>
Date: Wed, 24 Apr 2024 09:20:50 +0200
Subject: [PATCH] Resolve "Show bucket limits"

---
 Dockerfile                                    |   2 +-
 package-lock.json                             | 397 +++++++++---------
 src/App.vue                                   |   2 +-
 src/client/auth/index.ts                      |   2 +-
 src/client/auth/models/OIDCProvider.ts        |   4 +-
 src/client/s3proxy/index.ts                   |   2 +-
 src/client/s3proxy/models/BucketOut.ts        |  13 +-
 src/client/s3proxy/models/BucketSizeLimits.ts |  15 +
 src/client/s3proxy/models/Constraint.ts       |  11 -
 src/client/s3proxy/services/BucketService.ts  |  36 +-
 src/components/AppHeader.vue                  |   7 +
 .../admin/UpdateBucketLimitsModal.vue         | 236 +++++++++++
 src/components/modals/SearchUserModal.vue     |   4 +-
 .../object-storage/BucketLimitProgressBar.vue |  48 +++
 .../object-storage/BucketListItem.vue         |  38 +-
 .../modals/BucketDetailModal.vue              |  25 +-
 .../object-storage/modals/CopyObjectModal.vue |  27 +-
 .../modals/CreateFolderModal.vue              |   6 +-
 .../modals/UploadObjectModal.vue              |  70 +--
 .../ParameterSchemaFormComponent.vue          |   2 +-
 src/router/adminRoutes.ts                     |   9 +
 src/stores/buckets.ts                         |  85 ++--
 src/stores/s3objects.ts                       |  24 +-
 src/views/admin/AdminBucketsView.vue          | 216 ++++++++++
 src/views/object-storage/BucketView.vue       |   2 +-
 src/views/object-storage/BucketsView.vue      |   3 +-
 26 files changed, 953 insertions(+), 333 deletions(-)
 create mode 100644 src/client/s3proxy/models/BucketSizeLimits.ts
 delete mode 100644 src/client/s3proxy/models/Constraint.ts
 create mode 100644 src/components/admin/UpdateBucketLimitsModal.vue
 create mode 100644 src/components/object-storage/BucketLimitProgressBar.vue
 create mode 100644 src/views/admin/AdminBucketsView.vue

diff --git a/Dockerfile b/Dockerfile
index 3293d59..d640ca8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,7 +10,7 @@ RUN npm run build-only
 # production stage
 FROM nginx:stable-alpine as production-stage
 EXPOSE 80
-HEALTHCHECK --interval=10s --timeout=2s CMD curl --head -f http://localhost || exit 1
+HEALTHCHECK --interval=5s --timeout=2s CMD curl --head -f http://localhost || exit 1
 COPY nginx.conf /etc/nginx/conf.d/default.conf
 COPY --from=build-stage /app/dist /usr/share/nginx/html
 COPY --from=build-stage /app/src/assets/env.template.js /tmp
diff --git a/package-lock.json b/package-lock.json
index 4a18836..dd0ffb9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -70,9 +70,9 @@
       }
     },
     "node_modules/@apidevtools/json-schema-ref-parser": {
-      "version": "11.5.5",
-      "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.5.5.tgz",
-      "integrity": "sha512-hv/aXDILyroHioVW27etFMV+IX6FyNn41YwbeGIAt5h/7fUTQvHI5w3ols8qYAT8aQt3kzexq5ZwxFDxNHIhdQ==",
+      "version": "11.6.0",
+      "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.0.tgz",
+      "integrity": "sha512-I+d5/XrazqY86/kGsmjVercjjJ+w6MVXJj7vnHfUgXzaoLJAl0/tPk2WXVpHUeRqHqyJ6AGkXBqx6Dc3wJkrCQ==",
       "dev": true,
       "dependencies": {
         "@jsdevtools/ono": "^7.1.3",
@@ -1575,9 +1575,9 @@
       }
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz",
-      "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz",
+      "integrity": "sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==",
       "cpu": [
         "arm"
       ],
@@ -1588,9 +1588,9 @@
       ]
     },
     "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz",
-      "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz",
+      "integrity": "sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==",
       "cpu": [
         "arm64"
       ],
@@ -1601,9 +1601,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz",
-      "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz",
+      "integrity": "sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==",
       "cpu": [
         "arm64"
       ],
@@ -1614,9 +1614,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz",
-      "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz",
+      "integrity": "sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==",
       "cpu": [
         "x64"
       ],
@@ -1627,9 +1627,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz",
-      "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz",
+      "integrity": "sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==",
       "cpu": [
         "arm"
       ],
@@ -1640,9 +1640,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz",
-      "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz",
+      "integrity": "sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==",
       "cpu": [
         "arm"
       ],
@@ -1653,9 +1653,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz",
-      "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz",
+      "integrity": "sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==",
       "cpu": [
         "arm64"
       ],
@@ -1666,9 +1666,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz",
-      "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz",
+      "integrity": "sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==",
       "cpu": [
         "arm64"
       ],
@@ -1679,9 +1679,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz",
-      "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz",
+      "integrity": "sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==",
       "cpu": [
         "ppc64"
       ],
@@ -1692,9 +1692,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz",
-      "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz",
+      "integrity": "sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==",
       "cpu": [
         "riscv64"
       ],
@@ -1705,9 +1705,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz",
-      "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz",
+      "integrity": "sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==",
       "cpu": [
         "s390x"
       ],
@@ -1718,9 +1718,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz",
-      "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz",
+      "integrity": "sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==",
       "cpu": [
         "x64"
       ],
@@ -1731,9 +1731,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz",
-      "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz",
+      "integrity": "sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==",
       "cpu": [
         "x64"
       ],
@@ -1744,9 +1744,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz",
-      "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz",
+      "integrity": "sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==",
       "cpu": [
         "arm64"
       ],
@@ -1757,9 +1757,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz",
-      "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz",
+      "integrity": "sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==",
       "cpu": [
         "ia32"
       ],
@@ -1770,9 +1770,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz",
-      "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz",
+      "integrity": "sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==",
       "cpu": [
         "x64"
       ],
@@ -2495,16 +2495,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz",
-      "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz",
+      "integrity": "sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "7.7.0",
-        "@typescript-eslint/type-utils": "7.7.0",
-        "@typescript-eslint/utils": "7.7.0",
-        "@typescript-eslint/visitor-keys": "7.7.0",
+        "@typescript-eslint/scope-manager": "7.7.1",
+        "@typescript-eslint/type-utils": "7.7.1",
+        "@typescript-eslint/utils": "7.7.1",
+        "@typescript-eslint/visitor-keys": "7.7.1",
         "debug": "^4.3.4",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
@@ -2530,15 +2530,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz",
-      "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.1.tgz",
+      "integrity": "sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "7.7.0",
-        "@typescript-eslint/types": "7.7.0",
-        "@typescript-eslint/typescript-estree": "7.7.0",
-        "@typescript-eslint/visitor-keys": "7.7.0",
+        "@typescript-eslint/scope-manager": "7.7.1",
+        "@typescript-eslint/types": "7.7.1",
+        "@typescript-eslint/typescript-estree": "7.7.1",
+        "@typescript-eslint/visitor-keys": "7.7.1",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -2558,13 +2558,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz",
-      "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz",
+      "integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.7.0",
-        "@typescript-eslint/visitor-keys": "7.7.0"
+        "@typescript-eslint/types": "7.7.1",
+        "@typescript-eslint/visitor-keys": "7.7.1"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -2575,13 +2575,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz",
-      "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz",
+      "integrity": "sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "7.7.0",
-        "@typescript-eslint/utils": "7.7.0",
+        "@typescript-eslint/typescript-estree": "7.7.1",
+        "@typescript-eslint/utils": "7.7.1",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -2602,9 +2602,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz",
-      "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz",
+      "integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -2615,13 +2615,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz",
-      "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz",
+      "integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.7.0",
-        "@typescript-eslint/visitor-keys": "7.7.0",
+        "@typescript-eslint/types": "7.7.1",
+        "@typescript-eslint/visitor-keys": "7.7.1",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -2643,17 +2643,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz",
-      "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz",
+      "integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
         "@types/json-schema": "^7.0.15",
         "@types/semver": "^7.5.8",
-        "@typescript-eslint/scope-manager": "7.7.0",
-        "@typescript-eslint/types": "7.7.0",
-        "@typescript-eslint/typescript-estree": "7.7.0",
+        "@typescript-eslint/scope-manager": "7.7.1",
+        "@typescript-eslint/types": "7.7.1",
+        "@typescript-eslint/typescript-estree": "7.7.1",
         "semver": "^7.6.0"
       },
       "engines": {
@@ -2668,12 +2668,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz",
-      "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz",
+      "integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.7.0",
+        "@typescript-eslint/types": "7.7.1",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -2704,40 +2704,40 @@
       }
     },
     "node_modules/@volar/language-core": {
-      "version": "2.2.0-alpha.8",
-      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.2.0-alpha.8.tgz",
-      "integrity": "sha512-Ew1Iw7/RIRNuDLn60fWJdOLApAlfTVPxbPiSLzc434PReC9kleYtaa//Wo2WlN1oiRqneW0pWQQV0CwYqaimLQ==",
+      "version": "2.2.0-alpha.10",
+      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.2.0-alpha.10.tgz",
+      "integrity": "sha512-njVJLtpu0zMvDaEk7K5q4BRpOgbyEUljU++un9TfJoJNhxG0z/hWwpwgTRImO42EKvwIxF3XUzeMk+qatAFy7Q==",
       "dev": true,
       "dependencies": {
-        "@volar/source-map": "2.2.0-alpha.8"
+        "@volar/source-map": "2.2.0-alpha.10"
       }
     },
     "node_modules/@volar/source-map": {
-      "version": "2.2.0-alpha.8",
-      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.2.0-alpha.8.tgz",
-      "integrity": "sha512-E1ZVmXFJ5DU4fWDcWHzi8OLqqReqIDwhXvIMhVdk6+VipfMVv4SkryXu7/rs4GA/GsebcRyJdaSkKBB3OAkIcA==",
+      "version": "2.2.0-alpha.10",
+      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.2.0-alpha.10.tgz",
+      "integrity": "sha512-nrdWApVkP5cksAnDEyy1JD9rKdwOJsEq1B+seWO4vNXmZNcxQQCx4DULLBvKt7AzRUAQiAuw5aQkb9RBaSqdVA==",
       "dev": true,
       "dependencies": {
         "muggle-string": "^0.4.0"
       }
     },
     "node_modules/@volar/typescript": {
-      "version": "2.2.0-alpha.8",
-      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.2.0-alpha.8.tgz",
-      "integrity": "sha512-RLbRDI+17CiayHZs9HhSzlH0FhLl/+XK6o2qoiw2o2GGKcyD1aDoY6AcMd44acYncTOrqoTNoY6LuCiRyiJiGg==",
+      "version": "2.2.0-alpha.10",
+      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.2.0-alpha.10.tgz",
+      "integrity": "sha512-GCa0vTVVdA9ULUsu2Rx7jwsIuyZQPvPVT9o3NrANTbYv+523Ao1gv3glC5vzNSDPM6bUl37r94HbCj7KINQr+g==",
       "dev": true,
       "dependencies": {
-        "@volar/language-core": "2.2.0-alpha.8",
+        "@volar/language-core": "2.2.0-alpha.10",
         "path-browserify": "^1.0.1"
       }
     },
     "node_modules/@vue/compiler-core": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.22.tgz",
-      "integrity": "sha512-FBDRCBE/rFPA8OfTUrARx2c49N7zoImlGT7hsFikv0pZxQlFhffQwewpEXaLynZW0/DspVXmNA+QQ9dXINpWmg==",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.24.tgz",
+      "integrity": "sha512-vbW/tgbwJYj62N/Ww99x0zhFTkZDTcGh3uwJEuadZ/nF9/xuFMC4693P9r+3sxGXISABpDKvffY5ApH9pmdd1A==",
       "dependencies": {
-        "@babel/parser": "^7.24.1",
-        "@vue/shared": "3.4.22",
+        "@babel/parser": "^7.24.4",
+        "@vue/shared": "3.4.24",
         "entities": "^4.5.0",
         "estree-walker": "^2.0.2",
         "source-map-js": "^1.2.0"
@@ -2749,26 +2749,26 @@
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
     "node_modules/@vue/compiler-dom": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.22.tgz",
-      "integrity": "sha512-YkAS+jZc6Ip360kT3lZbMQZteiYBbHDSVKr94Jdd8Zjr7VjSkkXKAFFR/FW+2tNtBYXOps6xrWlOquy3GeYB0w==",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.24.tgz",
+      "integrity": "sha512-4XgABML/4cNndVsQndG6BbGN7+EoisDwi3oXNovqL/4jdNhwvP8/rfRMTb6FxkxIxUUtg6AI1/qZvwfSjxJiWA==",
       "dependencies": {
-        "@vue/compiler-core": "3.4.22",
-        "@vue/shared": "3.4.22"
+        "@vue/compiler-core": "3.4.24",
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/compiler-sfc": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.22.tgz",
-      "integrity": "sha512-Pncp5Vc8E2Ef1o5uveO8WA1IqM7rt0R1jN8D4qitQYOUxC97iITGYA8oMInQ3UcDS7ip+SegyA2HbAEB4V6NMQ==",
-      "dependencies": {
-        "@babel/parser": "^7.24.1",
-        "@vue/compiler-core": "3.4.22",
-        "@vue/compiler-dom": "3.4.22",
-        "@vue/compiler-ssr": "3.4.22",
-        "@vue/shared": "3.4.22",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.24.tgz",
+      "integrity": "sha512-nRAlJUK02FTWfA2nuvNBAqsDZuERGFgxZ8sGH62XgFSvMxO2URblzulExsmj4gFZ8e+VAyDooU9oAoXfEDNxTA==",
+      "dependencies": {
+        "@babel/parser": "^7.24.4",
+        "@vue/compiler-core": "3.4.24",
+        "@vue/compiler-dom": "3.4.24",
+        "@vue/compiler-ssr": "3.4.24",
+        "@vue/shared": "3.4.24",
         "estree-walker": "^2.0.2",
-        "magic-string": "^0.30.8",
+        "magic-string": "^0.30.10",
         "postcss": "^8.4.38",
         "source-map-js": "^1.2.0"
       }
@@ -2779,23 +2779,20 @@
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
     "node_modules/@vue/compiler-sfc/node_modules/magic-string": {
-      "version": "0.30.9",
-      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz",
-      "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==",
+      "version": "0.30.10",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
+      "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
       "dependencies": {
         "@jridgewell/sourcemap-codec": "^1.4.15"
-      },
-      "engines": {
-        "node": ">=12"
       }
     },
     "node_modules/@vue/compiler-ssr": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.22.tgz",
-      "integrity": "sha512-ycb2sL0SW6AkgVMrvaU/TIAEk7FQWyv/oYya44E/V9xURM+ij9Oev5bVobSS7GLJzkUieWW3SrYcK/PZpb5i4A==",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.24.tgz",
+      "integrity": "sha512-ZsAtr4fhaUFnVcDqwW3bYCSDwq+9Gk69q2r/7dAHDrOMw41kylaMgOP4zRnn6GIEJkQznKgrMOGPMFnLB52RbQ==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.22",
-        "@vue/shared": "3.4.22"
+        "@vue/compiler-dom": "3.4.24",
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/devtools-api": {
@@ -2842,12 +2839,12 @@
       }
     },
     "node_modules/@vue/language-core": {
-      "version": "2.0.13",
-      "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.13.tgz",
-      "integrity": "sha512-oQgM+BM66SU5GKtUMLQSQN0bxHFkFpLSSAiY87wVziPaiNQZuKVDt/3yA7GB9PiQw0y/bTNL0bOc0jM/siYjKg==",
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.14.tgz",
+      "integrity": "sha512-3q8mHSNcGTR7sfp2X6jZdcb4yt8AjBXAfKk0qkZIh7GAJxOnoZ10h5HToZglw4ToFvAnq+xu/Z2FFbglh9Icag==",
       "dev": true,
       "dependencies": {
-        "@volar/language-core": "2.2.0-alpha.8",
+        "@volar/language-core": "2.2.0-alpha.10",
         "@vue/compiler-dom": "^3.4.0",
         "@vue/shared": "^3.4.0",
         "computeds": "^0.0.1",
@@ -2865,48 +2862,48 @@
       }
     },
     "node_modules/@vue/reactivity": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.22.tgz",
-      "integrity": "sha512-+golHRRfcGoahBrhoTauFNIIAhxntRV3BI8HHqVvCdsuWivxW1MI0E9AOXVsz4H/ZlWM1ahudWTX6PhUrNR2yQ==",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.24.tgz",
+      "integrity": "sha512-nup3fSYg4i4LtNvu9slF/HF/0dkMQYfepUdORBcMSsankzRPzE7ypAFurpwyRBfU1i7Dn1kcwpYsE1wETSh91g==",
       "dependencies": {
-        "@vue/shared": "3.4.22"
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/runtime-core": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.22.tgz",
-      "integrity": "sha512-cbA8lcL4g1907EdY1a1KmP5IRWfbqjgBRcgJPkF//yn96XSC1/VAJBZiAGLiyw0P77Rw2Ao7d9U51vU1GC6yUQ==",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.24.tgz",
+      "integrity": "sha512-c7iMfj6cJMeAG3s5yOn9Rc5D9e2/wIuaozmGf/ICGCY3KV5H7mbTVdvEkd4ZshTq7RUZqj2k7LMJWVx+EBiY1g==",
       "dependencies": {
-        "@vue/reactivity": "3.4.22",
-        "@vue/shared": "3.4.22"
+        "@vue/reactivity": "3.4.24",
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/runtime-dom": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.22.tgz",
-      "integrity": "sha512-AXxRHrFkLX1y2+70CO2wDKRxW0WZcQKTOXS31AK+jZ1RLPtI6sEHVpYNfyE9WgbgXOqPtX4gfIfuoFYi8iCu2w==",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.24.tgz",
+      "integrity": "sha512-uXKzuh/Emfad2Y7Qm0ABsLZZV6H3mAJ5ZVqmAOlrNQRf+T5mxpPGZBfec1hkP41t6h6FwF6RSGCs/gd8WbuySQ==",
       "dependencies": {
-        "@vue/runtime-core": "3.4.22",
-        "@vue/shared": "3.4.22",
+        "@vue/runtime-core": "3.4.24",
+        "@vue/shared": "3.4.24",
         "csstype": "^3.1.3"
       }
     },
     "node_modules/@vue/server-renderer": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.22.tgz",
-      "integrity": "sha512-okiNxiCOhJlx6IOrTZvhIVwf2UYKay0hnIPqWu4h19bkNv1gmG4Ic6U3zXY287AWF26lQuFMa515Qzc+R0aAYg==",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.24.tgz",
+      "integrity": "sha512-H+DLK4sQF6sRgzKyofmlEVBIV/9KrQU6HIV7nt6yIwSGGKvSwlV8pqJlebUKLpbXaNHugdSfAbP6YmXF69lxow==",
       "dependencies": {
-        "@vue/compiler-ssr": "3.4.22",
-        "@vue/shared": "3.4.22"
+        "@vue/compiler-ssr": "3.4.24",
+        "@vue/shared": "3.4.24"
       },
       "peerDependencies": {
-        "vue": "3.4.22"
+        "vue": "3.4.24"
       }
     },
     "node_modules/@vue/shared": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.22.tgz",
-      "integrity": "sha512-cg7R9XNk4ovV3bKka/1a464O2oY0l5Fyt0rwGR4hSJRPjUJ0WVjrPdsr4W0JbUriwiM8EKcCcCjeKN5pRMs2Zg=="
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
+      "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw=="
     },
     "node_modules/@vue/tsconfig": {
       "version": "0.5.1",
@@ -5912,9 +5909,9 @@
       }
     },
     "node_modules/rollup": {
-      "version": "4.14.3",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz",
-      "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz",
+      "integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==",
       "dev": true,
       "dependencies": {
         "@types/estree": "1.0.5"
@@ -5927,22 +5924,22 @@
         "npm": ">=8.0.0"
       },
       "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.14.3",
-        "@rollup/rollup-android-arm64": "4.14.3",
-        "@rollup/rollup-darwin-arm64": "4.14.3",
-        "@rollup/rollup-darwin-x64": "4.14.3",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.14.3",
-        "@rollup/rollup-linux-arm-musleabihf": "4.14.3",
-        "@rollup/rollup-linux-arm64-gnu": "4.14.3",
-        "@rollup/rollup-linux-arm64-musl": "4.14.3",
-        "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3",
-        "@rollup/rollup-linux-riscv64-gnu": "4.14.3",
-        "@rollup/rollup-linux-s390x-gnu": "4.14.3",
-        "@rollup/rollup-linux-x64-gnu": "4.14.3",
-        "@rollup/rollup-linux-x64-musl": "4.14.3",
-        "@rollup/rollup-win32-arm64-msvc": "4.14.3",
-        "@rollup/rollup-win32-ia32-msvc": "4.14.3",
-        "@rollup/rollup-win32-x64-msvc": "4.14.3",
+        "@rollup/rollup-android-arm-eabi": "4.16.4",
+        "@rollup/rollup-android-arm64": "4.16.4",
+        "@rollup/rollup-darwin-arm64": "4.16.4",
+        "@rollup/rollup-darwin-x64": "4.16.4",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.16.4",
+        "@rollup/rollup-linux-arm-musleabihf": "4.16.4",
+        "@rollup/rollup-linux-arm64-gnu": "4.16.4",
+        "@rollup/rollup-linux-arm64-musl": "4.16.4",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.16.4",
+        "@rollup/rollup-linux-riscv64-gnu": "4.16.4",
+        "@rollup/rollup-linux-s390x-gnu": "4.16.4",
+        "@rollup/rollup-linux-x64-gnu": "4.16.4",
+        "@rollup/rollup-linux-x64-musl": "4.16.4",
+        "@rollup/rollup-win32-arm64-msvc": "4.16.4",
+        "@rollup/rollup-win32-ia32-msvc": "4.16.4",
+        "@rollup/rollup-win32-x64-msvc": "4.16.4",
         "fsevents": "~2.3.2"
       }
     },
@@ -6643,9 +6640,9 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.2.9",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz",
-      "integrity": "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==",
+      "version": "5.2.10",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz",
+      "integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==",
       "dev": true,
       "dependencies": {
         "esbuild": "^0.20.1",
@@ -6698,15 +6695,15 @@
       }
     },
     "node_modules/vue": {
-      "version": "3.4.22",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.22.tgz",
-      "integrity": "sha512-CIx7NiP+n5WHBCG/fDNaUPP4qbQ5CIa8XIHZE3HpfS/rb2vmSIsp74BxsZyrrGKF0vHW3GoToqP3l0hzrMTecw==",
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.24.tgz",
+      "integrity": "sha512-NPdx7dLGyHmKHGRRU5bMRYVE+rechR+KDU5R2tSTNG36PuMwbfAJ+amEvOAw7BPfZp5sQulNELSLm5YUkau+Sg==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.22",
-        "@vue/compiler-sfc": "3.4.22",
-        "@vue/runtime-dom": "3.4.22",
-        "@vue/server-renderer": "3.4.22",
-        "@vue/shared": "3.4.22"
+        "@vue/compiler-dom": "3.4.24",
+        "@vue/compiler-sfc": "3.4.24",
+        "@vue/runtime-dom": "3.4.24",
+        "@vue/server-renderer": "3.4.24",
+        "@vue/shared": "3.4.24"
       },
       "peerDependencies": {
         "typescript": "*"
@@ -6751,9 +6748,9 @@
       }
     },
     "node_modules/vue-router": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
-      "integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==",
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz",
+      "integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==",
       "dependencies": {
         "@vue/devtools-api": "^6.5.1"
       },
@@ -6775,13 +6772,13 @@
       }
     },
     "node_modules/vue-tsc": {
-      "version": "2.0.13",
-      "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.13.tgz",
-      "integrity": "sha512-a3nL3FvguCWVJUQW/jFrUxdeUtiEkbZoQjidqvMeBK//tuE2w6NWQAbdrEpY2+6nSa4kZoKZp8TZUMtHpjt4mQ==",
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.14.tgz",
+      "integrity": "sha512-DgAO3U1cnCHOUO7yB35LENbkapeRsBZ7Ugq5hGz/QOHny0+1VQN8eSwSBjYbjLVPfvfw6EY7sNPjbuHHUhckcg==",
       "dev": true,
       "dependencies": {
-        "@volar/typescript": "2.2.0-alpha.8",
-        "@vue/language-core": "2.0.13",
+        "@volar/typescript": "2.2.0-alpha.10",
+        "@vue/language-core": "2.0.14",
         "semver": "^7.5.4"
       },
       "bin": {
diff --git a/src/App.vue b/src/App.vue
index 88d98f8..62e5c50 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -108,7 +108,7 @@ onMounted(() => {
   if (userRepository.authenticated) {
     resourceRepository.fetchPublicResources();
     workflowRepository.fetchWorkflows();
-    bucketRepository.fetchBuckets();
+    bucketRepository.fetchOwnBuckets();
     s3KeyRepository.fetchS3Keys();
     if (!userRepository.foreignUser) {
       bucketRepository.fetchOwnPermissions();
diff --git a/src/client/auth/index.ts b/src/client/auth/index.ts
index 016081e..1995035 100644
--- a/src/client/auth/index.ts
+++ b/src/client/auth/index.ts
@@ -9,7 +9,7 @@ export type { OpenAPIConfig } from './core/OpenAPI';
 
 export type { ErrorDetail } from './models/ErrorDetail';
 export type { HTTPValidationError } from './models/HTTPValidationError';
-export type { OIDCProvider } from './models/OIDCProvider';
+export { OIDCProvider } from './models/OIDCProvider';
 export { RoleEnum } from './models/RoleEnum';
 export type { User } from './models/User';
 export type { ValidationError } from './models/ValidationError';
diff --git a/src/client/auth/models/OIDCProvider.ts b/src/client/auth/models/OIDCProvider.ts
index 55d0ffe..b8e466e 100644
--- a/src/client/auth/models/OIDCProvider.ts
+++ b/src/client/auth/models/OIDCProvider.ts
@@ -2,4 +2,6 @@
 /* istanbul ignore file */
 /* tslint:disable */
 /* eslint-disable */
-export type OIDCProvider = string;
+export enum OIDCProvider {
+    LIFESCIENCE = 'lifescience',
+}
diff --git a/src/client/s3proxy/index.ts b/src/client/s3proxy/index.ts
index 69ca124..37a68b1 100644
--- a/src/client/s3proxy/index.ts
+++ b/src/client/s3proxy/index.ts
@@ -13,8 +13,8 @@ export type { BucketOut } from './models/BucketOut';
 export type { BucketPermissionIn } from './models/BucketPermissionIn';
 export type { BucketPermissionOut } from './models/BucketPermissionOut';
 export type { BucketPermissionParameters } from './models/BucketPermissionParameters';
+export type { BucketSizeLimits } from './models/BucketSizeLimits';
 export { BucketType } from './models/BucketType';
-export { Constraint } from './models/Constraint';
 export type { ErrorDetail } from './models/ErrorDetail';
 export type { HTTPValidationError } from './models/HTTPValidationError';
 export { Permission } from './models/Permission';
diff --git a/src/client/s3proxy/models/BucketOut.ts b/src/client/s3proxy/models/BucketOut.ts
index 87a20c6..be48294 100644
--- a/src/client/s3proxy/models/BucketOut.ts
+++ b/src/client/s3proxy/models/BucketOut.ts
@@ -2,11 +2,18 @@
 /* istanbul ignore file */
 /* tslint:disable */
 /* eslint-disable */
-import type { Constraint } from './Constraint';
 /**
  * Schema for answering a request with a bucket.
  */
 export type BucketOut = {
+    /**
+     * Size limit of the bucket in KiB
+     */
+    size_limit?: (number | null);
+    /**
+     * Number of objects limit of the bucket
+     */
+    object_limit?: (number | null);
     /**
      * Name of the bucket
      */
@@ -23,10 +30,6 @@ export type BucketOut = {
      * UID of the owner
      */
     owner_id: string;
-    /**
-     * Constraint for the owner of the bucket
-     */
-    owner_constraint?: (Constraint | null);
     /**
      * Flag if the bucket is anonymously readable
      */
diff --git a/src/client/s3proxy/models/BucketSizeLimits.ts b/src/client/s3proxy/models/BucketSizeLimits.ts
new file mode 100644
index 0000000..22e59a7
--- /dev/null
+++ b/src/client/s3proxy/models/BucketSizeLimits.ts
@@ -0,0 +1,15 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type BucketSizeLimits = {
+    /**
+     * Size limit of the bucket in KiB
+     */
+    size_limit?: (number | null);
+    /**
+     * Number of objects limit of the bucket
+     */
+    object_limit?: (number | null);
+};
+
diff --git a/src/client/s3proxy/models/Constraint.ts b/src/client/s3proxy/models/Constraint.ts
deleted file mode 100644
index f33ad3b..0000000
--- a/src/client/s3proxy/models/Constraint.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/* generated using openapi-typescript-codegen -- do not edit */
-/* istanbul ignore file */
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Enumeration for the possible permission on a bucket.
- */
-export enum Constraint {
-    READ = 'READ',
-    WRITE = 'WRITE',
-}
diff --git a/src/client/s3proxy/services/BucketService.ts b/src/client/s3proxy/services/BucketService.ts
index 1984645..b30e775 100644
--- a/src/client/s3proxy/services/BucketService.ts
+++ b/src/client/s3proxy/services/BucketService.ts
@@ -5,6 +5,7 @@
 import type { Body_Bucket_update_bucket_public_state } from '../models/Body_Bucket_update_bucket_public_state';
 import type { BucketIn } from '../models/BucketIn';
 import type { BucketOut } from '../models/BucketOut';
+import type { BucketSizeLimits } from '../models/BucketSizeLimits';
 import type { BucketType } from '../models/BucketType';
 import type { CancelablePromise } from '../core/CancelablePromise';
 import { OpenAPI } from '../core/OpenAPI';
@@ -133,8 +134,8 @@ export class BucketService {
         });
     }
     /**
-     * update public status
-     * Toggle the buckets public state. A bucket with an owner constraint can't be made public.
+     * Update public status
+     * Update the buckets public state.
      *
      * Permission `bucket:update` required if the current user is the owner of the bucket,
      * otherwise `bucket:update_any` required.
@@ -164,4 +165,35 @@ export class BucketService {
             },
         });
     }
+    /**
+     * Update bucket limits
+     * Update the buckets size limits.
+     *
+     * Permission `bucket:update_any` required.
+     * @param bucketName Name of bucket
+     * @param requestBody
+     * @returns BucketOut Successful Response
+     * @throws ApiError
+     */
+    public static bucketUpdateBucketLimits(
+        bucketName: string,
+        requestBody: BucketSizeLimits,
+    ): CancelablePromise<BucketOut> {
+        return __request(OpenAPI, {
+            method: 'PATCH',
+            url: '/buckets/{bucket_name}/limits',
+            path: {
+                'bucket_name': bucketName,
+            },
+            body: requestBody,
+            mediaType: 'application/json',
+            errors: {
+                400: `Error decoding JWT Token`,
+                401: `Not authenticated`,
+                403: `Not authorized`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
 }
diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue
index 11802cc..857988f 100644
--- a/src/components/AppHeader.vue
+++ b/src/components/AppHeader.vue
@@ -216,6 +216,13 @@ watch(
                   >Users
                 </router-link>
               </li>
+              <li>
+                <router-link
+                  class="dropdown-item"
+                  :to="{ name: 'admin-buckets' }"
+                  >Buckets
+                </router-link>
+              </li>
               <li>
                 <router-link
                   class="dropdown-item"
diff --git a/src/components/admin/UpdateBucketLimitsModal.vue b/src/components/admin/UpdateBucketLimitsModal.vue
new file mode 100644
index 0000000..741679f
--- /dev/null
+++ b/src/components/admin/UpdateBucketLimitsModal.vue
@@ -0,0 +1,236 @@
+<script setup lang="ts">
+import { computed, onMounted, reactive, ref, watch } from "vue";
+import type { BucketOut, BucketSizeLimits } from "@/client/s3proxy";
+import BootstrapModal from "@/components/modals/BootstrapModal.vue";
+import { filesize } from "filesize";
+import { useBucketStore } from "@/stores/buckets";
+import BootstrapToast from "@/components/BootstrapToast.vue";
+import { Modal, Toast, Tooltip } from "bootstrap";
+import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
+
+const props = defineProps<{
+  modalId: string;
+  bucket: BucketOut;
+}>();
+
+type DataUnits = "MiB" | "GiB" | "TiB" | "KB" | "MB" | "GB" | "TB";
+const unitCalculatorState = reactive<{
+  selectedUnit: DataUnits;
+  input: number;
+}>({
+  selectedUnit: "GB",
+  input: 1,
+});
+const unitCalculatorResult = computed<number>(() => {
+  switch (unitCalculatorState.selectedUnit) {
+    case "KB":
+      return 1000 * (unitCalculatorState.input / 1024);
+    case "MB":
+      return 1000000 * (unitCalculatorState.input / 1024);
+    case "GB":
+      return 1000000000 * (unitCalculatorState.input / 1024);
+    case "TB":
+      return 1000000000000 * (unitCalculatorState.input / 1024);
+    case "MiB":
+      return 1024 * unitCalculatorState.input;
+    case "GiB":
+      return 1048576 * unitCalculatorState.input;
+    case "TiB":
+      return 1073741824 * unitCalculatorState.input;
+    default:
+      return 1;
+  }
+});
+
+const sizeState = reactive<{
+  limits: BucketSizeLimits;
+  loading: boolean;
+}>({
+  limits: {
+    size_limit: undefined,
+    object_limit: undefined,
+  },
+  loading: false,
+});
+
+const emit = defineEmits<{
+  (e: "updated-limits", bucket: BucketOut): void;
+}>();
+
+let successToast: Toast;
+let modal: Modal | null = null;
+const sizeForm = ref<HTMLFormElement | undefined>(undefined);
+
+watch(
+  () => props.bucket,
+  (newBucket, oldBucket) => {
+    if (newBucket.name != oldBucket.name) {
+      sizeState.limits.size_limit = newBucket.size_limit;
+      sizeState.limits.object_limit = newBucket.object_limit;
+    }
+  },
+);
+
+const bucketRepository = useBucketStore();
+
+const randomIDSuffix = Math.random().toString(16).substring(2, 8);
+const formId = `update-bucket-limits-modal-${randomIDSuffix}`;
+const successToastId = `update-bucket-limits-success-toast-${randomIDSuffix}`;
+
+function updateLimits() {
+  if (sizeForm.value?.checkValidity()) {
+    sizeState.loading = true;
+    bucketRepository
+      .updateBucketLimits(
+        props.bucket.name,
+        sizeState.limits.size_limit ? sizeState.limits.size_limit : null,
+        sizeState.limits.object_limit ? sizeState.limits.object_limit : null,
+      )
+      .then((bucket) => {
+        successToast?.show();
+        emit("updated-limits", bucket);
+        modal?.hide();
+      })
+      .finally(() => {
+        sizeState.loading = false;
+      });
+  }
+}
+
+function copyFromCalculatorToForm() {
+  sizeState.limits.size_limit = Math.round(unitCalculatorResult.value);
+}
+
+onMounted(() => {
+  successToast = Toast.getOrCreateInstance(`#${successToastId}`);
+  modal = Modal.getOrCreateInstance(`#${props.modalId}`);
+  new Tooltip(`#copy-to-size-limit-form-${randomIDSuffix}`);
+});
+</script>
+
+<template>
+  <bootstrap-toast :toast-id="successToastId">
+    Updated limits for bucket {{ bucket.name }}
+  </bootstrap-toast>
+  <bootstrap-modal
+    :modalId="props.modalId"
+    :static-backdrop="true"
+    modal-label="Update bucket size limits"
+    size-modifier-modal="lg"
+  >
+    <template #header
+      >Update limits for bucket <i>{{ bucket.name }}</i></template
+    >
+    <template #body>
+      <form :id="formId" @submit.prevent="updateLimits()" ref="sizeForm">
+        <div class="row mb-2">
+          <div class="col-5">
+            <label :for="`#size-limit-${randomIDSuffix}`" class="form-label"
+              >Size Limit:
+              <template v-if="sizeState.limits.size_limit"
+                >{{ filesize(1024 * sizeState.limits.size_limit) }} or
+                {{ filesize(1024 * sizeState.limits.size_limit, { base: 2 }) }}
+              </template>
+              <template v-else>No Limits</template>
+            </label>
+            <div class="d-flex align-content-center">
+              <input
+                :id="`size-limit-${randomIDSuffix}`"
+                style="text-align: right"
+                type="number"
+                v-model="sizeState.limits.size_limit"
+                placeholder="No limits"
+                min="1"
+                max="4294967295"
+                step="1"
+                class="form-control"
+                aria-label="size limit"
+              />
+              <div class="ms-1 fs-5">KiB</div>
+            </div>
+          </div>
+          <div class="col-6 offset-md-1">
+            <label :for="`#object-limit-${randomIDSuffix}`" class="form-label"
+              >Size Limit
+            </label>
+            <input
+              :id="`object-limit-${randomIDSuffix}`"
+              type="number"
+              class="form-control"
+              v-model="sizeState.limits.object_limit"
+              placeholder="No limits"
+              min="1"
+              max="4294967295"
+              step="1"
+              aria-label="object limit"
+            />
+          </div>
+        </div>
+      </form>
+      <h4 class="mt-4">Unit calculator</h4>
+      <div class="d-flex justify-content-center align-content-center">
+        <div class="input-group w-fit">
+          <input
+            class="form-control"
+            style="text-align: right"
+            type="number"
+            min="1"
+            max="1000"
+            step="0.01"
+            v-model="unitCalculatorState.input"
+          />
+          <select
+            v-model="unitCalculatorState.selectedUnit"
+            class="form-select"
+          >
+            <option>KB</option>
+            <option>MiB</option>
+            <option>MB</option>
+            <option>GiB</option>
+            <option>GB</option>
+            <option>TiB</option>
+            <option>TB</option>
+          </select>
+        </div>
+        <div class="fs-4 mx-4">
+          <font-awesome-icon icon="fa-solid fa-arrow-right-long" />
+        </div>
+        <div class="d-flex align-content-center">
+          <div class="input-group">
+            <span
+              :id="`copy-to-size-limit-form-${randomIDSuffix}`"
+              class="input-group-text hover-info cursor-pointer"
+              data-bs-toggle="tooltip"
+              data-bs-title="Copy to form"
+              @click="copyFromCalculatorToForm()"
+              ><font-awesome-icon icon="fa-solid fa-copy"
+            /></span>
+            <input
+              style="text-align: right"
+              type="number"
+              class="form-control"
+              readonly
+              :value="unitCalculatorResult"
+            />
+          </div>
+          <div class="ms-1 fs-5">KiB</div>
+        </div>
+      </div>
+    </template>
+    <template #footer>
+      <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
+        Close
+      </button>
+      <button
+        type="submit"
+        :form="formId"
+        class="btn btn-primary"
+        :disabled="sizeState.loading"
+      >
+        Save
+      </button>
+    </template>
+  </bootstrap-modal>
+</template>
+
+<style scoped></style>
diff --git a/src/components/modals/SearchUserModal.vue b/src/components/modals/SearchUserModal.vue
index b6396eb..6416a0a 100644
--- a/src/components/modals/SearchUserModal.vue
+++ b/src/components/modals/SearchUserModal.vue
@@ -88,8 +88,8 @@ function searchUser(name: string) {
     modal-label="Search User Modal"
     v-on="{ 'hidden.bs.modal': modalClosed, 'shown.bs.modal': modalShown }"
   >
-    <template v-slot:header>Search User</template>
-    <template v-slot:body>
+    <template #header>Search User</template>
+    <template #body>
       <div class="input-group mt-2 mb-4">
         <span class="input-group-text" id="objects-search-wrapping"
           ><font-awesome-icon icon="fa-solid fa-magnifying-glass"
diff --git a/src/components/object-storage/BucketLimitProgressBar.vue b/src/components/object-storage/BucketLimitProgressBar.vue
new file mode 100644
index 0000000..e0ebc5a
--- /dev/null
+++ b/src/components/object-storage/BucketLimitProgressBar.vue
@@ -0,0 +1,48 @@
+<script setup lang="ts">
+import { computed } from "vue";
+
+const props = defineProps<{
+  currentVal: number;
+  maximum: number;
+  label?: string;
+}>();
+
+const percentage = computed<number>(
+  () => (100 * props.currentVal) / props.maximum,
+);
+
+const cssWidth = computed<number>(() =>
+  Math.min(100, Math.round(percentage.value)),
+);
+
+const colorClass = computed(() => {
+  if (percentage.value > 90) {
+    return "text-bg-danger";
+  } else if (percentage.value > 75) {
+    return "text-bg-warning";
+  }
+  return "text-bg-success text-dark";
+});
+</script>
+
+<template>
+  <div
+    class="progress"
+    role="progressbar"
+    aria-label="Warning example"
+    :aria-valuenow="cssWidth"
+    aria-valuemin="0"
+    aria-valuemax="100"
+  >
+    <div
+      class="progress-bar overflow-visible"
+      :class="colorClass"
+      :style="{ width: cssWidth + '%' }"
+    >
+      <template v-if="label">{{ label }}</template>
+      <template v-else> {{ percentage.toFixed(2) }}% </template>
+    </div>
+  </div>
+</template>
+
+<style scoped></style>
diff --git a/src/components/object-storage/BucketListItem.vue b/src/components/object-storage/BucketListItem.vue
index 4a5bdcd..e27f990 100644
--- a/src/components/object-storage/BucketListItem.vue
+++ b/src/components/object-storage/BucketListItem.vue
@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import type { BucketOut, BucketPermissionOut } from "@/client/s3proxy";
-import { Constraint } from "@/client/s3proxy";
 import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
 import PermissionModal from "@/components/object-storage/modals/PermissionModal.vue";
 import BucketDetailModal from "@/components/object-storage/modals/BucketDetailModal.vue";
@@ -16,6 +15,7 @@ import { environment } from "@/environment";
 import { useS3ObjectStore } from "@/stores/s3objects";
 import { filesize } from "filesize";
 import BootstrapToast from "@/components/BootstrapToast.vue";
+import BucketLimitProgressBar from "@/components/object-storage/BucketLimitProgressBar.vue";
 
 const props = defineProps<{
   active: boolean;
@@ -70,7 +70,7 @@ function permissionDeleted() {
 function toggleBucketPublicState() {
   requestState.loading = true;
   bucketRepository
-    .togglePublicState(props.bucket.name, !props.bucket.public)
+    .updatePublicState(props.bucket.name, !props.bucket.public)
     .then(() => {
       successToast?.show();
     })
@@ -154,13 +154,7 @@ onMounted(() => {
         }"
       >
         <span class="text-truncate flex-grow-3">
-          <template v-if="bucket.owner_constraint === Constraint.READ"
-            >download-bucket</template
-          >
-          <template v-else-if="bucket.owner_constraint === Constraint.WRITE"
-            >upload-bucket</template
-          >
-          <template v-else>{{ bucket.name }}</template>
+          {{ bucket.name }}
         </span>
         <div class="text-nowrap">
           <font-awesome-icon
@@ -211,7 +205,7 @@ onMounted(() => {
         class="px-2 rounded-bottom border shadow-sm border-3 border-top-0 border-primary"
       >
         <div v-if="permission" class="ms-1 pt-1 text-info">Foreign Bucket</div>
-        <table class="table table-sm table-borderless mb-0">
+        <table class="table table-sm table-borderless mb-0 align-middle">
           <tbody>
             <tr v-if="permission">
               <th scope="row" class="fw-bold">Permission:</th>
@@ -264,15 +258,31 @@ onMounted(() => {
             </tr>
             <tr>
               <th scope="row" class="fw-bold">Objects:</th>
-              <td>{{ bucketMeta[0] }}</td>
+              <td v-if="bucket.object_limit">
+                <bucket-limit-progress-bar
+                  :maximum="bucket.object_limit"
+                  :current-val="bucketMeta[0]"
+                  :label="bucketMeta[0] + '/' + bucket.object_limit"
+                />
+              </td>
+              <td v-else>{{ bucketMeta[0] }}</td>
             </tr>
             <tr>
               <th scope="row" class="fw-bold">Size:</th>
-              <td>
-                {{ filesize(bucketMeta[1], { base: 2, standard: "jedec" }) }}
+              <td v-if="bucket.size_limit">
+                <bucket-limit-progress-bar
+                  :maximum="1024 * bucket.size_limit"
+                  :current-val="bucketMeta[1]"
+                  :label="
+                    filesize(bucketMeta[1]) +
+                    '/' +
+                    filesize(1024 * bucket.size_limit)
+                  "
+                />
               </td>
+              <td v-else>{{ filesize(bucketMeta[1]) }}</td>
             </tr>
-            <tr v-if="bucket.owner_constraint == undefined">
+            <tr>
               <th scope="row">
                 <div
                   :class="{ 'form-check': !loading && permission == undefined }"
diff --git a/src/components/object-storage/modals/BucketDetailModal.vue b/src/components/object-storage/modals/BucketDetailModal.vue
index 7115147..2a2b0b1 100644
--- a/src/components/object-storage/modals/BucketDetailModal.vue
+++ b/src/components/object-storage/modals/BucketDetailModal.vue
@@ -7,6 +7,7 @@ import { useS3ObjectStore } from "@/stores/s3objects";
 import { computed } from "vue";
 import { environment } from "../../../environment";
 import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
+import BucketLimitProgressBar from "@/components/object-storage/BucketLimitProgressBar.vue";
 
 const props = defineProps<{
   modalId: string;
@@ -40,7 +41,7 @@ const s3Link = computed<string>(() => "s3://" + props.bucket.name);
 
     <template v-slot:body>
       <div class="container-fluid">
-        <table class="table table-hover table-sm table-borderless">
+        <table class="table table-sm table-borderless">
           <tbody>
             <tr>
               <th scope="row" class="col-3">Name</th>
@@ -58,12 +59,30 @@ const s3Link = computed<string>(() => "s3://" + props.bucket.name);
             </tr>
             <tr>
               <th scope="row">Objects</th>
-              <td>{{ bucketMeta[0] }}</td>
+              <td>
+                {{ bucketMeta[0]
+                }}<span v-if="bucket.object_limit"
+                  >/{{ bucket.object_limit }}</span
+                >
+                <bucket-limit-progress-bar
+                  v-if="bucket.object_limit"
+                  :maximum="bucket.object_limit"
+                  :current-val="bucketMeta[0]"
+                />
+              </td>
             </tr>
             <tr>
               <th scope="row">Size</th>
               <td>
-                {{ filesize(bucketMeta[1], { base: 2, standard: "jedec" }) }}
+                {{ filesize(bucketMeta[1])
+                }}<span v-if="bucket.size_limit"
+                  >/{{ filesize(1024 * bucket.size_limit) }}</span
+                >
+                <bucket-limit-progress-bar
+                  v-if="bucket.size_limit"
+                  :maximum="1024 * bucket.size_limit"
+                  :current-val="bucketMeta[1]"
+                />
               </td>
             </tr>
             <tr>
diff --git a/src/components/object-storage/modals/CopyObjectModal.vue b/src/components/object-storage/modals/CopyObjectModal.vue
index ca3b28d..d03cfcb 100644
--- a/src/components/object-storage/modals/CopyObjectModal.vue
+++ b/src/components/object-storage/modals/CopyObjectModal.vue
@@ -6,6 +6,7 @@ import type { _Object as S3Object } from "@aws-sdk/client-s3";
 import { useBucketStore } from "@/stores/buckets";
 import { useS3ObjectStore } from "@/stores/s3objects";
 import BootstrapToast from "@/components/BootstrapToast.vue";
+import type { AbortController } from "@smithy/types";
 
 const objectRepository = useS3ObjectStore();
 
@@ -19,10 +20,14 @@ const formState = reactive<{
   destKey: string;
   destBucket: string;
   uploading: boolean;
+  err: string;
+  abortController?: AbortController;
 }>({
   destKey: "",
   destBucket: "",
   uploading: false,
+  err: "",
+  abortController: undefined,
 });
 const bucketRepository = useBucketStore();
 
@@ -43,6 +48,7 @@ function copyObject() {
   if (props.srcObject.Key == undefined) {
     return;
   }
+  formState.abortController = new AbortController();
   formState.uploading = true;
   objectRepository
     .copyObject(
@@ -50,6 +56,7 @@ function copyObject() {
       props.srcObject,
       formState.destBucket,
       formState.destKey,
+      formState.abortController,
     )
     .then(() => {
       copyModal?.hide();
@@ -57,11 +64,12 @@ function copyObject() {
       formState.destBucket = "";
     })
     .catch((e) => {
-      console.error(e);
+      formState.err = e.Code;
       errorToast?.show();
     })
     .finally(() => {
       formState.uploading = false;
+      formState.abortController = undefined;
     });
 }
 
@@ -92,7 +100,7 @@ onMounted(() => {
     color-class="danger"
   >
     There has been some Error.<br />
-    Try again later
+    Code: {{ formState.err }}
   </bootstrap-toast>
   <bootstrap-modal
     :modalId="modalId"
@@ -164,17 +172,18 @@ onMounted(() => {
         Close
       </button>
       <button
-        :disabled="formState.uploading"
+        v-if="formState.uploading"
+        class="btn btn-danger"
+        @click="formState.abortController?.abort()"
+      >
+        Cancel
+      </button>
+      <button
+        v-else
         type="submit"
         :form="'copyObjectForm' + randomIDSuffix"
         class="btn btn-primary"
       >
-        <span
-          v-if="formState.uploading"
-          class="spinner-border spinner-border-sm"
-          role="status"
-          aria-hidden="true"
-        ></span>
         Copy
       </button>
     </template>
diff --git a/src/components/object-storage/modals/CreateFolderModal.vue b/src/components/object-storage/modals/CreateFolderModal.vue
index e0b5b39..b7c5b48 100644
--- a/src/components/object-storage/modals/CreateFolderModal.vue
+++ b/src/components/object-storage/modals/CreateFolderModal.vue
@@ -24,9 +24,11 @@ const currentFolders = computed<string[]>(() => props.keyPrefix.split("/"));
 const formState = reactive<{
   folderName: string;
   uploading: boolean;
+  err: string;
 }>({
   folderName: "",
   uploading: false,
+  err: "",
 });
 
 function uploadFolder() {
@@ -52,7 +54,7 @@ function uploadFolder() {
       formState.folderName = "";
     })
     .catch((e) => {
-      console.error(e);
+      formState.err = e.name;
       errorToast?.show();
     })
     .finally(() => {
@@ -76,7 +78,7 @@ onMounted(() => {
     color-class="danger"
   >
     There has been some Error.<br />
-    Try again later
+    Code: {{ formState.err }}
   </bootstrap-toast>
   <bootstrap-modal
     :modalId="modalId"
diff --git a/src/components/object-storage/modals/UploadObjectModal.vue b/src/components/object-storage/modals/UploadObjectModal.vue
index eca85fd..c239510 100644
--- a/src/components/object-storage/modals/UploadObjectModal.vue
+++ b/src/components/object-storage/modals/UploadObjectModal.vue
@@ -5,6 +5,7 @@ import { Modal, Toast } from "bootstrap";
 import { partial } from "filesize";
 import { useS3ObjectStore } from "@/stores/s3objects";
 import BootstrapToast from "@/components/BootstrapToast.vue";
+import type { AbortController } from "@smithy/types";
 
 const fsize = partial({ base: 2, standard: "jedec" });
 const objectRepository = useS3ObjectStore();
@@ -31,18 +32,22 @@ watch(
   },
 );
 
-const formState = reactive({
-  file: {},
-  key: "",
-  uploading: false,
-  uploadDone: 0,
-  uploadTotal: 1,
-} as {
-  file: File;
+const formState = reactive<{
+  file?: File;
   key: string;
   uploading: boolean;
   uploadDone: number;
   uploadTotal: number;
+  err: string;
+  abortController?: AbortController;
+}>({
+  file: undefined,
+  key: "",
+  uploading: false,
+  uploadDone: 0,
+  uploadTotal: 1,
+  err: "",
+  abortController: undefined,
 });
 
 const uploadProgress = computed<number>(() =>
@@ -54,19 +59,32 @@ const editObject = computed<boolean>(
 );
 
 function uploadObject() {
+  if (formState.file == undefined) {
+    return;
+  }
   const key =
     props.keyPrefix.length > 0
       ? props.keyPrefix + "/" + formState.key
       : formState.key;
   formState.uploadDone = 0;
   formState.uploading = true;
+  formState.abortController = new AbortController();
+  formState.uploadTotal = formState.file.size;
   objectRepository
-    .uploadObjectFile(props.bucketName, key, formState.file, (progress) => {
-      if (progress.loaded != null && progress.total != null) {
-        formState.uploadDone = progress.loaded;
-        formState.uploadTotal = progress.total;
-      }
-    })
+    .uploadObjectFile(
+      props.bucketName,
+      key,
+      formState.file,
+      (progress) => {
+        if (progress.loaded != null) {
+          formState.uploadDone = progress.loaded;
+        }
+        if (progress.total != null) {
+          formState.uploadTotal = progress.total;
+        }
+      },
+      formState.abortController,
+    )
     .then(() => {
       uploadModal?.hide();
       successToast?.show();
@@ -76,11 +94,12 @@ function uploadObject() {
       }
     })
     .catch((e) => {
-      console.error(e);
+      formState.err = e.name;
       errorToast?.show();
     })
     .finally(() => {
       formState.uploading = false;
+      formState.abortController = undefined;
     });
 }
 
@@ -110,7 +129,7 @@ onMounted(() => {
     color-class="danger"
   >
     There has been some Error.<br />
-    Try again later
+    Code: {{ formState.err }}
   </bootstrap-toast>
   <bootstrap-modal
     :modal-id="modalId"
@@ -135,6 +154,7 @@ onMounted(() => {
     </template>
     <template #body>
       <div class="container-fluid">
+        <p>{{ formState.err }}</p>
         <div class="row">
           <form
             class="col-7"
@@ -207,7 +227,7 @@ onMounted(() => {
             {{ uploadProgress }}%
           </div>
         </div>
-        <span v-if="formState.uploadDone > 0">
+        <span>
           {{ fsize(formState.uploadDone) }} /
           {{ fsize(formState.uploadTotal) }}
         </span>
@@ -216,17 +236,19 @@ onMounted(() => {
         Close
       </button>
       <button
-        :disabled="formState.uploading"
+        v-if="formState.uploading"
+        type="button"
+        class="btn btn-danger"
+        @click="formState.abortController?.abort()"
+      >
+        Cancel
+      </button>
+      <button
+        v-else
         type="submit"
         :form="'uploadObjectForm' + randomIDSuffix"
         class="btn btn-primary"
       >
-        <span
-          v-if="formState.uploading"
-          class="spinner-border spinner-border-sm"
-          role="status"
-          aria-hidden="true"
-        ></span>
         Upload
       </button>
     </template>
diff --git a/src/components/parameter-schema/ParameterSchemaFormComponent.vue b/src/components/parameter-schema/ParameterSchemaFormComponent.vue
index eb230b0..1de0657 100644
--- a/src/components/parameter-schema/ParameterSchemaFormComponent.vue
+++ b/src/components/parameter-schema/ParameterSchemaFormComponent.vue
@@ -254,7 +254,7 @@ onMounted(() => {
   if (props.schema) updateSchema(props.schema);
   if (props.clowmInfo?.exampleParameters)
     Tooltip.getOrCreateInstance("#exampleDataButton");
-  bucketRepository.fetchBuckets();
+  bucketRepository.fetchOwnBuckets();
   bucketRepository.fetchOwnPermissions();
   keyRepository.fetchS3Keys();
   resourceRepository.fetchPublicResources();
diff --git a/src/router/adminRoutes.ts b/src/router/adminRoutes.ts
index 00093a7..0567cbd 100644
--- a/src/router/adminRoutes.ts
+++ b/src/router/adminRoutes.ts
@@ -19,6 +19,15 @@ export const adminRoutes: RouteRecordRaw[] = [
       title: "Manage Users",
     },
   },
+  {
+    path: "admin/buckets",
+    name: "admin-buckets",
+    component: () => import("../views/admin/AdminBucketsView.vue"),
+    meta: {
+      requiresAdminRole: true,
+      title: "Manage Buckets",
+    },
+  },
   {
     path: "admin/sync-requests",
     name: "admin-sync-requests",
diff --git a/src/stores/buckets.ts b/src/stores/buckets.ts
index 6c13662..53ada47 100644
--- a/src/stores/buckets.ts
+++ b/src/stores/buckets.ts
@@ -2,7 +2,7 @@ import { defineStore } from "pinia";
 import {
   BucketPermissionService,
   BucketService,
-  Constraint,
+  BucketType,
   Permission,
 } from "@/client/s3proxy";
 import type {
@@ -30,12 +30,6 @@ export const useBucketStore = defineStore({
     buckets(): BucketOut[] {
       const tempList = Object.values(this.bucketMapping);
       tempList.sort((bucketA, bucketB) => {
-        if (bucketA.owner_constraint) {
-          return -1;
-        }
-        if (bucketB.owner_constraint) {
-          return 1;
-        }
         return bucketA.name > bucketB.name ? 1 : -1;
       });
       return tempList;
@@ -63,11 +57,7 @@ export const useBucketStore = defineStore({
           return false;
         }
         // If the bucket doesn't exist, then false
-        if (this.bucketMapping[bucketName] == undefined) {
-          return false;
-        }
-        // If there is a constraint on the bucket, then false otherwise true
-        return this.bucketMapping[bucketName].owner_constraint == null;
+        return this.bucketMapping[bucketName] != undefined;
       };
     },
     writableBuckets(): BucketOut[] {
@@ -75,11 +65,7 @@ export const useBucketStore = defineStore({
         if (this.ownPermissions[bucket.name] != undefined) {
           return this.ownPermissions[bucket.name].permission !== "READ";
         }
-        // If the user owns the bucket, check the bucket constraint
-        return (
-          bucket.owner_constraint == null ||
-          bucket.owner_constraint == Constraint.WRITE
-        );
+        return true;
       });
     },
     writableBucket(): (bucketName: string) => boolean {
@@ -88,14 +74,8 @@ export const useBucketStore = defineStore({
         if (this.ownPermissions[bucketName] != undefined) {
           return this.ownPermissions[bucketName].permission !== "READ";
         }
-        // If the user owns the bucket, check the bucket constraint
-        if (this.bucketMapping[bucketName] != undefined) {
-          return (
-            this.bucketMapping[bucketName].owner_constraint == null ||
-            this.bucketMapping[bucketName].owner_constraint == Constraint.WRITE
-          );
-        }
-        return false;
+        // If the bucket doesn't exist, then false
+        return this.bucketMapping[bucketName] != undefined;
       };
     },
     readableBucket(): (bucketName: string) => boolean {
@@ -106,16 +86,29 @@ export const useBucketStore = defineStore({
         }
         // If the user owns the bucket, check the bucket constraint
         if (this.bucketMapping[bucketName] != null) {
-          return (
-            this.bucketMapping[bucketName].owner_constraint == null ||
-            this.bucketMapping[bucketName].owner_constraint == Constraint.READ
-          );
+          return true;
         }
         return false;
       };
     },
   },
   actions: {
+    fetchBuckets(
+      ownerId?: string,
+      bucketType?: BucketType,
+    ): Promise<BucketOut[]> {
+      return BucketService.bucketListBuckets(ownerId, bucketType).then(
+        (buckets) => {
+          const userRepository = useAuthStore();
+          userRepository.fetchUsernames(
+            buckets
+              .map((bucket) => bucket.owner_id)
+              .filter((owner) => owner != userRepository.currentUID),
+          );
+          return buckets;
+        },
+      );
+    },
     fetchOwnPermissions(
       onFinally?: () => void,
     ): Promise<BucketPermissionOut[]> {
@@ -140,22 +133,16 @@ export const useBucketStore = defineStore({
       delete this.ownPermissions[bucketName];
       delete this.bucketMapping[bucketName];
     },
-    fetchBuckets(onFinally?: () => void): Promise<BucketOut[]> {
+    fetchOwnBuckets(onFinally?: () => void): Promise<BucketOut[]> {
       if (this.buckets.length > 0) {
         onFinally?.();
       }
       const userStore = useAuthStore();
-      return BucketService.bucketListBuckets(userStore.currentUID)
+      return this.fetchBuckets(userStore.currentUID)
         .then((buckets) => {
           for (const bucket of buckets) {
             this.bucketMapping[bucket.name] = bucket;
           }
-          const userRepository = useAuthStore();
-          userRepository.fetchUsernames(
-            buckets
-              .map((bucket) => bucket.owner_id)
-              .filter((owner) => owner != userRepository.currentUID),
-          );
           return buckets;
         })
         .finally(onFinally);
@@ -267,15 +254,27 @@ export const useBucketStore = defineStore({
         return permissionOut;
       });
     },
-    togglePublicState(
+    async updatePublicState(
       bucketName: string,
       public_: boolean,
     ): Promise<BucketOut> {
-      return BucketService.bucketUpdateBucketPublicState(bucketName, {
-        public: public_,
-      }).then((bucket) => {
-        this.bucketMapping[bucketName] = bucket;
-        return bucket;
+      const bucket = await BucketService.bucketUpdateBucketPublicState(
+        bucketName,
+        {
+          public: public_,
+        },
+      );
+      this.bucketMapping[bucketName] = bucket;
+      return bucket;
+    },
+    updateBucketLimits(
+      bucketName: string,
+      size_limit?: number | null,
+      object_limit?: number | null,
+    ): Promise<BucketOut> {
+      return BucketService.bucketUpdateBucketLimits(bucketName, {
+        size_limit: size_limit,
+        object_limit: object_limit,
       });
     },
   },
diff --git a/src/stores/s3objects.ts b/src/stores/s3objects.ts
index e314ae5..149a3eb 100644
--- a/src/stores/s3objects.ts
+++ b/src/stores/s3objects.ts
@@ -19,6 +19,7 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
 import dayjs from "dayjs";
 import { Upload } from "@aws-sdk/lib-storage";
 import type { Progress } from "@aws-sdk/lib-storage";
+import type { AbortController } from "@smithy/types";
 
 export const useS3ObjectStore = defineStore({
   id: "s3objects",
@@ -192,6 +193,7 @@ export const useS3ObjectStore = defineStore({
       srcObject: S3Object,
       destBucket: string,
       destKey: string,
+      abortController?: AbortController,
     ): Promise<S3Object> {
       if (srcObject.Key == undefined) {
         return Promise.resolve({});
@@ -201,21 +203,24 @@ export const useS3ObjectStore = defineStore({
         CopySource: encodeURI(`/${srcBucket}/${srcObject.Key}`),
         Key: destKey,
       });
-      return this.client.send(command).then(() => {
-        const newObj = {
-          Key: destKey,
-          Size: srcObject.Size,
-          LastModified: dayjs().toDate(),
-        };
-        this._pushObject(destBucket, newObj);
-        return newObj;
-      });
+      return this.client
+        .send(command, { abortSignal: abortController?.signal })
+        .then(() => {
+          const newObj = {
+            Key: destKey,
+            Size: srcObject.Size,
+            LastModified: dayjs().toDate(),
+          };
+          this._pushObject(destBucket, newObj);
+          return newObj;
+        });
     },
     async uploadObjectFile(
       bucketName: string,
       key: string,
       file: File,
       onProgress?: (progress: Progress) => void,
+      abortController?: AbortController,
     ): Promise<S3Object> {
       const parallelUploads3 = new Upload({
         client: this.client,
@@ -227,6 +232,7 @@ export const useS3ObjectStore = defineStore({
         },
         queueSize: 4, // optional concurrency configuration
         leavePartsOnError: false, // optional manually handle dropped parts
+        abortController: abortController,
       });
       if (onProgress != undefined) {
         parallelUploads3.on("httpUploadProgress", onProgress);
diff --git a/src/views/admin/AdminBucketsView.vue b/src/views/admin/AdminBucketsView.vue
new file mode 100644
index 0000000..ae81c05
--- /dev/null
+++ b/src/views/admin/AdminBucketsView.vue
@@ -0,0 +1,216 @@
+<script setup lang="ts">
+import { computed, reactive } from "vue";
+import { type BucketOut, BucketType } from "@/client/s3proxy";
+import SearchUserModal from "@/components/modals/SearchUserModal.vue";
+import type { User } from "@/client/auth";
+import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
+import { useBucketStore } from "@/stores/buckets";
+import { useNameStore } from "@/stores/names";
+import { filesize } from "filesize";
+import dayjs from "dayjs";
+import UpdateBucketLimitsModal from "@/components/admin/UpdateBucketLimitsModal.vue";
+
+const bucketState = reactive<{
+  buckets: BucketOut[];
+  ownerId: string;
+  loading: boolean;
+  searched: boolean;
+  filterString: string;
+  updateLimitsBucket: BucketOut;
+}>({
+  buckets: [],
+  ownerId: "",
+  loading: false,
+  searched: false,
+  filterString: "",
+  updateLimitsBucket: {
+    name: "",
+    description: "",
+    owner_id: "",
+    public: false,
+    created_at: 0,
+  },
+});
+
+const bucketRepository = useBucketStore();
+const nameRepository = useNameStore();
+
+const updateLimitsModalId = "admin-update-bucket-limits-modal";
+
+const filteredBuckets = computed<BucketOut[]>(() =>
+  bucketState.buckets.filter((bucket) =>
+    bucket.name.includes(bucketState.filterString),
+  ),
+);
+
+function updateUser(user: User) {
+  bucketState.ownerId = user.uid;
+}
+
+function resetForm() {
+  bucketState.ownerId = "";
+  bucketState.filterString = "";
+  bucketState.buckets = [];
+  bucketState.searched = false;
+}
+
+function searchBuckets() {
+  bucketState.loading = true;
+  bucketRepository
+    .fetchBuckets(
+      bucketState.ownerId ? bucketState.ownerId : undefined,
+      BucketType.OWN,
+    )
+    .then((buckets) => {
+      bucketState.buckets = buckets;
+    })
+    .finally(() => {
+      bucketState.loading = false;
+      bucketState.searched = true;
+    });
+}
+
+function limitsUpdated(bucket: BucketOut) {
+  const bucketIndex = bucketState.buckets.findIndex(
+    (b) => b.name == bucket.name,
+  );
+  if (bucketIndex > -1) {
+    bucketState.buckets[bucketIndex] = bucket;
+  }
+}
+</script>
+
+<template>
+  <search-user-modal
+    modal-id="admin-bucket-search-user-modal"
+    @user-found="updateUser"
+  />
+  <update-bucket-limits-modal
+    :bucket="bucketState.updateLimitsBucket"
+    :modal-id="updateLimitsModalId"
+    @updated-limits="limitsUpdated"
+  />
+  <div class="border-bottom mb-4">
+    <h2>Manage Buckets</h2>
+  </div>
+  <form @submit.prevent="searchBuckets" id="admin-bucket-search-form">
+    <div class="d-flex justify-content-evenly align-content-center mb-4">
+      <div class="flex-fill mx-2">
+        <label for="admin-bucket-name-search" class="form-label"
+          >Name of bucket</label
+        >
+        <div class="input-group">
+          <div class="input-group-text">
+            <font-awesome-icon icon="fa-solid fa-magnifying-glass" />
+          </div>
+          <input
+            id="admin-bucket-name-search"
+            type="text"
+            class="form-control"
+            placeholder="Search Buckets"
+            maxlength="32"
+            v-model.trim="bucketState.filterString"
+          />
+        </div>
+      </div>
+      <div class="flex-fill mx-2">
+        <label for="admin-bucket-user-search" class="form-label"
+          >Name of the owner</label
+        >
+        <div class="input-group">
+          <div class="input-group-text">
+            <font-awesome-icon icon="fa-solid fa-user" />
+          </div>
+          <input
+            id="admin-bucket-user-search"
+            type="text"
+            class="form-control"
+            readonly
+            :value="nameRepository.getName(bucketState.ownerId)"
+            placeholder="Search for owner"
+            data-bs-toggle="modal"
+            data-bs-target="#admin-bucket-search-user-modal"
+          />
+        </div>
+      </div>
+    </div>
+    <button
+      type="submit"
+      class="btn btn-primary w-fit"
+      :disabled="bucketState.loading"
+    >
+      Search
+    </button>
+    <button
+      type="button"
+      class="btn-primary btn w-fit ms-4"
+      :disabled="bucketState.loading"
+      @click="resetForm"
+    >
+      Reset
+    </button>
+  </form>
+  <table class="table table-striped align-middle" v-if="bucketState.buckets">
+    <thead>
+      <tr>
+        <th scope="col"><b>Name</b></th>
+        <th scope="col" class="description-column">Description</th>
+        <th scope="col">Created at</th>
+        <th scope="col">Size Limit</th>
+        <th scope="col">Object Limit</th>
+        <th scope="col">Owner</th>
+        <th scope="col"></th>
+      </tr>
+    </thead>
+    <tbody v-if="filteredBuckets.length === 0">
+      <tr>
+        <td colspan="7" class="text-center fst-italic fw-light">
+          <template v-if="bucketState.searched"
+            >No bucket found with specified filters
+          </template>
+          <template v-else>Select a filter and search for buckets</template>
+        </td>
+      </tr>
+    </tbody>
+    <tbody v-else>
+      <tr v-for="bucket in filteredBuckets" :key="bucket.name">
+        <th scope="row">{{ bucket.name }}</th>
+        <td class="description-column">{{ bucket.description }}</td>
+        <td>
+          {{ dayjs.unix(bucket.created_at).format("DD.MM.YYYY HH:mm:ss") }}
+        </td>
+        <td>
+          <span v-if="bucket.size_limit">
+            {{ filesize(1024 * bucket.size_limit) }}
+          </span>
+          <span v-else>-</span>
+        </td>
+        <td>
+          <span v-if="bucket.object_limit">
+            {{ bucket.object_limit }}
+          </span>
+          <span v-else>-</span>
+        </td>
+        <td>{{ nameRepository.getName(bucket.owner_id) }}</td>
+        <td>
+          <button
+            type="button"
+            class="btn btn-secondary btn-sm"
+            @click="bucketState.updateLimitsBucket = bucket"
+            data-bs-toggle="modal"
+            :data-bs-target="'#' + updateLimitsModalId"
+          >
+            Edit limits
+          </button>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</template>
+
+<style scoped>
+.description-column {
+  word-break: break-all;
+  max-width: 400px;
+}
+</style>
diff --git a/src/views/object-storage/BucketView.vue b/src/views/object-storage/BucketView.vue
index 0608ca0..73f9c56 100644
--- a/src/views/object-storage/BucketView.vue
+++ b/src/views/object-storage/BucketView.vue
@@ -683,7 +683,7 @@ function getObjectFileName(key: string): string {
               >
             </td>
             <td>
-              {{ filesize(obj.Size ?? 0, { base: 2, standard: "jedec" }) }}
+              {{ filesize(obj.Size ?? 0) }}
             </td>
             <!-- Show buttons with dropdown menu if row is an object -->
             <td class="text-end">
diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue
index fc18443..1781a60 100644
--- a/src/views/object-storage/BucketsView.vue
+++ b/src/views/object-storage/BucketsView.vue
@@ -33,7 +33,7 @@ let refreshTimeout: NodeJS.Timeout | undefined = undefined;
 function fetchBuckets() {
   bucketRepository.fetchOwnPermissions();
   bucketRepository
-    .fetchBuckets(() => {
+    .fetchOwnBuckets(() => {
       bucketsState.loading = false;
     })
     .then((buckets) => {
@@ -176,7 +176,6 @@ onMounted(() => {
               created_at: 0,
               owner_id: '',
               public: false,
-              owner_constraint: null,
             }"
           ></bucket-list-item>
         </div>
-- 
GitLab