mirror of
https://github.com/rustfs/console.git
synced 2026-09-24 16:30:56 +08:00
refactor: migrate lifecycle management to shadcn components
This commit is contained in:
+407
-389
@@ -1,425 +1,443 @@
|
||||
<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,
|
||||
}"
|
||||
<AppModal
|
||||
v-model="visible"
|
||||
:title="`${t('Add Lifecycle Rule')} (${t('Bucket')}: ${bucketName})`"
|
||||
size="lg"
|
||||
:close-on-backdrop="false"
|
||||
>
|
||||
<n-card>
|
||||
<n-form class="my-4" ref="formRef" :model="formData" :rules="rules">
|
||||
<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="t('Time Cycle')" path="days">
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<n-input-number
|
||||
class="flex-auto"
|
||||
v-model:value="formData.days"
|
||||
:min="1"
|
||||
<div class="space-y-6">
|
||||
<Tabs v-model="activeTab" class="flex flex-col gap-4">
|
||||
<TabsList class="w-full justify-start overflow-x-auto">
|
||||
<TabsTrigger value="expire">{{ t('Expiration') }}</TabsTrigger>
|
||||
<TabsTrigger value="transition">{{ t('Transition') }}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="expire" class="mt-0 space-y-6">
|
||||
<AppCard padded class="space-y-4">
|
||||
<div v-if="versioningStatus" class="grid gap-2">
|
||||
<Label>{{ t('Object Version') }}</Label>
|
||||
<AppSelect v-model="formData.versionType" :options="versionOptions" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ t('Time Cycle') }}</Label>
|
||||
<div class="flex items-center gap-3">
|
||||
<AppInput
|
||||
v-model="formData.days"
|
||||
type="number"
|
||||
min="1"
|
||||
class="w-32"
|
||||
:placeholder="t('Days')"
|
||||
style="width: 100px"
|
||||
/>
|
||||
<span class="ms-4">{{ t('Days After') }}</span>
|
||||
<span class="text-sm text-muted-foreground">{{ 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:value="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 == 'current'">
|
||||
<n-collapse>
|
||||
<n-collapse-item :title="t('Advanced Settings')" name="advanced">
|
||||
<n-form-item :label="t('Delete Marker Handling')">
|
||||
<n-space>
|
||||
<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>
|
||||
</div>
|
||||
</AppCard>
|
||||
|
||||
<!-- <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="days">
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<n-input-number
|
||||
class="flex-auto"
|
||||
v-model:value="formData.days"
|
||||
:min="1"
|
||||
<AppCard padded class="space-y-4">
|
||||
<details>
|
||||
<summary class="cursor-pointer text-sm font-medium text-primary">{{ t('More Configurations') }}</summary>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ t('Prefix') }}</Label>
|
||||
<AppInput v-model="formData.prefix" :placeholder="t('Please enter prefix')" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label class="text-sm font-medium">{{ t('Tags') }}</Label>
|
||||
<AppButton variant="outline" size="sm" @click="addTag">
|
||||
<Icon name="ri:add-line" class="size-4" />
|
||||
{{ t('Add Tag') }}
|
||||
</AppButton>
|
||||
</div>
|
||||
<div v-if="formData.tags.length" class="space-y-3">
|
||||
<div
|
||||
v-for="(tag, index) in formData.tags"
|
||||
:key="index"
|
||||
class="grid gap-2 rounded-md border border-border/60 p-3 md:grid-cols-2 md:items-center md:gap-4"
|
||||
>
|
||||
<AppInput v-model="tag.key" :placeholder="t('Tag Name')" />
|
||||
<div class="flex items-center gap-2">
|
||||
<AppInput v-model="tag.value" :placeholder="t('Tag Value')" class="flex-1" />
|
||||
<AppButton
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="text-destructive"
|
||||
:disabled="formData.tags.length === 1"
|
||||
@click="removeTag(index)"
|
||||
>
|
||||
<Icon name="ri:delete-bin-line" class="size-4" />
|
||||
</AppButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</AppCard>
|
||||
|
||||
<AppCard v-if="formData.versionType === 'current'" padded>
|
||||
<details>
|
||||
<summary class="cursor-pointer text-sm font-medium text-primary">{{ t('Advanced Settings') }}</summary>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium">{{ t('Delete Marker Handling') }}</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t('If no versions remain, delete references to this object') }}
|
||||
</p>
|
||||
</div>
|
||||
<AppSwitch v-model="formData.expiredDeleteMark" />
|
||||
</div>
|
||||
</details>
|
||||
</AppCard>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="transition" class="mt-0 space-y-6">
|
||||
<AppCard padded class="space-y-4">
|
||||
<div v-if="versioningStatus" class="grid gap-2">
|
||||
<Label>{{ t('Object Version') }}</Label>
|
||||
<AppSelect v-model="formData.versionType" :options="versionOptions" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ t('Time Cycle') }}</Label>
|
||||
<div class="flex items-center gap-3">
|
||||
<AppInput
|
||||
v-model="formData.days"
|
||||
type="number"
|
||||
min="1"
|
||||
class="w-32"
|
||||
:placeholder="t('Days')"
|
||||
style="width: 100px"
|
||||
/>
|
||||
<span class="ms-4">{{ t('Days After') }}</span>
|
||||
<span class="text-sm text-muted-foreground">{{ t('Days After') }}</span>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Storage Type')" path="storageType">
|
||||
<n-select v-model:value="formData.storageType" :options="tiers" />
|
||||
</n-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 生命周期 -->
|
||||
<n-card>
|
||||
<n-collapse>
|
||||
<n-collapse-item :title="t('More Configurations')" name="advanced">
|
||||
<n-form-item :label="t('Prefix')">
|
||||
<n-input v-model:value="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-tab-pane>
|
||||
</n-tabs>
|
||||
</n-form>
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ t('Storage Type') }}</Label>
|
||||
<AppSelect
|
||||
v-model="formData.storageType"
|
||||
:options="tiers"
|
||||
:placeholder="t('Please select storage type')"
|
||||
/>
|
||||
</div>
|
||||
</AppCard>
|
||||
|
||||
<n-space justify="center">
|
||||
<n-button @click="handleCancel">{{ t('Cancel') }}</n-button>
|
||||
<n-button type="primary" @click="handleSave">{{ t('Save') }}</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
<AppCard padded class="space-y-4">
|
||||
<details>
|
||||
<summary class="cursor-pointer text-sm font-medium text-primary">{{ t('More Configurations') }}</summary>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ t('Prefix') }}</Label>
|
||||
<AppInput v-model="formData.prefix" :placeholder="t('Please enter prefix')" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label class="text-sm font-medium">{{ t('Tags') }}</Label>
|
||||
<AppButton variant="outline" size="sm" @click="addTag">
|
||||
<Icon name="ri:add-line" class="size-4" />
|
||||
{{ t('Add Tag') }}
|
||||
</AppButton>
|
||||
</div>
|
||||
<div v-if="formData.tags.length" class="space-y-3">
|
||||
<div
|
||||
v-for="(tag, index) in formData.tags"
|
||||
:key="index"
|
||||
class="grid gap-2 rounded-md border border-border/60 p-3 md:grid-cols-2 md:items-center md:gap-4"
|
||||
>
|
||||
<AppInput v-model="tag.key" :placeholder="t('Tag Name')" />
|
||||
<div class="flex items-center gap-2">
|
||||
<AppInput v-model="tag.value" :placeholder="t('Tag Value')" class="flex-1" />
|
||||
<AppButton
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="text-destructive"
|
||||
:disabled="formData.tags.length === 1"
|
||||
@click="removeTag(index)"
|
||||
>
|
||||
<Icon name="ri:delete-bin-line" class="size-4" />
|
||||
</AppButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</AppCard>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<AppButton variant="outline" @click="handleCancel">{{ t('Cancel') }}</AppButton>
|
||||
<AppButton variant="primary" :loading="submitting" @click="handleSave">{{ t('Save') }}</AppButton>
|
||||
</div>
|
||||
</template>
|
||||
</AppModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
NButton,
|
||||
NCollapse,
|
||||
NCollapseItem,
|
||||
NDynamicInput,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NSelect,
|
||||
} from 'naive-ui';
|
||||
// 使用 Web Crypto API 或 Node.js crypto 模块
|
||||
const randomUUID = () => {
|
||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
// 兜底方案
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { putBucketLifecycleConfiguration, getBucketVersioning, getBucketLifecycleConfiguration } = useBucket({});
|
||||
const { listTiers } = useTiers();
|
||||
const { t } = useI18n();
|
||||
const message = useMessage();
|
||||
// 定义 tiers 数据结构的接口
|
||||
interface TierItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
import { Icon } from '#components'
|
||||
import { AppButton, AppCard, AppInput, AppModal, AppSelect, AppSwitch } from '@/components/app'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const emit = defineEmits(['search']);
|
||||
const formRef = ref();
|
||||
const formData = ref({
|
||||
ruleName: '',
|
||||
type: null,
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const { putBucketLifecycleConfiguration, getBucketVersioning, getBucketLifecycleConfiguration } = useBucket({})
|
||||
const { listTiers } = useTiers()
|
||||
|
||||
const props = defineProps<{
|
||||
bucketName: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search'): void
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const activeTab = ref<'expire' | 'transition'>('expire')
|
||||
const submitting = ref(false)
|
||||
const versioningStatus = ref(false)
|
||||
|
||||
const versionOptions = computed(() => [
|
||||
{ label: t('Current Version'), value: 'current' },
|
||||
{ label: t('Non-current Version'), value: 'non-current' },
|
||||
])
|
||||
|
||||
const formData = reactive({
|
||||
versionType: 'current',
|
||||
days: null,
|
||||
action: 'expire',
|
||||
days: 1,
|
||||
storageType: '',
|
||||
prefix: '',
|
||||
expiredDeleteMark: false,
|
||||
deleteAllExpired: false,
|
||||
storageType: '',
|
||||
tags: [
|
||||
{
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
] as Array<{ key: string; value: string }>,
|
||||
})
|
||||
|
||||
const versionOptions = ref([
|
||||
{ label: t('Current Version'), value: 'current' },
|
||||
{ label: t('Non-current Version'), value: 'non-current' },
|
||||
]);
|
||||
watch(
|
||||
() => activeTab.value,
|
||||
value => {
|
||||
formState.action = value === 'expire' ? 'expire' : 'transition'
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
days: {
|
||||
required: true,
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value === null || value === undefined || value < 1) {
|
||||
return new Error(t('Please enter valid days'));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
trigger: ['blur', 'input', 'change'],
|
||||
},
|
||||
storageType: {
|
||||
validator: (rule: any, value: any) => {
|
||||
// 只在 transition 模式下验证 storageType
|
||||
if (formData.value.action === 'transition' && (!value || value === '')) {
|
||||
return new Error(t('Please select storage type'));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
};
|
||||
const formState = reactive({
|
||||
action: 'expire' as 'expire' | 'transition',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
bucketName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
// 已经存在的生命周期
|
||||
lifecycleList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const tiers = ref<Array<{ label: string; value: string }>>([])
|
||||
|
||||
const loadTiers = async () => {
|
||||
const res = await listTiers()
|
||||
if (res) {
|
||||
tiers.value = res.map((item: any) => ({
|
||||
label: item[item.type].name,
|
||||
value: item[item.type].name,
|
||||
}))
|
||||
}
|
||||
if (!formData.storageType && tiers.value.length > 0) {
|
||||
formData.storageType = tiers.value[0].value
|
||||
}
|
||||
}
|
||||
|
||||
const loadVersioningStatus = async () => {
|
||||
if (!props.bucketName) return
|
||||
try {
|
||||
const resp = await getBucketVersioning(props.bucketName)
|
||||
versioningStatus.value = resp.Status === 'Enabled'
|
||||
} catch (error) {
|
||||
versioningStatus.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.bucketName,
|
||||
newVal => {
|
||||
// 获取是都开启版本管理
|
||||
getVersioningStatus();
|
||||
() => {
|
||||
loadVersioningStatus()
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const visible = ref(false);
|
||||
const open = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
const addTag = () => {
|
||||
formData.tags.push({ key: '', value: '' })
|
||||
}
|
||||
|
||||
const removeTag = (index: number) => {
|
||||
if (formData.tags.length === 1) return
|
||||
formData.tags.splice(index, 1)
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
activeTab.value = 'expire'
|
||||
formState.action = 'expire'
|
||||
formData.versionType = 'current'
|
||||
formData.days = 1
|
||||
formData.storageType = tiers.value[0]?.value ?? ''
|
||||
formData.prefix = ''
|
||||
formData.expiredDeleteMark = false
|
||||
formData.tags = [{ key: '', value: '' }]
|
||||
}
|
||||
|
||||
const validate = () => {
|
||||
if (!formData.days || Number(formData.days) < 1) {
|
||||
message.error(t('Please enter valid days'))
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
formState.action === 'transition' &&
|
||||
(!formData.storageType || formData.storageType === '')
|
||||
) {
|
||||
message.error(t('Please select storage type'))
|
||||
return false
|
||||
}
|
||||
|
||||
if (!formData.tags.some(tag => tag.key && tag.value)) {
|
||||
// allow but no error
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const randomUUID = () => {
|
||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||
const r = (Math.random() * 16) | 0
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
const buildFilter = () => {
|
||||
const validTags = formData.tags.filter(tag => tag.key && tag.value)
|
||||
if (!formData.prefix && validTags.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const filter: any = {}
|
||||
|
||||
if (validTags.length === 1 && !formData.prefix) {
|
||||
filter.Tag = {
|
||||
Key: validTags[0].key,
|
||||
Value: validTags[0].value,
|
||||
}
|
||||
} else if (validTags.length > 0) {
|
||||
filter.And = {
|
||||
Tags: validTags.map(tag => ({
|
||||
Key: tag.key,
|
||||
Value: tag.value,
|
||||
})),
|
||||
}
|
||||
if (formData.prefix) {
|
||||
filter.And.Prefix = formData.prefix
|
||||
}
|
||||
} else if (formData.prefix) {
|
||||
filter.Prefix = formData.prefix
|
||||
}
|
||||
|
||||
if (formData.prefix && validTags.length === 1 && !filter.And) {
|
||||
filter.Prefix = formData.prefix
|
||||
filter.Tag = {
|
||||
Key: validTags[0].key,
|
||||
Value: validTags[0].value,
|
||||
}
|
||||
} else if (formData.prefix && validTags.length === 0) {
|
||||
filter.Prefix = formData.prefix
|
||||
}
|
||||
|
||||
return Object.keys(filter).length ? filter : undefined
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!validate()) return
|
||||
submitting.value = true
|
||||
try {
|
||||
const currentConfig = await getBucketLifecycleConfiguration(props.bucketName)
|
||||
const newRule: any = {
|
||||
ID: randomUUID(),
|
||||
Status: 'Enabled',
|
||||
Filter: buildFilter(),
|
||||
}
|
||||
|
||||
const daysValue = Number(formData.days)
|
||||
|
||||
if (formState.action === 'expire') {
|
||||
if (formData.versionType === 'non-current') {
|
||||
newRule.NoncurrentVersionExpiration = {
|
||||
NoncurrentDays: daysValue,
|
||||
}
|
||||
if (formData.expiredDeleteMark) {
|
||||
newRule.ExpiredObjectDeleteMarker = true
|
||||
}
|
||||
} else {
|
||||
newRule.Expiration = {
|
||||
Days: daysValue,
|
||||
}
|
||||
if (formData.expiredDeleteMark) {
|
||||
newRule.Expiration.ExpiredObjectDeleteMarker = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (formData.versionType === 'non-current') {
|
||||
newRule.NoncurrentVersionTransitions = [
|
||||
{
|
||||
NoncurrentDays: daysValue,
|
||||
StorageClass: formData.storageType,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
newRule.Transitions = [
|
||||
{
|
||||
Days: daysValue,
|
||||
StorageClass: formData.storageType,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const existingRules = currentConfig.Rules || []
|
||||
const payload = {
|
||||
Rules: [...existingRules, newRule],
|
||||
}
|
||||
|
||||
await putBucketLifecycleConfiguration(props.bucketName, payload)
|
||||
message.success(t('Create Success'))
|
||||
emit('search')
|
||||
handleCancel()
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || t('Failed to create rule'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
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) {
|
||||
// 额外的参数验证(与表单验证保持一致)
|
||||
if (formData.value.days === null || formData.value.days === undefined || formData.value.days < 1) {
|
||||
message.error(t('Please enter valid days'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
formData.value.action === 'transition' &&
|
||||
(!formData.value.storageType || formData.value.storageType === '')
|
||||
) {
|
||||
message.error(t('Please select storage type'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 先获取当前的生命周期配置
|
||||
getBucketLifecycleConfiguration(props.bucketName)
|
||||
.then(currentConfig => {
|
||||
// 创建新的规则
|
||||
const newRule: any = {
|
||||
ID: randomUUID(),
|
||||
Status: 'Enabled',
|
||||
};
|
||||
|
||||
// 设置 Filter 或 Prefix(不能同时使用)
|
||||
console.log('formData.value.prefix:', formData.value.prefix);
|
||||
console.log('formData.value.tags:', formData.value.tags);
|
||||
console.log('formData.value.expiredDeleteMark:', formData.value.expiredDeleteMark);
|
||||
|
||||
// 检查是否有有效的标签
|
||||
const validTags = formData.value.tags.filter((item: any) => item.key && item.value);
|
||||
const hasValidTags = validTags.length > 0;
|
||||
|
||||
if (formData.value.prefix || hasValidTags) {
|
||||
newRule.Filter = {};
|
||||
|
||||
if (hasValidTags) {
|
||||
if (validTags.length === 1) {
|
||||
newRule.Filter.Tag = {
|
||||
Key: validTags[0].key,
|
||||
Value: validTags[0].value,
|
||||
};
|
||||
if (formData.value.prefix) {
|
||||
newRule.Filter.Prefix = formData.value.prefix;
|
||||
}
|
||||
} else {
|
||||
newRule.Filter.And = {
|
||||
Tags: validTags.map((item: any) => ({
|
||||
Key: item.key,
|
||||
Value: item.value,
|
||||
})),
|
||||
};
|
||||
if (formData.value.prefix) {
|
||||
newRule.Filter.And.Prefix = formData.value.prefix;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 只有前缀时,直接添加到 Filter
|
||||
newRule.Filter.Prefix = formData.value.prefix;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据操作类型添加相应的配置
|
||||
if (formData.value.action === 'expire') {
|
||||
if (formData.value.versionType === 'non-current') {
|
||||
newRule.NoncurrentVersionExpiration = {
|
||||
NoncurrentDays: formData.value.days,
|
||||
};
|
||||
|
||||
// 如果启用了删除标记处理
|
||||
if (formData.value.expiredDeleteMark) {
|
||||
newRule.ExpiredObjectDeleteMarker = true;
|
||||
}
|
||||
} else {
|
||||
newRule.Expiration = {
|
||||
Days: formData.value.days,
|
||||
};
|
||||
|
||||
// 如果启用了删除标记处理(当前版本也支持)
|
||||
if (formData.value.expiredDeleteMark) {
|
||||
newRule.Expiration.ExpiredObjectDeleteMarker = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// transition 操作
|
||||
if (formData.value.versionType === 'non-current') {
|
||||
newRule.NoncurrentVersionTransitions = [
|
||||
{
|
||||
NoncurrentDays: formData.value.days,
|
||||
StorageClass: formData.value.storageType,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
newRule.Transitions = [
|
||||
{
|
||||
Days: formData.value.days,
|
||||
StorageClass: formData.value.storageType,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 合并现有规则和新规则
|
||||
const existingRules = currentConfig.Rules || [];
|
||||
const updatedRules = [...existingRules, newRule];
|
||||
|
||||
// 调用保存接口
|
||||
const params = {
|
||||
Rules: updatedRules,
|
||||
};
|
||||
|
||||
return 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: 'expire',
|
||||
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);
|
||||
}
|
||||
};
|
||||
open: async () => {
|
||||
await Promise.all([loadTiers(), loadVersioningStatus()])
|
||||
resetForm()
|
||||
visible.value = true
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getVersioningStatus();
|
||||
});
|
||||
const handleCancel = () => {
|
||||
// 取消逻辑
|
||||
visible.value = false;
|
||||
};
|
||||
loadTiers()
|
||||
loadVersioningStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user