From 2e46afacbb2dd732f54b8b643d56513e9d9c4aac 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:19:03 +0200
Subject: [PATCH] Add view to list buckets and edit their limits

#112
---
 package-lock.json                             | 332 +++++++++---------
 src/App.vue                                   |   2 +-
 src/components/AppHeader.vue                  |   7 +
 .../admin/UpdateBucketLimitsModal.vue         | 236 +++++++++++++
 src/components/modals/SearchUserModal.vue     |   4 +-
 .../object-storage/BucketListItem.vue         |  10 +-
 .../modals/BucketDetailModal.vue              |   8 +-
 .../ParameterSchemaFormComponent.vue          |   2 +-
 src/router/adminRoutes.ts                     |   9 +
 src/stores/buckets.ts                         |  64 ++--
 src/views/admin/AdminBucketsView.vue          | 216 ++++++++++++
 src/views/object-storage/BucketView.vue       |   2 +-
 src/views/object-storage/BucketsView.vue      |   2 +-
 13 files changed, 686 insertions(+), 208 deletions(-)
 create mode 100644 src/components/admin/UpdateBucketLimitsModal.vue
 create mode 100644 src/views/admin/AdminBucketsView.vue

diff --git a/package-lock.json b/package-lock.json
index bd37a59..dd0ffb9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1575,9 +1575,9 @@
       }
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.1.tgz",
-      "integrity": "sha512-92/y0TqNLRYOTXpm6Z7mnpvKAG9P7qmK7yJeRJSdzElNCUnsgbpAsGqerUboYRIQKzgfq4pWu9xVkgpWLfmNsw==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.1.tgz",
-      "integrity": "sha512-ttWB6ZCfRLuDIUiE0yiu5gcqOsYjA5F7kEV1ggHMj20FwLZ8A1FMeahZJFl/pnOmcnD2QL0z4AcDuo27utGU8A==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.1.tgz",
-      "integrity": "sha512-QLDvPLetbqjHojTGFw9+nuSP3YY/iz2k1cep6crYlr97sS+ZJ0W43b8Z0zC00+lnFZj6JSNxiA4DjboNQMuh1A==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.1.tgz",
-      "integrity": "sha512-TAUK/D8khRrRIa1KwRzo8JNKk3tcqaeXWdtsiLgA8zmACWwlWLjPCJ4DULGHQrMkeBjp1Cd3Yuwx04lZgFx5Vg==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.1.tgz",
-      "integrity": "sha512-KO+WGZjrh6zyFTD1alIFkfdtxf8B4BC+hqd3kBZHscPLvE5FR/6QKsyuCT0JlERxxYBSUKNUQ/UHyX5uwO1x2A==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.1.tgz",
-      "integrity": "sha512-NqxbllzIB1WoAo4ThUXVtd21iiM5IHMTTXmXySKBLVcZvkU0HIZmatlP7hLzb5yQubcmdIeWmncd2NdsjocEiw==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.1.tgz",
-      "integrity": "sha512-snma5NvV8y7IECQ5rq0sr0f3UUu+92NVmG/913JXJMcXo84h9ak9TA5UI9Cl2XRM9j3m37QwDBtEYnJzRkSmxA==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.1.tgz",
-      "integrity": "sha512-KOvqGprlD84ueivhCi2flvcUwDRD20mAsE3vxQNVEI2Di9tnPGAfEu6UcrSPZbM+jG2w1oSr43hrPo0RNg6GGg==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.1.tgz",
-      "integrity": "sha512-/gsNwtiGLqYwN4vP+EIdUC6Q6LTlpupWqokqIndvZcjn9ig/5P01WyaYCU2wvfL/2Z82jp5kX8c1mDBOvCP3zg==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.1.tgz",
-      "integrity": "sha512-uU8zuGkQfGqfD9w6VRJZI4IuG4JIfNxxJgEmLMAmPVHREKGsxFVfgHy5c6CexQF2vOfgjB33OsET3Vdn2lln9A==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.1.tgz",
-      "integrity": "sha512-lsjLtDgtcGFEuBP6yrXwkRN5/wKlvUZtfbKZZu0yaoNpiBL4epgnO21osAALIspVRnl4qZgyLFd8xjCYYWgwfw==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.1.tgz",
-      "integrity": "sha512-N2ZizKhUryqqrMfdCnjhJhZRgv61C6gK+hwVtCIKC8ts8J+go+vqENnGexwg21nHIOvLN5mBM8a7DI2vlyIOPg==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.1.tgz",
-      "integrity": "sha512-5ICeMxqg66FrOA2AbnBQ2TJVxfvZsKLxmof0ibvPLaYtbsJqnTUtJOofgWb46Gjd4uZcA4rdsp4JCxegzQPqCg==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.1.tgz",
-      "integrity": "sha512-1vIP6Ce02L+qWD7uZYRiFiuAJo3m9kARatWmFSnss0gZnVj2Id7OPUU9gm49JPGasgcR3xMqiH3fqBJ8t00yVg==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.1.tgz",
-      "integrity": "sha512-Y3M92DcVsT6LoP+wrKpoUWPaazaP1fzbNkp0a0ZSj5Y//+pQVfVe/tQdsYQQy7dwXR30ZfALUIc9PCh9Izir6w==",
+      "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.16.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.1.tgz",
-      "integrity": "sha512-x0fvpHMuF7fK5r8oZxSi8VYXkrVmRgubXpO/wcf15Lk3xZ4Jvvh5oG+u7Su1776A7XzVKZhD2eRc4t7H50gL3w==",
+      "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": {
@@ -2732,12 +2732,12 @@
       }
     },
     "node_modules/@vue/compiler-core": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.23.tgz",
