web/satellite/vuetify-poc: add bucket deletion option to buckets table
This change allows buckets to be deleted from the Buckets page through a dropdown menu in the buckets table. Before deletion, users are prompted to confirm their decision in a dialog box. Revoles #6114 Change-Id: Ie3a8bfbf94a29c4bc67b4dacbace6abe7e1bc2eb
This commit is contained in:
parent
f2e607c70f
commit
957d8d6ca0
@ -1,3 +0,0 @@
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.5039 4.47638C18.7779 4.47638 19 4.69847 19 4.97244C19 5.24641 18.7779 5.4685 18.5039 5.4685L16.6817 5.46836L15.4073 15.876C15.2332 17.2977 14.0259 18.3661 12.5936 18.3661H7.52282C6.10137 18.3661 4.89972 17.3134 4.71279 15.9043L3.32838 5.46836L1.49606 5.4685C1.22209 5.4685 1 5.24641 1 4.97244C1 4.69847 1.22209 4.47638 1.49606 4.47638H18.5039ZM15.6822 5.4685H4.32943L5.6963 15.7738C5.81584 16.6749 6.57385 17.3519 7.47823 17.3735L7.52282 17.374H12.5936C13.5099 17.374 14.2844 16.7014 14.4166 15.7993L14.4225 15.7554L15.6822 5.4685ZM8.47662 8.91592L8.48146 8.9424L8.4849 8.96941L8.98001 13.9215L8.98375 13.9527C9.01639 14.2247 8.82233 14.4717 8.55031 14.5043C8.28736 14.5358 8.04782 14.3556 8.00266 14.0978L7.99869 14.0709L7.99388 14.03L7.4977 9.06813C7.47043 8.79552 7.66933 8.55243 7.94194 8.52517C8.19637 8.49973 8.42509 8.67129 8.47662 8.91592ZM12.3071 8.4919L12.3342 8.49334C12.5982 8.51448 12.7973 8.73863 12.7906 9.00024L12.7891 9.02742L12.7857 9.06844L12.2899 14.0266C12.2627 14.2992 12.0196 14.4981 11.747 14.4708C11.4834 14.4445 11.2888 14.2164 11.3007 13.955L11.3027 13.9278L11.7977 8.97947L11.8002 8.94821C11.8206 8.69332 12.0302 8.49893 12.2801 8.4919H12.3071ZM12.8346 1.5C13.1086 1.5 13.3307 1.72209 13.3307 1.99606C13.3307 2.27003 13.1086 2.49213 12.8346 2.49213H7.16535C6.89139 2.49213 6.66929 2.27003 6.66929 1.99606C6.66929 1.72209 6.89139 1.5 7.16535 1.5H12.8346Z" fill="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
@ -64,24 +64,52 @@
|
||||
{{ item.raw.since.toLocaleString() }}
|
||||
</span>
|
||||
</template>
|
||||
<template #item.actions="{ item }">
|
||||
<v-menu location="bottom end" transition="scale-transition">
|
||||
<template #activator="{ props: activatorProps }">
|
||||
<v-btn
|
||||
icon="mdi-dots-vertical"
|
||||
color="default"
|
||||
variant="text"
|
||||
size="small"
|
||||
density="comfortable"
|
||||
v-bind="activatorProps"
|
||||
/>
|
||||
</template>
|
||||
<v-list class="pa-0">
|
||||
<v-list-item link @click="() => showDeleteBucketDialog(item.raw.name)">
|
||||
<template #prepend>
|
||||
<icon-trash />
|
||||
</template>
|
||||
<v-list-item-title class="text-body-2 ml-3">
|
||||
Delete bucket
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-data-table-server>
|
||||
</v-card>
|
||||
<delete-bucket-dialog v-model="isDeleteBucketDialogShown" :bucket-name="bucketToDelete" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { VCard, VTextField, VBtn } from 'vuetify/components';
|
||||
import { VCard, VTextField, VBtn, VMenu, VList, VListItem, VListItemTitle } from 'vuetify/components';
|
||||
import { VDataTableServer } from 'vuetify/labs/components';
|
||||
|
||||
import { BucketPage, BucketCursor } from '@/types/buckets';
|
||||
import { useBucketsStore } from '@/store/modules/bucketsStore';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { DEFAULT_PAGE_LIMIT } from '@/types/pagination';
|
||||
import { tableSizeOptions } from '@/types/common';
|
||||
|
||||
import IconTrash from '@poc/components/icons/IconTrash.vue';
|
||||
import DeleteBucketDialog from '@poc/components/dialogs/DeleteBucketDialog.vue';
|
||||
|
||||
const bucketsStore = useBucketsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const notify = useNotify();
|
||||
@ -92,6 +120,8 @@ const areBucketsFetching = ref<boolean>(true);
|
||||
const search = ref<string>('');
|
||||
const searchTimer = ref<NodeJS.Timeout>();
|
||||
const selected = ref([]);
|
||||
const isDeleteBucketDialogShown = ref<boolean>(false);
|
||||
const bucketToDelete = ref<string>('');
|
||||
|
||||
const headers = [
|
||||
{
|
||||
@ -105,6 +135,7 @@ const headers = [
|
||||
{ title: 'Objects', key: 'objectCount', sortable: false },
|
||||
{ title: 'Segments', key: 'segmentCount', sortable: false },
|
||||
{ title: 'Date Created', key: 'createdAt', sortable: false },
|
||||
{ key: 'actions', width: '0', sortable: false },
|
||||
];
|
||||
|
||||
/**
|
||||
@ -166,6 +197,14 @@ function openBucket(bucketName: string): void {
|
||||
router.push(`/projects/${projectsStore.state.selectedProject.id}/buckets/${bucketName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Delete Bucket dialog.
|
||||
*/
|
||||
function showDeleteBucketDialog(bucketName: string): void {
|
||||
bucketToDelete.value = bucketName;
|
||||
isDeleteBucketDialogShown.value = true;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchBuckets();
|
||||
});
|
||||
|
@ -94,7 +94,7 @@
|
||||
</template>
|
||||
<v-list-item v-else link @click="declineInvitation(item.raw)">
|
||||
<template #prepend>
|
||||
<img src="@poc/assets/icon-trash.svg" alt="Decline">
|
||||
<icon-trash />
|
||||
</template>
|
||||
<v-list-item-title class="text-body-2 ml-3">
|
||||
Decline
|
||||
@ -137,6 +137,7 @@ import { useNotify } from '@/utils/hooks';
|
||||
|
||||
import IconSettings from '@poc/components/icons/IconSettings.vue';
|
||||
import IconTeam from '@poc/components/icons/IconTeam.vue';
|
||||
import IconTrash from '@poc/components/icons/IconTrash.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
items: ProjectItemModel[],
|
||||
|
@ -0,0 +1,223 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="model"
|
||||
width="410px"
|
||||
transition="fade-transition"
|
||||
:persistent="isLoading"
|
||||
>
|
||||
<v-card rounded="xlg">
|
||||
<v-card-item class="pl-7 py-4">
|
||||
<template #prepend>
|
||||
<v-sheet
|
||||
class="bg-on-surface-variant d-flex justify-center align-center"
|
||||
width="40"
|
||||
height="40"
|
||||
rounded="lg"
|
||||
>
|
||||
<icon-trash />
|
||||
</v-sheet>
|
||||
</template>
|
||||
<v-card-title class="font-weight-bold">Delete Bucket</v-card-title>
|
||||
<template #append>
|
||||
<v-btn
|
||||
icon="$close"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="default"
|
||||
:disabled="isLoading"
|
||||
@click="model = false"
|
||||
/>
|
||||
</template>
|
||||
</v-card-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<div class="pa-7">
|
||||
The following bucket and all of its data will be deleted. This action cannot be undone.
|
||||
<br><br>
|
||||
<span class="font-weight-bold">{{ bucketName }}</span>
|
||||
</div>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="pa-7">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn variant="outlined" color="default" block :disabled="isLoading" @click="model = false">
|
||||
Cancel
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-btn color="error" variant="flat" block :loading="isLoading" @click="onDelete">
|
||||
Delete
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import {
|
||||
VDialog,
|
||||
VCard,
|
||||
VCardItem,
|
||||
VSheet,
|
||||
VCardTitle,
|
||||
VDivider,
|
||||
VCardActions,
|
||||
VRow,
|
||||
VCol,
|
||||
VBtn,
|
||||
} from 'vuetify/components';
|
||||
|
||||
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
|
||||
import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore';
|
||||
import { useBucketsStore, FILE_BROWSER_AG_NAME } from '@/store/modules/bucketsStore';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { useConfigStore } from '@/store/modules/configStore';
|
||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||
import { useLoading } from '@/composables/useLoading';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
|
||||
import IconTrash from '@poc/components/icons/IconTrash.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean,
|
||||
bucketName: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean],
|
||||
}>();
|
||||
|
||||
const model = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
const analyticsStore = useAnalyticsStore();
|
||||
const configStore = useConfigStore();
|
||||
const bucketsStore = useBucketsStore();
|
||||
const agStore = useAccessGrantsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { isLoading, withLoading } = useLoading();
|
||||
const notify = useNotify();
|
||||
|
||||
const worker = ref<Worker| null>(null);
|
||||
|
||||
/**
|
||||
* Returns API key from store.
|
||||
*/
|
||||
const apiKey = computed((): string => {
|
||||
return bucketsStore.state.apiKey;
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates unrestricted access grant and deletes bucket
|
||||
* when Delete button has been clicked.
|
||||
*/
|
||||
async function onDelete(): Promise<void> {
|
||||
await withLoading(async () => {
|
||||
const projectID = projectsStore.state.selectedProject.id;
|
||||
|
||||
try {
|
||||
if (!worker.value) throw new Error('Web worker is not initialized.');
|
||||
|
||||
if (!apiKey.value) {
|
||||
await agStore.deleteAccessGrantByNameAndProjectID(FILE_BROWSER_AG_NAME, projectID);
|
||||
const cleanAPIKey: AccessGrant = await agStore.createAccessGrant(FILE_BROWSER_AG_NAME, projectID);
|
||||
bucketsStore.setApiKey(cleanAPIKey.secret);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const inOneHour = new Date(now.setHours(now.getHours() + 1));
|
||||
|
||||
worker.value.postMessage({
|
||||
'type': 'SetPermission',
|
||||
'isDownload': false,
|
||||
'isUpload': false,
|
||||
'isList': true,
|
||||
'isDelete': true,
|
||||
'notAfter': inOneHour.toISOString(),
|
||||
'buckets': JSON.stringify([props.bucketName]),
|
||||
'apiKey': apiKey.value,
|
||||
});
|
||||
|
||||
const grantEvent: MessageEvent = await new Promise(resolve => {
|
||||
if (worker.value) {
|
||||
worker.value.onmessage = resolve;
|
||||
}
|
||||
});
|
||||
if (grantEvent.data.error) {
|
||||
notify.error(grantEvent.data.error, AnalyticsErrorEventSource.DELETE_BUCKET_MODAL);
|
||||
return;
|
||||
}
|
||||
|
||||
const salt = await projectsStore.getProjectSalt(projectsStore.state.selectedProject.id);
|
||||
const satelliteNodeURL: string = configStore.state.config.satelliteNodeURL;
|
||||
|
||||
worker.value.postMessage({
|
||||
'type': 'GenerateAccess',
|
||||
'apiKey': grantEvent.data.value,
|
||||
'passphrase': '',
|
||||
'salt': salt,
|
||||
'satelliteNodeURL': satelliteNodeURL,
|
||||
});
|
||||
|
||||
const accessGrantEvent: MessageEvent = await new Promise(resolve => {
|
||||
if (worker.value) {
|
||||
worker.value.onmessage = resolve;
|
||||
}
|
||||
});
|
||||
if (accessGrantEvent.data.error) {
|
||||
notify.error(accessGrantEvent.data.error, AnalyticsErrorEventSource.DELETE_BUCKET_MODAL);
|
||||
return;
|
||||
}
|
||||
|
||||
const accessGrant = accessGrantEvent.data.value;
|
||||
|
||||
const edgeCredentials: EdgeCredentials = await agStore.getEdgeCredentials(accessGrant);
|
||||
bucketsStore.setEdgeCredentialsForDelete(edgeCredentials);
|
||||
await bucketsStore.deleteBucket(props.bucketName);
|
||||
analyticsStore.eventTriggered(AnalyticsEvent.BUCKET_DELETED);
|
||||
await fetchBuckets();
|
||||
} catch (error) {
|
||||
notify.notifyError(error, AnalyticsErrorEventSource.DELETE_BUCKET_MODAL);
|
||||
return;
|
||||
}
|
||||
|
||||
notify.success('Bucket deleted.');
|
||||
model.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches bucket using api.
|
||||
*/
|
||||
async function fetchBuckets(): Promise<void> {
|
||||
try {
|
||||
await bucketsStore.getBuckets(1, projectsStore.state.selectedProject.id);
|
||||
} catch (error) {
|
||||
notify.error(`Unable to fetch buckets. ${error.message}`, AnalyticsErrorEventSource.DELETE_BUCKET_MODAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets local worker with worker instantiated in store.
|
||||
*/
|
||||
watch(() => agStore.state.accessGrantsWebWorker, value => {
|
||||
worker.value = value;
|
||||
if (!value) return;
|
||||
value.onerror = (error: ErrorEvent) => {
|
||||
notify.error(error.message, AnalyticsErrorEventSource.DELETE_BUCKET_MODAL);
|
||||
};
|
||||
}, { immediate: true });
|
||||
</script>
|
@ -0,0 +1,8 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.5039 4.47638C18.7779 4.47638 19 4.69847 19 4.97244C19 5.24641 18.7779 5.4685 18.5039 5.4685L16.6817 5.46836L15.4073 15.876C15.2332 17.2977 14.0259 18.3661 12.5936 18.3661H7.52282C6.10137 18.3661 4.89972 17.3134 4.71279 15.9043L3.32838 5.46836L1.49606 5.4685C1.22209 5.4685 1 5.24641 1 4.97244C1 4.69847 1.22209 4.47638 1.49606 4.47638H18.5039ZM15.6822 5.4685H4.32943L5.6963 15.7738C5.81584 16.6749 6.57385 17.3519 7.47823 17.3735L7.52282 17.374H12.5936C13.5099 17.374 14.2844 16.7014 14.4166 15.7993L14.4225 15.7554L15.6822 5.4685ZM8.47662 8.91592L8.48146 8.9424L8.4849 8.96941L8.98001 13.9215L8.98375 13.9527C9.01639 14.2247 8.82233 14.4717 8.55031 14.5043C8.28736 14.5358 8.04782 14.3556 8.00266 14.0978L7.99869 14.0709L7.99388 14.03L7.4977 9.06813C7.47043 8.79552 7.66933 8.55243 7.94194 8.52517C8.19637 8.49973 8.42509 8.67129 8.47662 8.91592ZM12.3071 8.4919L12.3342 8.49334C12.5982 8.51448 12.7973 8.73863 12.7906 9.00024L12.7891 9.02742L12.7857 9.06844L12.2899 14.0266C12.2627 14.2992 12.0196 14.4981 11.747 14.4708C11.4834 14.4445 11.2888 14.2164 11.3007 13.955L11.3027 13.9278L11.7977 8.97947L11.8002 8.94821C11.8206 8.69332 12.0302 8.49893 12.2801 8.4919H12.3071ZM12.8346 1.5C13.1086 1.5 13.3307 1.72209 13.3307 1.99606C13.3307 2.27003 13.1086 2.49213 12.8346 2.49213H7.16535C6.89139 2.49213 6.66929 2.27003 6.66929 1.99606C6.66929 1.72209 6.89139 1.5 7.16535 1.5H12.8346Z" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
@ -313,3 +313,8 @@ table {
|
||||
.v-alert__close .v-btn {
|
||||
color: rgb(var(--v-theme-on-surface)) !important;
|
||||
}
|
||||
|
||||
.bg-on-surface-variant {
|
||||
background-color: rgb(var(--v-theme-on-surface-variant)) !important;
|
||||
color: rgb(var(--v-theme-surface-variant)) !important;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user