feat: add admin file health filters

This commit is contained in:
Lan
2026-06-03 05:55:18 +08:00
parent 3ab44b3cce
commit a573f1f5cc
5 changed files with 205 additions and 28 deletions
+117 -24
View File
@@ -12,6 +12,7 @@ import type {
AdminFileDetailViewItem,
AdminFileInsightSeverity,
AdminFilePatchPayload,
AdminFileHealthFilter,
AdminFileListParams,
AdminFilePolicyAction,
AdminFilePolicyActionRequest,
@@ -39,6 +40,12 @@ const emptySummary = (): AdminFileSummary => ({
textCount: 0,
fileCount: 0,
chunkedCount: 0,
healthAttentionCount: 0,
healthDangerCount: 0,
healthWarningCount: 0,
expiringSoonCount: 0,
storageIssueCount: 0,
neverRetrievedCount: 0,
storageUsed: 0,
usedCount: 0
})
@@ -72,6 +79,12 @@ type DetailPolicyActionOption = {
description: string
}
type HealthFilterOption = {
value: AdminFileHealthFilter
label: string
count?: number
}
const emptyBatchEditForm = (): AdminBatchEditForm => ({
mode: 'expiresAt',
expired_at: '',
@@ -106,6 +119,7 @@ export function useAdminFiles() {
keyword: '',
status: 'all',
type: 'all',
health: 'all',
sortBy: 'created_at',
sortOrder: 'desc'
})
@@ -157,6 +171,7 @@ export function useAdminFiles() {
keyword: params.value.keyword,
status: params.value.status === 'all' ? undefined : params.value.status,
type: params.value.type === 'all' ? undefined : params.value.type,
health: params.value.health === 'all' ? undefined : params.value.health,
sortBy: params.value.sortBy,
sortOrder: params.value.sortOrder
}))
@@ -165,9 +180,49 @@ export function useAdminFiles() {
() =>
Boolean(params.value.keyword?.trim()) ||
params.value.status !== 'all' ||
params.value.type !== 'all'
params.value.type !== 'all' ||
params.value.health !== 'all'
)
const healthFilterOptions = computed<HealthFilterOption[]>(() => [
{ value: 'all', label: t('fileManage.healthFilters.all'), count: summary.value.totalFiles },
{
value: 'attention',
label: t('fileManage.healthFilters.attention'),
count: summary.value.healthAttentionCount
},
{
value: 'danger',
label: t('fileManage.healthFilters.danger'),
count: summary.value.healthDangerCount
},
{
value: 'warning',
label: t('fileManage.healthFilters.warning'),
count: summary.value.healthWarningCount
},
{
value: 'expiring_soon',
label: t('fileManage.healthFilters.expiringSoon'),
count: summary.value.expiringSoonCount
},
{
value: 'storage_issue',
label: t('fileManage.healthFilters.storageIssue'),
count: summary.value.storageIssueCount
},
{
value: 'never_retrieved',
label: t('fileManage.healthFilters.neverRetrieved'),
count: summary.value.neverRetrievedCount
},
{
value: 'healthy',
label: t('fileManage.healthFilters.healthy'),
count: Math.max(summary.value.totalFiles - summary.value.healthAttentionCount, 0)
}
])
const detailPolicyActionOptions = computed<DetailPolicyActionOption[]>(() => [
{
action: 'extend_24h',
@@ -237,6 +292,19 @@ export function useAdminFiles() {
textCount: files.filter((file) => file.isTextFile).length,
fileCount: files.filter((file) => !file.isTextFile).length,
chunkedCount: files.filter((file) => file.isChunkedFile).length,
healthAttentionCount: files.filter((file) =>
['danger', 'warning'].includes(file.statusInsightSeverity)
).length,
healthDangerCount: files.filter((file) => file.statusInsightSeverity === 'danger').length,
healthWarningCount: files.filter((file) => file.statusInsightSeverity === 'warning').length,
expiringSoonCount: files.filter((file) => file.statusInsightReasons.includes('expires_soon'))
.length,
storageIssueCount: files.filter((file) =>
file.statusInsightReasons.includes('storage_metadata_incomplete')
).length,
neverRetrievedCount: files.filter((file) =>
file.statusInsightReasons.includes('never_retrieved')
).length,
storageUsed: files.reduce((totalSize, file) => totalSize + normalizeCount(file.size), 0),
usedCount: files.reduce(
(totalUsed, file) => totalUsed + normalizeCount(file.usedCount ?? file.used_count),
@@ -244,29 +312,6 @@ export function useAdminFiles() {
)
})
const createFileViewItem = (file: FileListItem): AdminFileViewItem => {
const isTextFile = inferIsText(file)
const isExpiredFile = inferIsExpired(file)
const isChunkedFile = inferIsChunked(file)
const remainingDownloadsValue = getRemainingDownloads(file)
const usedCount = normalizeCount(file.usedCount ?? file.used_count)
return {
...file,
displayName: file.name || `${file.prefix}${file.suffix}` || file.code,
displaySize: formatFileSize(file.size),
displayExpiredAt: file.expired_at
? formatTimestamp(file.expired_at)
: t('send.expiration.units.forever'),
displayUsage: `${usedCount} ${t('common.times')}`,
isTextFile,
isExpiredFile,
isChunkedFile,
remainingDownloadsValue,
canPreviewText: isTextFile
}
}
const normalizeInsightSeverity = (
severity: AdminFileInsightSeverity | undefined
): AdminFileInsightSeverity => {
@@ -279,6 +324,45 @@ export function useAdminFiles() {
return 'neutral'
}
const createFileViewItem = (file: FileListItem): AdminFileViewItem => {
const isTextFile = inferIsText(file)
const isExpiredFile = inferIsExpired(file)
const isChunkedFile = inferIsChunked(file)
const remainingDownloadsValue = getRemainingDownloads(file)
const usedCount = normalizeCount(file.usedCount ?? file.used_count)
const statusInsights = file.statusInsights ?? file.status_insights
const isPermanentFile =
!file.expired_at &&
(file.expired_count === null || file.expired_count === undefined || file.expired_count < 0)
const statusInsightState =
statusInsights?.state ??
(isExpiredFile ? 'expired' : isPermanentFile ? 'permanent' : 'available')
const statusInsightNextAction =
statusInsights?.nextAction ?? statusInsights?.next_action ?? 'monitor'
const statusInsightSeverity = normalizeInsightSeverity(statusInsights?.severity)
return {
...file,
displayName: file.name || `${file.prefix}${file.suffix}` || file.code,
displaySize: formatFileSize(file.size),
displayExpiredAt: file.expired_at
? formatTimestamp(file.expired_at)
: t('send.expiration.units.forever'),
displayUsage: `${usedCount} ${t('common.times')}`,
displayHealthState: t(`fileManage.insightStates.${statusInsightState}`),
displayHealthAction: t(`fileManage.insightActions.${statusInsightNextAction}`),
isTextFile,
isExpiredFile,
isChunkedFile,
remainingDownloadsValue,
canPreviewText: isTextFile,
statusInsightSeverity,
statusInsightState,
statusInsightNextAction,
statusInsightReasons: statusInsights?.reasons || []
}
}
const formatTimelineValue = (item: AdminFileDetailTimelineItem) => {
if (item.key === 'download_limit') {
if (item.status === 'unlimited') return t('fileManage.unlimited')
@@ -627,6 +711,7 @@ export function useAdminFiles() {
params.value.keyword = ''
params.value.status = 'all'
params.value.type = 'all'
params.value.health = 'all'
params.value.sortBy = 'created_at'
params.value.sortOrder = 'desc'
clearSelection()
@@ -645,6 +730,12 @@ export function useAdminFiles() {
await loadFiles()
}
const setHealthFilter = async (health: AdminFileHealthFilter) => {
params.value.health = health
params.value.page = 1
await loadFiles()
}
const handlePageChange = async (page: number | string) => {
if (typeof page === 'string') return
if (page < 1 || page > totalPages.value) return
@@ -1010,6 +1101,7 @@ export function useAdminFiles() {
batchEditForm,
detailPolicyActionOptions,
downloadingFileId,
healthFilterOptions,
hasSelectedFiles,
params,
previewFile,
@@ -1050,6 +1142,7 @@ export function useAdminFiles() {
openTextPreview,
refreshFiles,
resetFilters,
setHealthFilter,
setStatusFilter,
setTypeFilter,
toggleCurrentPageSelection,