-      "integrity": "sha512-HAFmuVEwNqNdmk+w4VCQ2pkLk1Vw4XYiiyxEp3z/xvl14aLTUBw2OfVH3vBcx+FtGsynQLkkhK410Nah1N2yyQ==",
+      "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.23",
+        "@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.23",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.23.tgz",
-      "integrity": "sha512-t0b9WSTnCRrzsBGrDd1LNR5HGzYTr7LX3z6nNBG+KGvZLqrT0mY6NsMzOqlVMBKKXKVuusbbB5aOOFgTY+senw==",
+      "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.23",
-        "@vue/shared": "3.4.23"
+        "@vue/compiler-core": "3.4.24",
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/compiler-sfc": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.23.tgz",
-      "integrity": "sha512-fSDTKTfzaRX1kNAUiaj8JB4AokikzStWgHooMhaxyjZerw624L+IAP/fvI4ZwMpwIh8f08PVzEnu4rg8/Npssw==",
-      "dependencies": {
-        "@babel/parser": "^7.24.1",
-        "@vue/compiler-core": "3.4.23",
-        "@vue/compiler-dom": "3.4.23",
-        "@vue/compiler-ssr": "3.4.23",
-        "@vue/shared": "3.4.23",
+      "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"
       }
@@ -2787,12 +2787,12 @@
       }
     },
     "node_modules/@vue/compiler-ssr": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.23.tgz",
-      "integrity": "sha512-hb6Uj2cYs+tfqz71Wj6h3E5t6OKvb4MVcM2Nl5i/z1nv1gjEhw+zYaNOV+Xwn+SSN/VZM0DgANw5TuJfxfezPg==",
+      "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.23",
-        "@vue/shared": "3.4.23"
+        "@vue/compiler-dom": "3.4.24",
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/devtools-api": {
@@ -2862,48 +2862,48 @@
       }
     },
     "node_modules/@vue/reactivity": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.23.tgz",
-      "integrity": "sha512-GlXR9PL+23fQ3IqnbSQ8OQKLodjqCyoCrmdLKZk3BP7jN6prWheAfU7a3mrltewTkoBm+N7qMEb372VHIkQRMQ==",
+      "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.23"
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/runtime-core": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.23.tgz",
-      "integrity": "sha512-FeQ9MZEXoFzFkFiw9MQQ/FWs3srvrP+SjDKSeRIiQHIhtkzoj0X4rWQlRNHbGuSwLra6pMyjAttwixNMjc/xLw==",
+      "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.23",
-        "@vue/shared": "3.4.23"
+        "@vue/reactivity": "3.4.24",
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/runtime-dom": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.23.tgz",
-      "integrity": "sha512-RXJFwwykZWBkMiTPSLEWU3kgVLNAfActBfWFlZd0y79FTUxexogd0PLG4HH2LfOktjRxV47Nulygh0JFXe5f9A==",
+      "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.23",
-        "@vue/shared": "3.4.23",
+        "@vue/runtime-core": "3.4.24",
+        "@vue/shared": "3.4.24",
         "csstype": "^3.1.3"
       }
     },
     "node_modules/@vue/server-renderer": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.23.tgz",
-      "integrity": "sha512-LDwGHtnIzvKFNS8dPJ1SSU5Gvm36p2ck8wCZc52fc3k/IfjKcwCyrWEf0Yag/2wTFUBXrqizfhK9c/mC367dXQ==",
+      "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.23",
-        "@vue/shared": "3.4.23"
+        "@vue/compiler-ssr": "3.4.24",
+        "@vue/shared": "3.4.24"
       },
       "peerDependencies": {
-        "vue": "3.4.23"
+        "vue": "3.4.24"
       }
     },
     "node_modules/@vue/shared": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.23.tgz",
-      "integrity": "sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg=="
+      "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",
@@ -5909,9 +5909,9 @@
       }
     },
     "node_modules/rollup": {
-      "version": "4.16.1",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.1.tgz",
-      "integrity": "sha512-5CaD3MPDlPKfhqzRvWXK96G6ELJfPZNb3LHiZxTHgDdC6jvwfGz2E8nY+9g1ONk4ttHsK1WaFP19Js4PSr1E3g==",
+      "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"
@@ -5924,22 +5924,22 @@
         "npm": ">=8.0.0"
       },
       "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.16.1",
