diff --git a/src/components/BucketView.vue b/src/components/BucketView.vue index b43e2c3e2178902528043e3a4c0ee5a817a37d4b..9444ecac83397a3ce8ab8ea42489c32809f06c49 100644 --- a/src/components/BucketView.vue +++ b/src/components/BucketView.vue @@ -17,7 +17,7 @@ import CopyObjectModal from "@/components/Modals/CopyObjectModal.vue"; import PermissionModal from "@/components/Modals/PermissionModal.vue"; import ObjectDetailModal from "@/components/Modals/ObjectDetailModal.vue"; import CreateFolderModal from "@/components/Modals/CreateFolderModal.vue"; -import DeleteModal from "@/components/Modals/DeleteModal.vue" +import DeleteModal from "@/components/Modals/DeleteModal.vue"; import { S3Client, DeleteObjectCommand, @@ -114,16 +114,25 @@ type FolderTree = { // Reactive State // ----------------------------------------------------------------------------- + +const deleteObjectsState = reactive({ + deletedItem: "", + potentialObjectToDelete: "", + deleteFolder: true, +} as { + deletedItem: string; + potentialObjectToDelete: string; + deleteFolder: boolean; +}); + const objectState = reactive({ objects: [], loading: true, + filterString: "", bucketNotFoundError: false, bucketPermissionError: false, createdPermission: undefined, - deletedItem: "", editObjectKey: "", - potentialObjectToDelete: "", - deleteFolder: true, copyObject: { key: "", size: 0, @@ -139,13 +148,11 @@ const objectState = reactive({ } as { objects: S3ObjectMetaInformation[]; loading: boolean; + filterString: string; bucketNotFoundError: boolean; bucketPermissionError: boolean; createdPermission: undefined | BucketPermission; - deletedItem: string; editObjectKey: string; - potentialObjectToDelete: string; - deleteFolder: boolean; copyObject: S3ObjectMetaInformation; viewDetailObject: S3ObjectMetaInformation; }); @@ -164,6 +171,15 @@ watch( // Computed Properties // ----------------------------------------------------------------------------- +const filteredObjects: ComputedRef<(S3ObjectWithFolder | S3PseudoFolder)[]> = + computed(() => { + return objectState.filterString.length > 0 + ? visibleObjects.value.filter((obj) => + obj.key.includes(objectState.filterString) + ) + : visibleObjects.value; + }); + const folderStructure: ComputedRef<FolderTree> = computed(() => { /** * Store the entire folder structure in a bucket in a tree-like data structure @@ -394,8 +410,8 @@ function objectCopied(copiedObject: S3ObjectMetaInformation) { } function deleteObject(key: string) { - objectState.potentialObjectToDelete = key; - objectState.deleteFolder = false; + deleteObjectsState.potentialObjectToDelete = key; + deleteObjectsState.deleteFolder = false; } /** @@ -411,7 +427,7 @@ function confirmedDeleteObject(key: string) { .send(command) .then(() => { const splittedKey = key.split("/"); - objectState.deletedItem = splittedKey[splittedKey.length - 1]; + deleteObjectsState.deletedItem = splittedKey[splittedKey.length - 1]; successToast?.show(); objectState.objects = objectState.objects.filter( (obj) => obj.key !== key @@ -443,8 +459,8 @@ async function downloadObject(key: string, bucket: string) { } function deleteFolder(folderPath: string) { - objectState.potentialObjectToDelete = folderPath; - objectState.deleteFolder = true; + deleteObjectsState.potentialObjectToDelete = folderPath; + deleteObjectsState.deleteFolder = true; } /** @@ -466,7 +482,7 @@ function confirmedDeleteFolder(folderPath: string) { .send(command) .then(() => { const splittedPath = folderPath.split("/"); - objectState.deletedItem = splittedPath[splittedPath.length - 2]; + deleteObjectsState.deletedItem = splittedPath[splittedPath.length - 2]; successToast?.show(); objectState.objects = objectState.objects.filter( (obj) => !obj.key.startsWith(folderPath) @@ -510,7 +526,7 @@ watch( > <div class="d-flex"> <div class="toast-body"> - Successfully deleted {{ objectState.deletedItem }} + Successfully deleted {{ deleteObjectsState.deletedItem }} </div> <button type="button" @@ -524,9 +540,13 @@ watch( <DeleteModal modalID="delete-object-modal" modal-label="some-label" - :object-name-delete="objectState.potentialObjectToDelete" + :object-name-delete="deleteObjectsState.potentialObjectToDelete" :back-modal-id="undefined" - @confirm-delete="objectState.deleteFolder ? confirmedDeleteFolder(objectState.potentialObjectToDelete) : confirmedDeleteObject(objectState.potentialObjectToDelete)" + @confirm-delete=" + deleteObjectsState.deleteFolder + ? confirmedDeleteFolder(deleteObjectsState.potentialObjectToDelete) + : confirmedDeleteObject(deleteObjectsState.potentialObjectToDelete) + " /> <!-- Navbar Breadcrumb --> <nav aria-label="breadcrumb" class="fs-2"> @@ -577,7 +597,7 @@ watch( placeholder="Search Objects" aria-label="Search Objects" aria-describedby="objects-search-wrapping" - disabled + v-model.trim="objectState.filterString" /> </div> </div> @@ -723,8 +743,8 @@ watch( <td></td> </tr> </tbody> - <!-- Table body when no objects are in the bcuket --> - <tbody v-else-if="visibleObjects.length === 0"> + <!-- Table body when no objects are in the bucket --> + <tbody v-else-if="filteredObjects.length === 0"> <tr> <td colspan="4" class="text-center fst-italic fw-light"> No objects to display @@ -733,7 +753,7 @@ watch( </tbody> <!-- Table body when showing objects --> <tbody v-else> - <tr v-for="obj in visibleObjects" :key="obj.key"> + <tr v-for="obj in filteredObjects" :key="obj.key"> <th scope="row" class="text-truncate"> <!-- Show file name if row is an object --> <div v-if="isS3Object(obj)">{{ obj.pseudoFileName }}</div> diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue index 58422c9a24f31941c91b31b52198a2421b2b619b..ad7d4e5b542a5dde3de0b409273cbd67bbb6dbae 100644 --- a/src/views/object-storage/BucketsView.vue +++ b/src/views/object-storage/BucketsView.vue @@ -18,11 +18,13 @@ const authStore = useAuthStore(); const bucketsState = reactive({ buckets: [], permissions: {}, + filterString: "", potentialDeleteBucketName: "", loading: true, } as { buckets: BucketOut[]; loading: boolean; + filterString: string; permissions: Record<string, BucketPermission>; potentialDeleteBucketName: string; }); @@ -63,6 +65,14 @@ const currentPermission: ComputedRef<BucketPermission | undefined> = computed( } ); +const filteredBuckets: ComputedRef<BucketOut[]> = computed(() => { + return bucketsState.filterString.length > 0 + ? bucketsState.buckets.filter((bucket) => + bucket.name.includes(bucketsState.filterString) + ) + : bucketsState.buckets; +}); + function addBucket(bucket: BucketOut) { bucketsState.buckets.push(bucket); } @@ -143,25 +153,38 @@ onMounted(() => { placeholder="Search Buckets" aria-label="Search Buckets" aria-describedby="buckets-search-wrapping" - disabled + v-model.trim="bucketsState.filterString" /> </div> <div class="list-group mt-3"> <div v-if="!bucketsState.loading"> - <bucket-list-item - v-for="bucket in bucketsState.buckets" - :key="bucket.name" - :active=" - route.params.bucketName != null && - route.params.bucketName === bucket.name - " - :bucket="bucket" - :loading="false" - :permission="bucketsState.permissions[bucket.name]" - @delete-bucket="deleteBucket" - @permission-deleted="(bucketName) => bucketDeleted(bucketName)" - /> + <div v-if="filteredBuckets.length > 0"> + <bucket-list-item + v-for="bucket in filteredBuckets" + :key="bucket.name" + :active=" + route.params.bucketName != null && + route.params.bucketName === bucket.name + " + :bucket="bucket" + :loading="false" + :permission="bucketsState.permissions[bucket.name]" + @delete-bucket="deleteBucket" + @permission-deleted="(bucketName) => bucketDeleted(bucketName)" + /> + </div> + <div v-else class="text-center fs-2 mt-5"> + <bootstrap-icon + icon="search" + class="mb-2" + :width="56" + :height="56" + style="color: var(--bs-secondary)" + fill="currentColor" + /><br /> + Could not find any Buckets + </div> </div> <div v-else> <bucket-list-item