diff --git a/components/modal.vue b/components/modal.vue index 0a05c84..f1410e6 100644 --- a/components/modal.vue +++ b/components/modal.vue @@ -61,8 +61,12 @@ const handleEscape = (event: Event) => { diff --git a/components/object/list.vue b/components/object/list.vue index 5a9d390..3432d8d 100644 --- a/components/object/list.vue +++ b/components/object/list.vue @@ -13,7 +13,13 @@ {{ t('Upload File') }}/{{ t('Folder') }} - @@ -28,7 +34,12 @@ - +
- - + + @@ -263,14 +285,14 @@ const columns = computed[]>(() => { h('div', { class: 'flex items-center gap-2' }, [ row.original.type === 'object' ? h( - Button, - { - variant: 'outline', - size: 'sm', - onClick: () => downloadFile(row.original.Key), - }, - () => [h(Icon, { name: 'ri:download-cloud-2-line', class: 'size-4' }), h('span', t('Download'))] - ) + Button, + { + variant: 'outline', + size: 'sm', + onClick: () => downloadFile(row.original.Key), + }, + () => [h(Icon, { name: 'ri:download-cloud-2-line', class: 'size-4' }), h('span', t('Download'))] + ) : null, h( Button, diff --git a/components/object/upload/picker.vue b/components/object/upload/picker.vue index f583110..ba988f4 100644 --- a/components/object/upload/picker.vue +++ b/components/object/upload/picker.vue @@ -7,7 +7,9 @@

{{ t('Target Bucket') }}: {{ bucketName }}

-

{{ t('Current Prefix') }}: {{ prefix || '/' }}

+

+ {{ t('Current Prefix') }}: {{ prefix || '/' }} +

@@ -28,7 +30,10 @@ - + {{ t('Large File Count Warning', { count: totalFileCount.toLocaleString(), max: MAX_FILES_LIMIT }) }} @@ -41,16 +46,30 @@

{{ t('Total Files') }}: {{ totalFileCount.toLocaleString() }}

{{ t('Memory Usage') }}: {{ getMemoryUsageLevel() }}

- -
-
+
+

{{ t('No Selection') }}

@@ -77,7 +96,10 @@

- +
{{ item.name }}/ @@ -124,7 +146,12 @@ -
@@ -213,7 +240,7 @@ const clearAllFiles = () => { if (typeof window !== 'undefined' && 'gc' in window && typeof (window as any).gc === 'function') { try { - ; (window as any).gc() + ;(window as any).gc() } catch { // ignore } diff --git a/components/object/versions.vue b/components/object/versions.vue index a3f9915..f1e353b 100644 --- a/components/object/versions.vue +++ b/components/object/versions.vue @@ -118,8 +118,8 @@ watch( const fetchVersions = async () => { loading.value = true try { - const response = await objectApi.getObjectVersions(props.objectKey) - versions.value = response.Versions ?? [] + const response = await objectApi.listObjectVersions(props.objectKey) + versions.value = response.Versions } catch (error) { message.error(t('Failed to fetch versions')) versions.value = [] diff --git a/composables/useObject.ts b/composables/useObject.ts index b0f696c..4d2d2be 100644 --- a/composables/useObject.ts +++ b/composables/useObject.ts @@ -1,18 +1,18 @@ import { DeleteObjectCommand, + DeleteObjectTaggingCommand, GetObjectCommand, + GetObjectLegalHoldCommand, + GetObjectRetentionCommand, + GetObjectTaggingCommand, HeadObjectCommand, ListObjectsV2Command, - PutObjectCommand, - GetObjectTaggingCommand, - PutObjectTaggingCommand, - DeleteObjectTaggingCommand, - GetObjectRetentionCommand, - PutObjectRetentionCommand, - GetObjectLegalHoldCommand, - PutObjectLegalHoldCommand, ListObjectVersionsCommand, ObjectLockRetentionMode, + PutObjectCommand, + PutObjectLegalHoldCommand, + PutObjectRetentionCommand, + PutObjectTaggingCommand, } from '@aws-sdk/client-s3' import { getSignedUrl as _getSignedUrl } from '@aws-sdk/s3-request-presigner' @@ -211,19 +211,29 @@ export function useObject({ bucket, region }: { bucket: string; region?: string return await $client.send(new PutObjectLegalHoldCommand(params)) } - async function getObjectVersions(key: string) { + async function listObjectVersions(key: string) { const params = { Bucket: bucket, - Prefix: key, + Prefix: key, // match "abc" and "abcd" + Delimiter: '/', // has no effect on "abcd", can keep or remove } - return await $client.send(new ListObjectVersionsCommand(params)) + const res = await $client.send(new ListObjectVersionsCommand(params)) + + const Versions = (res.Versions ?? []).filter(v => v.Key === key) + const DeleteMarkers = (res.DeleteMarkers ?? []).filter(m => m.Key === key) + + // Sort by LastModified desc + Versions.sort((a, b) => +new Date(b.LastModified!) - +new Date(a.LastModified!)) + DeleteMarkers.sort((a, b) => +new Date(b.LastModified!) - +new Date(a.LastModified!)) + + return { ...res, Versions, DeleteMarkers } } async function deleteAllVersions(key: string) { try { // Get all versions of the object - const versions = await getObjectVersions(key) + const versions = await listObjectVersions(key) const versionsToDelete = versions.Versions || [] const deleteMarkers = versions.DeleteMarkers || [] @@ -258,7 +268,7 @@ export function useObject({ bucket, region }: { bucket: string; region?: string getObjectLegalHold, putObjectLegalHold, setLegalHold: (key: string, enabled: boolean) => putObjectLegalHold(key, { Status: enabled ? 'ON' : 'OFF' }), - getObjectVersions, + listObjectVersions, deleteAllVersions, } }