diff --git a/src/components/object-storage/BucketListItem.vue b/src/components/object-storage/BucketListItem.vue index 2bbfd5607bf450de84cb3f42bbe2f7d23a930072..76c1025eb5c42a45c953e8c5656060d3de7dbf23 100644 --- a/src/components/object-storage/BucketListItem.vue +++ b/src/components/object-storage/BucketListItem.vue @@ -13,6 +13,7 @@ import { computed, onMounted } from "vue"; import { Tooltip } from "bootstrap"; import { useBucketStore } from "@/stores/buckets"; import { useRouter } from "vue-router"; +import { useAuthStore } from "@/stores/users"; const props = defineProps<{ active: boolean; @@ -23,6 +24,7 @@ const props = defineProps<{ const randomIDSuffix = Math.random().toString(16).substring(2, 8); const permissionRepository = useBucketStore(); +const userRepository = useAuthStore(); const router = useRouter(); const permission = computed<BucketPermissionOut | undefined>( @@ -41,6 +43,8 @@ function permissionDeleted(perm: BucketPermissionIn) { onMounted(() => { if (!props.loading) { new Tooltip("#tooltip-" + randomIDSuffix); + new Tooltip("#ownBucketIcon-" + randomIDSuffix); + new Tooltip("#sharedBucketIcon-" + randomIDSuffix); } }); </script> @@ -86,8 +90,44 @@ onMounted(() => { params: { bucketName: bucket.name, subFolders: [] }, }" > - <span class="text-truncate" style="width: 80%">{{ bucket.name }}</span> - <div> + <span class="text-truncate flex-grow-3"> + <template v-if="bucket.owner_constraint === 'READ'" + >Upload Bucket</template + > + <template v-else-if="bucket.owner_constraint === 'WRITE'" + >Download Bucket</template + > + <template v-else>{{ bucket.name }}</template> + </span> + <div class="text-nowrap"> + <font-awesome-icon + :hidden="bucket.owner !== userRepository.currentUID" + :id="'ownBucketIcon-' + randomIDSuffix" + icon="fa-solid fa-user" + :class="{ + 'me-2': + props.active || + permission || + ( + permissionRepository.bucketPermissionsMapping[bucket.name] ?? + [] + ).length > 0, + }" + data-bs-toogle="tooltip" + data-bs-title="Own Bucket" + /> + <font-awesome-icon + :hidden=" + !permission && + (permissionRepository.bucketPermissionsMapping[bucket.name] ?? []) + .length === 0 + " + :id="'sharedBucketIcon-' + randomIDSuffix" + icon="fa-solid fa-users" + :class="{ 'me-2': props.active }" + data-bs-toogle="tooltip" + data-bs-title="Shared Bucket" + /> <font-awesome-icon v-if="props.active && !permission && props.deletable" icon="fa-solid fa-trash" @@ -111,7 +151,7 @@ onMounted(() => { <table class="table table-sm table-borderless mb-0"> <tbody> <tr v-if="permission"> - <th scope="row" class="fw-bold">Permission</th> + <th scope="row" class="fw-bold">Permission:</th> <td> <a href="#" @@ -121,6 +161,30 @@ onMounted(() => { > </td> </tr> + <tr v-if="permission"> + <th scope="row" class="fw-bold">Owner:</th> + <td class="text-truncate"> + {{ userRepository.userMapping[bucket.owner] }} + </td> + </tr> + <tr + v-if=" + ( + permissionRepository.bucketPermissionsMapping[bucket.name] ?? + [] + ).length > 0 + " + > + <th scope="row" class="fw-bold">Permissions:</th> + <td> + <a + href="#" + data-bs-toggle="modal" + data-bs-target="#permission-list-modal" + >View</a + > + </td> + </tr> <tr> <th scope="row" class="fw-bold">Created:</th> <td> @@ -156,12 +220,15 @@ onMounted(() => { .delete-icon { color: white; } + .delete-icon:hover { color: var(--bs-danger) !important; } + .info-icon { color: white; } + .info-icon:hover { color: var(--bs-info) !important; } diff --git a/src/stores/buckets.ts b/src/stores/buckets.ts index f9a0147355d4dbfc116f6720ccf1d41182d03ca8..c33eda777fa16e9ee958e48ca0ff60df044177a1 100644 --- a/src/stores/buckets.ts +++ b/src/stores/buckets.ts @@ -28,7 +28,17 @@ export const useBucketStore = defineStore({ }, getters: { buckets(): BucketOut[] { - return Object.values(this.bucketMapping); + const tempList = Object.values(this.bucketMapping); + tempList.sort((bucketA, bucketB) => { + if (bucketA.owner_constraint) { + return -1; + } + if (bucketB.owner_constraint) { + return 1; + } + return bucketA.name > bucketB.name ? 1 : -1; + }); + return tempList; }, ownBucketsAndFullPermissions(): string[] { const authStore = useAuthStore(); @@ -143,6 +153,12 @@ export const useBucketStore = defineStore({ for (const bucket of buckets) { this.bucketMapping[bucket.name] = bucket; } + const userRepository = useAuthStore(); + userRepository.fetchUsernames( + buckets + .map((bucket) => bucket.owner) + .filter((owner) => owner != userRepository.currentUID), + ); return buckets; }) .finally(onFinally); diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue index f55ae09f8aba518be1a5a3ce00c8ee69f880c02f..e93c4b7f098cea89cc5d3f4fc88d86dc0f3674e7 100644 --- a/src/views/object-storage/BucketsView.vue +++ b/src/views/object-storage/BucketsView.vue @@ -103,7 +103,7 @@ onMounted(() => { class="btn btn-light border shadow-sm" data-bs-title="Refresh Data Buckets" data-bs-toggle="tooltip" - @click.stop.prevent="refreshBuckets" + @click="refreshBuckets" > <font-awesome-icon icon="fa-solid fa-arrow-rotate-right" diff --git a/src/views/object-storage/S3KeysView.vue b/src/views/object-storage/S3KeysView.vue index 47b89e2324fcdf520c193e22371a644adf5a5fc3..7a6ab5ded4a6802f6108a418cf68c4949869aac5 100644 --- a/src/views/object-storage/S3KeysView.vue +++ b/src/views/object-storage/S3KeysView.vue @@ -9,13 +9,8 @@ import { Toast, Tooltip } from "bootstrap"; const authStore = useAuthStore(); -authStore.$onAction(({ name, args }) => { - if (name === "updateUser") { - refreshKeys(args[0].uid); - } -}); - let successToast: Toast | null = null; +let refreshTimeout: NodeJS.Timeout | undefined = undefined; const keyState = reactive<{ keys: S3Key[]; @@ -31,8 +26,8 @@ const keyState = reactive<{ const allowKeyDeletion = computed<boolean>(() => keyState.keys.length > 1); -function refreshKeys(uid: string) { - S3KeyService.s3KeyGetUserKeys(uid) +function fetchKeys() { + S3KeyService.s3KeyGetUserKeys(authStore.currentUID) .then((keys) => { if (keyState.activeKey >= keys.length) { keyState.activeKey = keys.length - 1; @@ -40,7 +35,16 @@ function refreshKeys(uid: string) { keyState.keys = keys; }) .catch((err) => console.error(err)) - .finally(() => (keyState.initialLoading = false)); + .finally(() => { + keyState.initialLoading = false; + }); +} + +function refreshKeys() { + clearTimeout(refreshTimeout); + refreshTimeout = setTimeout(() => { + fetchKeys(); + }, 500); } function deleteKey(accessKey: string) { @@ -75,7 +79,7 @@ function createKey() { onMounted(() => { successToast = new Toast("#successKeyToast"); if (authStore.authenticated) { - refreshKeys(authStore.currentUID); + fetchKeys(); } new Tooltip("#createS3KeyButton"); new Tooltip("#refreshS3KeysButton"); @@ -118,7 +122,7 @@ onMounted(() => { id="refreshS3KeysButton" data-bs-title="Refresh S3 Keys" data-bs-toggle="tooltip" - @click="refreshKeys(authStore.currentUID)" + @click="refreshKeys()" > <font-awesome-icon icon="fa-solid fa-arrow-rotate-right"