feat : retention

This commit is contained in:
马登山
2025-05-30 11:36:58 +08:00
parent 3a8044d8b0
commit 4b94949cc7
9 changed files with 790 additions and 348 deletions
+3
View File
@@ -43,6 +43,7 @@ declare module 'vue' {
NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NLi: typeof import('naive-ui')['NLi']
@@ -56,6 +57,8 @@ declare module 'vue' {
NPageHeader: typeof import('naive-ui')['NPageHeader']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NProgress: typeof import('naive-ui')['NProgress']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']
+55 -5
View File
@@ -68,6 +68,35 @@
:loading="statusLoading"
@update:value="handleChangeVersionStatus" />
</n-descriptions-item>
<n-descriptions-item>
<template #label>保留</template>
<n-switch
v-model:value="retentionEnabled"
:loading="objectLockLoading"
:round="false"
@update:value="handleChangeVersionStatus" />
<n-space v-if="retentionEnabled">
<div v-if="retentionEnabled" class="flex flex-col gap-4">
<div v-if="retentionEnabled">
<div class="flex items-center justify-between">
<n-space>{{ t("Retention Mode") }}:</n-space>
<n-radio-group v-model:value="retentionMode">
<n-radio value="COMPLIANCE">Compliance</n-radio>
<n-radio value="GOVERNANCE">Governance</n-radio>
</n-radio-group>
</div>
<div class="flex items-center justify-between gap-4">
<n-space class="w-24">{{ t("Validity") }}*</n-space>
<n-input-group class="justify-end">
<n-input-number v-model:value="retentionPeriod" :min="1" />
<n-select v-model:value="retentionUnit" :options="retentionUnitOptions" class="w-16" />
</n-input-group>
</div>
</div>
</div>
</n-space>
</n-descriptions-item>
</n-descriptions>
</n-drawer-content>
@@ -132,6 +161,8 @@
</template>
<script setup lang="ts">
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const dialog = useDialog();
const visibel = ref(false);
const bucketName = ref("");
@@ -145,13 +176,18 @@ defineExpose({
openDrawer,
});
const getData = () => {
getbucketPolicy();
getTags();
getVersioningStatus();
getObjectLockConfig();
const getData = async () => {
await getTags();
await getVersioningStatus();
await getObjectLockConfig();
await getbucketPolicy();
};
const retentionUnitOptions = [
{ label: t("Day"), value: "DAYS" },
{ label: t("Year"), value: "YEARS" },
];
const message = useMessage();
const {
getBucketTagging,
@@ -168,14 +204,28 @@ const {
/**********object lock ***********************/
const lockStatus = ref(false);
const objectLockLoading = ref(false);
const retentionEnabled = ref(false);
const retentionMode = ref("COMPLIANCE");
const retentionPeriod = ref(180);
const retentionUnit = ref("DAYS");
const getObjectLockConfig = async () => {
objectLockLoading.value = true;
getObjectLockConfiguration(bucketName.value)
.then((res) => {
if (res.ObjectLockConfiguration?.ObjectLockEnabled) {
lockStatus.value = res.ObjectLockConfiguration?.ObjectLockEnabled == "Enabled" ? true : false;
console.log(res.ObjectLockConfiguration?.Rule);
if (res.ObjectLockConfiguration?.Rule) {
console.log(1111);
retentionEnabled.value = true;
retentionMode.value = res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Mode || "COMPLIANCE";
retentionPeriod.value = res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Days || 180;
retentionUnit.value = res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Years ? "YEARS" : "DAYS";
}
} else {
lockStatus.value = false;
retentionEnabled.value = false;
}
})
.finally(() => {
+107 -51
View File
@@ -2,9 +2,9 @@
<n-modal :show="show" @update:show="(val: boolean) => $emit('update:show', val)" size="huge">
<n-card class="max-w-screen-sm">
<template #header>
<div style="display:flex; justify-content: space-between; align-items:center;">
<span>{{ t('Create Bucket') }}</span>
<n-button size="small" ghost @click="closeModal">{{ t('Close') }}</n-button>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>{{ t("Create Bucket") }}</span>
<n-button size="small" ghost @click="closeModal">{{ t("Close") }}</n-button>
</div>
</template>
<div class="flex flex-col gap-4">
@@ -20,17 +20,43 @@
<div class="flex items-center gap-4">
<n-input :placeholder="t('Please enter name')" v-model:value="objectKey" />
</div>
<div class="flex items-center gap-4">
<n-space class=" w-12">{{ t('Version') }}:</n-space>
<n-switch v-model:value="version" @change="handleVersionChange"/>
<div class="flex items-center justify-between gap-4">
<n-space class="w-24">{{ t("Version") }}:</n-space>
<n-switch v-model:value="version" @change="handleVersionChange" />
</div>
<div class="flex items-center gap-4">
<n-space class=" w-12">{{ t('Object Lock') }}:</n-space>
<n-switch v-model:value="objectLock" @change="handleObjectLock"/>
<div class="flex items-center justify-between gap-4">
<n-space class="w-24">{{ t("Object Lock") }}:</n-space>
<n-switch v-model:value="objectLock" @change="handleObjectLock" />
</div>
<!-- Retention 配置区块仅对象锁开启时显示 -->
<div v-if="objectLock" class="flex flex-col gap-4">
<div class="flex items-center justify-between gap-4">
<n-space class="w-24">{{ t("Retention") }}</n-space>
<n-switch v-model:value="retentionEnabled" />
</div>
<div v-if="retentionEnabled">
<div class="flex items-center justify-between gap-4">
<n-space class="w-24">{{ t("Retention Mode") }}:</n-space>
<n-radio-group v-model:value="retentionMode">
<n-radio value="COMPLIANCE">Compliance</n-radio>
<n-radio value="GOVERNANCE">Governance</n-radio>
</n-radio-group>
</div>
<div class="flex items-center justify-between gap-4">
<n-space class="w-24">{{ t("Validity") }}*</n-space>
<n-input-group class="justify-end">
<n-input-number v-model:value="retentionPeriod" :min="1" class="w-96" />
<n-select v-model:value="retentionUnit" :options="retentionUnitOptions" class="w-16" />
</n-input-group>
</div>
</div>
</div>
<div class="flex justify-center gap-4">
<n-button type="primary" :disabled="objectKey.trim().length < 1" @click="handlePutObject">{{ t('Create') }}</n-button>
<n-button type="primary" :disabled="objectKey.trim().length < 1" @click="handlePutObject">
{{ t("Create") }}
</n-button>
</div>
</div>
</n-card>
@@ -38,64 +64,94 @@
</template>
<script setup lang="ts">
import { defineEmits, defineProps } from 'vue';
import { useI18n } from 'vue-i18n';
import { defineEmits, defineProps } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n()
const emit = defineEmits(['update:show'])
const { t } = useI18n();
const emit = defineEmits(["update:show"]);
const props = defineProps<{ show: boolean; }>()
const props = defineProps<{ show: boolean }>();
const closeModal = () => emit('update:show', false)
const closeModal = () => emit("update:show", false);
const objectKey = ref('')
const version = ref(false)
const objectLock = ref(false)
const objectKey = ref("");
const version = ref(false);
const objectLock = ref(false);
const { createBucket ,putBucketVersioning} = useBucket({})
// Retention 相关变量
const retentionEnabled = ref(false);
const retentionMode = ref("COMPLIANCE");
const retentionPeriod = ref(180);
const retentionUnit = ref("day");
const retentionUnitOptions = [
{ label: t("Day"), value: "day" },
{ label: t("Year"), value: "year" },
];
const $message = useMessage()
const { createBucket, putBucketVersioning, putObjectLockConfiguration } = useBucket({});
const $message = useMessage();
// 如果开启对象锁,必须开启版本控制
const handleObjectLock = () => {
if (objectLock.value) {
version.value = true
}
}
version.value = true;
}
};
// 如果关闭版本控制,关闭对象锁
const handleVersionChange = () => {
if (!version.value) {
objectLock.value = false
objectLock.value = false;
}
}
};
const handlePutObject = () => {
const params = {
const params: any = {
Bucket: objectKey.value,
ObjectLockEnabledForBucket: objectLock.value,
}
createBucket(params).then(() => {
// 开启版本控制
if (version.value) {
putBucketVersioning(
objectKey.value,
version.value?"Enabled":"Suspended",
).then(() => {
emit('update:show', false)
objectKey.value = ''
$message.success(t('Create Success'))
}).catch((e) => {
$message.error(t('Create Failed'))
})
}else {
emit('update:show', false)
objectKey.value = ''
$message.success(t('Create Success'))
}
}).catch((e) => {
$message.error(e.message)
})
}
};
createBucket(params)
.then(() => {
// 开启版本控制
const afterVersioning = () => {
// 如果需要设置 retention
if (objectLock.value && retentionEnabled.value) {
putObjectLockConfiguration(objectKey.value, {
ObjectLockEnabled: "Enabled",
Rule: {
DefaultRetention: {
Mode: retentionMode.value,
Days: retentionUnit.value === "day" ? retentionPeriod.value : undefined,
Years: retentionUnit.value === "year" ? retentionPeriod.value : undefined,
},
},
})
.then(() => {
emit("update:show", false);
objectKey.value = "";
$message.success(t("Create Success"));
})
.catch((e) => {
$message.error(t("Retention Save Failed"));
});
} else {
emit("update:show", false);
objectKey.value = "";
$message.success(t("Create Success"));
}
};
if (version.value) {
putBucketVersioning(objectKey.value, version.value ? "Enabled" : "Suspended")
.then(afterVersioning)
.catch(() => $message.error(t("Create Failed")));
} else {
afterVersioning();
}
})
.catch((e) => {
$message.error(e.message);
});
};
</script>
+363 -147
View File
@@ -1,128 +1,122 @@
<template>
<n-modal v-model:show="visible" :mask-closable="false" preset="card" :title="t('Add Lifecycle Rule') + ` (${t('Bucket')}: ${bucketName})`" class="max-w-screen-md" :segmented="{
content: true,
action: true,
}">
<n-modal
v-model:show="visible"
:mask-closable="false"
preset="card"
:title="t('Add Lifecycle Rule') + ` (${t('Bucket')}: ${bucketName})`"
class="max-w-screen-md"
:segmented="{
content: true,
action: true,
}">
<n-card>
<n-tabs default-value="expire" justify-content="space-evenly" type="line">
<n-tab-pane name="expire" :tab="t('Expiration')">
<n-form class="my-4" ref="formRef" :model="formData">
<n-form-item :label="t('Object Version')" path="versionType">
<n-form class="my-4" ref="formRef" :model="formData">
<n-tabs default-value="expire" justify-content="space-evenly" type="line" @update:value="handleUpdateValue">
<n-tab-pane name="expire" :tab="t('Expiration')">
<n-form-item :label="t('Object Version')" path="versionType" v-if="versioningStatus">
<n-select v-model:value="formData.versionType" :options="versionOptions" />
</n-form-item>
<n-form-item label="" path="type">
<n-form-item label="时间周期" path="type">
<div class="w-full flex items-center justify-between">
<span>{{ t('After') }}</span>
<n-input-number class="flex-auto mx-4" v-model:value="formData.days" :min="1" :placeholder="t('Days')" style="width: 100px" />
<span>{{ t('days execute') }}</span>
<!-- <n-select
class="flex-auto mx-4"
v-model:value="formData.action"
:options="actionOptions"
style="width: 120px"
/> -->
<n-input-number
class="flex-auto"
v-model:value="formData.days"
:min="1"
:placeholder="t('Days')"
style="width: 100px" />
<span class="ms-4">{{ t("Days After") }}</span>
</div>
</n-form-item>
<!-- 生命周期 -->
<n-card>
<n-collapse>
<n-collapse-item :title="t('More Configurations')" name="advanced">
<n-form-item :label="t('Prefix')">
<n-input v-model="formData.prefix" :placeholder="t('Please enter prefix')" />
</n-form-item>
<n-form-item :label="t('Tags')">
<n-dynamic-input
v-model:value="formData.tags"
preset="pair"
:key-placeholder="t('Tag Name')"
:value-placeholder="t('Tag Value')" />
</n-form-item>
</n-collapse-item>
</n-collapse>
</n-card>
<!-- 高级设置 -->
<n-card class="my-4" v-if="formData.versionType == 'non-current'">
<n-collapse>
<n-collapse-item title="高级设置" name="advanced">
<n-form-item label="删除标记处理">
<n-space>
<n-switch v-model:value="formData.expiredDeleteMark" :round="false" />
<span class="ml-4 text-gray-500">如果没有留下任何版本请删除对该对象的引用</span>
</n-space>
</n-form-item>
<!-- <n-form-item label="版本清理">
<n-space >
<n-switch
v-model:value="formData.deleteAllExpired"
:round="false"
/>
<span class="ml-4 text-gray-500">删除所有过期版本</span>
</n-space>
</n-form-item> -->
</n-collapse-item>
</n-collapse>
</n-card>
</n-tab-pane>
<n-tab-pane name="transition" :tab="t('Transition')">
<n-form-item :label="t('Object Version')" path="versionType" v-if="versioningStatus">
<n-select v-model:value="formData.versionType" :options="versionOptions" />
</n-form-item>
<n-form-item :label="t('Time Cycle')" path="type">
<div class="w-full flex items-center justify-between">
<n-input-number
class="flex-auto"
v-model:value="formData.days"
:min="1"
:placeholder="t('Days')"
style="width: 100px" />
<span class="ms-4">{{ t("Days After") }}</span>
</div>
</n-form-item>
<n-form-item :label="t('Stroage Type')" path="storageType">
<n-select v-model:value="formData.storageType" :options="tiers" />
</n-form-item>
<!-- 生命周期 -->
<n-card>
<n-collapse>
<n-collapse-item :title="t('Search Lifecycle')" name="advanced">
<n-collapse-item :title="t('More Configurations')" name="advanced">
<n-form-item :label="t('Prefix')">
<n-input v-model="formData.prefix" :placeholder="t('Please enter prefix')" />
</n-form-item>
<n-form-item :label="t('Tags')">
<n-dynamic-input v-model:value="formData.tags" preset="pair" :key-placeholder="t('Tag Name')" :value-placeholder="t('Tag Value')" />
<n-dynamic-input
v-model:value="formData.tags"
preset="pair"
:key-placeholder="t('Tag Name')"
:value-placeholder="t('Tag Value')" />
</n-form-item>
</n-collapse-item>
</n-collapse>
</n-card>
<!-- 高级设置 -->
<n-card class="my-4">
<n-collapse>
<n-collapse-item :title="t('Advanced Settings')" name="advanced">
<n-form-item :label="t('Delete Marker Handling')">
<n-space vertical>
<n-switch v-model:value="formData.expiredDeleteMark" :round="false" />
<span class="ml-4 text-gray-500">{{ t('If no versions remain, delete references to this object') }}</span>
</n-space>
</n-form-item>
<n-form-item :label="t('Version Cleanup')">
<n-space vertical>
<n-switch v-model:value="formData.deleteAllExpired" :round="false" />
<span class="ml-4 text-gray-500">{{ t('Delete all expired versions') }}</span>
</n-space>
</n-form-item>
</n-collapse-item>
</n-collapse>
</n-card>
</n-form>
</n-tab-pane>
<n-tab-pane name="transition" :tab="t('Transition')">
<n-form class="my-4" ref="formRef" :model="formData">
<n-form-item :label="t('Object Version')" path="versionType">
<n-select v-model:value="formData.versionType" :options="versionOptions" />
</n-form-item>
<n-form-item label="" path="type">
<div class="w-full flex items-center justify-between">
<span>{{ t('After') }}</span>
<n-input-number class="flex-auto mx-4" v-model:value="formData.days" :min="1" :placeholder="t('Days')" style="width: 100px" />
<span>{{ t('days execute') }}</span>
<n-select class="flex-auto mx-4" v-model:value="formData.action" :options="actionOptions" style="width: 120px" />
</div>
</n-form-item>
<!-- 生命周期 -->
<n-card>
<n-collapse>
<n-collapse-item :title="t('Search Lifecycle')" name="advanced">
<n-form-item :label="t('Prefix')">
<n-input v-model="formData.prefix" :placeholder="t('Please enter prefix')" />
</n-form-item>
<n-form-item :label="t('Tags')">
<n-dynamic-input v-model:value="formData.tags" preset="pair" :key-placeholder="t('Tag Name')" :value-placeholder="t('Tag Value')" />
</n-form-item>
</n-collapse-item>
</n-collapse>
</n-card>
<!-- 高级设置 -->
<n-card class="my-4">
<n-collapse>
<n-collapse-item :title="t('Advanced Settings')" name="advanced">
<n-form-item :label="t('Delete Marker Handling')">
<n-space vertical>
<n-switch v-model:value="formData.expiredDeleteMark" :round="false" />
<span class="ml-4 text-gray-500">{{ t('If no versions remain, delete references to this object') }}</span>
</n-space>
</n-form-item>
<n-form-item :label="t('Version Cleanup')">
<n-space vertical>
<n-switch v-model:value="formData.deleteAllExpired" :round="false" />
<span class="ml-4 text-gray-500">{{ t('Delete all expired versions') }}</span>
</n-space>
</n-form-item>
</n-collapse-item>
</n-collapse>
</n-card>
</n-form>
</n-tab-pane>
</n-tabs>
</n-tab-pane>
</n-tabs>
</n-form>
<n-space justify="center">
<n-button @click="handleCancel">{{ t('Cancel') }}</n-button>
<n-button type="primary" @click="handleSave">{{ t('Save') }}</n-button>
<n-button @click="handleCancel">{{ t("Cancel") }}</n-button>
<n-button type="primary" @click="handleSave">{{ t("Save") }}</n-button>
</n-space>
</n-card>
</n-modal>
</template>
<script setup>
<script setup lang="ts">
import {
NButton,
NCollapse,
@@ -133,69 +127,291 @@ import {
NInput,
NInputNumber,
NSelect,
NSwitch
} from 'naive-ui'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
} from "naive-ui";
import { randomUUID } from "uncrypto";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
const { putBucketLifecycleConfiguration, getBucketVersioning } = useBucket({});
const { listTiers } = useTiers();
const { t } = useI18n();
const message = useMessage();
// 定义 tiers 数据结构的接口
interface TierItem {
label: string;
value: string;
}
const { t } = useI18n()
const formRef = ref(null)
const emit = defineEmits(["search"]);
const formRef = ref();
const formData = ref({
ruleName: '',
ruleName: "",
type: null,
versionType: 'current',
versionType: "current",
days: null,
action: 'transition',
tags: [{
key: '',
value: ''
}],
action: "transition",
prefix: "",
expiredDeleteMark: false,
deleteAllExpired: false
})
deleteAllExpired: false,
storageType: "",
tags: [
{
key: "",
value: "",
},
],
});
const typeOptions = [
{ label: t('Expiration'), value: 'expire' },
{ label: t('Transition'), value: 'transition' }
]
const versionOptions = [
{ label: t('Current Version'), value: 'current' },
{ label: t('Non-current Version'), value: 'non-current' }
]
const actionOptions = [
{ label: t('Transition'), value: 'transition' },
{ label: t('Delete'), value: 'delete' }
]
const versionOptions = ref([
{ label: t("Current Version"), value: "current" },
{ label: t("Non-current Version"), value: "non-current" },
]);
const props = defineProps({
bucketName: {
type: String,
required: true
}
})
required: true,
},
// 已经存在的生命周期
lifecycleList: {
type: Array,
default: () => [],
},
});
const visible = ref(false)
watch(
() => props.bucketName,
(newVal) => {
// 获取是都开启版本管理
getVersioningStatus();
}
);
const visible = ref(false);
const open = () => {
visible.value = true
}
visible.value = true;
};
defineExpose({
open
})
const handleSave = () => {
formRef.value?.validate((errors) => {
if (!errors) {
console.log('提交数据:', formData.value)
// 调用保存接口
}
})
}
open,
});
const handleUpdateValue = (value: string) => {
if (value === "expire") {
formData.value.action = "expire";
} else {
formData.value.action = "transition";
}
};
const handleSave = () => {
formRef.value?.validate((errors: any) => {
if (!errors) {
// 调用保存接口
// const params = {
// Rules: [
// {
// Status: 'Enabled',
// Filter: {
// Prefix: formData.value.prefix || '',
// Tag: formData.value.tags.filter(item => item.key && item.value).map(item => {
// return {
// Key: item.key,
// Value: item.value
// }
// }) || []
// },
// Expiration: {
// Date: new Date(),
// Days: formData.value.days,
// },
// }
// ]
// }
const params = {
Rules: [
{
Status: "Enabled",
Filter: {
Prefix: "logs/", // 仅对 logs/ 前缀的对象生效
},
Expiration: {
// "Days": 7,
},
Transition: {
Days: 7,
StorageClass: "MINIO",
},
},
],
};
// const params = {
// Rules: [
// {
// "ID": randomUUID(),
// "Expiration": {
// },
// "Status": "Enabled",
// "Transition": {
// "Days": 3,
// "StorageClass": "MINIO"
// }
// },
// {
// "ID": randomUUID(),
// "Expiration": {
// "Days": 5
// },
// "Status": "Disabled",
// "Transition": {
// }
// },
// {
// "ID": randomUUID(),
// "Expiration": {
// },
// "Status": "Enabled",
// "Transition": {
// "StorageClass": "MINIO",
// "Days": 4
// }
// },
// {
// "ID": randomUUID(),
// "Expiration": {
// "Days": 3
// },
// "Status": "Enabled",
// "Transition": {
// }
// },
// {
// "ID": randomUUID(),
// "Expiration": {
// "Days": 5
// },
// "Status": "Enabled",
// "Transition": {
// }
// },
// {
// "ID": randomUUID(),
// "Expiration": {
// "ExpiredObjectDeleteMarker": true,
// "Days": 2
// },
// "Status": "Enabled",
// "Transition": {
// }
// },
// {
// "ID": randomUUID(),
// "Expiration": {
// "ExpiredObjectDeleteMarker": true,
// "Days": 730
// },
// "Status": "Enabled",
// "Transition": {
// }
// }
// ]
// }
// const params = {
// Rules: [
// {
// "Status": "Enabled",
// "Filter": { "Prefix": "archives/" },
// "Transitions": [
// {
// "StorageClass": "SBHJA",
// "Days": 90 // 90天后归档到Glacier
// }
// ],
// "Expiration": {
// "Days": 5, // 5天后删除当前版本对象
// "ExpiredObjectDeleteMarker": true // 清理过期删除标记
// },
// "NoncurrentVersionTransitions": [
// {
// "StorageClass": "SBHJA",
// "NoncurrentDays": 5 // 非当前版本180天后转入深度归档
// }
// ],
// "NoncurrentVersionExpiration": {
// "NoncurrentDays": 5 // 非当前版本5天后彻底删除
// }
// }
// ]
// }
console.log("🚀 ~ formRef.value?.validate ~ params:", params);
putBucketLifecycleConfiguration(props.bucketName, params)
.then((res) => {
visible.value = false;
emit("search");
message.success(t("Create Success"));
formData.value = {
ruleName: "",
type: null,
versionType: "current",
days: null,
action: "transition",
expiredDeleteMark: false,
deleteAllExpired: false,
storageType: "",
prefix: "",
tags: [
{
key: "",
value: "",
},
],
};
})
.catch((e) => {
message.error(e.message);
});
}
});
};
// 分层列表
const tiers = ref<TierItem[]>([]);
const getTiers = async () => {
const res = await listTiers();
if (res) {
tiers.value = res.map((item: any) => {
return {
label: item[item.type].name,
value: item[item.type].name,
};
});
}
if (tiers.value.length) {
formData.value.storageType = tiers.value[0].value;
}
};
getTiers();
// 获取版本控制状态
const versioningStatus: any = ref(false);
const getVersioningStatus = async () => {
try {
const resp = await getBucketVersioning(props.bucketName);
versioningStatus.value = resp.Status == "Enabled";
} catch (error) {
console.error("获取版本控制状态失败:", error);
}
};
onMounted(() => {
getVersioningStatus();
});
const handleCancel = () => {
// 取消逻辑
visible.value = false
}
visible.value = false;
};
</script>
+74 -54
View File
@@ -15,63 +15,65 @@ import {
GetBucketPolicyStatusCommand,
GetObjectLockConfigurationCommand,
PutObjectLockConfigurationCommand,
PutObjectRetentionCommand,
GetObjectRetentionCommand,
// GetBucketEncryptionCommand,
GetBucketLifecycleConfigurationCommand,
PutBucketLifecycleConfigurationCommand,
DeleteBucketLifecycleCommand,
} from "@aws-sdk/client-s3"
} from "@aws-sdk/client-s3";
export function useBucket({ region }: { region?: string }) {
const $client = useNuxtApp().$s3Client
const $client = useNuxtApp().$s3Client;
const listBuckets = async () => {
return await $client.send(new ListBucketsCommand({}))
}
return await $client.send(new ListBucketsCommand({}));
};
const createBucket = async (params: any) => {
return await $client.send(new CreateBucketCommand(params))
}
return await $client.send(new CreateBucketCommand(params));
};
const headBucket = async (bucket: string) => {
const params = {
Bucket: bucket,
}
};
return await $client.send(new HeadBucketCommand(params));
};
return await $client.send(new HeadBucketCommand(params))
}
const deleteBucket = async (bucket: string) => {
const params = {
Bucket: bucket,
}
};
return await $client.send(new DeleteBucketCommand(params))
}
return await $client.send(new DeleteBucketCommand(params));
};
const getBucketTagging = async (bucket: string) => {
const params = {
Bucket: bucket,
}
};
return await $client.send(new GetBucketTaggingCommand(params))
}
return await $client.send(new GetBucketTaggingCommand(params));
};
const putBucketTagging = async (bucket: string, tagging: any) => {
const params = {
Bucket: bucket,
Tagging: tagging,
}
};
return await $client.send(new PutBucketTaggingCommand(params))
}
return await $client.send(new PutBucketTaggingCommand(params));
};
const deleteBucketTagging = async (bucket: string) => {
const params = {
Bucket: bucket,
}
};
return await $client.send(new DeleteBucketTaggingCommand(params))
}
return await $client.send(new DeleteBucketTaggingCommand(params));
};
const putBucketVersioning = async (bucket: string, status: string) => {
const params = {
@@ -80,80 +82,96 @@ export function useBucket({ region }: { region?: string }) {
Status: status == "Enabled" ? BucketVersioningStatus.Enabled : BucketVersioningStatus.Suspended,
MFADelete: MFADelete.Enabled,
},
}
};
return await $client.send(new PutBucketVersioningCommand(params))
}
return await $client.send(new PutBucketVersioningCommand(params));
};
const getBucketVersioning = async (bucket: string) => {
const params = {
Bucket: bucket,
}
};
return await $client.send(new GetBucketVersioningCommand(params))
}
return await $client.send(new GetBucketVersioningCommand(params));
};
const getBucketPolicy = async (bucket: string) => {
const params = {
Bucket: bucket,
}
};
return await $client.send(new GetBucketPolicyCommand(params))
}
return await $client.send(new GetBucketPolicyCommand(params));
};
const getBucketPolicyStatus = async (bucket: string) => {
const params = {
Bucket: bucket,
}
};
return await $client.send(new GetBucketPolicyStatusCommand(params))
}
return await $client.send(new GetBucketPolicyStatusCommand(params));
};
const putBucketPolicy = async (bucket: string, policy: string) => {
const params = {
Bucket: bucket,
Policy: policy,
}
};
return await $client.send(new PutBucketPolicyCommand(params))
}
return await $client.send(new PutBucketPolicyCommand(params));
};
const getObjectLockConfiguration = async (bucket: string) => {
const params = {
Bucket: bucket,
}
};
return await $client.send(new GetObjectLockConfigurationCommand(params))
}
return await $client.send(new GetObjectLockConfigurationCommand(params));
};
const putObjectLockConfiguration = async (bucket: string, objectLockConfiguration: any) => {
const params = {
Bucket: bucket,
ObjectLockConfiguration: objectLockConfiguration,
}
};
return await $client.send(new PutObjectLockConfigurationCommand(params))
}
return await $client.send(new PutObjectLockConfigurationCommand(params));
};
const getBucketLifecycleConfiguration = async (bucket: string) => {
const params = {
Bucket: bucket,
}
return await $client.send(new GetBucketLifecycleConfigurationCommand(params))
}
};
return await $client.send(new GetBucketLifecycleConfigurationCommand(params));
};
const putBucketLifecycleConfiguration = async (bucket: string, lifecycleConfiguration: any) => {
const params = {
Bucket: bucket,
LifecycleConfiguration: lifecycleConfiguration,
}
return await $client.send(new PutBucketLifecycleConfigurationCommand(params))
}
};
return await $client.send(new PutBucketLifecycleConfigurationCommand(params));
};
const deleteBucketLifecycle = async (bucket: string) => {
const params = {
Bucket: bucket,
}
return await $client.send(new DeleteBucketLifecycleCommand(params))
}
};
return await $client.send(new DeleteBucketLifecycleCommand(params));
};
const putObjectRetention = async (bucket: string, key: string, retention: any) => {
const params = {
Bucket: bucket,
Key: key,
Retention: retention,
};
return await $client.send(new PutObjectRetentionCommand(params));
};
const getObjectRetention = async (bucket: string, key: string) => {
const params = {
Bucket: bucket,
Key: key,
};
return await $client.send(new GetObjectRetentionCommand(params));
};
return {
listBuckets,
@@ -172,6 +190,8 @@ export function useBucket({ region }: { region?: string }) {
putObjectLockConfiguration,
getBucketLifecycleConfiguration,
putBucketLifecycleConfiguration,
deleteBucketLifecycle
}
deleteBucketLifecycle,
putObjectRetention,
getObjectRetention,
};
}
+22 -11
View File
@@ -259,6 +259,11 @@
"Rule Name Required": "Rule Name Required",
"Rule Type Required": "Rule Type Required",
"Version Type Required": "Version Type Required",
"Time Cycle": "Time Cycle",
"Transition": "Transition",
"Days": "Days",
"Current Version": "Current Version",
"Non-current Version": "Non-current Version",
"Events Management": "Events Management",
"Events List": "Events List",
"Add Site Replication": "Add Site Replication",
@@ -280,19 +285,25 @@
"S3 Compatible": "S3 Compatible",
"Reliable distributed file system": "Reliable distributed file system",
"Visit website": "Visit website",
"Update Key": "Update Key" ,
"Update Key": "Update Key",
"Update Success": "Update Success",
"Update Failed": "Update Failed",
"TierNameAlreadyExist": "tier name already exists!",
"TierNameNotUppercase" :"tier name not uppercase!",
"TierNameBackendInUse" :"tier name backend in use!",
"TierConnectError" :"tier connect error!",
"TierNameAlreadyExist": "tier name already exists!",
"TierNameNotUppercase": "tier name not uppercase!",
"TierNameBackendInUse": "tier name backend in use!",
"TierConnectError": "tier connect error!",
"XRustFSAdminTierInvalidCredentials": "Invalid remote tier credentials",
"TierAddFailed" :"tier add failed",
"TierNotFound" :"tier not found!",
"TierAddFailed": "tier add failed",
"TierNotFound": "tier not found!",
"TierMissingCredentials": "tier missing credentials!",
"TierEditFailed" : "tier edit failed",
"TierRemoveFailed" : "tier remove failed",
"Usage": "Usage"
"TierEditFailed": "tier edit failed",
"TierRemoveFailed": "tier remove failed",
"Usage": "Usage",
"Retention": "Retention",
"Compliance": "Compliance",
"Governance": "Governance",
"Retention Mode": "Mode",
"Validity": "Validity",
"Day": "Day",
"Year": "Year"
}
+26 -12
View File
@@ -14,7 +14,7 @@
"Bucket Events": "存储桶事件",
"Bucket Replication": "存储桶复制",
"Tiered Storage": "分层存储",
"Lifecycle": "生命周期",
"Lifecycle": "生命周期",
"Site Replication": "站点复制",
"Performance": "性能",
"Documentation": "文档",
@@ -361,7 +361,15 @@
"If no versions remain, delete references to this object": "如果没有剩余版本,则删除此对象的引用",
"Version Cleanup": "版本清理",
"Delete all expired versions": "删除所有过期的版本",
"Transition": "转换",
"Object Version": "对象版本",
"Time Cycle": "时间周期",
"Transition": "迁移",
"Current Version": "当前版本",
"Non-current Version": "非当前版本",
"Days After": "天之后",
"Days": "天",
"More Configurations": "更多配置",
"Stroage Type": "存储类型",
"Add Bucket Replication Rule": "添加存储桶复制规则",
"Definitions": "定义",
"License definition paragraph": "许可证定义段落",
@@ -472,16 +480,22 @@
"Update Key": "更新密钥",
"Update Success": "更新成功",
"Update Failed": "更新失败",
"TierNameAlreadyExist": "",
"TierNameNotUppercase" :"分层名称必须大写!",
"TierNameBackendInUse" :"分层名称已占用!",
"TierConnectError" :"连接错误!",
"TierNameAlreadyExist": "",
"TierNameNotUppercase": "分层名称必须大写!",
"TierNameBackendInUse": "分层名称已占用!",
"TierConnectError": "连接错误!",
"XRustFSAdminTierInvalidCredentials": "远程验证错误",
"TierAddFailed" :"分层添加失败",
"TierNotFound" :"未找到分层!",
"TierAddFailed": "分层添加失败",
"TierNotFound": "未找到分层!",
"TierMissingCredentials": "验证缺失!",
"TierEditFailed" : "分层修改失败",
"TierRemoveFailed" : "分层删除失败",
"Usage": "使用情况"
"TierEditFailed": "分层修改失败",
"TierRemoveFailed": "分层删除失败",
"Usage": "使用情况",
"Retention": "保留",
"Compliance": "合规",
"Governance": "治理",
"Retention Mode": "保留模式",
"Validity": "有效期",
"Day": "天",
"Year": "年"
}
+139 -67
View File
@@ -2,107 +2,149 @@
<div>
<page-header>
<template #title>
<h1 class="text-2xl font-bold">{{ t('Lifecycle') }}</h1>
<h1 class="text-2xl font-bold">{{ t("Lifecycle") }}</h1>
</template>
</page-header>
<page-content class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<div style="width:300px">
<div style="width: 300px">
<n-form-item :label="t('Bucket')" path="" class="flex-auto" label-placement="left">
<n-select filterable v-model:value="bucketName" :placeholder="t('Please select bucket')" :options="bucketList" />
<n-select
filterable
v-model:value="bucketName"
:placeholder="t('Please select bucket')"
:options="bucketList" />
</n-form-item>
</div>
<div class="flex items-center gap-4">
<n-button @click="() => handleNew()">
<Icon name="ri:add-line" class="mr-2" />
<span>{{ t('Add Lifecycle Rule') }}</span>
<span>{{ t("Add Lifecycle Rule") }}</span>
</n-button>
<n-button @click="">
<n-button @click="refresh">
<Icon name="ri:refresh-line" class="mr-2" />
<span>{{ t('Refresh') }}</span>
<span>{{ t("Refresh") }}</span>
</n-button>
</div>
</div>
<n-card class="flex flex-center" style="height:400px">
<n-empty :description="t('No Data')" ></n-empty>
<n-data-table
class="border dark:border-neutral-700 rounded overflow-hidden"
v-if="pageData.length > 0"
:columns="columns"
:data="pageData"
:pagination="false"
:bordered="false" />
<n-card class="flex flex-center" style="height: 400px" v-else>
<n-empty :description="t('No Data')"></n-empty>
</n-card>
<!-- <n-data-table class="border dark:border-neutral-700 rounded overflow-hidden" :columns="columns" :data="pageData" :pagination="false" :bordered="false" /> -->
<lifecycle-new-form ref="newRef" :bucketName="bucketName"></lifecycle-new-form>
<lifecycle-new-form ref="newRef" :bucketName="bucketName" @search="refresh"></lifecycle-new-form>
</page-content>
</div>
</template>
<script lang="ts" setup>
import { Icon } from '#components'
import { NButton, NSpace, type DataTableColumns } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { Icon } from "#components";
import { NButton, NSpace, NPopconfirm, type DataTableColumns } from "naive-ui";
import { useI18n } from "vue-i18n";
const { t } = useI18n()
const { listBuckets } = useBucket({});
const { t } = useI18n();
const message = useMessage();
const { listBuckets, getBucketLifecycleConfiguration, deleteBucketLifecycle, putBucketLifecycleConfiguration } =
useBucket({});
const formVisible = ref(false);
const searchTerm = ref('');
interface RowData {
Name: string;
CreationDate: string;
Status?: string;
ID?: string;
Expiration?: {
StorageClass?: string;
Days?: number;
Date?: string;
};
Filter?: {
Prefix?: string;
};
Transition?: {
Days?: number;
StorageClass?: string;
};
}
const columns: DataTableColumns<RowData> = [
{
title: t('Type'),
key: '',
title: t("Type"),
key: "",
render: (row) => {
return h(NSpace, {}, () => {
console.log(row?.Transition);
if (row?.Transition?.StorageClass) {
return t("Expire");
} else {
return t("Transition");
}
});
},
},
{
title: t('Version'),
key: '',
},
{
title: t('Expiration Delete Mark'),
key: '',
},
{
title: t('Tier'),
key: '',
},
{
title: t('Prefix'),
key: '',
},
{
title: t('Time'),
key: '',
},
{
title: t('Status'),
key: '',
title: t("Version"),
key: "Transition",
render: (row) => {
return h(NSpace, {}, row?.Transition ? t("Current Version") : t("Non-current Version"));
},
},
{
title: t('Actions'),
key: 'actions',
align: 'center',
title: t("Expiration Delete Mark"),
key: "",
},
{
title: t("Tier"),
key: "",
render: (row) => {
return h(NSpace, {}, row?.Expiration?.StorageClass || "--");
},
},
{
title: t("Prefix"),
key: "Filter",
render: (row) => {
return h(NSpace, {}, row?.Filter?.Prefix || "");
},
},
{
title: t("Status"),
key: "Status",
},
{
title: t("Actions"),
key: "actions",
align: "center",
width: 100,
render: (row: RowData) => {
return h(
NSpace,
{
justify: 'center',
justify: "center",
},
{
default: () => [
h(
NButton,
NPopconfirm,
{ onPositiveClick: () => handleRowDelete(row) },
{
size: 'small',
secondary: true,
onClick: (e) => handleRowDelete(row,e),
},
{
default: () => '',
icon: () => h(Icon, { name: 'ri:delete-bin-7-line' }),
default: () => t("Confirm Delete"),
trigger: () =>
h(
NButton,
{ size: "small", secondary: true },
{
default: () => "",
icon: () => h(Icon, { name: "ri:delete-bin-5-line" }),
}
),
}
),
],
@@ -114,10 +156,14 @@ const columns: DataTableColumns<RowData> = [
// 获取桶列表
const { data } = await useAsyncData(
'buckets',
"buckets",
async () => {
const response = await listBuckets();
return response.Buckets?.sort((a:any, b:any) => {return a.Name.localeCompare(b.Name) }) || [];
return (
response.Buckets?.sort((a: any, b: any) => {
return a.Name.localeCompare(b.Name);
}) || []
);
},
{ default: () => [] }
);
@@ -129,19 +175,45 @@ const bucketList = computed(() => {
}));
});
const bucketName = ref<string>(
bucketList.value.length > 0 ? bucketList.value[0]?.value ?? '' : ''
const bucketName = ref<string>(bucketList.value.length > 0 ? bucketList.value[0]?.value ?? "" : "");
const pageData = ref<any[]>([]);
const loading = ref<boolean>(false);
watch(
() => bucketName.value,
async (newVal) => {
if (!newVal) return;
loading.value = true;
try {
refresh();
} catch (error) {
pageData.value = [];
} finally {
loading.value = false;
}
},
{ immediate: true }
);
const pageData = ref([])
const handleRowDelete = (row: RowData, e: Event) => {
e.stopPropagation();
console.log(row);
const handleRowDelete = (row: RowData) => {
const params = pageData.value.filter((item) => item.ID !== row.ID);
putBucketLifecycleConfiguration(bucketName.value, { Rules: params })
.then(async (res) => {
message.success(t("Delete Success"));
refresh();
})
.catch((e) => {
message.error(e.message);
});
};
const newRef= ref()
const refresh = async () => {
const response = await getBucketLifecycleConfiguration(bucketName.value);
pageData.value = response.Rules?.sort((a: any, b: any) => a.ID.localeCompare(b.ID)) || [];
};
const newRef = ref();
const handleNew = () => {
newRef.value.open()
}
newRef.value.open();
};
</script>
+1 -1
View File
@@ -140,7 +140,7 @@ const columns: DataTableColumns<RowData> = [
},
{
default: () => [
h(
h(
NPopconfirm,
{ onPositiveClick: () => deleteItem(row) },
{