From 09137e991d206a037cb56ba009c11ceab5e0ba72 Mon Sep 17 00:00:00 2001 From: Wilfred Asomani Date: Fri, 8 Mar 2024 10:22:42 +0000 Subject: [PATCH] web/satellite: add object browser page actions This change adds a dropdown menu action to the object browser with options; delete bucket, share bucket and toggle versioning. Issue: https://github.com/storj/storj/issues/6811 Change-Id: If2934e9e48332793698665ffd80823eadcb02518 --- web/satellite/src/api/buckets.ts | 28 +++- .../BrowserBreadcrumbsComponent.vue | 2 +- .../components/dialogs/DeleteBucketDialog.vue | 3 + .../src/components/icons/IconDropdown.vue | 9 + .../src/store/modules/bucketsStore.ts | 16 +- web/satellite/src/types/buckets.ts | 11 +- web/satellite/src/views/Bucket.vue | 154 +++++++++++++++++- 7 files changed, 199 insertions(+), 24 deletions(-) create mode 100644 web/satellite/src/components/icons/IconDropdown.vue diff --git a/web/satellite/src/api/buckets.ts b/web/satellite/src/api/buckets.ts index dd16e79ccf59..165400626575 100644 --- a/web/satellite/src/api/buckets.ts +++ b/web/satellite/src/api/buckets.ts @@ -1,10 +1,17 @@ // Copyright (C) 2019 Storj Labs, Inc. // See LICENSE for copying information. -import { Bucket, BucketCursor, BucketPage, BucketPlacement, BucketsApi } from '@/types/buckets'; +import { + Bucket, + BucketCursor, + BucketMetadata, + BucketPage, + BucketsApi, +} from '@/types/buckets'; import { HttpClient } from '@/utils/httpClient'; import { APIError } from '@/utils/error'; import { getVersioning } from '@/types/versioning'; +import { Placement } from '@/types/placements.js'; /** * BucketsHttpApi is an HTTP implementation of the Buckets API. @@ -90,25 +97,32 @@ export class BucketsHttpApi implements BucketsApi { } /** - * Fetch all bucket placements. + * Fetch all bucket metadata. * - * @returns BucketPlacement[] + * @returns BucketMetadata[] * @throws Error */ - public async getAllBucketPlacements(projectId: string): Promise { - const path = `${this.ROOT_PATH}/bucket-placements?publicID=${projectId}`; + public async getAllBucketMetadata(projectId: string): Promise { + const path = `${this.ROOT_PATH}/bucket-metadata?publicID=${projectId}`; const response = await this.client.get(path); if (!response.ok) { throw new APIError({ status: response.status, - message: 'Can not get bucket placements', + message: 'Can not get bucket metadata', requestID: response.headers.get('x-request-id'), }); } const result = await response.json(); - return result ? result : []; + return result?.map(bVersioning => new BucketMetadata( + bVersioning.name, + getVersioning(bVersioning.versioning), + new Placement( + bVersioning.placement.defaultPlacement, + bVersioning.placement.location, + ), + )) || []; } } diff --git a/web/satellite/src/components/BrowserBreadcrumbsComponent.vue b/web/satellite/src/components/BrowserBreadcrumbsComponent.vue index b66b5ec85992..1cd7af987ba0 100644 --- a/web/satellite/src/components/BrowserBreadcrumbsComponent.vue +++ b/web/satellite/src/components/BrowserBreadcrumbsComponent.vue @@ -40,7 +40,7 @@ const bucketName = computed(() => bucketsStore.state.fileComponentBucket * Returns the location of the selected bucket. */ const bucketLocation = computed((): string => { - const bucket = bucketsStore.state.allBucketPlacements.find((el) => el.name === bucketName.value); + const bucket = bucketsStore.state.allBucketMetadata.find((el) => el.name === bucketName.value); if (!bucket) { return ''; } diff --git a/web/satellite/src/components/dialogs/DeleteBucketDialog.vue b/web/satellite/src/components/dialogs/DeleteBucketDialog.vue index c148996d246b..88a518b7ec49 100644 --- a/web/satellite/src/components/dialogs/DeleteBucketDialog.vue +++ b/web/satellite/src/components/dialogs/DeleteBucketDialog.vue @@ -100,6 +100,8 @@ const props = defineProps<{ const model = defineModel({ required: true }); +const emit = defineEmits(['deleted']); + const analyticsStore = useAnalyticsStore(); const configStore = useConfigStore(); const bucketsStore = useBucketsStore(); @@ -197,6 +199,7 @@ async function onDelete(): Promise { notify.success('Bucket deleted.'); model.value = false; + emit('deleted'); }); } diff --git a/web/satellite/src/components/icons/IconDropdown.vue b/web/satellite/src/components/icons/IconDropdown.vue new file mode 100644 index 000000000000..ac352d9e9c08 --- /dev/null +++ b/web/satellite/src/components/icons/IconDropdown.vue @@ -0,0 +1,9 @@ +// Copyright (C) 2024 Storj Labs, Inc. +// See LICENSE for copying information. + + diff --git a/web/satellite/src/store/modules/bucketsStore.ts b/web/satellite/src/store/modules/bucketsStore.ts index 6827bfb1a301..518efcb46fcf 100644 --- a/web/satellite/src/store/modules/bucketsStore.ts +++ b/web/satellite/src/store/modules/bucketsStore.ts @@ -14,7 +14,13 @@ import { } from '@aws-sdk/client-s3'; import { SignatureV4 } from '@smithy/signature-v4'; -import { Bucket, BucketCursor, BucketPlacement, BucketPage, BucketsApi } from '@/types/buckets'; +import { + Bucket, + BucketCursor, + BucketPage, + BucketsApi, + BucketMetadata, +} from '@/types/buckets'; import { BucketsHttpApi } from '@/api/buckets'; import { AccessGrant, EdgeCredentials } from '@/types/accessGrants'; import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore'; @@ -27,7 +33,7 @@ export const FILE_BROWSER_AG_NAME = 'Web file browser API key'; export class BucketsState { public allBucketNames: string[] = []; - public allBucketPlacements: BucketPlacement[] = []; + public allBucketMetadata: BucketMetadata[] = []; public cursor: BucketCursor = { limit: DEFAULT_PAGE_LIMIT, search: '', page: FIRST_PAGE }; public page: BucketPage = { buckets: new Array(), currentPage: 1, pageCount: 1, offset: 0, limit: DEFAULT_PAGE_LIMIT, search: '', totalCount: 0 }; public edgeCredentials: EdgeCredentials = new EdgeCredentials(); @@ -81,8 +87,8 @@ export const useBucketsStore = defineStore('buckets', () => { state.allBucketNames = await api.getAllBucketNames(projectID); } - async function getAllBucketsPlacements(projectID: string): Promise { - state.allBucketPlacements = await api.getAllBucketPlacements(projectID); + async function getAllBucketsMetadata(projectID: string): Promise { + state.allBucketMetadata = await api.getAllBucketMetadata(projectID); } function setPromptForPassphrase(value: boolean): void { @@ -347,7 +353,7 @@ export const useBucketsStore = defineStore('buckets', () => { setBucketsSearch, getBuckets, getAllBucketsNames, - getAllBucketsPlacements, + getAllBucketsMetadata, setPromptForPassphrase, setEdgeCredentials, setEdgeCredentialsForDelete, diff --git a/web/satellite/src/types/buckets.ts b/web/satellite/src/types/buckets.ts index 4db0ba6b5f5a..22b33f883c2b 100644 --- a/web/satellite/src/types/buckets.ts +++ b/web/satellite/src/types/buckets.ts @@ -27,12 +27,12 @@ export interface BucketsApi { /** * - * Fetch all bucket placements + * Fetch all bucket metadata * - * @returns BucketPlacement[] + * @returns BucketMetadata[] * @throws Error */ - getAllBucketPlacements(projectId: string): Promise + getAllBucketMetadata(projectId: string): Promise } /** @@ -80,11 +80,12 @@ export class BucketCursor { } /** - * BucketPlacement class holds bucket name, placement ID, and location. + * BucketMeta class holds misc bucket metadata. */ -export class BucketPlacement { +export class BucketMetadata { public constructor( public name: string = '', + public versioning: Versioning = Versioning.NotSupported, public placement: Placement = new Placement(), ) { } } \ No newline at end of file diff --git a/web/satellite/src/views/Bucket.vue b/web/satellite/src/views/Bucket.vue index 20a91caeb1bb..aaea10a3fde9 100644 --- a/web/satellite/src/views/Bucket.vue +++ b/web/satellite/src/views/Bucket.vue @@ -70,7 +70,7 @@ @@ -78,6 +78,74 @@ New Folder + + + + + + + {{ + bucket.versioning !== Versioning.Enabled ? 'Enable Versioning' : 'Suspend Versioning' + }} + + + + + + Share Bucket + + + + + + + Delete Bucket + + + + + @@ -127,6 +195,8 @@ + +