mirror of
https://github.com/rustfs/console.git
synced 2026-09-19 01:47:44 +08:00
feat :ObjectLegalHold
This commit is contained in:
+168
-78
@@ -1,45 +1,77 @@
|
||||
<template>
|
||||
<n-drawer v-model:show="visibel" :width="550">
|
||||
<n-drawer-content :title="t('Object Details')">
|
||||
<div class="flex items-center gap-4 ml-auto">
|
||||
<div class="flex items-center flex-wrap gap-4 ml-auto">
|
||||
<n-button @click="download">
|
||||
<Icon name="ri:download-line" class="mr-2" />
|
||||
<span>{{ t('Download') }}</span>
|
||||
<span>{{ t("Download") }}</span>
|
||||
</n-button>
|
||||
|
||||
<n-button @click="() => showPreview = true">
|
||||
<n-button @click="() => (showPreview = true)">
|
||||
<Icon name="ri:eye-line" class="mr-2" />
|
||||
<span>{{ t('Preview') }}</span>
|
||||
<span>{{ t("Preview") }}</span>
|
||||
</n-button>
|
||||
|
||||
<n-button @click="() => showTagView = true">
|
||||
<n-button @click="() => (showTagView = true)">
|
||||
<Icon name="ri:price-tag-3-line" class="mr-2" />
|
||||
<span>{{ t('Tags') }}</span>
|
||||
<span>{{ t("Tags") }}</span>
|
||||
</n-button>
|
||||
|
||||
<!-- <n-button v-if="lockStatus" @click="() => true == true">
|
||||
<Icon name="ri:save-2-line" class="mr-2" />
|
||||
<span>{{ t("Retention") }}</span>
|
||||
</n-button>
|
||||
<n-button v-if="lockStatus" @click="() => true == true">
|
||||
<Icon name="ri:auction-line" class="mr-2" />
|
||||
<span>{{ t("Legal Hold") }}</span>
|
||||
</n-button> -->
|
||||
|
||||
<n-button id="copyTag" ref="copyRef" @click="copySignUrl">
|
||||
<Icon name="ri:file-copy-line" class="mr-2" />
|
||||
<span>{{ t('Copy URL') }}</span>
|
||||
<span>{{ t("Copy URL") }}</span>
|
||||
</n-button>
|
||||
|
||||
</div>
|
||||
<n-card :title="t('Info')" class="mt-4">
|
||||
<div v-if="loading === 'pending'" class="flex items-center justify-center">
|
||||
<n-spin size="small" />
|
||||
</div>
|
||||
<n-descriptions :column="1">
|
||||
<n-descriptions-item :label="t('Object Name')"><span class="select-all">{{ key }}</span></n-descriptions-item>
|
||||
<n-descriptions-item :label="t('Object Name')">
|
||||
<span class="select-all">{{ key }}</span>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item :label="t('Object Size')">{{ object?.ContentLength }}</n-descriptions-item>
|
||||
<n-descriptions-item :label="t('Object Type')">{{ object?.ContentType }}</n-descriptions-item>
|
||||
<!-- <n-descriptions-item label="存储类型">{{ object?.StorageClass }}</n-descriptions-item> -->
|
||||
<n-descriptions-item label="ETag"><span class="select-all">{{ object?.ETag }}</span></n-descriptions-item>
|
||||
<n-descriptions-item label="ETag">
|
||||
<span class="select-all">{{ object?.ETag }}</span>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item :label="t('Last Modified Time')">{{ object?.LastModified }}</n-descriptions-item>
|
||||
|
||||
<!-- 合法保留 -->
|
||||
<n-descriptions-item :label="t('Legal Hold')" v-if="lockStatus">
|
||||
<n-switch
|
||||
v-model:value="legalHold"
|
||||
:loading="legalHoldLoading"
|
||||
:round="false"
|
||||
@update:value="handleChangeLegalStatus">
|
||||
<template #checked>
|
||||
{{ t("Enabled") }}
|
||||
</template>
|
||||
<template #unchecked>
|
||||
{{ t("Disabled") }}
|
||||
</template>
|
||||
</n-switch>
|
||||
</n-descriptions-item>
|
||||
|
||||
<!-- 保留 -->
|
||||
<n-descriptions-item :label="t('Retention') + t('Policy')">compliance</n-descriptions-item>
|
||||
|
||||
<!-- <n-descriptions-item label="版本ID">{{ object?.VersionId }}</n-descriptions-item>
|
||||
<n-descriptions-item label="存储类型">{{ object?.StorageClass }}</n-descriptions-item> -->
|
||||
<n-descriptions-item :label="t('Temporary URL')">
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<n-input v-model:value="signedUrl" id="signedUrl" :placeholder="t('Temporary URL')" />
|
||||
<n-button @click="copySignUrl">{{ t('Copy') }}</n-button>
|
||||
<n-button @click="copySignUrl">{{ t("Copy") }}</n-button>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
@@ -50,7 +82,12 @@
|
||||
<n-modal v-model:show="showTagView" preset="card" :title="t('Set Tags')" draggable class="max-w-screen-md">
|
||||
<n-card class="max-w-screen-md">
|
||||
<n-space class="my-4">
|
||||
<n-tag class="m-2 align-middle" v-for="(tag, index) in tags" type="info" closable @close="handledeleteTag(index)">
|
||||
<n-tag
|
||||
class="m-2 align-middle"
|
||||
v-for="(tag, index) in tags"
|
||||
type="info"
|
||||
closable
|
||||
@close="handledeleteTag(index)">
|
||||
{{ tag.Key }}:{{ tag.Value }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
@@ -62,12 +99,11 @@
|
||||
<n-input v-model:value="tagFormValue.Value" :placeholder="t('Tag Value Placeholder')" />
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-button type="primary" @click="submitTagForm">{{ t('Add') }}</n-button>
|
||||
<n-button class="mx-4" @click="showTagView = false">{{ t('Cancel') }}</n-button>
|
||||
<n-button type="primary" @click="submitTagForm">{{ t("Add") }}</n-button>
|
||||
<n-button class="mx-4" @click="showTagView = false">{{ t("Cancel") }}</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</n-drawer-content>
|
||||
@@ -75,55 +111,111 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const visibel = ref(false)
|
||||
const bucketName = ref('')
|
||||
const key = ref('')
|
||||
const visibel = ref(false);
|
||||
const bucketName = ref("");
|
||||
const key = ref("");
|
||||
|
||||
let objectApi: any
|
||||
let objectApi: any;
|
||||
const openDrawer = (bucket: string, objName: string) => {
|
||||
visibel.value = true
|
||||
bucketName.value = bucket
|
||||
key.value = objName
|
||||
visibel.value = true;
|
||||
bucketName.value = bucket;
|
||||
key.value = objName;
|
||||
objectApi = useObject({ bucket: bucketName.value });
|
||||
getTags()
|
||||
getObject()
|
||||
}
|
||||
getTags();
|
||||
getObject();
|
||||
getObjectLockConfig();
|
||||
};
|
||||
defineExpose({
|
||||
openDrawer
|
||||
})
|
||||
|
||||
openDrawer,
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
// 当前路径的前缀, example: '/folder1/folder2/'
|
||||
// 预览内容
|
||||
const showPreview = ref(false)
|
||||
const showPreview = ref(false);
|
||||
// 标签
|
||||
const showTagView = ref(false)
|
||||
const showTagView = ref(false);
|
||||
const tagFormValue = ref({
|
||||
Key: '',
|
||||
Value: ''
|
||||
})
|
||||
Key: "",
|
||||
Value: "",
|
||||
});
|
||||
interface Tag {
|
||||
Key: string
|
||||
Value: string
|
||||
Key: string;
|
||||
Value: string;
|
||||
}
|
||||
// 获取tags
|
||||
const tags = ref<Tag[]>([])
|
||||
const tags = ref<Tag[]>([]);
|
||||
const getTags = async () => {
|
||||
const { getObjectTagging } = useObject({ bucket: bucketName.value })
|
||||
const { getObjectTagging } = useObject({ bucket: bucketName.value });
|
||||
|
||||
const resp: any = await getObjectTagging(key.value)
|
||||
tags.value = resp.TagSet || []
|
||||
const resp: any = await getObjectTagging(key.value);
|
||||
tags.value = resp.TagSet || [];
|
||||
};
|
||||
|
||||
}
|
||||
/****************************************************************** */
|
||||
// 获取object lock 保留与合法保留相关
|
||||
/****************************************************************** */
|
||||
// const { getObjectRetention, putObjectRetention, getObjectLegalHold, putObjectLegalHold } = useObject({
|
||||
// bucket: bucketName.value,
|
||||
// });
|
||||
|
||||
// 桶的object lock状态
|
||||
const lockStatus = ref(false);
|
||||
// 合法保留状态
|
||||
const legalHold = ref(false);
|
||||
const legalHoldLoading = ref(false);
|
||||
|
||||
// 获取桶的object lock
|
||||
const getObjectLockConfig = async () => {
|
||||
const { getObjectLockConfiguration } = useBucket({});
|
||||
getObjectLockConfiguration(bucketName.value).then((res) => {
|
||||
if (res.ObjectLockConfiguration?.ObjectLockEnabled) {
|
||||
lockStatus.value = res.ObjectLockConfiguration?.ObjectLockEnabled == "Enabled" ? true : false;
|
||||
// 如果桶开启了object lock,则获取合法保留状态
|
||||
if (lockStatus.value) {
|
||||
getObjectLegalHoldFn();
|
||||
}
|
||||
} else {
|
||||
lockStatus.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 获取合法保留状态
|
||||
const getObjectLegalHoldFn = () => {
|
||||
legalHoldLoading.value = true;
|
||||
const { getObjectLegalHold } = useObject({
|
||||
bucket: bucketName.value,
|
||||
});
|
||||
getObjectLegalHold(key.value)
|
||||
.then((res) => {
|
||||
legalHold.value = res.LegalHold?.Status == "ON" ? true : false;
|
||||
})
|
||||
.finally(() => {
|
||||
legalHoldLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeLegalStatus = () => {
|
||||
const { putObjectLegalHold } = useObject({
|
||||
bucket: bucketName.value,
|
||||
});
|
||||
putObjectLegalHold(key.value, {
|
||||
Status: legalHold.value ? "ON" : "OFF",
|
||||
}).finally(() => {
|
||||
getObjectLegalHoldFn();
|
||||
});
|
||||
};
|
||||
|
||||
/****************************************************************** */
|
||||
|
||||
// 删除标签
|
||||
const handledeleteTag = async (index: number) => {
|
||||
const { putObjectTagging } = useObject({ bucket: bucketName.value })
|
||||
const { putObjectTagging } = useObject({ bucket: bucketName.value });
|
||||
dialog.error({
|
||||
title: t("Warning"),
|
||||
content: t("Delete Tag Confirm"),
|
||||
@@ -132,72 +224,70 @@ const handledeleteTag = async (index: number) => {
|
||||
onPositiveClick: async () => {
|
||||
putObjectTagging(key.value, { TagSet: tags.value.filter((item, keyIndex) => keyIndex !== index) })
|
||||
.then(() => {
|
||||
message.success(t("Tag Update Success"))
|
||||
getTags()
|
||||
message.success(t("Tag Update Success"));
|
||||
getTags();
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error(t("Tag Delete Failed", { error: error.message }))
|
||||
})
|
||||
message.error(t("Tag Delete Failed", { error: error.message }));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const submitTagForm = async () => {
|
||||
const { putObjectTagging } = useObject({ bucket: bucketName.value })
|
||||
const { putObjectTagging } = useObject({ bucket: bucketName.value });
|
||||
const tag = {
|
||||
Key: tagFormValue.value.Key,
|
||||
Value: tagFormValue.value.Value
|
||||
}
|
||||
Value: tagFormValue.value.Value,
|
||||
};
|
||||
putObjectTagging(key.value, {
|
||||
TagSet: [...tags.value, tag]
|
||||
TagSet: [...tags.value, tag],
|
||||
}).then(() => {
|
||||
message.success(t('Tag Set Success'))
|
||||
getTags()
|
||||
})
|
||||
}
|
||||
message.success(t("Tag Set Success"));
|
||||
getTags();
|
||||
});
|
||||
};
|
||||
|
||||
const object = ref()
|
||||
const loading = ref('pending')
|
||||
const signedUrl = ref('')
|
||||
const object = ref();
|
||||
const loading = ref("pending");
|
||||
const signedUrl = ref("");
|
||||
|
||||
const getObject = () => {
|
||||
objectApi.headObject(key.value).then(async (res: any) => {
|
||||
object.value = res
|
||||
loading.value = 'fulfilled'
|
||||
object.value = res;
|
||||
loading.value = "fulfilled";
|
||||
signedUrl.value = await objectApi.getSignedUrl(key.value);
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const refreshSignedUrl = async () => {
|
||||
try {
|
||||
signedUrl.value = await objectApi.getSignedUrl(key.value);
|
||||
message.success(t('Get Success'));
|
||||
message.success(t("Get Success"));
|
||||
} catch (error) {
|
||||
message.error(t('Get Failed'));
|
||||
message.error(t("Get Failed"));
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
import ClipboardJS from 'clipboard'
|
||||
const dialog = useDialog()
|
||||
import ClipboardJS from "clipboard";
|
||||
const dialog = useDialog();
|
||||
const copySignUrl = async () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
ClipboardJS.copy(document.querySelector('#signedUrl'))
|
||||
console.log('复制成功', signedUrl.value);
|
||||
ClipboardJS.copy(document.querySelector("#signedUrl"));
|
||||
console.log("复制成功", signedUrl.value);
|
||||
} catch (error) {
|
||||
message.error(t('Copy Failed'))
|
||||
console.error('复制失败', error)
|
||||
message.error(t("Copy Failed"));
|
||||
console.error("复制失败", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const download = async () => {
|
||||
const msg = message.loading(t('Getting URL'));
|
||||
await refreshSignedUrl()
|
||||
const msg = message.loading(t("Getting URL"));
|
||||
await refreshSignedUrl();
|
||||
const url = await objectApi.getSignedUrl(key.value);
|
||||
msg.destroy();
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
window.open(url, "_blank");
|
||||
};
|
||||
</script>
|
||||
|
||||
+96
-33
@@ -1,57 +1,69 @@
|
||||
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand,GetObjectTaggingCommand, PutObjectTaggingCommand,DeleteObjectTaggingCommand} from '@aws-sdk/client-s3'
|
||||
import { getSignedUrl as _getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
||||
import {
|
||||
DeleteObjectCommand,
|
||||
GetObjectCommand,
|
||||
HeadObjectCommand,
|
||||
ListObjectsV2Command,
|
||||
PutObjectCommand,
|
||||
GetObjectTaggingCommand,
|
||||
PutObjectTaggingCommand,
|
||||
DeleteObjectTaggingCommand,
|
||||
GetObjectRetentionCommand,
|
||||
PutObjectRetentionCommand,
|
||||
GetObjectLegalHoldCommand,
|
||||
PutObjectLegalHoldCommand,
|
||||
} from "@aws-sdk/client-s3";
|
||||
import { getSignedUrl as _getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||
|
||||
export function useObject({ bucket, region }: { bucket: string, region?: string }) {
|
||||
const $client = useNuxtApp().$s3Client
|
||||
export function useObject({ bucket, region }: { bucket: string; region?: string }) {
|
||||
const $client = useNuxtApp().$s3Client;
|
||||
|
||||
const headObject = async (key: string) => {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key
|
||||
}
|
||||
Key: key,
|
||||
};
|
||||
|
||||
return await $client.send(new HeadObjectCommand(params))
|
||||
}
|
||||
return await $client.send(new HeadObjectCommand(params));
|
||||
};
|
||||
|
||||
const getSignedUrl = async (key: string, expiresIn = 3600) => {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
})
|
||||
});
|
||||
|
||||
return await _getSignedUrl($client, command, { expiresIn })
|
||||
}
|
||||
return await _getSignedUrl($client, command, { expiresIn });
|
||||
};
|
||||
|
||||
const putObject = async (key: string, body: Blob | string) => {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Body: body
|
||||
}
|
||||
Body: body,
|
||||
};
|
||||
|
||||
return await $client.send(new PutObjectCommand(params))
|
||||
}
|
||||
return await $client.send(new PutObjectCommand(params));
|
||||
};
|
||||
|
||||
const deleteObject = async (key: string) => {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key
|
||||
}
|
||||
Key: key,
|
||||
};
|
||||
|
||||
return await $client.send(new DeleteObjectCommand(params))
|
||||
}
|
||||
return await $client.send(new DeleteObjectCommand(params));
|
||||
};
|
||||
|
||||
const listObject = async (bucket:string,prefix :string| undefined = undefined,pageSize :number = 25) => {
|
||||
const listObject = async (bucket: string, prefix: string | undefined = undefined, pageSize: number = 25) => {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Prefix: prefix,
|
||||
MaxKeys: pageSize ,
|
||||
MaxKeys: pageSize,
|
||||
Delimiter: "/",
|
||||
}
|
||||
|
||||
return await $client.send(new ListObjectsV2Command(params))
|
||||
}
|
||||
};
|
||||
|
||||
return await $client.send(new ListObjectsV2Command(params));
|
||||
};
|
||||
|
||||
async function mapAllFiles(bucketName: string, prefix: string, callback: (fileKey: string) => void) {
|
||||
let isTruncated = true;
|
||||
@@ -89,29 +101,80 @@ export function useObject({ bucket, region }: { bucket: string, region?: string
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
}
|
||||
};
|
||||
|
||||
return await $client.send(new GetObjectTaggingCommand(params))
|
||||
return await $client.send(new GetObjectTaggingCommand(params));
|
||||
}
|
||||
|
||||
async function putObjectTagging(key: string, tagging: any) {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Tagging: tagging
|
||||
}
|
||||
Tagging: tagging,
|
||||
};
|
||||
|
||||
return await $client.send(new PutObjectTaggingCommand(params))
|
||||
return await $client.send(new PutObjectTaggingCommand(params));
|
||||
}
|
||||
|
||||
async function deleteObjectTagging(key: string) {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
}
|
||||
};
|
||||
|
||||
return await $client.send(new DeleteObjectTaggingCommand(params))
|
||||
return await $client.send(new DeleteObjectTaggingCommand(params));
|
||||
}
|
||||
|
||||
return { headObject, putObject, deleteObject, getSignedUrl, mapAllFiles,listObject,getObjectTagging ,putObjectTagging,deleteObjectTagging}
|
||||
async function getObjectRetention(key: string) {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
};
|
||||
|
||||
return await $client.send(new GetObjectRetentionCommand(params));
|
||||
}
|
||||
|
||||
async function putObjectRetention(key: string, retention: any) {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
};
|
||||
return await $client.send(new PutObjectRetentionCommand(params));
|
||||
}
|
||||
|
||||
async function getObjectLegalHold(key: string) {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
};
|
||||
console.log("🚀 ~ getObjectLegalHold ~ params:", params);
|
||||
|
||||
return await $client.send(new GetObjectLegalHoldCommand(params));
|
||||
}
|
||||
|
||||
async function putObjectLegalHold(key: string, legalHold: any) {
|
||||
const params = {
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
LegalHold: legalHold,
|
||||
};
|
||||
|
||||
return await $client.send(new PutObjectLegalHoldCommand(params));
|
||||
}
|
||||
|
||||
return {
|
||||
headObject,
|
||||
putObject,
|
||||
deleteObject,
|
||||
getSignedUrl,
|
||||
mapAllFiles,
|
||||
listObject,
|
||||
getObjectTagging,
|
||||
putObjectTagging,
|
||||
deleteObjectTagging,
|
||||
getObjectRetention,
|
||||
putObjectRetention,
|
||||
getObjectLegalHold,
|
||||
putObjectLegalHold,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -327,5 +327,6 @@
|
||||
"Retention Period": "Retention Period",
|
||||
"Please enter retention period": "Please enter retention period",
|
||||
"Please select retention unit": "Please select retention unit",
|
||||
"Set Retention": "Set Retention"
|
||||
"Set Retention": "Set Retention",
|
||||
"Legal Hold": "Legal Hold"
|
||||
}
|
||||
|
||||
@@ -519,5 +519,6 @@
|
||||
"Retention Period": "保留期限",
|
||||
"Please enter retention period": "请输入保留期限",
|
||||
"Please select retention unit": "请选择保留单位",
|
||||
"Set Retention": "设置保留"
|
||||
"Set Retention": "设置保留",
|
||||
"Legal Hold": "合法保留"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user