Skip to content

Commit

Permalink
web/satellite: reworked AG deletion process
Browse files Browse the repository at this point in the history
Reworked the way access grants are deleted.
Made it to use another endpoint which receives an array of IDs to be deleted.
Cleaned up access grants pinia store module.

Issue:
#6919

Change-Id: I1936b2305fa91389dca5ef566b3f31db9f3013ff
  • Loading branch information
VitaliiShpital authored and Storj Robot committed Apr 19, 2024
1 parent 49a2af6 commit 8d6eed6
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 145 deletions.
24 changes: 12 additions & 12 deletions web/satellite/src/components/AccessTableComponent.vue
Expand Up @@ -2,7 +2,7 @@
// See LICENSE for copying information.

<template>
<v-card variant="outlined" :border="true" rounded="xlg">
<v-card variant="outlined" border rounded="xlg">
<v-text-field
v-model="search"
label="Search"
Expand All @@ -25,7 +25,7 @@
:loading="areGrantsFetching"
:items-length="page.totalCount"
:items-per-page-options="tableSizeOptions(page.totalCount)"
item-value="name"
:item-value="(item: AccessGrant) => item"
no-data-text="No results found"
select-strategy="page"
show-select
Expand Down Expand Up @@ -55,7 +55,7 @@
<v-icon :icon="mdiDotsHorizontal" />
<v-menu activator="parent">
<v-list class="pa-1">
<v-list-item class="text-error" density="comfortable" link rounded="lg" @click="() => onDeleteClick(item.name)">
<v-list-item class="text-error" density="comfortable" link rounded="lg" @click="() => onDeleteClick(item)">
<template #prepend>
<icon-trash />
</template>
Expand All @@ -72,7 +72,7 @@

<delete-access-dialog
v-model="isDeleteAccessDialogShown"
:access-names="accessesToDelete"
:accesses="accessesToDelete"
@deleted="() => onUpdatePage(FIRST_PAGE)"
/>

