Skip to content
Snippets Groups Projects
Commit 4ecec278 authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Merge branch 'feature/8-10-filter-by-name' into 'development'

Filter Buckets and objects by their name

Closes #10 and #8

See merge request denbi/object-storage-access-ui!14
parents 35aae1b8 83a06cc3
No related branches found
No related tags found
2 merge requests!22Version 1.0.0,!14Filter Buckets and objects by their name
...@@ -17,7 +17,7 @@ import CopyObjectModal from "@/components/Modals/CopyObjectModal.vue"; ...@@ -17,7 +17,7 @@ import CopyObjectModal from "@/components/Modals/CopyObjectModal.vue";
import PermissionModal from "@/components/Modals/PermissionModal.vue"; import PermissionModal from "@/components/Modals/PermissionModal.vue";
import ObjectDetailModal from "@/components/Modals/ObjectDetailModal.vue"; import ObjectDetailModal from "@/components/Modals/ObjectDetailModal.vue";
import CreateFolderModal from "@/components/Modals/CreateFolderModal.vue"; import CreateFolderModal from "@/components/Modals/CreateFolderModal.vue";
import DeleteModal from "@/components/Modals/DeleteModal.vue" import DeleteModal from "@/components/Modals/DeleteModal.vue";
import { import {
S3Client, S3Client,
DeleteObjectCommand, DeleteObjectCommand,
...@@ -114,16 +114,25 @@ type FolderTree = { ...@@ -114,16 +114,25 @@ type FolderTree = {
// Reactive State // Reactive State
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const deleteObjectsState = reactive({
deletedItem: "",
potentialObjectToDelete: "",
deleteFolder: true,
} as {
deletedItem: string;
potentialObjectToDelete: string;
deleteFolder: boolean;
});
const objectState = reactive({ const objectState = reactive({
objects: [], objects: [],
loading: true, loading: true,
filterString: "",
bucketNotFoundError: false, bucketNotFoundError: false,
bucketPermissionError: false, bucketPermissionError: false,
createdPermission: undefined, createdPermission: undefined,
deletedItem: "",
editObjectKey: "", editObjectKey: "",
potentialObjectToDelete: "",
deleteFolder: true,
copyObject: { copyObject: {
key: "", key: "",
size: 0, size: 0,
...@@ -139,13 +148,11 @@ const objectState = reactive({ ...@@ -139,13 +148,11 @@ const objectState = reactive({
} as { } as {
objects: S3ObjectMetaInformation[]; objects: S3ObjectMetaInformation[];
loading: boolean; loading: boolean;
filterString: string;
bucketNotFoundError: boolean; bucketNotFoundError: boolean;
bucketPermissionError: boolean; bucketPermissionError: boolean;
createdPermission: undefined | BucketPermission; createdPermission: undefined | BucketPermission;
deletedItem: string;
editObjectKey: string; editObjectKey: string;
potentialObjectToDelete: string;
deleteFolder: boolean;
copyObject: S3ObjectMetaInformation; copyObject: S3ObjectMetaInformation;
viewDetailObject: S3ObjectMetaInformation; viewDetailObject: S3ObjectMetaInformation;
}); });
...@@ -164,6 +171,15 @@ watch( ...@@ -164,6 +171,15 @@ watch(
// Computed Properties // 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(() => { const folderStructure: ComputedRef<FolderTree> = computed(() => {
/** /**
* Store the entire folder structure in a bucket in a tree-like data structure * Store the entire folder structure in a bucket in a tree-like data structure
...@@ -394,8 +410,8 @@ function objectCopied(copiedObject: S3ObjectMetaInformation) { ...@@ -394,8 +410,8 @@ function objectCopied(copiedObject: S3ObjectMetaInformation) {
} }
function deleteObject(key: string) { function deleteObject(key: string) {
objectState.potentialObjectToDelete = key; deleteObjectsState.potentialObjectToDelete = key;
objectState.deleteFolder = false; deleteObjectsState.deleteFolder = false;
} }
/** /**
...@@ -411,7 +427,7 @@ function confirmedDeleteObject(key: string) { ...@@ -411,7 +427,7 @@ function confirmedDeleteObject(key: string) {
.send(command) .send(command)
.then(() => { .then(() => {
const splittedKey = key.split("/"); const splittedKey = key.split("/");
objectState.deletedItem = splittedKey[splittedKey.length - 1]; deleteObjectsState.deletedItem = splittedKey[splittedKey.length - 1];
successToast?.show(); successToast?.show();
objectState.objects = objectState.objects.filter( objectState.objects = objectState.objects.filter(
(obj) => obj.key !== key (obj) => obj.key !== key
...@@ -443,8 +459,8 @@ async function downloadObject(key: string, bucket: string) { ...@@ -443,8 +459,8 @@ async function downloadObject(key: string, bucket: string) {
} }
function deleteFolder(folderPath: string) { function deleteFolder(folderPath: string) {
objectState.potentialObjectToDelete = folderPath; deleteObjectsState.potentialObjectToDelete = folderPath;
objectState.deleteFolder = true; deleteObjectsState.deleteFolder = true;
} }
/** /**
...@@ -466,7 +482,7 @@ function confirmedDeleteFolder(folderPath: string) { ...@@ -466,7 +482,7 @@ function confirmedDeleteFolder(folderPath: string) {
.send(command) .send(command)
.then(() => { .then(() => {
const splittedPath = folderPath.split("/"); const splittedPath = folderPath.split("/");
objectState.deletedItem = splittedPath[splittedPath.length - 2]; deleteObjectsState.deletedItem = splittedPath[splittedPath.length - 2];
successToast?.show(); successToast?.show();
objectState.objects = objectState.objects.filter( objectState.objects = objectState.objects.filter(
(obj) => !obj.key.startsWith(folderPath) (obj) => !obj.key.startsWith(folderPath)
...@@ -510,7 +526,7 @@ watch( ...@@ -510,7 +526,7 @@ watch(
> >
<div class="d-flex"> <div class="d-flex">
<div class="toast-body"> <div class="toast-body">
Successfully deleted {{ objectState.deletedItem }} Successfully deleted {{ deleteObjectsState.deletedItem }}
</div> </div>
<button <button
type="button" type="button"
...@@ -524,9 +540,13 @@ watch( ...@@ -524,9 +540,13 @@ watch(
<DeleteModal <DeleteModal
modalID="delete-object-modal" modalID="delete-object-modal"
modal-label="some-label" modal-label="some-label"
:object-name-delete="objectState.potentialObjectToDelete" :object-name-delete="deleteObjectsState.potentialObjectToDelete"
:back-modal-id="undefined" :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 --> <!-- Navbar Breadcrumb -->
<nav aria-label="breadcrumb" class="fs-2"> <nav aria-label="breadcrumb" class="fs-2">
...@@ -577,7 +597,7 @@ watch( ...@@ -577,7 +597,7 @@ watch(
placeholder="Search Objects" placeholder="Search Objects"
aria-label="Search Objects" aria-label="Search Objects"
aria-describedby="objects-search-wrapping" aria-describedby="objects-search-wrapping"
disabled v-model.trim="objectState.filterString"
/> />
</div> </div>
</div> </div>
...@@ -723,8 +743,8 @@ watch( ...@@ -723,8 +743,8 @@ watch(
<td></td> <td></td>
</tr> </tr>
</tbody> </tbody>
<!-- Table body when no objects are in the bcuket --> <!-- Table body when no objects are in the bucket -->
<tbody v-else-if="visibleObjects.length === 0"> <tbody v-else-if="filteredObjects.length === 0">
<tr> <tr>
<td colspan="4" class="text-center fst-italic fw-light"> <td colspan="4" class="text-center fst-italic fw-light">
No objects to display No objects to display
...@@ -733,7 +753,7 @@ watch( ...@@ -733,7 +753,7 @@ watch(
</tbody> </tbody>
<!-- Table body when showing objects --> <!-- Table body when showing objects -->
<tbody v-else> <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"> <th scope="row" class="text-truncate">
<!-- Show file name if row is an object --> <!-- Show file name if row is an object -->
<div v-if="isS3Object(obj)">{{ obj.pseudoFileName }}</div> <div v-if="isS3Object(obj)">{{ obj.pseudoFileName }}</div>
......
...@@ -18,11 +18,13 @@ const authStore = useAuthStore(); ...@@ -18,11 +18,13 @@ const authStore = useAuthStore();
const bucketsState = reactive({ const bucketsState = reactive({
buckets: [], buckets: [],
permissions: {}, permissions: {},
filterString: "",
potentialDeleteBucketName: "", potentialDeleteBucketName: "",
loading: true, loading: true,
} as { } as {
buckets: BucketOut[]; buckets: BucketOut[];
loading: boolean; loading: boolean;
filterString: string;
permissions: Record<string, BucketPermission>; permissions: Record<string, BucketPermission>;
potentialDeleteBucketName: string; potentialDeleteBucketName: string;
}); });
...@@ -63,6 +65,14 @@ const currentPermission: ComputedRef<BucketPermission | undefined> = computed( ...@@ -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) { function addBucket(bucket: BucketOut) {
bucketsState.buckets.push(bucket); bucketsState.buckets.push(bucket);
} }
...@@ -143,25 +153,38 @@ onMounted(() => { ...@@ -143,25 +153,38 @@ onMounted(() => {
placeholder="Search Buckets" placeholder="Search Buckets"
aria-label="Search Buckets" aria-label="Search Buckets"
aria-describedby="buckets-search-wrapping" aria-describedby="buckets-search-wrapping"
disabled v-model.trim="bucketsState.filterString"
/> />
</div> </div>
<div class="list-group mt-3"> <div class="list-group mt-3">
<div v-if="!bucketsState.loading"> <div v-if="!bucketsState.loading">
<bucket-list-item <div v-if="filteredBuckets.length > 0">
v-for="bucket in bucketsState.buckets" <bucket-list-item
:key="bucket.name" v-for="bucket in filteredBuckets"
:active=" :key="bucket.name"
route.params.bucketName != null && :active="
route.params.bucketName === bucket.name route.params.bucketName != null &&
" route.params.bucketName === bucket.name
:bucket="bucket" "
:loading="false" :bucket="bucket"
:permission="bucketsState.permissions[bucket.name]" :loading="false"
@delete-bucket="deleteBucket" :permission="bucketsState.permissions[bucket.name]"
@permission-deleted="(bucketName) => bucketDeleted(bucketName)" @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>
<div v-else> <div v-else>
<bucket-list-item <bucket-list-item
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment