From 56ac322ed111ada3fcd4cfef0ab017628c9cf803 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de>
Date: Wed, 17 Aug 2022 11:42:06 +0200
Subject: [PATCH] Add ability to delete a permission in the permission modal

#19
---
 src/components/BucketListItem.vue        |  9 ++-
 src/components/BucketView.vue            |  1 +
 src/components/PermissionModal.vue       | 86 +++++++++++++++++++++---
 src/views/object-storage/BucketsView.vue | 17 +++--
 4 files changed, 94 insertions(+), 19 deletions(-)

diff --git a/src/components/BucketListItem.vue b/src/components/BucketListItem.vue
index dd79c9a..69b1069 100644
--- a/src/components/BucketListItem.vue
+++ b/src/components/BucketListItem.vue
@@ -17,7 +17,8 @@ const props = defineProps<{
 const tooltipID = Math.random().toString(16).substr(2, 8);
 
 const emit = defineEmits<{
-  (e: "delete-bucket", bucket_name: string): void;
+  (e: "delete-bucket", bucketName: string): void;
+  (e: "permission-deleted", bucketName: string): void;
 }>();
 
 onMounted(() => {
@@ -37,6 +38,8 @@ onMounted(() => {
     :edit-user-permission="props.permission"
     :readonly="true"
     :editable="false"
+    :deletable="true"
+    @permission-deleted="(bucketName) => emit('permission-deleted', bucketName)"
   />
   <div class="mt-2 mb-2">
     <div
@@ -72,9 +75,9 @@ onMounted(() => {
       </router-link>
       <div
         :hidden="!props.active"
-        class="ps-2 rounded-bottom bg-light text-bg-light"
+        class="ps-2 pe-2 rounded-bottom bg-light text-bg-light border border-3 border-top-0 border-primary"
       >
-        <table class="table table-sm table-borderless">
+        <table class="table table-sm table-borderless mb-0">
           <tbody>
             <tr>
               <th scope="row" class="fw-bold">Created:</th>
diff --git a/src/components/BucketView.vue b/src/components/BucketView.vue
index 21fe2c3..acec756 100644
--- a/src/components/BucketView.vue
+++ b/src/components/BucketView.vue
@@ -364,6 +364,7 @@ watch(
         :edit-user-permission="undefined"
         :editable="false"
         :readonly="false"
+        :deletable="false"
       />
       <!-- Add folder button -->
       <button
diff --git a/src/components/PermissionModal.vue b/src/components/PermissionModal.vue
index 9adb360..b850422 100644
--- a/src/components/PermissionModal.vue
+++ b/src/components/PermissionModal.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { onMounted, reactive, watch, computed } from "vue";
+import { onMounted, reactive, watch, ref, computed, defineEmits } from "vue";
 import BootstrapModal from "@/components/BootstrapModal.vue";
 import { Modal } from "bootstrap";
 import dayjs from "dayjs";
@@ -8,7 +8,7 @@ import type {
   S3ObjectMetaInformation,
   BucketPermissionParameters,
 } from "@/client";
-import type { ComputedRef } from "vue";
+import type { ComputedRef, Ref } from "vue";
 import { PermissionEnum, BucketPermissionsService } from "@/client";
 import { Toast } from "bootstrap";
 import BootstrapIcon from "@/components/BootstrapIcon.vue";
@@ -35,12 +35,13 @@ const props = defineProps<{
   editUserPermission: BucketPermission | undefined;
   readonly: boolean;
   editable: boolean;
+  deletable: boolean;
 }>();
 
 // Variables
 // -----------------------------------------------------------------------------
 const toastID = Math.random().toString(16).substr(2, 8);
-let createPermissionModal: Modal | null = null;
+let permissionModal: Modal | null = null;
 let successToast: Toast | null = null;
 
 // Reactive State
@@ -64,6 +65,8 @@ const permission = reactive({
   bucket_name: props.bucketName,
 } as BucketPermission);
 
+const permissionDeleted: Ref<boolean> = ref(false);
+
 // Computes Properties
 // -----------------------------------------------------------------------------
 const editPermission: ComputedRef<boolean> = computed(
@@ -93,6 +96,12 @@ watch(
   () => updatePermission()
 );
 
+// Events
+// -----------------------------------------------------------------------------
+const emit = defineEmits<{
+  (e: "permission-deleted", bucket_name: string): void;
+}>();
+
 // Functions
 // -----------------------------------------------------------------------------
 /**
@@ -107,7 +116,14 @@ function modalClosed() {
 }
 
 /**
- * Check if a input should be visible based on its state
+ * Callback when the toast is hidden again.
+ */
+function toastHidden() {
+  permissionDeleted.value = false;
+}
+
+/**
+ * Check if an input should be visible based on its state
  * @param input Input which visibility should be determined.
  */
 function inputVisible(input: string | undefined): boolean {
@@ -204,12 +220,39 @@ function formSubmit() {
         );
     serverAnswerPromise
       .then(() => {
-        createPermissionModal?.hide();
+        permissionModal?.hide();
+        successToast?.show();
+        updatePermission();
+      })
+      .catch(() => {
+        formState.error = true;
+      })
+      .finally(() => {
+        formState.loading = false;
+      });
+  }
+}
+
+/**
+ * Delete a permission for a bucket user combination
+ * @param bucketName Bucket to delete
+ * @param uid ID of grantee of the permission
+ */
+function deletePermission(bucketName: string, uid: string) {
+  if (!formState.loading) {
+    formState.loading = true;
+    BucketPermissionsService.bucketPermissionsDeletePermissionForBucket(
+      bucketName,
+      uid
+    )
+      .then(() => {
+        permissionDeleted.value = true;
+        permissionModal?.hide();
         successToast?.show();
+        emit("permission-deleted", bucketName);
       })
-      .catch((err) => {
+      .catch(() => {
         formState.error = true;
-        console.error(err);
       })
       .finally(() => {
         formState.loading = false;
@@ -220,7 +263,7 @@ function formSubmit() {
 // Lifecycle Hooks
 // -----------------------------------------------------------------------------
 onMounted(() => {
-  createPermissionModal = new Modal("#" + props.modalID);
+  permissionModal = new Modal("#" + props.modalID);
   successToast = new Toast("#" + "toast-" + toastID, { autohide: true });
   updatePermission();
 });
@@ -235,12 +278,14 @@ onMounted(() => {
       class="toast text-bg-success align-items-center border-0"
       data-bs-autohide="false"
       :id="'toast-' + toastID"
+      v-on="{ 'hidden.bs.toast': toastHidden }"
     >
       <div class="d-flex">
         <div class="toast-body">
           Successfully
-          <span v-if="editPermission">created</span>
-          <span v-else>edited</span>
+          <span v-if="permissionDeleted">deleted</span>
+          <span v-else-if="editPermission">edited</span>
+          <span v-else>created</span>
           Permission
         </div>
         <button
@@ -265,8 +310,19 @@ onMounted(() => {
       >Edit Permission
     </template>
     <template v-slot:header v-else> Create new Permission </template>
-    <template v-slot:extra-button v-if="formState.readonly && props.editable">
+    <template v-slot:extra-button>
+      <bootstrap-icon
+        v-if="props.deletable"
+        icon="trash-fill"
+        :height="15"
+        :width="15"
+        fill="currentColor"
+        class="me-2"
+        :class="{ 'delete-icon': !formState.loading }"
+        @click="deletePermission(permission.bucket_name, permission.uid)"
+      />
       <bootstrap-icon
+        v-if="formState.readonly && props.editable"
         icon="pencil-fill"
         :height="15"
         :width="15"
@@ -438,4 +494,12 @@ onMounted(() => {
 .pseudo-link:hover {
   color: var(--bs-primary);
 }
+
+.delete-icon {
+  color: var(--bs-secondary);
+  cursor: pointer;
+}
+.delete-icon:hover {
+  color: var(--bs-danger);
+}
 </style>
diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue
index ba6c70b..0fab4f5 100644
--- a/src/views/object-storage/BucketsView.vue
+++ b/src/views/object-storage/BucketsView.vue
@@ -56,13 +56,19 @@ function addBucket(bucket: BucketOut) {
 
 function deleteBucket(bucketName: string) {
   BucketService.bucketDeleteBucket(bucketName).then(() => {
-    router.push({ name: "buckets" });
-    bucketsState.buckets = bucketsState.buckets.filter(
-      (bucket) => bucket.name !== bucketName
-    );
+    bucketDeleted(bucketName);
   });
 }
 
+function bucketDeleted(bucketName: string) {
+  if (bucketsState.buckets.map((bucket) => bucket.name).includes(bucketName)) {
+    router.push({ name: "buckets" });
+  }
+  bucketsState.buckets = bucketsState.buckets.filter(
+    (bucket) => bucket.name !== bucketName
+  );
+}
+
 onMounted(() => {
   fetchBuckets();
 });
@@ -113,7 +119,7 @@ onMounted(() => {
         />
       </div>
 
-      <div class="list-group overflow-scroll mt-3">
+      <div class="list-group mt-3">
         <div v-if="!bucketsState.loading">
           <bucket-list-item
             v-for="bucket in bucketsState.buckets"
@@ -126,6 +132,7 @@ onMounted(() => {
             :loading="false"
             :permission="bucketsState.permissions[bucket.name]"
             @delete-bucket="deleteBucket"
+            @permission-deleted="(bucketName) => bucketDeleted(bucketName)"
           />
         </div>
         <div v-else>
-- 
GitLab