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