mirror of
https://github.com/rustfs/console.git
synced 2026-11-01 21:22:41 +08:00
* feat: refactory bucket setting * fix: translate * feat:permission * feat : lockfile * fix: update lockfile * fix: i18n variable
200 lines
5.2 KiB
Vue
200 lines
5.2 KiB
Vue
<template>
|
|
<div class="flex flex-col gap-4">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-lg font-medium">{{ t('Bucket Replication') }}</h2>
|
|
<div class="flex gap-2">
|
|
<Button variant="outline" @click="openForm">
|
|
<Icon name="ri:add-line" class="size-4" />
|
|
<span>{{ t('Add Replication Rule') }}</span>
|
|
</Button>
|
|
<Button variant="outline" @click="() => loadReplication()">
|
|
<Icon name="ri:refresh-line" class="size-4" />
|
|
<span>{{ t('Refresh') }}</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<DataTable
|
|
:table="table"
|
|
:is-loading="loading"
|
|
:empty-title="t('No Data')"
|
|
:empty-description="t('Add replication rules to sync objects across buckets.')"
|
|
/>
|
|
|
|
<replication-new-form ref="addFormRef" :bucketName="bucketName" @success="() => loadReplication()" />
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
import { Icon } from '#components'
|
|
import DataTable from '@/components/data-table/data-table.vue'
|
|
import { useDataTable } from '@/components/data-table/useDataTable'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { useBucket } from '@/composables/useBucket'
|
|
import type { ColumnDef } from '@tanstack/vue-table'
|
|
import { h, ref, watch } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
const props = defineProps<{
|
|
bucketName: string
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
const message = useMessage()
|
|
const dialog = useDialog()
|
|
|
|
const { getBucketReplication, putBucketReplication, deleteBucketReplication, deleteRemoteReplicationTarget } =
|
|
useBucket({})
|
|
|
|
interface ReplicationRule {
|
|
ID?: string
|
|
Status?: string
|
|
Priority?: number
|
|
Filter?: {
|
|
Prefix?: string
|
|
}
|
|
Destination?: {
|
|
Bucket?: string
|
|
StorageClass?: string
|
|
}
|
|
}
|
|
|
|
const rules = ref<ReplicationRule[]>([])
|
|
const loading = ref(false)
|
|
|
|
const columns: ColumnDef<ReplicationRule>[] = [
|
|
{
|
|
accessorKey: 'ID',
|
|
header: () => t('Rule ID'),
|
|
cell: ({ row }) => h('span', row.original.ID || '-'),
|
|
},
|
|
{
|
|
accessorKey: 'Status',
|
|
header: () => t('Status'),
|
|
cell: ({ row }) =>
|
|
h(Badge, { variant: row.original.Status === 'Enabled' ? 'secondary' : 'outline' }, () =>
|
|
row.original.Status === 'Enabled' ? t('Enabled') : t('Disabled')
|
|
),
|
|
},
|
|
{
|
|
accessorKey: 'Priority',
|
|
header: () => t('Priority'),
|
|
cell: ({ row }) => h('span', String(row.original.Priority ?? '-')),
|
|
},
|
|
{
|
|
id: 'Filter',
|
|
header: () => t('Prefix'),
|
|
cell: ({ row }) => h('span', row.original.Filter?.Prefix || '-'),
|
|
},
|
|
{
|
|
id: 'destination-bucket',
|
|
header: () => t('Destination Bucket'),
|
|
cell: ({ row }) => {
|
|
const bucketArn = row.original.Destination?.Bucket || ''
|
|
return h('span', bucketArn.replace(/^arn:aws:s3:::/, '') || '-')
|
|
},
|
|
},
|
|
{
|
|
id: 'destination-storage',
|
|
header: () => t('Storage Class'),
|
|
cell: ({ row }) => h('span', row.original.Destination?.StorageClass || '-'),
|
|
},
|
|
{
|
|
id: 'actions',
|
|
header: () => t('Actions'),
|
|
enableSorting: false,
|
|
cell: ({ row }) =>
|
|
h('div', { class: 'flex items-center gap-2' }, [
|
|
h(
|
|
Button,
|
|
{
|
|
variant: 'outline',
|
|
size: 'sm',
|
|
onClick: () => confirmDelete(row.original),
|
|
},
|
|
() => [h(Icon, { name: 'ri:delete-bin-7-line', class: 'size-4' }), h('span', t('Delete'))]
|
|
),
|
|
]),
|
|
},
|
|
]
|
|
|
|
const { table } = useDataTable<ReplicationRule>({
|
|
data: rules,
|
|
columns,
|
|
getRowId: row => row.ID ?? JSON.stringify(row),
|
|
})
|
|
|
|
const loadReplication = async () => {
|
|
if (!props.bucketName) {
|
|
rules.value = []
|
|
return
|
|
}
|
|
loading.value = true
|
|
try {
|
|
const res = await getBucketReplication(props.bucketName)
|
|
rules.value = res?.ReplicationConfiguration?.Rules ?? []
|
|
} catch (error) {
|
|
rules.value = []
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.bucketName,
|
|
() => {
|
|
loadReplication()
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
const confirmDelete = (rule: ReplicationRule) => {
|
|
dialog.error({
|
|
title: t('Warning'),
|
|
content: t('Are you sure you want to delete this replication rule?'),
|
|
positiveText: t('Confirm'),
|
|
negativeText: t('Cancel'),
|
|
onPositiveClick: () => handleRowDelete(rule),
|
|
})
|
|
}
|
|
|
|
const handleRowDelete = async (rule: ReplicationRule) => {
|
|
const remaining = rules.value.filter(item => item !== rule)
|
|
|
|
if (!props.bucketName) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
if (remaining.length === 0) {
|
|
await deleteBucketReplication(props.bucketName)
|
|
await deleteRemoteReplicationTarget(props.bucketName, rule.Destination?.Bucket ?? '')
|
|
} else {
|
|
// 获取当前配置以获取 Role 字段
|
|
const currentConfig = await getBucketReplication(props.bucketName)
|
|
const role = currentConfig?.ReplicationConfiguration?.Role
|
|
|
|
if (!role) {
|
|
throw new Error('Replication configuration Role is missing')
|
|
}
|
|
|
|
await putBucketReplication(props.bucketName, {
|
|
Role: role,
|
|
Rules: remaining,
|
|
})
|
|
}
|
|
message.success(t('Delete Success'))
|
|
await loadReplication()
|
|
} catch (error: any) {
|
|
message.error(error?.message || t('Delete Failed'))
|
|
}
|
|
}
|
|
|
|
const addFormRef = ref<{ open: () => void }>()
|
|
const openForm = () => {
|
|
addFormRef.value?.open()
|
|
}
|
|
</script>
|