-        "@rollup/rollup-android-arm64": "4.16.1",
-        "@rollup/rollup-darwin-arm64": "4.16.1",
-        "@rollup/rollup-darwin-x64": "4.16.1",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.16.1",
-        "@rollup/rollup-linux-arm-musleabihf": "4.16.1",
-        "@rollup/rollup-linux-arm64-gnu": "4.16.1",
-        "@rollup/rollup-linux-arm64-musl": "4.16.1",
-        "@rollup/rollup-linux-powerpc64le-gnu": "4.16.1",
-        "@rollup/rollup-linux-riscv64-gnu": "4.16.1",
-        "@rollup/rollup-linux-s390x-gnu": "4.16.1",
-        "@rollup/rollup-linux-x64-gnu": "4.16.1",
-        "@rollup/rollup-linux-x64-musl": "4.16.1",
-        "@rollup/rollup-win32-arm64-msvc": "4.16.1",
-        "@rollup/rollup-win32-ia32-msvc": "4.16.1",
-        "@rollup/rollup-win32-x64-msvc": "4.16.1",
+        "@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"
       }
     },
@@ -6695,15 +6695,15 @@
       }
     },
     "node_modules/vue": {
-      "version": "3.4.23",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.23.tgz",
-      "integrity": "sha512-X1y6yyGJ28LMUBJ0k/qIeKHstGd+BlWQEOT40x3auJFTmpIhpbKLgN7EFsqalnJXq1Km5ybDEsp6BhuWKciUDg==",
-      "dependencies": {
-        "@vue/compiler-dom": "3.4.23",
-        "@vue/compiler-sfc": "3.4.23",
-        "@vue/runtime-dom": "3.4.23",
-        "@vue/server-renderer": "3.4.23",
-        "@vue/shared": "3.4.23"
+      "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.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": "*"
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/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/BucketListItem.vue b/src/components/object-storage/BucketListItem.vue
index 8798943..e27f990 100644
--- a/src/components/object-storage/BucketListItem.vue
+++ b/src/components/object-storage/BucketListItem.vue
@@ -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();
     })
@@ -274,15 +274,13 @@ onMounted(() => {
                   :maximum="1024 * bucket.size_limit"
                   :current-val="bucketMeta[1]"
                   :label="
-                    filesize(bucketMeta[1], { base: 2 }) +
+                    filesize(bucketMeta[1]) +
                     '/' +
-                    filesize(1024 * bucket.size_limit, {
-                      base: 2,
-                    })
+                    filesize(1024 * bucket.size_limit)
                   "
                 />
               </td>
-              <td v-else>{{ filesize(bucketMeta[1], { base: 2 }) }}</td>
+              <td v-else>{{ filesize(bucketMeta[1]) }}</td>
             </tr>
             <tr>
               <th scope="row">
diff --git a/src/components/object-storage/modals/BucketDetailModal.vue b/src/components/object-storage/modals/BucketDetailModal.vue
index 7b56297..2a2b0b1 100644
--- a/src/components/object-storage/modals/BucketDetailModal.vue
+++ b/src/components/object-storage/modals/BucketDetailModal.vue
@@ -74,13 +74,9 @@ const s3Link = computed<string>(() => "s3://" + props.bucket.name);
             <tr>
               <th scope="row">Size</th>
               <td>
-                {{ filesize(bucketMeta[1], { base: 2 })
+                {{ filesize(bucketMeta[1])
                 }}<span v-if="bucket.size_limit"
-                  >/{{
-                    filesize(1024 * bucket.size_limit, {
-                      base: 2,
-                    })
-                  }}</span
+                  >/{{ filesize(1024 * bucket.size_limit) }}</span
                 >
                 <bucket-limit-progress-bar
                   v-if="bucket.size_limit"
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 d450eea..53ada47 100644
--- a/src/stores/buckets.ts
+++ b/src/stores/buckets.ts
@@ -2,6 +2,7 @@ import { defineStore } from "pinia";
 import {
   BucketPermissionService,
   BucketService,
+  BucketType,
   Permission,
 } from "@/client/s3proxy";
 import type {
@@ -56,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 true;
+        return this.bucketMapping[bucketName] != undefined;
       };
     },
     writableBuckets(): BucketOut[] {
@@ -77,11 +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 true;
-        }
-        return false;
+        // If the bucket doesn't exist, then false
+        return this.bucketMapping[bucketName] != undefined;
       };
     },
     readableBucket(): (bucketName: string) => boolean {
@@ -99,6 +93,22 @@ export const useBucketStore = defineStore({
     },
   },
   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[]> {
@@ -123,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);
@@ -250,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/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 a9cb8d4..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) => {
-- 
GitLab