✨ 优化管理页交互与历史同步
This commit is contained in:
@@ -6,9 +6,14 @@
|
|||||||
class="border-b px-6 py-4"
|
class="border-b px-6 py-4"
|
||||||
:class="[isDarkMode ? 'border-gray-700/80' : 'border-[#d9e4e6]']"
|
:class="[isDarkMode ? 'border-gray-700/80' : 'border-[#d9e4e6]']"
|
||||||
>
|
>
|
||||||
<h3 class="text-lg font-medium" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
{{ title }}
|
<h3 class="text-lg font-medium" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||||
</h3>
|
{{ title }}
|
||||||
|
</h3>
|
||||||
|
<div v-if="slots.actions" class="flex shrink-0 flex-wrap items-center gap-2">
|
||||||
|
<slot name="actions"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="slots.toolbar" class="mt-4">
|
<div v-if="slots.toolbar" class="mt-4">
|
||||||
<slot name="toolbar"></slot>
|
<slot name="toolbar"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ const formatBytes = (value: number | string | undefined) => {
|
|||||||
return `${(bytes / 1024 ** index).toFixed(index === 0 ? 0 : 1)} ${units[index]}`
|
return `${(bytes / 1024 ** index).toFixed(index === 0 ? 0 : 1)} ${units[index]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const lerp = (start: number, end: number, amount: number) => start + (end - start) * amount
|
|
||||||
|
|
||||||
const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value))
|
const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value))
|
||||||
|
|
||||||
const getDefaultWindow = () => {
|
const getDefaultWindow = () => {
|
||||||
@@ -259,52 +257,38 @@ const draw = () => {
|
|||||||
|
|
||||||
if (hoverX.value !== null && points.length && total) {
|
if (hoverX.value !== null && points.length && total) {
|
||||||
const hoverRatio = clamp((hoverX.value - padLeft) / width, 0, 1)
|
const hoverRatio = clamp((hoverX.value - padLeft) / width, 0, 1)
|
||||||
const sourceIndex = clamp(offset.value + hoverRatio * denominator, 0, total - 1)
|
const sourceIndex = Math.round(clamp(offset.value + hoverRatio * denominator, 0, total - 1))
|
||||||
const leftIndex = Math.floor(sourceIndex)
|
const row = props.rows[sourceIndex]
|
||||||
const rightIndex = Math.min(total - 1, Math.ceil(sourceIndex))
|
const snapX = padLeft + ((sourceIndex - offset.value) / denominator) * width
|
||||||
const mix = sourceIndex - leftIndex
|
const downloads = Number(row.downloads || 0)
|
||||||
const leftRow = props.rows[leftIndex]
|
const uploads = Number(row.uploads || 0)
|
||||||
const rightRow = props.rows[rightIndex] || leftRow
|
const downloadTraffic = Number(row.downloadTraffic || 0)
|
||||||
const interpolatedX = padLeft + ((sourceIndex - offset.value) / denominator) * width
|
const uploadTraffic = Number(row.uploadTraffic || 0)
|
||||||
const downloads = lerp(Number(leftRow.downloads || 0), Number(rightRow.downloads || 0), mix)
|
|
||||||
const uploads = lerp(Number(leftRow.uploads || 0), Number(rightRow.uploads || 0), mix)
|
|
||||||
const downloadTraffic = lerp(
|
|
||||||
Number(leftRow.downloadTraffic || 0),
|
|
||||||
Number(rightRow.downloadTraffic || 0),
|
|
||||||
mix
|
|
||||||
)
|
|
||||||
const uploadTraffic = lerp(
|
|
||||||
Number(leftRow.uploadTraffic || 0),
|
|
||||||
Number(rightRow.uploadTraffic || 0),
|
|
||||||
mix
|
|
||||||
)
|
|
||||||
const downloadY = padTop + height - (downloads / max) * height
|
const downloadY = padTop + height - (downloads / max) * height
|
||||||
const uploadY = padTop + height - (uploads / max) * height
|
const uploadY = padTop + height - (uploads / max) * height
|
||||||
const anchorY = Math.min(downloadY, uploadY)
|
const anchorY = Math.min(downloadY, uploadY)
|
||||||
const placeBelow = anchorY < 104
|
const placeBelow = anchorY < 104
|
||||||
const tooltipInset = Math.min(mobile ? 116 : 132, Math.max(48, rect.width / 2 - 8))
|
const tooltipInset = Math.min(mobile ? 116 : 132, Math.max(48, rect.width / 2 - 8))
|
||||||
const dateText =
|
|
||||||
leftRow.date === rightRow.date ? leftRow.date : `${leftRow.date} - ${rightRow.date}`
|
|
||||||
|
|
||||||
ctx.strokeStyle = props.isDarkMode ? 'rgba(148, 163, 184, 0.35)' : 'rgba(91, 121, 132, 0.28)'
|
ctx.strokeStyle = props.isDarkMode ? 'rgba(148, 163, 184, 0.35)' : 'rgba(91, 121, 132, 0.28)'
|
||||||
ctx.lineWidth = 1
|
ctx.lineWidth = 1
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(interpolatedX, padTop)
|
ctx.moveTo(snapX, padTop)
|
||||||
ctx.lineTo(interpolatedX, padTop + height)
|
ctx.lineTo(snapX, padTop + height)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|
||||||
tooltip.value = {
|
tooltip.value = {
|
||||||
left: clamp(interpolatedX, tooltipInset, rect.width - tooltipInset),
|
left: clamp(snapX, tooltipInset, rect.width - tooltipInset),
|
||||||
top: placeBelow ? anchorY + 16 : anchorY - 12,
|
top: placeBelow ? anchorY + 16 : anchorY - 12,
|
||||||
date: dateText,
|
date: row.date,
|
||||||
downloads: Math.round(downloads),
|
downloads,
|
||||||
uploads: Math.round(uploads),
|
uploads,
|
||||||
downloadTraffic: formatBytes(downloadTraffic),
|
downloadTraffic: formatBytes(downloadTraffic),
|
||||||
uploadTraffic: formatBytes(uploadTraffic),
|
uploadTraffic: formatBytes(uploadTraffic),
|
||||||
transform: placeBelow ? 'translate(-50%, 0)' : 'translate(-50%, -100%)'
|
transform: placeBelow ? 'translate(-50%, 0)' : 'translate(-50%, -100%)'
|
||||||
}
|
}
|
||||||
drawHoverPoint({ x: interpolatedX, y: downloadY }, downloadColor)
|
drawHoverPoint({ x: snapX, y: downloadY }, downloadColor)
|
||||||
drawHoverPoint({ x: interpolatedX, y: uploadY }, uploadColor)
|
drawHoverPoint({ x: snapX, y: uploadY }, uploadColor)
|
||||||
} else {
|
} else {
|
||||||
tooltip.value = null
|
tooltip.value = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -567,10 +567,10 @@ export default {
|
|||||||
permanent: 'Permanent'
|
permanent: 'Permanent'
|
||||||
},
|
},
|
||||||
insightActions: {
|
insightActions: {
|
||||||
monitor: 'Keep monitoring',
|
monitor: 'No action needed',
|
||||||
extend_or_delete: 'Extend retention or clean up',
|
extend_or_delete: 'Extend or clean up',
|
||||||
inspect_storage: 'Inspect storage path and file name',
|
inspect_storage: 'Check storage info',
|
||||||
extend_expiration: 'Extend expiration if needed'
|
extend_expiration: 'Extend validity'
|
||||||
},
|
},
|
||||||
insightReasons: {
|
insightReasons: {
|
||||||
expired: 'Past expiration time',
|
expired: 'Past expiration time',
|
||||||
|
|||||||
@@ -564,10 +564,10 @@ export default {
|
|||||||
permanent: '永久有效'
|
permanent: '永久有效'
|
||||||
},
|
},
|
||||||
insightActions: {
|
insightActions: {
|
||||||
monitor: '保持观察',
|
monitor: '无需处理',
|
||||||
extend_or_delete: '延长有效期或清理记录',
|
extend_or_delete: '建议延长或清理',
|
||||||
inspect_storage: '检查存储路径与文件名',
|
inspect_storage: '检查存储信息',
|
||||||
extend_expiration: '按需延长过期时间'
|
extend_expiration: '建议延长有效期'
|
||||||
},
|
},
|
||||||
insightReasons: {
|
insightReasons: {
|
||||||
expired: '已超过过期时间',
|
expired: '已超过过期时间',
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
{{ t('admin.dashboard.lastUpdated', { time: lastUpdatedText }) }}
|
{{ t('admin.dashboard.lastUpdated', { time: lastUpdatedText }) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<BaseButton variant="secondary" :loading="isLoading" @click="fetchDashboardData">
|
<BaseButton variant="secondary" :loading="isDashboardRefreshing" @click="refreshDashboardPage">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
||||||
</template>
|
</template>
|
||||||
{{ isLoading ? t('admin.dashboard.refreshing') : t('admin.dashboard.refresh') }}
|
{{
|
||||||
|
isDashboardRefreshing ? t('admin.dashboard.refreshing') : t('admin.dashboard.refresh')
|
||||||
|
}}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -260,13 +262,28 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 gap-2 sm:flex sm:justify-end">
|
<div class="grid grid-cols-3 gap-2 sm:flex sm:justify-end">
|
||||||
<BaseButton variant="outline" class="h-11 min-w-20" @click="trendCanvas?.zoom(0.75)">
|
<BaseButton
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="h-9 min-w-16"
|
||||||
|
@click="trendCanvas?.zoom(0.75)"
|
||||||
|
>
|
||||||
放大
|
放大
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton variant="outline" class="h-11 min-w-20" @click="trendCanvas?.zoom(1.33)">
|
<BaseButton
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="h-9 min-w-16"
|
||||||
|
@click="trendCanvas?.zoom(1.33)"
|
||||||
|
>
|
||||||
缩小
|
缩小
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton variant="outline" class="h-11 min-w-20" @click="trendCanvas?.resetWindow()">
|
<BaseButton
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="h-9 min-w-16"
|
||||||
|
@click="trendCanvas?.resetWindow()"
|
||||||
|
>
|
||||||
重置
|
重置
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -476,6 +493,7 @@ const analyticsStart = ref(daysAgo(120))
|
|||||||
const analyticsEnd = ref(today())
|
const analyticsEnd = ref(today())
|
||||||
const analyticsData = ref<AnalyticsData | null>(null)
|
const analyticsData = ref<AnalyticsData | null>(null)
|
||||||
const analyticsError = ref('')
|
const analyticsError = ref('')
|
||||||
|
const isAnalyticsLoading = ref(false)
|
||||||
const analyticsWindowText = ref('无数据')
|
const analyticsWindowText = ref('无数据')
|
||||||
const analyticsDailyRows = computed(() => analyticsData.value?.daily || [])
|
const analyticsDailyRows = computed(() => analyticsData.value?.daily || [])
|
||||||
const analyticsTopFiles = computed<AnalyticsFileRow[]>(() => analyticsData.value?.topFiles || [])
|
const analyticsTopFiles = computed<AnalyticsFileRow[]>(() => analyticsData.value?.topFiles || [])
|
||||||
@@ -497,12 +515,19 @@ const formatBytes = (value: number | string | undefined) => formatFileSize(Numbe
|
|||||||
|
|
||||||
const fetchAnalyticsData = async () => {
|
const fetchAnalyticsData = async () => {
|
||||||
analyticsError.value = ''
|
analyticsError.value = ''
|
||||||
|
isAnalyticsLoading.value = true
|
||||||
try {
|
try {
|
||||||
analyticsData.value = await fetchAnalytics(analyticsStart.value, analyticsEnd.value)
|
analyticsData.value = await fetchAnalytics(analyticsStart.value, analyticsEnd.value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
analyticsError.value = error instanceof Error ? error.message : '统计加载失败'
|
analyticsError.value = error instanceof Error ? error.message : '统计加载失败'
|
||||||
|
} finally {
|
||||||
|
isAnalyticsLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const isDashboardRefreshing = computed(() => isLoading.value || isAnalyticsLoading.value)
|
||||||
|
const refreshDashboardPage = async () => {
|
||||||
|
await Promise.all([fetchDashboardData(), fetchAnalyticsData()])
|
||||||
|
}
|
||||||
|
|
||||||
const healthActions = computed<DashboardHealthAction[]>(() => [
|
const healthActions = computed<DashboardHealthAction[]>(() => [
|
||||||
{
|
{
|
||||||
@@ -648,6 +673,6 @@ const PolicyRow = defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void Promise.all([fetchDashboardData(), fetchAnalyticsData()])
|
void refreshDashboardPage()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,12 +10,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<BaseButton variant="secondary" :loading="isLoading" @click="refreshFiles">
|
|
||||||
<template #icon>
|
|
||||||
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
{{ t('fileManage.refresh') }}
|
|
||||||
</BaseButton>
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="hasActiveFilters"
|
v-if="hasActiveFilters"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -262,6 +256,14 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DataTable :title="t('fileManage.allFiles')" :headers="fileTableHeaders">
|
<DataTable :title="t('fileManage.allFiles')" :headers="fileTableHeaders">
|
||||||
|
<template #actions>
|
||||||
|
<BaseButton variant="secondary" :loading="isLoading" @click="refreshFileList">
|
||||||
|
<template #icon>
|
||||||
|
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ t('fileManage.refresh') }}
|
||||||
|
</BaseButton>
|
||||||
|
</template>
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<div
|
<div
|
||||||
v-if="tableData.length > 0"
|
v-if="tableData.length > 0"
|
||||||
@@ -306,7 +308,7 @@
|
|||||||
:title="option.description"
|
:title="option.description"
|
||||||
:disabled="option.disabled || !hasSelectedFiles || isBatchDeleting || isBatchUpdating"
|
:disabled="option.disabled || !hasSelectedFiles || isBatchDeleting || isBatchUpdating"
|
||||||
:loading="isBatchPolicyActionRunning"
|
:loading="isBatchPolicyActionRunning"
|
||||||
@click="applySelectedPolicyAction(option.action)"
|
@click="applySelectedPolicyActionAndRefreshHistory(option.action)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<component :is="getDetailPolicyActionIcon(option.action)" class="mr-2 h-4 w-4" />
|
<component :is="getDetailPolicyActionIcon(option.action)" class="mr-2 h-4 w-4" />
|
||||||
@@ -330,7 +332,7 @@
|
|||||||
size="sm"
|
size="sm"
|
||||||
:disabled="!hasSelectedFiles || isBatchUpdating || isBatchPolicyActionRunning"
|
:disabled="!hasSelectedFiles || isBatchUpdating || isBatchPolicyActionRunning"
|
||||||
:loading="isBatchDeleting"
|
:loading="isBatchDeleting"
|
||||||
@click="deleteSelectedFiles"
|
@click="deleteSelectedFilesAndRefreshHistory"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<TrashIcon class="mr-2 h-4 w-4" />
|
<TrashIcon class="mr-2 h-4 w-4" />
|
||||||
@@ -357,7 +359,7 @@
|
|||||||
<p class="font-medium" :class="[primaryTextClass]">
|
<p class="font-medium" :class="[primaryTextClass]">
|
||||||
{{ t('fileManage.loadError') }}
|
{{ t('fileManage.loadError') }}
|
||||||
</p>
|
</p>
|
||||||
<BaseButton class="mt-4" variant="secondary" @click="refreshFiles">
|
<BaseButton class="mt-4" variant="secondary" @click="refreshFileList">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
||||||
</template>
|
</template>
|
||||||
@@ -437,6 +439,12 @@
|
|||||||
>
|
>
|
||||||
{{ file.displayHealthState }}
|
{{ file.displayHealthState }}
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
class="inline-flex max-w-full items-center rounded-full px-2 py-0.5 text-xs font-medium"
|
||||||
|
:class="getInsightActionBadgeClass(file.statusInsightNextAction)"
|
||||||
|
>
|
||||||
|
{{ file.displayHealthAction }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -553,7 +561,7 @@
|
|||||||
? 'bg-[#dc2626] text-white shadow-[#7f1d1d]/35 hover:bg-[#b91c1c]'
|
? 'bg-[#dc2626] text-white shadow-[#7f1d1d]/35 hover:bg-[#b91c1c]'
|
||||||
: 'bg-[#dc2626] text-white shadow-[#f87171]/35 hover:bg-[#b91c1c]'
|
: 'bg-[#dc2626] text-white shadow-[#f87171]/35 hover:bg-[#b91c1c]'
|
||||||
]"
|
]"
|
||||||
@click="deleteFile(file.id)"
|
@click="deleteFileAndRefreshHistory(file.id)"
|
||||||
>
|
>
|
||||||
<TrashIcon class="mr-1.5 h-4 w-4" />
|
<TrashIcon class="mr-1.5 h-4 w-4" />
|
||||||
{{ t('common.delete') }}
|
{{ t('common.delete') }}
|
||||||
@@ -942,12 +950,20 @@
|
|||||||
{{ getInsightStateLabel(selectedFileDetail.statusInsightState) }}
|
{{ getInsightStateLabel(selectedFileDetail.statusInsightState) }}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
class="inline-flex w-fit items-center rounded-full px-2.5 py-1 text-xs font-medium"
|
<span
|
||||||
:class="getInsightBadgeClass(selectedFileDetail.statusInsightSeverity)"
|
class="inline-flex w-fit items-center rounded-full px-2.5 py-1 text-xs font-medium"
|
||||||
>
|
:class="getInsightBadgeClass(selectedFileDetail.statusInsightSeverity)"
|
||||||
{{ getInsightSeverityLabel(selectedFileDetail.statusInsightSeverity) }}
|
>
|
||||||
</span>
|
{{ getInsightSeverityLabel(selectedFileDetail.statusInsightSeverity) }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="inline-flex w-fit items-center rounded-full px-2.5 py-1 text-xs font-medium"
|
||||||
|
:class="getInsightActionBadgeClass(selectedFileDetail.statusInsightNextAction)"
|
||||||
|
>
|
||||||
|
{{ selectedFileDetail.displayHealthAction }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="detailInsightReasonLabels.length > 0" class="mt-3 flex flex-wrap gap-2">
|
<div v-if="detailInsightReasonLabels.length > 0" class="mt-3 flex flex-wrap gap-2">
|
||||||
@@ -966,11 +982,11 @@
|
|||||||
v-for="action in detailPolicyActionOptions"
|
v-for="action in detailPolicyActionOptions"
|
||||||
:key="action.action"
|
:key="action.action"
|
||||||
type="button"
|
type="button"
|
||||||
class="flex min-h-16 items-start gap-2 rounded-lg border px-3 py-2 text-left transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
class="theme-2026-card-hover flex min-h-16 items-start gap-2 rounded-[30px] border px-3 py-2 text-left shadow-md transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:translate-y-0 disabled:hover:shadow-md"
|
||||||
:class="detailPolicyActionClass"
|
:class="[detailPolicyActionClass, cardHoverClass]"
|
||||||
:disabled="isDetailPolicyActionRunning || isDetailLoading || action.disabled"
|
:disabled="isDetailPolicyActionRunning || isDetailLoading || action.disabled"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
@click="applyDetailPolicyAction(action.action)"
|
@click="applyDetailPolicyActionAndRefreshHistory(action.action)"
|
||||||
>
|
>
|
||||||
<RefreshCwIcon
|
<RefreshCwIcon
|
||||||
v-if="isDetailPolicyActionRunning"
|
v-if="isDetailPolicyActionRunning"
|
||||||
@@ -1185,7 +1201,7 @@
|
|||||||
<BaseButton variant="secondary" :disabled="isSaving" @click="closeEditModal">
|
<BaseButton variant="secondary" :disabled="isSaving" @click="closeEditModal">
|
||||||
{{ t('common.cancel') }}
|
{{ t('common.cancel') }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton :loading="isSaving" @click="handleUpdate">
|
<BaseButton :loading="isSaving" @click="handleUpdateAndRefreshHistory">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<CheckIcon class="w-4 h-4 mr-2" />
|
<CheckIcon class="w-4 h-4 mr-2" />
|
||||||
</template>
|
</template>
|
||||||
@@ -1330,7 +1346,7 @@
|
|||||||
<BaseButton variant="secondary" :disabled="isBatchUpdating" @click="closeBatchEditModal()">
|
<BaseButton variant="secondary" :disabled="isBatchUpdating" @click="closeBatchEditModal()">
|
||||||
{{ t('common.cancel') }}
|
{{ t('common.cancel') }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton :loading="isBatchUpdating" @click="handleBatchUpdate">
|
<BaseButton :loading="isBatchUpdating" @click="handleBatchUpdateAndRefreshHistory">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<CheckIcon class="w-4 h-4 mr-2" />
|
<CheckIcon class="w-4 h-4 mr-2" />
|
||||||
</template>
|
</template>
|
||||||
@@ -1525,25 +1541,25 @@ const {
|
|||||||
copyDetailRetrieveLink,
|
copyDetailRetrieveLink,
|
||||||
copyText,
|
copyText,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
deleteFile,
|
deleteFile: deleteFileRaw,
|
||||||
deleteSelectedFiles,
|
deleteSelectedFiles: deleteSelectedFilesRaw,
|
||||||
deleteSelectedViewPreset,
|
deleteSelectedViewPreset,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
exportPreviewText,
|
exportPreviewText,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePageSizeChange,
|
handlePageSizeChange,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
applyDetailPolicyAction,
|
applyDetailPolicyAction: applyDetailPolicyActionRaw,
|
||||||
applySelectedPolicyAction,
|
applySelectedPolicyAction: applySelectedPolicyActionRaw,
|
||||||
handleBatchUpdate,
|
handleBatchUpdate: handleBatchUpdateRaw,
|
||||||
handleUpdate,
|
handleUpdate: handleUpdateRaw,
|
||||||
loadViewPresets,
|
loadViewPresets,
|
||||||
loadFiles,
|
loadFiles,
|
||||||
openBatchEditModal,
|
openBatchEditModal,
|
||||||
openEditModal,
|
openEditModal,
|
||||||
openFileDetail,
|
openFileDetail,
|
||||||
openTextPreview,
|
openTextPreview,
|
||||||
refreshFiles,
|
refreshFiles: refreshFileList,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
saveCurrentViewPreset,
|
saveCurrentViewPreset,
|
||||||
setHealthFilter,
|
setHealthFilter,
|
||||||
@@ -1575,6 +1591,21 @@ const loadHistoryFiles = async () => {
|
|||||||
isHistoryLoading.value = false
|
isHistoryLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const runFileMutationAndRefreshHistory = async (operation: () => Promise<void>) => {
|
||||||
|
await operation()
|
||||||
|
await loadHistoryFiles()
|
||||||
|
}
|
||||||
|
const handleUpdateAndRefreshHistory = () => runFileMutationAndRefreshHistory(handleUpdateRaw)
|
||||||
|
const handleBatchUpdateAndRefreshHistory = () =>
|
||||||
|
runFileMutationAndRefreshHistory(handleBatchUpdateRaw)
|
||||||
|
const deleteFileAndRefreshHistory = (id: number) =>
|
||||||
|
runFileMutationAndRefreshHistory(() => deleteFileRaw(id))
|
||||||
|
const deleteSelectedFilesAndRefreshHistory = () =>
|
||||||
|
runFileMutationAndRefreshHistory(deleteSelectedFilesRaw)
|
||||||
|
const applySelectedPolicyActionAndRefreshHistory = (action: AdminFilePolicyAction) =>
|
||||||
|
runFileMutationAndRefreshHistory(() => applySelectedPolicyActionRaw(action))
|
||||||
|
const applyDetailPolicyActionAndRefreshHistory = (action: AdminFilePolicyAction) =>
|
||||||
|
runFileMutationAndRefreshHistory(() => applyDetailPolicyActionRaw(action))
|
||||||
const handleHistoryPageSizeChange = (size: number) => {
|
const handleHistoryPageSizeChange = (size: number) => {
|
||||||
historyPageSize.value = size
|
historyPageSize.value = size
|
||||||
historyPage.value = 1
|
historyPage.value = 1
|
||||||
@@ -1992,6 +2023,16 @@ const getInsightBadgeClass = (severity: AdminFileInsightSeverity) => {
|
|||||||
return isDarkMode.value ? darkClasses[severity] : lightClasses[severity]
|
return isDarkMode.value ? darkClasses[severity] : lightClasses[severity]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getInsightActionBadgeClass = (action: string) => {
|
||||||
|
if (action === 'extend_or_delete' || action === 'extend_expiration') {
|
||||||
|
return isDarkMode.value ? 'bg-amber-900/40 text-amber-200' : 'bg-amber-100 text-amber-700'
|
||||||
|
}
|
||||||
|
if (action === 'inspect_storage') {
|
||||||
|
return isDarkMode.value ? 'bg-red-900/40 text-red-200' : 'bg-red-100 text-red-700'
|
||||||
|
}
|
||||||
|
return isDarkMode.value ? 'bg-gray-700 text-gray-300' : 'bg-gray-100 text-gray-700'
|
||||||
|
}
|
||||||
|
|
||||||
const getTimelineDotClass = (severity: AdminFileInsightSeverity) => {
|
const getTimelineDotClass = (severity: AdminFileInsightSeverity) => {
|
||||||
const classes: Record<AdminFileInsightSeverity, string> = {
|
const classes: Record<AdminFileInsightSeverity, string> = {
|
||||||
success: 'bg-emerald-500',
|
success: 'bg-emerald-500',
|
||||||
|
|||||||
Reference in New Issue
Block a user