Expand Down Expand Up @@ -127,7 +127,7 @@ import { mdiDotsHorizontal, mdiMagnify } from '@mdi/js';
import { Time } from '@/utils/time';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { AccessGrantCursor, AccessGrantsOrderBy, AccessGrantsPage } from '@/types/accessGrants';
import { AccessGrant, AccessGrantCursor, AccessGrantsOrderBy, AccessGrantsPage } from '@/types/accessGrants';
import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore';
import { useNotify } from '@/utils/hooks';
import { useProjectsStore } from '@/store/modules/projectsStore';
Expand All @@ -145,9 +145,9 @@ const FIRST_PAGE = 1;
const areGrantsFetching = ref<boolean>(true);
const search = ref<string>('');
const searchTimer = ref<NodeJS.Timeout>();
const selected = ref([]);
const isDeleteAccessDialogShown = ref<boolean>(false);
const accessNameToDelete = ref<string>('');
const accessToDelete = ref<AccessGrant | undefined>();
const selected = ref<AccessGrant[]>([]);
const headers = [
{
Expand Down Expand Up @@ -176,8 +176,8 @@ const page = computed((): AccessGrantsPage => {
/**
* Returns the selected accesses to the delete dialog.
*/
const accessesToDelete = computed<string[]>(() => {
if (accessNameToDelete.value) return [accessNameToDelete.value];
const accessesToDelete = computed<AccessGrant[]>(() => {
if (accessToDelete.value) return [accessToDelete.value];
return selected.value;
});
Expand Down Expand Up @@ -205,7 +205,7 @@ function onUpdateLimit(limit: number): void {
*/
function onUpdatePage(page: number): void {
selected.value = [];
accessNameToDelete.value = '';
accessToDelete.value = undefined;
fetch(page, cursor.value.limit);
}
Expand All @@ -226,8 +226,8 @@ function onUpdateSortBy(sortBy: {key: keyof AccessGrantsOrderBy, order: keyof So
/**
* Displays the Delete Access dialog.
*/
function onDeleteClick(accessName: string): void {
accessNameToDelete.value = accessName;
function onDeleteClick(access: AccessGrant): void {
accessToDelete.value = access;
isDeleteAccessDialogShown.value = true;
}
Expand Down
27 changes: 13 additions & 14 deletions web/satellite/src/components/dialogs/DeleteAccessDialog.vue
Expand Up @@ -21,7 +21,7 @@
</v-sheet>
</template>
<v-card-title class="font-weight-bold">
Delete Access Key{{ accessNames.length > 1 ? 's' : '' }}
Delete Access Key{{ accesses.length > 1 ? 's' : '' }}
</v-card-title>
<template #append>
<v-btn
Expand All @@ -39,13 +39,13 @@

<div class="px-7 py-6">
<p class="mb-3">
The following access key{{ accessNames.length > 1 ? 's' : '' }}
The following access key{{ accesses.length > 1 ? 's' : '' }}
will be deleted. Any publicly shared links using
{{ accessNames.length > 1 ? 'these access keys' : 'this access key' }} will no longer work.
{{ accesses.length > 1 ? 'these access keys' : 'this access key' }} will no longer work.
</p>
<p v-for="accessName of accessNames" :key="accessName" class="mt-2">
<v-chip :title="accessName" class="font-weight-bold text-wrap h-100 py-2">
{{ accessName }}
<p v-for="item of accesses" :key="item.id" class="mt-2">
<v-chip :title="item.name" class="font-weight-bold text-wrap h-100 py-2">
{{ item.name }}
</v-chip>
</p>
</div>
Expand All @@ -71,7 +71,6 @@
</template>

<script setup lang="ts">
import { computed } from 'vue';
import {
VDialog,
VCard,
Expand All @@ -87,20 +86,20 @@ import {
} from 'vuetify/components';
import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useLoading } from '@/composables/useLoading';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { AccessGrant } from '@/types/accessGrants';
import IconTrash from '@/components/icons/IconTrash.vue';
const agStore = useAccessGrantsStore();
const projectsStore = useProjectsStore();
const notify = useNotify();
const { isLoading, withLoading } = useLoading();
const props = defineProps<{
accessNames: string[];
accesses: AccessGrant[];
}>();
const emit = defineEmits<{
Expand All @@ -112,13 +111,13 @@ const model = defineModel<boolean>({ required: true });
async function onDeleteClick(): Promise<void> {
await withLoading(async () => {
try {
const projId = projectsStore.state.selectedProject.id;
await Promise.all(props.accessNames.map(n => agStore.deleteAccessGrantByNameAndProjectID(n, projId)));
notify.success(`Access Grant${props.accessNames.length > 1 ? 's' : ''} deleted successfully`);
const ids: string[] = props.accesses.map(ag => ag.id);
await agStore.deleteAccessGrants(ids);
notify.success(`Access Grant${props.accesses.length > 1 ? 's' : ''} deleted successfully`);
emit('deleted');
model.value = false;
} catch (error) {
error.message = `Error deleting access grant${props.accessNames.length > 1 ? 's' : ''}. ${error.message}`;
error.message = `Error deleting access grant${props.accesses.length > 1 ? 's' : ''}. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.CONFIRM_DELETE_AG_MODAL);
}
});
Expand Down
121 changes: 2 additions & 119 deletions web/satellite/src/store/modules/accessGrantsStore.ts
Expand Up @@ -9,7 +9,6 @@ import {
AccessGrantCursor,
AccessGrantsOrderBy,
AccessGrantsPage,
DurationPermission,
EdgeCredentials,
} from '@/types/accessGrants';
import { SortDirection } from '@/types/common';
Expand All @@ -22,18 +21,9 @@ class AccessGrantsState {
public allAGNames: string[] = [];
public cursor: AccessGrantCursor = new AccessGrantCursor();
public page: AccessGrantsPage = new AccessGrantsPage();
public selectedAccessGrantsIds: string[] = [];
public selectedBucketNames: string[] = [];
public permissionNotBefore: Date | null = null;
public permissionNotAfter: Date | null = null;
public isDownload = true;
public isUpload = true;
public isList = true;
public isDelete = true;
public edgeCredentials: EdgeCredentials = new EdgeCredentials();
public accessGrantsWebWorker: Worker | null = null;
public isAccessGrantsWebWorkerReady = false;
public accessNameToDelete = '';
}

export const useAccessGrantsStore = defineStore('accessGrants', () => {
Expand Down Expand Up @@ -90,13 +80,6 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
const accessGrantsPage: AccessGrantsPage = await api.get(projectID, state.cursor);

state.page = accessGrantsPage;
state.page.accessGrants = state.page.accessGrants.map(accessGrant => {
if (state.selectedAccessGrantsIds.includes(accessGrant.id)) {
accessGrant.isSelected = true;
}

return accessGrant;
});

return accessGrantsPage;
}
Expand All @@ -105,12 +88,8 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
return await api.create(projectID, name);
}

async function deleteAccessGrants(): Promise<void> {
await api.delete(state.selectedAccessGrantsIds);
}

async function deleteAccessGrantByNameAndProjectID(name: string, projectID: string): Promise<void> {
await api.deleteByNameAndProjectID(name, projectID);
async function deleteAccessGrants(ids: string[]): Promise<void> {
await api.delete(ids);
}

async function getEdgeCredentials(accessGrant: string, isPublic?: boolean): Promise<EdgeCredentials> {
Expand All @@ -131,100 +110,15 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
state.cursor.order = order;
}

function setAccessNameToDelete(name: string): void {
state.accessNameToDelete = name;
}

function setDurationPermission(permission: DurationPermission): void {
state.permissionNotBefore = permission.notBefore;
state.permissionNotAfter = permission.notAfter;
}

function toggleSelection(accessGrant: AccessGrant): void {
if (!state.selectedAccessGrantsIds.includes(accessGrant.id)) {
state.page.accessGrants.forEach((grant: AccessGrant) => {
if (grant.id === accessGrant.id) {
grant.isSelected = true;
}
});
state.selectedAccessGrantsIds.push(accessGrant.id);

return;
}

state.page.accessGrants.forEach((grant: AccessGrant) => {
if (grant.id === accessGrant.id) {
grant.isSelected = false;
}
});
state.selectedAccessGrantsIds = state.selectedAccessGrantsIds.filter(accessGrantId => {
return accessGrant.id !== accessGrantId;
});
}

function toggleBucketSelection(bucketName: string): void {
if (!state.selectedBucketNames.includes(bucketName)) {
state.selectedBucketNames.push(bucketName);

return;
}

state.selectedBucketNames = state.selectedBucketNames.filter(name => {
return bucketName !== name;
});
}

function setSortingDirection(direction: SortDirection): void {
state.cursor.orderDirection = direction;
}

function toggleSortingDirection(): void {
let direction = SortDirection.desc;
if (state.cursor.orderDirection === SortDirection.desc) {
direction = SortDirection.asc;
}
state.cursor.orderDirection = direction;
}

function toggleIsDownloadPermission(): void {
state.isDownload = !state.isDownload;
}

function toggleIsUploadPermission(): void {
state.isUpload = !state.isUpload;
}

function toggleIsListPermission(): void {
state.isList = !state.isList;
}

function toggleIsDeletePermission(): void {
state.isDelete = !state.isDelete;
}

function clearSelection(): void {
state.selectedBucketNames = [];
state.selectedAccessGrantsIds = [];
state.page.accessGrants = state.page.accessGrants.map((accessGrant: AccessGrant) => {
accessGrant.isSelected = false;

return accessGrant;
});
}

function clear(): void {
state.allAGNames = [];
state.cursor = new AccessGrantCursor();
state.page = new AccessGrantsPage();
state.selectedAccessGrantsIds = [];
state.selectedBucketNames = [];
state.permissionNotBefore = null;
state.permissionNotAfter = null;
state.edgeCredentials = new EdgeCredentials();
state.isDownload = true;
state.isUpload = true;
state.isList = true;
state.isDelete = true;
state.accessGrantsWebWorker = null;
state.isAccessGrantsWebWorkerReady = false;
}
Expand All @@ -242,21 +136,10 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
getAccessGrants,
createAccessGrant,
deleteAccessGrants,
deleteAccessGrantByNameAndProjectID,
getEdgeCredentials,
setSearchQuery,
setSortingBy,
setAccessNameToDelete,
setSortingDirection,
toggleSortingDirection,
setDurationPermission,
toggleSelection,
toggleBucketSelection,
toggleIsDownloadPermission,
toggleIsUploadPermission,
toggleIsListPermission,
toggleIsDeletePermission,
clearSelection,
clear,
};
});

0 comments on commit 8d6eed6

Please sign in to comment.