refactor: remove admin recommendation summaries
This commit is contained in:
@@ -22,10 +22,6 @@ import type {
|
|||||||
AdminFileStatusFilter,
|
AdminFileStatusFilter,
|
||||||
AdminFileSummary,
|
AdminFileSummary,
|
||||||
AdminFileTypeFilter,
|
AdminFileTypeFilter,
|
||||||
AdminFileViewSummary,
|
|
||||||
AdminFileViewSummaryItem,
|
|
||||||
AdminFileViewSummaryViewData,
|
|
||||||
AdminFileViewSummaryViewItem,
|
|
||||||
AdminFileViewPreset,
|
AdminFileViewPreset,
|
||||||
AdminFileViewPresetParams,
|
AdminFileViewPresetParams,
|
||||||
AdminFileViewPresetRequest,
|
AdminFileViewPresetRequest,
|
||||||
@@ -62,18 +58,6 @@ const emptySummary = (): AdminFileSummary => ({
|
|||||||
usedCount: 0
|
usedCount: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const emptyViewSummary = (): AdminFileViewSummaryViewData => ({
|
|
||||||
total: 0,
|
|
||||||
filteredTotal: 0,
|
|
||||||
allTotal: 0,
|
|
||||||
activeFilterCount: 0,
|
|
||||||
hasFilters: false,
|
|
||||||
strongestSeverity: 'success',
|
|
||||||
summary: emptySummary(),
|
|
||||||
cards: [],
|
|
||||||
actions: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const normalizeCount = (value: number | string | null | undefined) => Number(value || 0)
|
const normalizeCount = (value: number | string | null | undefined) => Number(value || 0)
|
||||||
|
|
||||||
const isExpiredByDate = (value: string | null | undefined) => {
|
const isExpiredByDate = (value: string | null | undefined) => {
|
||||||
@@ -228,7 +212,6 @@ export function useAdminFiles() {
|
|||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const isSaving = ref(false)
|
const isSaving = ref(false)
|
||||||
const summary = ref<AdminFileSummary>(emptySummary())
|
const summary = ref<AdminFileSummary>(emptySummary())
|
||||||
const viewSummary = ref<AdminFileViewSummaryViewData>(emptyViewSummary())
|
|
||||||
const params = ref<AdminFileListParams & { total: number }>({
|
const params = ref<AdminFileListParams & { total: number }>({
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
@@ -540,7 +523,7 @@ export function useAdminFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizeInsightSeverity = (
|
const normalizeInsightSeverity = (
|
||||||
severity: AdminFileInsightSeverity | string | undefined
|
severity: AdminFileInsightSeverity | undefined
|
||||||
): AdminFileInsightSeverity => {
|
): AdminFileInsightSeverity => {
|
||||||
if (severity === 'success' || severity === 'warning' || severity === 'danger') {
|
if (severity === 'success' || severity === 'warning' || severity === 'danger') {
|
||||||
return severity
|
return severity
|
||||||
@@ -551,131 +534,6 @@ export function useAdminFiles() {
|
|||||||
return 'neutral'
|
return 'neutral'
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeViewSummaryItems = (
|
|
||||||
items: AdminFileViewSummaryItem[] = []
|
|
||||||
): AdminFileViewSummaryViewItem[] =>
|
|
||||||
items
|
|
||||||
.filter((item) => item && item.key)
|
|
||||||
.map((item) => {
|
|
||||||
const actionTypeValue = item.actionType ?? item.action_type ?? 'filter'
|
|
||||||
const sourceKeyValue = item.sourceKey ?? item.source_key ?? item.key
|
|
||||||
const suggestedActionValue = item.suggestedAction ?? item.suggested_action ?? sourceKeyValue
|
|
||||||
const targetHealthValue = item.targetHealth ?? item.target_health ?? item.health ?? 'all'
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
count: normalizeCount(item.count),
|
|
||||||
priority: normalizeCount(item.priority),
|
|
||||||
severity: normalizeInsightSeverity(item.severity),
|
|
||||||
actionTypeValue,
|
|
||||||
sourceKeyValue,
|
|
||||||
suggestedActionValue,
|
|
||||||
targetHealthValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const buildFallbackViewSummary = (
|
|
||||||
files: AdminFileViewItem[],
|
|
||||||
total: number,
|
|
||||||
rawSummary?: Partial<AdminFileSummary>
|
|
||||||
): AdminFileViewSummaryViewData => {
|
|
||||||
const normalizedSummary = normalizeSummary(rawSummary, files, total)
|
|
||||||
const cards: AdminFileViewSummaryItem[] = [
|
|
||||||
{
|
|
||||||
key: 'storage_issue',
|
|
||||||
severity: 'danger',
|
|
||||||
priority: 100,
|
|
||||||
count: normalizedSummary.storageIssueCount,
|
|
||||||
targetHealth: 'storage_issue'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'expired',
|
|
||||||
severity: normalizedSummary.expiredCount >= 10 ? 'danger' : 'warning',
|
|
||||||
priority: 90,
|
|
||||||
count: normalizedSummary.expiredCount,
|
|
||||||
targetHealth: 'expired'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'expiring_soon',
|
|
||||||
severity: 'warning',
|
|
||||||
priority: 80,
|
|
||||||
count: normalizedSummary.expiringSoonCount,
|
|
||||||
targetHealth: 'expiring_soon'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'never_retrieved',
|
|
||||||
severity: 'neutral',
|
|
||||||
priority: 60,
|
|
||||||
count: normalizedSummary.neverRetrievedCount,
|
|
||||||
targetHealth: 'never_retrieved'
|
|
||||||
}
|
|
||||||
].filter((item) => item.count > 0)
|
|
||||||
|
|
||||||
if (cards.length === 0) {
|
|
||||||
cards.push({
|
|
||||||
key: normalizedSummary.totalFiles > 0 ? 'healthy' : 'empty',
|
|
||||||
severity: normalizedSummary.totalFiles > 0 ? 'success' : 'neutral',
|
|
||||||
priority: 10,
|
|
||||||
count: normalizedSummary.totalFiles,
|
|
||||||
targetHealth: normalizedSummary.totalFiles > 0 ? 'healthy' : 'all'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedCards = normalizeViewSummaryItems(cards)
|
|
||||||
return {
|
|
||||||
total,
|
|
||||||
filteredTotal: total,
|
|
||||||
allTotal: summary.value.totalFiles || total,
|
|
||||||
activeFilterCount: hasActiveFilters.value ? 1 : 0,
|
|
||||||
hasFilters: hasActiveFilters.value,
|
|
||||||
strongestSeverity: normalizedCards[0]?.severity ?? 'success',
|
|
||||||
summary: normalizedSummary,
|
|
||||||
cards: normalizedCards,
|
|
||||||
actions: normalizedCards.filter((item) => item.severity !== 'success')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizeViewSummary = (
|
|
||||||
rawViewSummary: AdminFileViewSummary | undefined,
|
|
||||||
files: AdminFileViewItem[],
|
|
||||||
total: number,
|
|
||||||
rawSummary?: Partial<AdminFileSummary>
|
|
||||||
): AdminFileViewSummaryViewData => {
|
|
||||||
if (!rawViewSummary) {
|
|
||||||
return buildFallbackViewSummary(files, total, rawSummary)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cards = normalizeViewSummaryItems(rawViewSummary.cards ?? rawViewSummary.items ?? [])
|
|
||||||
const actions = normalizeViewSummaryItems(rawViewSummary.actions ?? [])
|
|
||||||
const rawSummarySource =
|
|
||||||
rawViewSummary.summary ??
|
|
||||||
rawViewSummary.healthSummary ??
|
|
||||||
rawViewSummary.health_summary ??
|
|
||||||
rawSummary
|
|
||||||
const filteredTotal = rawViewSummary.filteredTotal ?? rawViewSummary.filtered_total ?? total
|
|
||||||
const allTotal = rawViewSummary.allTotal ?? rawViewSummary.all_total ?? summary.value.totalFiles
|
|
||||||
const strongestSeverity =
|
|
||||||
rawViewSummary.strongestSeverity ??
|
|
||||||
rawViewSummary.strongest_severity ??
|
|
||||||
cards[0]?.severity ??
|
|
||||||
'success'
|
|
||||||
const normalizedSummary = normalizeSummary(rawSummarySource, files, total)
|
|
||||||
|
|
||||||
return {
|
|
||||||
total: normalizeCount(rawViewSummary.total ?? total),
|
|
||||||
filteredTotal: normalizeCount(filteredTotal),
|
|
||||||
allTotal: normalizeCount(allTotal),
|
|
||||||
activeFilterCount: normalizeCount(
|
|
||||||
rawViewSummary.activeFilterCount ?? rawViewSummary.active_filter_count
|
|
||||||
),
|
|
||||||
hasFilters: Boolean(rawViewSummary.hasFilters ?? rawViewSummary.has_filters),
|
|
||||||
strongestSeverity: normalizeInsightSeverity(strongestSeverity),
|
|
||||||
summary: normalizedSummary,
|
|
||||||
cards: cards.length > 0 ? cards : buildFallbackViewSummary(files, total, rawSummary).cards,
|
|
||||||
actions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const createFileViewItem = (file: FileListItem): AdminFileViewItem => {
|
const createFileViewItem = (file: FileListItem): AdminFileViewItem => {
|
||||||
const isTextFile = inferIsText(file)
|
const isTextFile = inferIsText(file)
|
||||||
const isExpiredFile = inferIsExpired(file)
|
const isExpiredFile = inferIsExpired(file)
|
||||||
@@ -1242,17 +1100,6 @@ export function useAdminFiles() {
|
|||||||
tableData.value = res.detail.data.map(createFileViewItem)
|
tableData.value = res.detail.data.map(createFileViewItem)
|
||||||
params.value.total = res.detail.total
|
params.value.total = res.detail.total
|
||||||
summary.value = normalizeSummary(res.detail.summary, tableData.value, res.detail.total)
|
summary.value = normalizeSummary(res.detail.summary, tableData.value, res.detail.total)
|
||||||
viewSummary.value = normalizeViewSummary(
|
|
||||||
res.detail.viewSummary ??
|
|
||||||
res.detail.view_summary ??
|
|
||||||
res.detail.currentViewSummary ??
|
|
||||||
res.detail.current_view_summary ??
|
|
||||||
res.detail.actionSummary ??
|
|
||||||
res.detail.action_summary,
|
|
||||||
tableData.value,
|
|
||||||
res.detail.total,
|
|
||||||
res.detail.summary
|
|
||||||
)
|
|
||||||
syncSelectedFilesWithCurrentPage()
|
syncSelectedFilesWithCurrentPage()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
hasLoadError.value = true
|
hasLoadError.value = true
|
||||||
@@ -1820,7 +1667,6 @@ export function useAdminFiles() {
|
|||||||
selectedViewPresetId,
|
selectedViewPresetId,
|
||||||
storageUsedText,
|
storageUsedText,
|
||||||
summary,
|
summary,
|
||||||
viewSummary,
|
|
||||||
showBatchEditModal,
|
showBatchEditModal,
|
||||||
showEditModal,
|
showEditModal,
|
||||||
showFileDetailModal,
|
showFileDetailModal,
|
||||||
|
|||||||
@@ -7,13 +7,6 @@ import type {
|
|||||||
DashboardActivityViewItem,
|
DashboardActivityViewItem,
|
||||||
DashboardData,
|
DashboardData,
|
||||||
DashboardHealthSummary,
|
DashboardHealthSummary,
|
||||||
DashboardInsightSeverity,
|
|
||||||
DashboardMaintenanceQueue,
|
|
||||||
DashboardMaintenanceQueueItem,
|
|
||||||
DashboardMaintenanceQueueSummary,
|
|
||||||
DashboardMaintenanceQueueViewItem,
|
|
||||||
DashboardOperationalInsight,
|
|
||||||
DashboardOperationalInsightViewItem,
|
|
||||||
DashboardViewData
|
DashboardViewData
|
||||||
} from '@/types'
|
} from '@/types'
|
||||||
import { formatFileSize, getErrorMessage } from '@/utils/common'
|
import { formatFileSize, getErrorMessage } from '@/utils/common'
|
||||||
@@ -25,54 +18,49 @@ type UseDashboardStatsOptions = {
|
|||||||
|
|
||||||
const activityTimelineLimit = 40
|
const activityTimelineLimit = 40
|
||||||
|
|
||||||
function emptyDashboardData(): DashboardViewData {
|
const emptyDashboardData = (): DashboardViewData => ({
|
||||||
return {
|
hasExtendedStats: false,
|
||||||
hasExtendedStats: false,
|
totalFiles: 0,
|
||||||
totalFiles: 0,
|
storageUsed: 0,
|
||||||
storageUsed: 0,
|
yesterdayCount: 0,
|
||||||
yesterdayCount: 0,
|
todayCount: 0,
|
||||||
todayCount: 0,
|
yesterdaySize: 0,
|
||||||
yesterdaySize: 0,
|
todaySize: 0,
|
||||||
todaySize: 0,
|
sysUptime: null,
|
||||||
sysUptime: null,
|
activeCount: 0,
|
||||||
activeCount: 0,
|
expiredCount: 0,
|
||||||
expiredCount: 0,
|
textCount: 0,
|
||||||
textCount: 0,
|
fileCount: 0,
|
||||||
fileCount: 0,
|
chunkedCount: 0,
|
||||||
chunkedCount: 0,
|
usedCount: 0,
|
||||||
usedCount: 0,
|
storageBackend: '-',
|
||||||
storageBackend: '-',
|
uploadSizeLimit: 0,
|
||||||
uploadSizeLimit: 0,
|
openUpload: 0,
|
||||||
openUpload: 0,
|
enableChunk: 0,
|
||||||
enableChunk: 0,
|
maxSaveSeconds: 0,
|
||||||
maxSaveSeconds: 0,
|
healthAttentionCount: 0,
|
||||||
healthAttentionCount: 0,
|
healthDangerCount: 0,
|
||||||
healthDangerCount: 0,
|
healthWarningCount: 0,
|
||||||
healthWarningCount: 0,
|
expiringSoonCount: 0,
|
||||||
expiringSoonCount: 0,
|
storageIssueCount: 0,
|
||||||
storageIssueCount: 0,
|
neverRetrievedCount: 0,
|
||||||
neverRetrievedCount: 0,
|
healthyCount: 0,
|
||||||
healthyCount: 0,
|
permanentCount: 0,
|
||||||
permanentCount: 0,
|
topSuffixes: [],
|
||||||
topSuffixes: [],
|
recentFiles: [],
|
||||||
recentFiles: [],
|
recentActivities: [],
|
||||||
recentActivities: [],
|
storageUsedText: '0 Bytes',
|
||||||
operationalInsights: [],
|
yesterdaySizeText: '0 Bytes',
|
||||||
maintenanceItems: [],
|
todaySizeText: '0 Bytes',
|
||||||
maintenanceSummary: emptyMaintenanceSummary(),
|
uploadSizeLimitText: '0 Bytes',
|
||||||
storageUsedText: '0 Bytes',
|
sysUptimeText: '-',
|
||||||
yesterdaySizeText: '0 Bytes',
|
activeRatio: 0,
|
||||||
todaySizeText: '0 Bytes',
|
textRatio: 0,
|
||||||
uploadSizeLimitText: '0 Bytes',
|
fileRatio: 0,
|
||||||
sysUptimeText: '-',
|
healthyRatio: 0,
|
||||||
activeRatio: 0,
|
healthAttentionRatio: 0,
|
||||||
textRatio: 0,
|
todaySizeRatio: 0
|
||||||
fileRatio: 0,
|
})
|
||||||
healthyRatio: 0,
|
|
||||||
healthAttentionRatio: 0,
|
|
||||||
todaySizeRatio: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const toNumber = (value: number | string | null | undefined) => Number(value || 0)
|
const toNumber = (value: number | string | null | undefined) => Number(value || 0)
|
||||||
|
|
||||||
@@ -121,150 +109,6 @@ const normalizeActivityOptions = (
|
|||||||
count: toNumber(option.count)
|
count: toNumber(option.count)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const insightSeverities: DashboardInsightSeverity[] = ['danger', 'warning', 'success', 'neutral']
|
|
||||||
|
|
||||||
const normalizeInsightSeverity = (severity: string | undefined): DashboardInsightSeverity =>
|
|
||||||
insightSeverities.includes(severity as DashboardInsightSeverity)
|
|
||||||
? (severity as DashboardInsightSeverity)
|
|
||||||
: 'neutral'
|
|
||||||
|
|
||||||
const normalizeOperationalInsights = (
|
|
||||||
insights: DashboardOperationalInsight[] = []
|
|
||||||
): DashboardOperationalInsightViewItem[] =>
|
|
||||||
insights
|
|
||||||
.filter((insight) => insight && insight.key)
|
|
||||||
.map((insight) => {
|
|
||||||
const action = insight.action && typeof insight.action === 'object' ? insight.action : {}
|
|
||||||
const actionTypeValue =
|
|
||||||
insight.actionType ??
|
|
||||||
insight.action_type ??
|
|
||||||
action.actionType ??
|
|
||||||
action.action_type ??
|
|
||||||
action.type ??
|
|
||||||
'file_queue'
|
|
||||||
const targetHealthValue =
|
|
||||||
insight.targetHealth ??
|
|
||||||
insight.target_health ??
|
|
||||||
action.targetHealth ??
|
|
||||||
action.target_health ??
|
|
||||||
action.health ??
|
|
||||||
null
|
|
||||||
|
|
||||||
return {
|
|
||||||
...insight,
|
|
||||||
count: toNumber(insight.count),
|
|
||||||
priority: toNumber(insight.priority),
|
|
||||||
severity: normalizeInsightSeverity(insight.severity),
|
|
||||||
actionTypeValue,
|
|
||||||
targetHealthValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function emptyMaintenanceSummary(): DashboardMaintenanceQueueSummary {
|
|
||||||
return {
|
|
||||||
total: 0,
|
|
||||||
actionableCount: 0,
|
|
||||||
dangerCount: 0,
|
|
||||||
warningCount: 0,
|
|
||||||
successCount: 0,
|
|
||||||
neutralCount: 0,
|
|
||||||
fileQueueCount: 0,
|
|
||||||
settingsCount: 0,
|
|
||||||
strongestSeverity: 'success'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizeMaintenanceQueueItems = (
|
|
||||||
items: DashboardMaintenanceQueueItem[] = []
|
|
||||||
): DashboardMaintenanceQueueViewItem[] =>
|
|
||||||
items
|
|
||||||
.filter((item) => item && item.key)
|
|
||||||
.map((item) => {
|
|
||||||
const action = item.action && typeof item.action === 'object' ? item.action : {}
|
|
||||||
const actionTypeValue =
|
|
||||||
item.actionType ??
|
|
||||||
item.action_type ??
|
|
||||||
action.actionType ??
|
|
||||||
action.action_type ??
|
|
||||||
action.type ??
|
|
||||||
'file_queue'
|
|
||||||
const targetHealthValue =
|
|
||||||
item.targetHealth ??
|
|
||||||
item.target_health ??
|
|
||||||
action.targetHealth ??
|
|
||||||
action.target_health ??
|
|
||||||
action.health ??
|
|
||||||
null
|
|
||||||
const suggestedActionValue =
|
|
||||||
item.suggestedAction ??
|
|
||||||
item.suggested_action ??
|
|
||||||
action.suggestedAction ??
|
|
||||||
action.suggested_action ??
|
|
||||||
'monitor'
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
count: toNumber(item.count),
|
|
||||||
priority: toNumber(item.priority),
|
|
||||||
severity: normalizeInsightSeverity(item.severity),
|
|
||||||
actionTypeValue,
|
|
||||||
suggestedActionValue,
|
|
||||||
targetHealthValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const normalizeMaintenanceSummary = (
|
|
||||||
summary: Partial<DashboardMaintenanceQueueSummary> | undefined,
|
|
||||||
items: DashboardMaintenanceQueueViewItem[]
|
|
||||||
): DashboardMaintenanceQueueSummary => ({
|
|
||||||
total: toNumber(summary?.total ?? items.length),
|
|
||||||
actionableCount: toNumber(summary?.actionableCount ?? summary?.actionable_count),
|
|
||||||
actionable_count: toNumber(summary?.actionableCount ?? summary?.actionable_count),
|
|
||||||
dangerCount: toNumber(summary?.dangerCount ?? summary?.danger_count),
|
|
||||||
danger_count: toNumber(summary?.dangerCount ?? summary?.danger_count),
|
|
||||||
warningCount: toNumber(summary?.warningCount ?? summary?.warning_count),
|
|
||||||
warning_count: toNumber(summary?.warningCount ?? summary?.warning_count),
|
|
||||||
successCount: toNumber(summary?.successCount ?? summary?.success_count),
|
|
||||||
success_count: toNumber(summary?.successCount ?? summary?.success_count),
|
|
||||||
neutralCount: toNumber(summary?.neutralCount ?? summary?.neutral_count),
|
|
||||||
neutral_count: toNumber(summary?.neutralCount ?? summary?.neutral_count),
|
|
||||||
fileQueueCount: toNumber(summary?.fileQueueCount ?? summary?.file_queue_count),
|
|
||||||
file_queue_count: toNumber(summary?.fileQueueCount ?? summary?.file_queue_count),
|
|
||||||
settingsCount: toNumber(summary?.settingsCount ?? summary?.settings_count),
|
|
||||||
settings_count: toNumber(summary?.settingsCount ?? summary?.settings_count),
|
|
||||||
strongestSeverity: normalizeInsightSeverity(
|
|
||||||
summary?.strongestSeverity ?? summary?.strongest_severity
|
|
||||||
),
|
|
||||||
strongest_severity: normalizeInsightSeverity(
|
|
||||||
summary?.strongestSeverity ?? summary?.strongest_severity
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const normalizeMaintenanceQueue = (
|
|
||||||
detail: DashboardData | DashboardMaintenanceQueue
|
|
||||||
): Pick<DashboardViewData, 'maintenanceItems' | 'maintenanceSummary'> => {
|
|
||||||
const source = detail as DashboardData & DashboardMaintenanceQueue
|
|
||||||
const queue = source.maintenanceQueue ?? source.maintenance_queue ?? source
|
|
||||||
const items = normalizeMaintenanceQueueItems(
|
|
||||||
queue?.items ??
|
|
||||||
queue?.maintenanceItems ??
|
|
||||||
queue?.maintenance_items ??
|
|
||||||
source.maintenanceItems ??
|
|
||||||
source.maintenance_items ??
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const rawSummary =
|
|
||||||
queue?.summary ??
|
|
||||||
queue?.maintenanceSummary ??
|
|
||||||
queue?.maintenance_summary ??
|
|
||||||
source.maintenanceSummary ??
|
|
||||||
source.maintenance_summary
|
|
||||||
return {
|
|
||||||
maintenanceItems: items,
|
|
||||||
maintenanceSummary: normalizeMaintenanceSummary(rawSummary, items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const healthSummaryKeys: (keyof DashboardHealthSummary)[] = [
|
const healthSummaryKeys: (keyof DashboardHealthSummary)[] = [
|
||||||
'healthAttentionCount',
|
'healthAttentionCount',
|
||||||
'healthDangerCount',
|
'healthDangerCount',
|
||||||
@@ -366,12 +210,6 @@ export function useDashboardStats(options: UseDashboardStatsOptions = {}) {
|
|||||||
dashboardData.recentActivities = normalizeRecentActivities(
|
dashboardData.recentActivities = normalizeRecentActivities(
|
||||||
detail.recentActivities ?? detail.recent_activities ?? []
|
detail.recentActivities ?? detail.recent_activities ?? []
|
||||||
)
|
)
|
||||||
dashboardData.operationalInsights = normalizeOperationalInsights(
|
|
||||||
detail.operationalInsights ?? detail.operational_insights ?? detail.insights ?? []
|
|
||||||
)
|
|
||||||
const maintenanceQueue = normalizeMaintenanceQueue(detail)
|
|
||||||
dashboardData.maintenanceItems = maintenanceQueue.maintenanceItems
|
|
||||||
dashboardData.maintenanceSummary = maintenanceQueue.maintenanceSummary
|
|
||||||
|
|
||||||
dashboardData.storageUsedText = formatFileSize(dashboardData.storageUsed)
|
dashboardData.storageUsedText = formatFileSize(dashboardData.storageUsed)
|
||||||
dashboardData.yesterdaySizeText = formatFileSize(dashboardData.yesterdaySize)
|
dashboardData.yesterdaySizeText = formatFileSize(dashboardData.yesterdaySize)
|
||||||
|
|||||||
@@ -90,65 +90,6 @@ export default {
|
|||||||
textType: 'Text',
|
textType: 'Text',
|
||||||
recentActivities: 'Recent Activity',
|
recentActivities: 'Recent Activity',
|
||||||
recentActivitiesDesc: 'Key admin changes for quick operational traceability.',
|
recentActivitiesDesc: 'Key admin changes for quick operational traceability.',
|
||||||
operationalInsightsTitle: 'Operational Insights',
|
|
||||||
operationalInsightsDesc:
|
|
||||||
'Suggested next actions generated from file health, upload policy, and recent capacity.',
|
|
||||||
operationalInsightActionSettings: 'Adjust settings',
|
|
||||||
operationalInsightActionFileQueue: 'View queue',
|
|
||||||
operationalInsightSeverity: {
|
|
||||||
danger: 'High risk',
|
|
||||||
warning: 'Action needed',
|
|
||||||
success: 'Stable',
|
|
||||||
neutral: 'Suggestion'
|
|
||||||
},
|
|
||||||
maintenanceQueueTitle: 'Maintenance Queue',
|
|
||||||
maintenanceQueueDesc:
|
|
||||||
'Groups file health, retention policy, and upload settings into actionable queues. Click an item to open the related view.',
|
|
||||||
maintenanceActionable: 'Actionable',
|
|
||||||
maintenanceFileQueue: 'File queue',
|
|
||||||
maintenanceSettings: 'Settings',
|
|
||||||
maintenanceQueueActionSettings: 'Adjust settings',
|
|
||||||
maintenanceQueueActionFileQueue: 'View queue',
|
|
||||||
maintenanceQueueItems: {
|
|
||||||
storage_issue: {
|
|
||||||
title: '{count} storage issues',
|
|
||||||
description:
|
|
||||||
'Review records missing download metadata and verify storage settings and file details.'
|
|
||||||
},
|
|
||||||
expired_cleanup: {
|
|
||||||
title: '{count} expired items',
|
|
||||||
description:
|
|
||||||
'Review expired shares, delete stale records, or extend files that should stay available.'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '{count} expiring soon',
|
|
||||||
description: 'These shares will expire soon. Extend them early or notify recipients.'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '{count} never retrieved',
|
|
||||||
description:
|
|
||||||
'Review shares that have not been retrieved to identify ineffective uploads or follow-up gaps.'
|
|
||||||
},
|
|
||||||
permanent_review: {
|
|
||||||
title: '{count} permanent shares',
|
|
||||||
description:
|
|
||||||
'Periodically review long-retention shares to prevent unbounded historical growth.'
|
|
||||||
},
|
|
||||||
guest_upload_retention: {
|
|
||||||
title: 'Guest uploads lack retention limits',
|
|
||||||
description:
|
|
||||||
'Guest uploads are enabled without a maximum retention period. Add a default retention policy.'
|
|
||||||
},
|
|
||||||
chunking_disabled: {
|
|
||||||
title: 'Large-file experience can improve',
|
|
||||||
description:
|
|
||||||
"Today's capacity is near the single-file limit. Enable chunked upload for better reliability."
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: 'Maintenance queue is stable',
|
|
||||||
description: 'No maintenance item needs priority attention right now.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewAllActivities: 'View all',
|
viewAllActivities: 'View all',
|
||||||
activityTimelineTitle: 'Activity Timeline',
|
activityTimelineTitle: 'Activity Timeline',
|
||||||
activityTimelineFiltered: 'Current Results',
|
activityTimelineFiltered: 'Current Results',
|
||||||
@@ -191,43 +132,6 @@ export default {
|
|||||||
local_file: 'Local file',
|
local_file: 'Local file',
|
||||||
system: 'System'
|
system: 'System'
|
||||||
},
|
},
|
||||||
operationalInsights: {
|
|
||||||
storage_issue: {
|
|
||||||
title: 'Fix {count} storage issues',
|
|
||||||
description:
|
|
||||||
'Some records are missing download metadata. Review storage settings and file details first.'
|
|
||||||
},
|
|
||||||
expired_cleanup: {
|
|
||||||
title: 'Clean up {count} expired files',
|
|
||||||
description:
|
|
||||||
'Expired records add management noise. Delete them in bulk or extend the files that should remain available.'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '{count} files expiring soon',
|
|
||||||
description:
|
|
||||||
'These shares will become unavailable soon. Extend them early or notify the recipients.'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '{count} files never retrieved',
|
|
||||||
description:
|
|
||||||
'Shares that were never retrieved can reveal ineffective uploads or follow-up gaps.'
|
|
||||||
},
|
|
||||||
guest_upload_retention: {
|
|
||||||
title: 'Guest uploads lack retention limits',
|
|
||||||
description:
|
|
||||||
'Guest upload is enabled with unlimited retention. Set a default retention window to reduce storage pressure.'
|
|
||||||
},
|
|
||||||
chunking_disabled: {
|
|
||||||
title: 'Consider enabling chunked upload',
|
|
||||||
description:
|
|
||||||
"Today's added capacity is near the single-file limit. Chunked upload can improve large-file reliability."
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: 'Operations look stable',
|
|
||||||
description:
|
|
||||||
'No high-priority operational risk was detected. Keep watching the file health trend.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
recentFiles: 'Recent Shares',
|
recentFiles: 'Recent Shares',
|
||||||
recentFilesDesc: 'Recently created share records for quick status checks.',
|
recentFilesDesc: 'Recently created share records for quick status checks.',
|
||||||
available: 'Available',
|
available: 'Available',
|
||||||
@@ -576,57 +480,6 @@ export default {
|
|||||||
viewPresetDeleteSuccess: 'View deleted',
|
viewPresetDeleteSuccess: 'View deleted',
|
||||||
viewPresetDeleteFailed: 'Failed to delete view',
|
viewPresetDeleteFailed: 'Failed to delete view',
|
||||||
viewPresetLoadFailed: 'Failed to load saved views; using built-in views',
|
viewPresetLoadFailed: 'Failed to load saved views; using built-in views',
|
||||||
viewSummary: {
|
|
||||||
title: 'Current View Action Summary',
|
|
||||||
description:
|
|
||||||
'Groups risks and next actions from the current filters. Click an item to open its queue.',
|
|
||||||
filteredTotal: 'Current results',
|
|
||||||
allTotal: 'All records',
|
|
||||||
activeFilters: 'Filters',
|
|
||||||
action: 'View queue',
|
|
||||||
reset: 'Clear filters',
|
|
||||||
severity: {
|
|
||||||
success: 'Stable',
|
|
||||||
warning: 'Action needed',
|
|
||||||
danger: 'High risk',
|
|
||||||
info: 'Info',
|
|
||||||
neutral: 'Watch'
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
storage_issue: {
|
|
||||||
title: '{count} storage issues',
|
|
||||||
description:
|
|
||||||
'Review records missing download metadata and verify storage paths, file names, and settings.'
|
|
||||||
},
|
|
||||||
expired: {
|
|
||||||
title: '{count} expired items',
|
|
||||||
description:
|
|
||||||
'Handle unavailable shares by deleting stale records or extending files that should remain available.'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '{count} expiring soon',
|
|
||||||
description:
|
|
||||||
'These shares expire within 24 hours. Extend them early or notify recipients.'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '{count} never retrieved',
|
|
||||||
description:
|
|
||||||
'Review shares that have not been retrieved to identify ineffective uploads or follow-up gaps.'
|
|
||||||
},
|
|
||||||
permanent: {
|
|
||||||
title: '{count} permanent shares',
|
|
||||||
description: 'Review long-retention shares to prevent unbounded historical growth.'
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: 'Current view is stable',
|
|
||||||
description: 'No file in the current filters needs priority handling.'
|
|
||||||
},
|
|
||||||
empty: {
|
|
||||||
title: 'No results in this view',
|
|
||||||
description: 'Adjust or clear filters to inspect other file queues.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
all: 'All',
|
all: 'All',
|
||||||
active: 'Available',
|
active: 'Available',
|
||||||
expired: 'Expired',
|
expired: 'Expired',
|
||||||
@@ -1081,65 +934,6 @@ export default {
|
|||||||
textType: 'Text',
|
textType: 'Text',
|
||||||
recentActivities: 'Recent Activity',
|
recentActivities: 'Recent Activity',
|
||||||
recentActivitiesDesc: 'Key admin changes for quick operational traceability.',
|
recentActivitiesDesc: 'Key admin changes for quick operational traceability.',
|
||||||
operationalInsightsTitle: 'Operational Insights',
|
|
||||||
operationalInsightsDesc:
|
|
||||||
'Suggested next actions generated from file health, upload policy, and recent capacity.',
|
|
||||||
operationalInsightActionSettings: 'Adjust settings',
|
|
||||||
operationalInsightActionFileQueue: 'View queue',
|
|
||||||
operationalInsightSeverity: {
|
|
||||||
danger: 'High risk',
|
|
||||||
warning: 'Action needed',
|
|
||||||
success: 'Stable',
|
|
||||||
neutral: 'Suggestion'
|
|
||||||
},
|
|
||||||
maintenanceQueueTitle: 'Maintenance Queue',
|
|
||||||
maintenanceQueueDesc:
|
|
||||||
'Groups file health, retention policy, and upload settings into actionable queues. Click an item to open the related view.',
|
|
||||||
maintenanceActionable: 'Actionable',
|
|
||||||
maintenanceFileQueue: 'File queue',
|
|
||||||
maintenanceSettings: 'Settings',
|
|
||||||
maintenanceQueueActionSettings: 'Adjust settings',
|
|
||||||
maintenanceQueueActionFileQueue: 'View queue',
|
|
||||||
maintenanceQueueItems: {
|
|
||||||
storage_issue: {
|
|
||||||
title: '{count} storage issues',
|
|
||||||
description:
|
|
||||||
'Review records missing download metadata and verify storage settings and file details.'
|
|
||||||
},
|
|
||||||
expired_cleanup: {
|
|
||||||
title: '{count} expired items',
|
|
||||||
description:
|
|
||||||
'Review expired shares, delete stale records, or extend files that should stay available.'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '{count} expiring soon',
|
|
||||||
description: 'These shares will expire soon. Extend them early or notify recipients.'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '{count} never retrieved',
|
|
||||||
description:
|
|
||||||
'Review shares that have not been retrieved to identify ineffective uploads or follow-up gaps.'
|
|
||||||
},
|
|
||||||
permanent_review: {
|
|
||||||
title: '{count} permanent shares',
|
|
||||||
description:
|
|
||||||
'Periodically review long-retention shares to prevent unbounded historical growth.'
|
|
||||||
},
|
|
||||||
guest_upload_retention: {
|
|
||||||
title: 'Guest uploads lack retention limits',
|
|
||||||
description:
|
|
||||||
'Guest uploads are enabled without a maximum retention period. Add a default retention policy.'
|
|
||||||
},
|
|
||||||
chunking_disabled: {
|
|
||||||
title: 'Large-file experience can improve',
|
|
||||||
description:
|
|
||||||
"Today's capacity is near the single-file limit. Enable chunked upload for better reliability."
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: 'Maintenance queue is stable',
|
|
||||||
description: 'No maintenance item needs priority attention right now.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewAllActivities: 'View all',
|
viewAllActivities: 'View all',
|
||||||
activityTimelineTitle: 'Activity Timeline',
|
activityTimelineTitle: 'Activity Timeline',
|
||||||
activityTimelineFiltered: 'Current Results',
|
activityTimelineFiltered: 'Current Results',
|
||||||
@@ -1182,43 +976,6 @@ export default {
|
|||||||
local_file: 'Local file',
|
local_file: 'Local file',
|
||||||
system: 'System'
|
system: 'System'
|
||||||
},
|
},
|
||||||
operationalInsights: {
|
|
||||||
storage_issue: {
|
|
||||||
title: 'Fix {count} storage issues',
|
|
||||||
description:
|
|
||||||
'Some records are missing download metadata. Review storage settings and file details first.'
|
|
||||||
},
|
|
||||||
expired_cleanup: {
|
|
||||||
title: 'Clean up {count} expired files',
|
|
||||||
description:
|
|
||||||
'Expired records add management noise. Delete them in bulk or extend the files that should remain available.'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '{count} files expiring soon',
|
|
||||||
description:
|
|
||||||
'These shares will become unavailable soon. Extend them early or notify the recipients.'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '{count} files never retrieved',
|
|
||||||
description:
|
|
||||||
'Shares that were never retrieved can reveal ineffective uploads or follow-up gaps.'
|
|
||||||
},
|
|
||||||
guest_upload_retention: {
|
|
||||||
title: 'Guest uploads lack retention limits',
|
|
||||||
description:
|
|
||||||
'Guest upload is enabled with unlimited retention. Set a default retention window to reduce storage pressure.'
|
|
||||||
},
|
|
||||||
chunking_disabled: {
|
|
||||||
title: 'Consider enabling chunked upload',
|
|
||||||
description:
|
|
||||||
"Today's added capacity is near the single-file limit. Chunked upload can improve large-file reliability."
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: 'Operations look stable',
|
|
||||||
description:
|
|
||||||
'No high-priority operational risk was detected. Keep watching the file health trend.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
recentFiles: 'Recent Shares',
|
recentFiles: 'Recent Shares',
|
||||||
recentFilesDesc: 'Recently created share records for quick status checks.',
|
recentFilesDesc: 'Recently created share records for quick status checks.',
|
||||||
available: 'Available',
|
available: 'Available',
|
||||||
|
|||||||
@@ -90,57 +90,6 @@ export default {
|
|||||||
textType: '文本',
|
textType: '文本',
|
||||||
recentActivities: '最近操作',
|
recentActivities: '最近操作',
|
||||||
recentActivitiesDesc: '记录管理员最近的关键改动,便于回溯处理链路。',
|
recentActivitiesDesc: '记录管理员最近的关键改动,便于回溯处理链路。',
|
||||||
operationalInsightsTitle: '运营建议',
|
|
||||||
operationalInsightsDesc: '根据文件健康、上传策略和近期容量自动生成下一步动作。',
|
|
||||||
operationalInsightActionSettings: '调整设置',
|
|
||||||
operationalInsightActionFileQueue: '查看队列',
|
|
||||||
operationalInsightSeverity: {
|
|
||||||
danger: '高风险',
|
|
||||||
warning: '需处理',
|
|
||||||
success: '稳定',
|
|
||||||
neutral: '建议'
|
|
||||||
},
|
|
||||||
maintenanceQueueTitle: '维护队列',
|
|
||||||
maintenanceQueueDesc: '把文件健康、保留策略和上传配置聚合成可处理队列,点击进入对应视图。',
|
|
||||||
maintenanceActionable: '待处理',
|
|
||||||
maintenanceFileQueue: '文件队列',
|
|
||||||
maintenanceSettings: '配置项',
|
|
||||||
maintenanceQueueActionSettings: '调整设置',
|
|
||||||
maintenanceQueueActionFileQueue: '查看队列',
|
|
||||||
maintenanceQueueItems: {
|
|
||||||
storage_issue: {
|
|
||||||
title: '存储异常 {count} 项',
|
|
||||||
description: '优先核对缺少下载元数据的记录,确认存储配置和文件详情。'
|
|
||||||
},
|
|
||||||
expired_cleanup: {
|
|
||||||
title: '已过期 {count} 项',
|
|
||||||
description: '集中检查过期分享,删除无效记录或续期仍需保留的文件。'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '即将过期 {count} 项',
|
|
||||||
description: '这些分享将在短时间内失效,适合提前续期或通知使用方。'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '未取件 {count} 项',
|
|
||||||
description: '查看创建后尚未被取件的分享,用于识别无效上传或触达缺口。'
|
|
||||||
},
|
|
||||||
permanent_review: {
|
|
||||||
title: '永久有效 {count} 项',
|
|
||||||
description: '定期复核长期保留的分享,避免历史文件无限增长。'
|
|
||||||
},
|
|
||||||
guest_upload_retention: {
|
|
||||||
title: '游客上传缺少保留上限',
|
|
||||||
description: '游客上传已开启且没有最长保存时间,建议补充默认保留策略。'
|
|
||||||
},
|
|
||||||
chunking_disabled: {
|
|
||||||
title: '大文件体验可优化',
|
|
||||||
description: '今日容量接近单文件上限,开启分片上传可提升上传稳定性。'
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: '维护队列稳定',
|
|
||||||
description: '当前未发现需要优先处理的维护事项。'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewAllActivities: '查看全部',
|
viewAllActivities: '查看全部',
|
||||||
activityTimelineTitle: '操作时间线',
|
activityTimelineTitle: '操作时间线',
|
||||||
activityTimelineFiltered: '当前结果',
|
activityTimelineFiltered: '当前结果',
|
||||||
@@ -183,36 +132,6 @@ export default {
|
|||||||
local_file: '本地文件',
|
local_file: '本地文件',
|
||||||
system: '系统'
|
system: '系统'
|
||||||
},
|
},
|
||||||
operationalInsights: {
|
|
||||||
storage_issue: {
|
|
||||||
title: '修复 {count} 个存储异常',
|
|
||||||
description: '部分记录缺少下载元数据,建议优先核对存储配置和文件详情。'
|
|
||||||
},
|
|
||||||
expired_cleanup: {
|
|
||||||
title: '清理 {count} 个已过期文件',
|
|
||||||
description: '已过期记录会占用管理视野,建议批量删除或续期仍需保留的文件。'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '{count} 个文件即将过期',
|
|
||||||
description: '这些分享将在短时间内失效,适合提前续期或通知使用方。'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '{count} 个文件未被取件',
|
|
||||||
description: '创建后长期无人取件的分享可用于判断无效上传或触达效果。'
|
|
||||||
},
|
|
||||||
guest_upload_retention: {
|
|
||||||
title: '游客上传缺少保留上限',
|
|
||||||
description: '游客上传已开启且保存时间不受限,建议设置默认保留期降低存储压力。'
|
|
||||||
},
|
|
||||||
chunking_disabled: {
|
|
||||||
title: '建议开启分片上传',
|
|
||||||
description: '今日新增容量已接近单文件上限,开启分片上传可提升大文件体验。'
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: '运行状态稳定',
|
|
||||||
description: '当前未发现优先级较高的运营风险,可继续观察文件健康趋势。'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
recentFiles: '最近分享',
|
recentFiles: '最近分享',
|
||||||
recentFilesDesc: '最近创建的分享记录,便于快速核对状态。',
|
recentFilesDesc: '最近创建的分享记录,便于快速核对状态。',
|
||||||
available: '可取件',
|
available: '可取件',
|
||||||
@@ -559,52 +478,6 @@ export default {
|
|||||||
viewPresetDeleteSuccess: '视图已删除',
|
viewPresetDeleteSuccess: '视图已删除',
|
||||||
viewPresetDeleteFailed: '视图删除失败',
|
viewPresetDeleteFailed: '视图删除失败',
|
||||||
viewPresetLoadFailed: '视图预设加载失败,已使用内置视图',
|
viewPresetLoadFailed: '视图预设加载失败,已使用内置视图',
|
||||||
viewSummary: {
|
|
||||||
title: '当前视图处置摘要',
|
|
||||||
description: '基于当前筛选结果聚合风险和下一步动作,点击可切换到对应队列。',
|
|
||||||
filteredTotal: '当前结果',
|
|
||||||
allTotal: '全部记录',
|
|
||||||
activeFilters: '筛选条件',
|
|
||||||
action: '查看队列',
|
|
||||||
reset: '清空筛选',
|
|
||||||
severity: {
|
|
||||||
success: '稳定',
|
|
||||||
warning: '需处理',
|
|
||||||
danger: '高风险',
|
|
||||||
info: '提示',
|
|
||||||
neutral: '观察'
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
storage_issue: {
|
|
||||||
title: '存储异常 {count} 项',
|
|
||||||
description: '优先检查缺少下载元数据的记录,确认存储路径、文件名和配置。'
|
|
||||||
},
|
|
||||||
expired: {
|
|
||||||
title: '已过期 {count} 项',
|
|
||||||
description: '集中处理已不可取件的分享,删除无效记录或续期仍需保留的文件。'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '即将过期 {count} 项',
|
|
||||||
description: '这些分享将在 24 小时内失效,适合提前续期或提醒使用方。'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '未取件 {count} 项',
|
|
||||||
description: '查看创建后尚未被取件的分享,用于识别无效上传或触达缺口。'
|
|
||||||
},
|
|
||||||
permanent: {
|
|
||||||
title: '永久有效 {count} 项',
|
|
||||||
description: '复核长期保留的分享,避免历史文件无限增长。'
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: '当前视图稳定',
|
|
||||||
description: '当前筛选结果未发现需要优先处理的文件。'
|
|
||||||
},
|
|
||||||
empty: {
|
|
||||||
title: '当前视图无结果',
|
|
||||||
description: '调整筛选条件或清空筛选,查看其他文件队列。'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
all: '全部',
|
all: '全部',
|
||||||
active: '可取件',
|
active: '可取件',
|
||||||
expired: '已过期',
|
expired: '已过期',
|
||||||
@@ -1015,57 +888,6 @@ export default {
|
|||||||
textType: '文本',
|
textType: '文本',
|
||||||
recentActivities: '最近操作',
|
recentActivities: '最近操作',
|
||||||
recentActivitiesDesc: '记录管理员最近的关键改动,便于回溯处理链路。',
|
recentActivitiesDesc: '记录管理员最近的关键改动,便于回溯处理链路。',
|
||||||
operationalInsightsTitle: '运营建议',
|
|
||||||
operationalInsightsDesc: '根据文件健康、上传策略和近期容量自动生成下一步动作。',
|
|
||||||
operationalInsightActionSettings: '调整设置',
|
|
||||||
operationalInsightActionFileQueue: '查看队列',
|
|
||||||
operationalInsightSeverity: {
|
|
||||||
danger: '高风险',
|
|
||||||
warning: '需处理',
|
|
||||||
success: '稳定',
|
|
||||||
neutral: '建议'
|
|
||||||
},
|
|
||||||
maintenanceQueueTitle: '维护队列',
|
|
||||||
maintenanceQueueDesc: '把文件健康、保留策略和上传配置聚合成可处理队列,点击进入对应视图。',
|
|
||||||
maintenanceActionable: '待处理',
|
|
||||||
maintenanceFileQueue: '文件队列',
|
|
||||||
maintenanceSettings: '配置项',
|
|
||||||
maintenanceQueueActionSettings: '调整设置',
|
|
||||||
maintenanceQueueActionFileQueue: '查看队列',
|
|
||||||
maintenanceQueueItems: {
|
|
||||||
storage_issue: {
|
|
||||||
title: '存储异常 {count} 项',
|
|
||||||
description: '优先核对缺少下载元数据的记录,确认存储配置和文件详情。'
|
|
||||||
},
|
|
||||||
expired_cleanup: {
|
|
||||||
title: '已过期 {count} 项',
|
|
||||||
description: '集中检查过期分享,删除无效记录或续期仍需保留的文件。'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '即将过期 {count} 项',
|
|
||||||
description: '这些分享将在短时间内失效,适合提前续期或通知使用方。'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '未取件 {count} 项',
|
|
||||||
description: '查看创建后尚未被取件的分享,用于识别无效上传或触达缺口。'
|
|
||||||
},
|
|
||||||
permanent_review: {
|
|
||||||
title: '永久有效 {count} 项',
|
|
||||||
description: '定期复核长期保留的分享,避免历史文件无限增长。'
|
|
||||||
},
|
|
||||||
guest_upload_retention: {
|
|
||||||
title: '游客上传缺少保留上限',
|
|
||||||
description: '游客上传已开启且没有最长保存时间,建议补充默认保留策略。'
|
|
||||||
},
|
|
||||||
chunking_disabled: {
|
|
||||||
title: '大文件体验可优化',
|
|
||||||
description: '今日容量接近单文件上限,开启分片上传可提升上传稳定性。'
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: '维护队列稳定',
|
|
||||||
description: '当前未发现需要优先处理的维护事项。'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewAllActivities: '查看全部',
|
viewAllActivities: '查看全部',
|
||||||
activityTimelineTitle: '操作时间线',
|
activityTimelineTitle: '操作时间线',
|
||||||
activityTimelineFiltered: '当前结果',
|
activityTimelineFiltered: '当前结果',
|
||||||
@@ -1108,36 +930,6 @@ export default {
|
|||||||
local_file: '本地文件',
|
local_file: '本地文件',
|
||||||
system: '系统'
|
system: '系统'
|
||||||
},
|
},
|
||||||
operationalInsights: {
|
|
||||||
storage_issue: {
|
|
||||||
title: '修复 {count} 个存储异常',
|
|
||||||
description: '部分记录缺少下载元数据,建议优先核对存储配置和文件详情。'
|
|
||||||
},
|
|
||||||
expired_cleanup: {
|
|
||||||
title: '清理 {count} 个已过期文件',
|
|
||||||
description: '已过期记录会占用管理视野,建议批量删除或续期仍需保留的文件。'
|
|
||||||
},
|
|
||||||
expiring_soon: {
|
|
||||||
title: '{count} 个文件即将过期',
|
|
||||||
description: '这些分享将在短时间内失效,适合提前续期或通知使用方。'
|
|
||||||
},
|
|
||||||
never_retrieved: {
|
|
||||||
title: '{count} 个文件未被取件',
|
|
||||||
description: '创建后长期无人取件的分享可用于判断无效上传或触达效果。'
|
|
||||||
},
|
|
||||||
guest_upload_retention: {
|
|
||||||
title: '游客上传缺少保留上限',
|
|
||||||
description: '游客上传已开启且保存时间不受限,建议设置默认保留期降低存储压力。'
|
|
||||||
},
|
|
||||||
chunking_disabled: {
|
|
||||||
title: '建议开启分片上传',
|
|
||||||
description: '今日新增容量已接近单文件上限,开启分片上传可提升大文件体验。'
|
|
||||||
},
|
|
||||||
healthy: {
|
|
||||||
title: '运行状态稳定',
|
|
||||||
description: '当前未发现优先级较高的运营风险,可继续观察文件健康趋势。'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
recentFiles: '最近分享',
|
recentFiles: '最近分享',
|
||||||
recentFilesDesc: '最近创建的分享记录,便于快速核对状态。',
|
recentFilesDesc: '最近创建的分享记录,便于快速核对状态。',
|
||||||
available: '可取件',
|
available: '可取件',
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import type {
|
|||||||
ApiResponse,
|
ApiResponse,
|
||||||
DashboardActivitiesParams,
|
DashboardActivitiesParams,
|
||||||
DashboardActivitiesResponse,
|
DashboardActivitiesResponse,
|
||||||
DashboardData,
|
DashboardData
|
||||||
DashboardMaintenanceQueue
|
|
||||||
} from '@/types'
|
} from '@/types'
|
||||||
|
|
||||||
export class StatsService {
|
export class StatsService {
|
||||||
@@ -12,10 +11,6 @@ export class StatsService {
|
|||||||
return api.get('/admin/dashboard')
|
return api.get('/admin/dashboard')
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getMaintenanceQueue(): Promise<ApiResponse<DashboardMaintenanceQueue>> {
|
|
||||||
return api.get('/admin/dashboard/maintenance-queue')
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getActivities(
|
static async getActivities(
|
||||||
params: DashboardActivitiesParams
|
params: DashboardActivitiesParams
|
||||||
): Promise<ApiResponse<DashboardActivitiesResponse>> {
|
): Promise<ApiResponse<DashboardActivitiesResponse>> {
|
||||||
|
|||||||
@@ -43,15 +43,6 @@ export interface DashboardData {
|
|||||||
recentFiles?: DashboardRecentFile[]
|
recentFiles?: DashboardRecentFile[]
|
||||||
recentActivities?: DashboardActivity[]
|
recentActivities?: DashboardActivity[]
|
||||||
recent_activities?: DashboardActivity[]
|
recent_activities?: DashboardActivity[]
|
||||||
operationalInsights?: DashboardOperationalInsight[]
|
|
||||||
operational_insights?: DashboardOperationalInsight[]
|
|
||||||
insights?: DashboardOperationalInsight[]
|
|
||||||
maintenanceQueue?: DashboardMaintenanceQueue
|
|
||||||
maintenance_queue?: DashboardMaintenanceQueue
|
|
||||||
maintenanceItems?: DashboardMaintenanceQueueItem[]
|
|
||||||
maintenance_items?: DashboardMaintenanceQueueItem[]
|
|
||||||
maintenanceSummary?: Partial<DashboardMaintenanceQueueSummary>
|
|
||||||
maintenance_summary?: Partial<DashboardMaintenanceQueueSummary>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardSuffixStat {
|
export interface DashboardSuffixStat {
|
||||||
@@ -129,99 +120,6 @@ export interface DashboardActivitiesResponse {
|
|||||||
target_type_options?: DashboardActivityOption[]
|
target_type_options?: DashboardActivityOption[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DashboardInsightSeverity = 'danger' | 'warning' | 'success' | 'neutral'
|
|
||||||
|
|
||||||
export interface DashboardMaintenanceQueueAction {
|
|
||||||
type?: string
|
|
||||||
actionType?: string
|
|
||||||
action_type?: string
|
|
||||||
health?: AdminFileHealthFilter | string | null
|
|
||||||
targetHealth?: AdminFileHealthFilter | string | null
|
|
||||||
target_health?: AdminFileHealthFilter | string | null
|
|
||||||
suggestedAction?: string
|
|
||||||
suggested_action?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardMaintenanceQueueItem {
|
|
||||||
key: string
|
|
||||||
severity: DashboardInsightSeverity | string
|
|
||||||
category?: string
|
|
||||||
priority?: number
|
|
||||||
count: number
|
|
||||||
action?: DashboardMaintenanceQueueAction
|
|
||||||
actionType?: string
|
|
||||||
action_type?: string
|
|
||||||
suggestedAction?: string
|
|
||||||
suggested_action?: string
|
|
||||||
targetHealth?: AdminFileHealthFilter | string | null
|
|
||||||
target_health?: AdminFileHealthFilter | string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardMaintenanceQueueSummary {
|
|
||||||
total: number
|
|
||||||
actionableCount: number
|
|
||||||
actionable_count?: number
|
|
||||||
dangerCount: number
|
|
||||||
danger_count?: number
|
|
||||||
warningCount: number
|
|
||||||
warning_count?: number
|
|
||||||
successCount: number
|
|
||||||
success_count?: number
|
|
||||||
neutralCount: number
|
|
||||||
neutral_count?: number
|
|
||||||
fileQueueCount: number
|
|
||||||
file_queue_count?: number
|
|
||||||
settingsCount: number
|
|
||||||
settings_count?: number
|
|
||||||
strongestSeverity: DashboardInsightSeverity
|
|
||||||
strongest_severity?: DashboardInsightSeverity
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardMaintenanceQueue {
|
|
||||||
items?: DashboardMaintenanceQueueItem[]
|
|
||||||
maintenanceItems?: DashboardMaintenanceQueueItem[]
|
|
||||||
maintenance_items?: DashboardMaintenanceQueueItem[]
|
|
||||||
summary?: Partial<DashboardMaintenanceQueueSummary>
|
|
||||||
maintenanceSummary?: Partial<DashboardMaintenanceQueueSummary>
|
|
||||||
maintenance_summary?: Partial<DashboardMaintenanceQueueSummary>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardMaintenanceQueueViewItem extends DashboardMaintenanceQueueItem {
|
|
||||||
severity: DashboardInsightSeverity
|
|
||||||
priority: number
|
|
||||||
actionTypeValue: string
|
|
||||||
suggestedActionValue: string
|
|
||||||
targetHealthValue: AdminFileHealthFilter | string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardOperationalInsightAction {
|
|
||||||
type?: string
|
|
||||||
actionType?: string
|
|
||||||
action_type?: string
|
|
||||||
health?: AdminFileHealthFilter | string | null
|
|
||||||
targetHealth?: AdminFileHealthFilter | string | null
|
|
||||||
target_health?: AdminFileHealthFilter | string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardOperationalInsight {
|
|
||||||
key: string
|
|
||||||
severity: DashboardInsightSeverity | string
|
|
||||||
priority?: number
|
|
||||||
count: number
|
|
||||||
action?: DashboardOperationalInsightAction
|
|
||||||
actionType?: string
|
|
||||||
action_type?: string
|
|
||||||
targetHealth?: AdminFileHealthFilter | string | null
|
|
||||||
target_health?: AdminFileHealthFilter | string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardOperationalInsightViewItem extends DashboardOperationalInsight {
|
|
||||||
severity: DashboardInsightSeverity
|
|
||||||
priority: number
|
|
||||||
actionTypeValue: string
|
|
||||||
targetHealthValue: AdminFileHealthFilter | string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DashboardViewData = Omit<
|
export type DashboardViewData = Omit<
|
||||||
DashboardData,
|
DashboardData,
|
||||||
| keyof DashboardHealthSummary
|
| keyof DashboardHealthSummary
|
||||||
@@ -240,15 +138,6 @@ export type DashboardViewData = Omit<
|
|||||||
| 'recentFiles'
|
| 'recentFiles'
|
||||||
| 'recentActivities'
|
| 'recentActivities'
|
||||||
| 'recent_activities'
|
| 'recent_activities'
|
||||||
| 'operationalInsights'
|
|
||||||
| 'operational_insights'
|
|
||||||
| 'insights'
|
|
||||||
| 'maintenanceQueue'
|
|
||||||
| 'maintenance_queue'
|
|
||||||
| 'maintenanceItems'
|
|
||||||
| 'maintenance_items'
|
|
||||||
| 'maintenanceSummary'
|
|
||||||
| 'maintenance_summary'
|
|
||||||
| 'storageUsed'
|
| 'storageUsed'
|
||||||
| 'yesterdaySize'
|
| 'yesterdaySize'
|
||||||
| 'todaySize'
|
| 'todaySize'
|
||||||
@@ -269,9 +158,6 @@ export type DashboardViewData = Omit<
|
|||||||
topSuffixes: DashboardSuffixStat[]
|
topSuffixes: DashboardSuffixStat[]
|
||||||
recentFiles: DashboardRecentFile[]
|
recentFiles: DashboardRecentFile[]
|
||||||
recentActivities: DashboardActivityViewItem[]
|
recentActivities: DashboardActivityViewItem[]
|
||||||
operationalInsights: DashboardOperationalInsightViewItem[]
|
|
||||||
maintenanceItems: DashboardMaintenanceQueueViewItem[]
|
|
||||||
maintenanceSummary: DashboardMaintenanceQueueSummary
|
|
||||||
storageUsed: number
|
storageUsed: number
|
||||||
yesterdaySize: number
|
yesterdaySize: number
|
||||||
todaySize: number
|
todaySize: number
|
||||||
|
|||||||
@@ -102,64 +102,6 @@ export interface AdminFileListParams {
|
|||||||
sortOrder?: AdminFileSortOrder
|
sortOrder?: AdminFileSortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminFileViewSummaryItem {
|
|
||||||
key: string
|
|
||||||
severity: AdminFileInsightSeverity | string
|
|
||||||
priority?: number
|
|
||||||
count: number
|
|
||||||
actionType?: string
|
|
||||||
action_type?: string
|
|
||||||
health?: AdminFileHealthFilter | string
|
|
||||||
targetHealth?: AdminFileHealthFilter | string
|
|
||||||
target_health?: AdminFileHealthFilter | string
|
|
||||||
suggestedAction?: string
|
|
||||||
suggested_action?: string
|
|
||||||
sourceKey?: string
|
|
||||||
source_key?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminFileViewSummary {
|
|
||||||
total?: number
|
|
||||||
filteredTotal?: number
|
|
||||||
filtered_total?: number
|
|
||||||
allTotal?: number
|
|
||||||
all_total?: number
|
|
||||||
activeFilterCount?: number
|
|
||||||
active_filter_count?: number
|
|
||||||
hasFilters?: boolean
|
|
||||||
has_filters?: boolean
|
|
||||||
strongestSeverity?: AdminFileInsightSeverity | string
|
|
||||||
strongest_severity?: AdminFileInsightSeverity | string
|
|
||||||
filters?: Record<string, unknown>
|
|
||||||
summary?: Partial<AdminFileSummary>
|
|
||||||
healthSummary?: Partial<AdminFileSummary>
|
|
||||||
health_summary?: Partial<AdminFileSummary>
|
|
||||||
cards?: AdminFileViewSummaryItem[]
|
|
||||||
items?: AdminFileViewSummaryItem[]
|
|
||||||
actions?: AdminFileViewSummaryItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminFileViewSummaryViewItem extends AdminFileViewSummaryItem {
|
|
||||||
severity: AdminFileInsightSeverity
|
|
||||||
priority: number
|
|
||||||
actionTypeValue: string
|
|
||||||
sourceKeyValue: string
|
|
||||||
suggestedActionValue: string
|
|
||||||
targetHealthValue: AdminFileHealthFilter | string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminFileViewSummaryViewData {
|
|
||||||
total: number
|
|
||||||
filteredTotal: number
|
|
||||||
allTotal: number
|
|
||||||
activeFilterCount: number
|
|
||||||
hasFilters: boolean
|
|
||||||
strongestSeverity: AdminFileInsightSeverity
|
|
||||||
summary: AdminFileSummary
|
|
||||||
cards: AdminFileViewSummaryViewItem[]
|
|
||||||
actions: AdminFileViewSummaryViewItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminFileViewPresetParams {
|
export interface AdminFileViewPresetParams {
|
||||||
keyword: string
|
keyword: string
|
||||||
status: AdminFileStatusFilter
|
status: AdminFileStatusFilter
|
||||||
@@ -247,12 +189,6 @@ export interface FileListResponse {
|
|||||||
page: number
|
page: number
|
||||||
size: number
|
size: number
|
||||||
summary?: AdminFileSummary
|
summary?: AdminFileSummary
|
||||||
viewSummary?: AdminFileViewSummary
|
|
||||||
view_summary?: AdminFileViewSummary
|
|
||||||
currentViewSummary?: AdminFileViewSummary
|
|
||||||
current_view_summary?: AdminFileViewSummary
|
|
||||||
actionSummary?: AdminFileViewSummary
|
|
||||||
action_summary?: AdminFileViewSummary
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminFilePreviewResponse {
|
export interface AdminFilePreviewResponse {
|
||||||
|
|||||||
@@ -84,130 +84,6 @@
|
|||||||
</StatCard>
|
</StatCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section
|
|
||||||
v-if="dashboardData.hasExtendedStats && dashboardData.operationalInsights.length > 0"
|
|
||||||
class="mt-6"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-semibold" :class="[primaryTextClass]">
|
|
||||||
{{ t('admin.dashboard.operationalInsightsTitle') }}
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm" :class="[mutedTextClass]">
|
|
||||||
{{ t('admin.dashboard.operationalInsightsDesc') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<LightbulbIcon
|
|
||||||
class="hidden h-5 w-5 sm:block"
|
|
||||||
:class="[isDarkMode ? 'text-amber-300' : 'text-amber-500']"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4 grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
||||||
<button
|
|
||||||
v-for="insight in dashboardData.operationalInsights"
|
|
||||||
:key="insight.key"
|
|
||||||
type="button"
|
|
||||||
class="group flex min-h-40 flex-col rounded-lg border p-4 text-left transition-colors"
|
|
||||||
:class="getOperationalInsightClass(insight.severity)"
|
|
||||||
@click="openOperationalInsight(insight)"
|
|
||||||
>
|
|
||||||
<span class="flex items-start justify-between gap-3">
|
|
||||||
<span class="rounded-full px-2.5 py-1 text-xs font-medium ring-1 ring-current/15">
|
|
||||||
{{ getOperationalInsightSeverityLabel(insight.severity) }}
|
|
||||||
</span>
|
|
||||||
<component :is="getOperationalInsightIcon(insight.severity)" class="h-5 w-5 shrink-0" />
|
|
||||||
</span>
|
|
||||||
<strong class="mt-4 text-base">
|
|
||||||
{{ getOperationalInsightTitle(insight) }}
|
|
||||||
</strong>
|
|
||||||
<span class="mt-2 line-clamp-3 text-sm opacity-80">
|
|
||||||
{{ getOperationalInsightDescription(insight) }}
|
|
||||||
</span>
|
|
||||||
<span class="mt-auto flex items-center justify-between gap-2 pt-4 text-sm font-medium">
|
|
||||||
<span>{{ getOperationalInsightActionLabel(insight) }}</span>
|
|
||||||
<ArrowRightIcon
|
|
||||||
class="h-4 w-4 shrink-0 transition-transform group-hover:translate-x-0.5"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section
|
|
||||||
v-if="dashboardData.hasExtendedStats && dashboardData.maintenanceItems.length > 0"
|
|
||||||
class="mt-6 rounded-lg p-5 shadow-sm"
|
|
||||||
:class="[panelClass]"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-semibold" :class="[primaryTextClass]">
|
|
||||||
{{ t('admin.dashboard.maintenanceQueueTitle') }}
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm" :class="[mutedTextClass]">
|
|
||||||
{{ t('admin.dashboard.maintenanceQueueDesc') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-2 sm:min-w-[24rem]">
|
|
||||||
<div class="rounded-lg border px-3 py-2" :class="[subtlePanelClass]">
|
|
||||||
<p class="text-xs" :class="[mutedTextClass]">
|
|
||||||
{{ t('admin.dashboard.maintenanceActionable') }}
|
|
||||||
</p>
|
|
||||||
<strong class="mt-1 block text-xl" :class="[primaryTextClass]">
|
|
||||||
{{ dashboardData.maintenanceSummary.actionableCount }}
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-lg border px-3 py-2" :class="[subtlePanelClass]">
|
|
||||||
<p class="text-xs" :class="[mutedTextClass]">
|
|
||||||
{{ t('admin.dashboard.maintenanceFileQueue') }}
|
|
||||||
</p>
|
|
||||||
<strong class="mt-1 block text-xl" :class="[primaryTextClass]">
|
|
||||||
{{ dashboardData.maintenanceSummary.fileQueueCount }}
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-lg border px-3 py-2" :class="[subtlePanelClass]">
|
|
||||||
<p class="text-xs" :class="[mutedTextClass]">
|
|
||||||
{{ t('admin.dashboard.maintenanceSettings') }}
|
|
||||||
</p>
|
|
||||||
<strong class="mt-1 block text-xl" :class="[primaryTextClass]">
|
|
||||||
{{ dashboardData.maintenanceSummary.settingsCount }}
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4 grid grid-cols-1 gap-3 lg:grid-cols-2 xl:grid-cols-3">
|
|
||||||
<button
|
|
||||||
v-for="item in dashboardData.maintenanceItems"
|
|
||||||
:key="item.key"
|
|
||||||
type="button"
|
|
||||||
class="group flex min-h-36 flex-col rounded-lg border p-4 text-left transition-colors"
|
|
||||||
:class="getMaintenanceQueueClass(item.severity)"
|
|
||||||
@click="openMaintenanceQueueItem(item)"
|
|
||||||
>
|
|
||||||
<span class="flex items-start justify-between gap-3">
|
|
||||||
<span class="rounded-full px-2.5 py-1 text-xs font-medium ring-1 ring-current/15">
|
|
||||||
{{ getOperationalInsightSeverityLabel(item.severity) }}
|
|
||||||
</span>
|
|
||||||
<component :is="getMaintenanceQueueIcon(item.severity)" class="h-5 w-5 shrink-0" />
|
|
||||||
</span>
|
|
||||||
<strong class="mt-3 text-base">
|
|
||||||
{{ getMaintenanceQueueTitle(item) }}
|
|
||||||
</strong>
|
|
||||||
<span class="mt-2 line-clamp-2 text-sm opacity-80">
|
|
||||||
{{ getMaintenanceQueueDescription(item) }}
|
|
||||||
</span>
|
|
||||||
<span class="mt-auto flex items-center justify-between gap-2 pt-4 text-sm font-medium">
|
|
||||||
<span>{{ getMaintenanceQueueActionLabel(item) }}</span>
|
|
||||||
<ArrowRightIcon
|
|
||||||
class="h-4 w-4 shrink-0 transition-transform group-hover:translate-x-0.5"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div v-if="dashboardData.hasExtendedStats" class="mt-6 grid grid-cols-1 gap-6 xl:grid-cols-3">
|
<div v-if="dashboardData.hasExtendedStats" class="mt-6 grid grid-cols-1 gap-6 xl:grid-cols-3">
|
||||||
<section class="xl:col-span-2 rounded-lg p-5 shadow-sm" :class="[panelClass]">
|
<section class="xl:col-span-2 rounded-lg p-5 shadow-sm" :class="[panelClass]">
|
||||||
<div class="mb-5 flex items-center justify-between">
|
<div class="mb-5 flex items-center justify-between">
|
||||||
@@ -793,7 +669,6 @@ import {
|
|||||||
FileTextIcon,
|
FileTextIcon,
|
||||||
HardDriveIcon,
|
HardDriveIcon,
|
||||||
HistoryIcon,
|
HistoryIcon,
|
||||||
LightbulbIcon,
|
|
||||||
ListFilterIcon,
|
ListFilterIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
RefreshCwIcon,
|
RefreshCwIcon,
|
||||||
@@ -809,13 +684,7 @@ import { useDashboardStats, useInjectedDarkMode } from '@/composables'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { formatFileSize, formatTimestamp } from '@/utils/common'
|
import { formatFileSize, formatTimestamp } from '@/utils/common'
|
||||||
import { ROUTES } from '@/constants'
|
import { ROUTES } from '@/constants'
|
||||||
import type {
|
import type { DashboardActivityViewItem, DashboardHealthAction } from '@/types'
|
||||||
DashboardActivityViewItem,
|
|
||||||
DashboardHealthAction,
|
|
||||||
DashboardInsightSeverity,
|
|
||||||
DashboardMaintenanceQueueViewItem,
|
|
||||||
DashboardOperationalInsightViewItem
|
|
||||||
} from '@/types'
|
|
||||||
|
|
||||||
const isDarkMode = useInjectedDarkMode()
|
const isDarkMode = useInjectedDarkMode()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -927,102 +796,6 @@ const healthActions = computed<DashboardHealthAction[]>(() => [
|
|||||||
|
|
||||||
const getSuffixRatio = (count: number) => Math.round((count / maxSuffixCount.value) * 100)
|
const getSuffixRatio = (count: number) => Math.round((count / maxSuffixCount.value) * 100)
|
||||||
|
|
||||||
const operationalInsightIconMap: Record<DashboardInsightSeverity, Component> = {
|
|
||||||
danger: AlertTriangleIcon,
|
|
||||||
warning: ClockIcon,
|
|
||||||
success: CheckCircleIcon,
|
|
||||||
neutral: ShieldCheckIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOperationalInsightIcon = (severity: DashboardInsightSeverity) =>
|
|
||||||
operationalInsightIconMap[severity]
|
|
||||||
|
|
||||||
const getOperationalInsightClass = (severity: DashboardInsightSeverity) => {
|
|
||||||
const darkClasses: Record<DashboardInsightSeverity, string> = {
|
|
||||||
danger: 'border-red-500/25 bg-red-500/10 text-red-100 hover:border-red-400/60',
|
|
||||||
warning: 'border-amber-500/25 bg-amber-500/10 text-amber-100 hover:border-amber-400/60',
|
|
||||||
success: 'border-emerald-500/25 bg-emerald-500/10 text-emerald-100 hover:border-emerald-400/60',
|
|
||||||
neutral: 'border-gray-700 bg-gray-800/70 text-gray-100 hover:border-indigo-500/50'
|
|
||||||
}
|
|
||||||
const lightClasses: Record<DashboardInsightSeverity, string> = {
|
|
||||||
danger: 'border-red-100 bg-red-50 text-red-800 hover:border-red-300',
|
|
||||||
warning: 'border-amber-100 bg-amber-50 text-amber-800 hover:border-amber-300',
|
|
||||||
success: 'border-emerald-100 bg-emerald-50 text-emerald-800 hover:border-emerald-300',
|
|
||||||
neutral: 'border-gray-100 bg-white text-gray-800 shadow-sm hover:border-indigo-200'
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDarkMode.value ? darkClasses[severity] : lightClasses[severity]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOperationalInsightSeverityLabel = (severity: DashboardInsightSeverity) =>
|
|
||||||
t(`admin.dashboard.operationalInsightSeverity.${severity}`)
|
|
||||||
|
|
||||||
const getOperationalInsightTitle = (insight: DashboardOperationalInsightViewItem) => {
|
|
||||||
const key = `admin.dashboard.operationalInsights.${insight.key}.title`
|
|
||||||
const title = t(key, { count: insight.count })
|
|
||||||
return title === key ? insight.key : title
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOperationalInsightDescription = (insight: DashboardOperationalInsightViewItem) => {
|
|
||||||
const key = `admin.dashboard.operationalInsights.${insight.key}.description`
|
|
||||||
const description = t(key, { count: insight.count })
|
|
||||||
return description === key ? insight.key : description
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOperationalInsightActionLabel = (insight: DashboardOperationalInsightViewItem) =>
|
|
||||||
insight.actionTypeValue === 'settings'
|
|
||||||
? t('admin.dashboard.operationalInsightActionSettings')
|
|
||||||
: t('admin.dashboard.operationalInsightActionFileQueue')
|
|
||||||
|
|
||||||
const openOperationalInsight = (insight: DashboardOperationalInsightViewItem) => {
|
|
||||||
if (insight.actionTypeValue === 'settings') {
|
|
||||||
void router.push({ path: ROUTES.SETTINGS })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const health = insight.targetHealthValue
|
|
||||||
void router.push({
|
|
||||||
path: ROUTES.FILE_MANAGE,
|
|
||||||
query: health ? { health } : {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMaintenanceQueueIcon = (severity: DashboardInsightSeverity) =>
|
|
||||||
operationalInsightIconMap[severity]
|
|
||||||
|
|
||||||
const getMaintenanceQueueClass = (severity: DashboardInsightSeverity) =>
|
|
||||||
getOperationalInsightClass(severity)
|
|
||||||
|
|
||||||
const getMaintenanceQueueTitle = (item: DashboardMaintenanceQueueViewItem) => {
|
|
||||||
const key = `admin.dashboard.maintenanceQueueItems.${item.key}.title`
|
|
||||||
const title = t(key, { count: item.count })
|
|
||||||
return title === key ? item.key : title
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMaintenanceQueueDescription = (item: DashboardMaintenanceQueueViewItem) => {
|
|
||||||
const key = `admin.dashboard.maintenanceQueueItems.${item.key}.description`
|
|
||||||
const description = t(key, { count: item.count })
|
|
||||||
return description === key ? item.key : description
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMaintenanceQueueActionLabel = (item: DashboardMaintenanceQueueViewItem) =>
|
|
||||||
item.actionTypeValue === 'settings'
|
|
||||||
? t('admin.dashboard.maintenanceQueueActionSettings')
|
|
||||||
: t('admin.dashboard.maintenanceQueueActionFileQueue')
|
|
||||||
|
|
||||||
const openMaintenanceQueueItem = (item: DashboardMaintenanceQueueViewItem) => {
|
|
||||||
if (item.actionTypeValue === 'settings') {
|
|
||||||
void router.push({ path: ROUTES.SETTINGS })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const health = item.targetHealthValue
|
|
||||||
void router.push({
|
|
||||||
path: ROUTES.FILE_MANAGE,
|
|
||||||
query: health ? { health } : {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatCreatedAt = (value: string | null) => {
|
const formatCreatedAt = (value: string | null) => {
|
||||||
if (!value) return '-'
|
if (!value) return '-'
|
||||||
return formatTimestamp(value, 'datetime')
|
return formatTimestamp(value, 'datetime')
|
||||||
|
|||||||
@@ -51,83 +51,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="mb-6 rounded-lg border p-4" :class="[panelClass]">
|
|
||||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
|
||||||
<div class="min-w-0">
|
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
|
||||||
<div
|
|
||||||
class="rounded-lg p-2"
|
|
||||||
:class="isDarkMode ? 'bg-blue-500/10 text-blue-300' : 'bg-blue-50 text-blue-600'"
|
|
||||||
>
|
|
||||||
<ShieldCheckIcon class="h-5 w-5" />
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0">
|
|
||||||
<h3 class="text-base font-semibold" :class="[primaryTextClass]">
|
|
||||||
{{ t('fileManage.viewSummary.title') }}
|
|
||||||
</h3>
|
|
||||||
<p class="mt-1 text-sm" :class="[mutedTextClass]">
|
|
||||||
{{ t('fileManage.viewSummary.description') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-2 sm:min-w-[360px]">
|
|
||||||
<div
|
|
||||||
v-for="metric in viewSummaryMetrics"
|
|
||||||
:key="metric.label"
|
|
||||||
class="rounded-lg border px-3 py-2"
|
|
||||||
:class="[subtleSectionClass]"
|
|
||||||
>
|
|
||||||
<p class="text-xs" :class="[mutedTextClass]">{{ metric.label }}</p>
|
|
||||||
<p class="mt-1 text-lg font-semibold" :class="[primaryTextClass]">
|
|
||||||
{{ metric.value }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4 grid grid-cols-1 gap-3 lg:grid-cols-2 xl:grid-cols-4">
|
|
||||||
<button
|
|
||||||
v-for="item in viewSummary.cards"
|
|
||||||
:key="item.key"
|
|
||||||
type="button"
|
|
||||||
class="group flex min-h-28 items-start gap-3 rounded-lg border px-4 py-3 text-left transition-colors"
|
|
||||||
:class="getViewSummaryItemClass(item)"
|
|
||||||
@click="openViewSummaryItem(item)"
|
|
||||||
>
|
|
||||||
<span class="rounded-lg p-2" :class="getViewSummaryIconClass(item)">
|
|
||||||
<component :is="getViewSummaryIcon(item)" class="h-4 w-4" />
|
|
||||||
</span>
|
|
||||||
<span class="min-w-0 flex-1">
|
|
||||||
<span class="flex items-start justify-between gap-2">
|
|
||||||
<span class="text-sm font-semibold" :class="[primaryTextClass]">
|
|
||||||
{{ getViewSummaryTitle(item) }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium"
|
|
||||||
:class="getInsightBadgeClass(item.severity)"
|
|
||||||
>
|
|
||||||
{{ getViewSummarySeverityLabel(item) }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="mt-1 block text-xs leading-5" :class="[mutedTextClass]">
|
|
||||||
{{ getViewSummaryDescription(item) }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="mt-3 inline-flex items-center text-xs font-medium"
|
|
||||||
:class="[isDarkMode ? 'text-blue-300' : 'text-blue-600']"
|
|
||||||
>
|
|
||||||
{{ getViewSummaryActionLabel(item) }}
|
|
||||||
<ChevronRightIcon
|
|
||||||
class="ml-1 h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="mb-6 rounded-lg border p-4" :class="[panelClass]">
|
<section class="mb-6 rounded-lg border p-4" :class="[panelClass]">
|
||||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
<div class="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||||
<div class="flex min-w-0 flex-1 flex-col gap-3 md:flex-row">
|
<div class="flex min-w-0 flex-1 flex-col gap-3 md:flex-row">
|
||||||
@@ -1250,16 +1173,12 @@ import type {
|
|||||||
AdminFileSortOrder,
|
AdminFileSortOrder,
|
||||||
AdminFileStatusFilter,
|
AdminFileStatusFilter,
|
||||||
AdminFileTypeFilter,
|
AdminFileTypeFilter,
|
||||||
AdminFileViewSummaryViewItem,
|
|
||||||
AdminFileViewItem
|
AdminFileViewItem
|
||||||
} from '@/types'
|
} from '@/types'
|
||||||
import {
|
import {
|
||||||
ActivityIcon,
|
ActivityIcon,
|
||||||
AlertTriangleIcon,
|
|
||||||
ArchiveIcon,
|
ArchiveIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
CheckCircleIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
@@ -1273,7 +1192,6 @@ import {
|
|||||||
RefreshCwIcon,
|
RefreshCwIcon,
|
||||||
RotateCcwIcon,
|
RotateCcwIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
ShieldCheckIcon,
|
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
XIcon
|
XIcon
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
@@ -1337,7 +1255,6 @@ const {
|
|||||||
selectedViewPresetId,
|
selectedViewPresetId,
|
||||||
storageUsedText,
|
storageUsedText,
|
||||||
summary,
|
summary,
|
||||||
viewSummary,
|
|
||||||
showBatchEditModal,
|
showBatchEditModal,
|
||||||
showEditModal,
|
showEditModal,
|
||||||
showFileDetailModal,
|
showFileDetailModal,
|
||||||
@@ -1567,21 +1484,6 @@ const summaryCards = computed(() => [
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const viewSummaryMetrics = computed(() => [
|
|
||||||
{
|
|
||||||
label: t('fileManage.viewSummary.filteredTotal'),
|
|
||||||
value: viewSummary.value.filteredTotal
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('fileManage.viewSummary.allTotal'),
|
|
||||||
value: viewSummary.value.allTotal
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('fileManage.viewSummary.activeFilters'),
|
|
||||||
value: viewSummary.value.activeFilterCount
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const statusFilterOptions = computed<{ value: AdminFileStatusFilter; label: string }[]>(() => [
|
const statusFilterOptions = computed<{ value: AdminFileStatusFilter; label: string }[]>(() => [
|
||||||
{ value: 'all', label: t('fileManage.all') },
|
{ value: 'all', label: t('fileManage.all') },
|
||||||
{ value: 'active', label: t('fileManage.active') },
|
{ value: 'active', label: t('fileManage.active') },
|
||||||
@@ -1622,78 +1524,10 @@ const healthFilterValues: AdminFileHealthFilter[] = [
|
|||||||
'permanent'
|
'permanent'
|
||||||
]
|
]
|
||||||
|
|
||||||
const isHealthFilterValue = (value: string): value is AdminFileHealthFilter =>
|
|
||||||
healthFilterValues.includes(value as AdminFileHealthFilter)
|
|
||||||
|
|
||||||
const getViewSummaryItemKey = (item: AdminFileViewSummaryViewItem) =>
|
|
||||||
item.sourceKeyValue || item.key
|
|
||||||
|
|
||||||
const getViewSummaryTitle = (item: AdminFileViewSummaryViewItem) =>
|
|
||||||
t(`fileManage.viewSummary.items.${getViewSummaryItemKey(item)}.title`, {
|
|
||||||
count: item.count
|
|
||||||
})
|
|
||||||
|
|
||||||
const getViewSummaryDescription = (item: AdminFileViewSummaryViewItem) =>
|
|
||||||
t(`fileManage.viewSummary.items.${getViewSummaryItemKey(item)}.description`, {
|
|
||||||
count: item.count
|
|
||||||
})
|
|
||||||
|
|
||||||
const getViewSummaryActionLabel = (item: AdminFileViewSummaryViewItem) =>
|
|
||||||
item.targetHealthValue === 'all'
|
|
||||||
? t('fileManage.viewSummary.reset')
|
|
||||||
: t('fileManage.viewSummary.action')
|
|
||||||
|
|
||||||
const getViewSummarySeverityLabel = (item: AdminFileViewSummaryViewItem) =>
|
|
||||||
t(`fileManage.viewSummary.severity.${item.severity}`)
|
|
||||||
|
|
||||||
const getViewSummaryIcon = (item: AdminFileViewSummaryViewItem) => {
|
|
||||||
if (item.severity === 'success') return CheckCircleIcon
|
|
||||||
if (item.severity === 'danger' || item.severity === 'warning') return AlertTriangleIcon
|
|
||||||
return ShieldCheckIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
const getViewSummaryItemClass = (item: AdminFileViewSummaryViewItem) => {
|
|
||||||
const darkClasses: Record<AdminFileInsightSeverity, string> = {
|
|
||||||
success: 'border-emerald-500/20 bg-emerald-500/10 hover:border-emerald-500/40',
|
|
||||||
warning: 'border-amber-500/20 bg-amber-500/10 hover:border-amber-500/40',
|
|
||||||
danger: 'border-red-500/20 bg-red-500/10 hover:border-red-500/40',
|
|
||||||
info: 'border-blue-500/20 bg-blue-500/10 hover:border-blue-500/40',
|
|
||||||
neutral: 'border-gray-700 bg-gray-700/30 hover:border-gray-600'
|
|
||||||
}
|
|
||||||
const lightClasses: Record<AdminFileInsightSeverity, string> = {
|
|
||||||
success: 'border-emerald-100 bg-emerald-50 hover:border-emerald-200',
|
|
||||||
warning: 'border-amber-100 bg-amber-50 hover:border-amber-200',
|
|
||||||
danger: 'border-red-100 bg-red-50 hover:border-red-200',
|
|
||||||
info: 'border-blue-100 bg-blue-50 hover:border-blue-200',
|
|
||||||
neutral: 'border-gray-200 bg-gray-50 hover:border-gray-300'
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDarkMode.value ? darkClasses[item.severity] : lightClasses[item.severity]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getViewSummaryIconClass = (item: AdminFileViewSummaryViewItem) => {
|
|
||||||
const darkClasses: Record<AdminFileInsightSeverity, string> = {
|
|
||||||
success: 'bg-emerald-900/40 text-emerald-200',
|
|
||||||
warning: 'bg-amber-900/40 text-amber-200',
|
|
||||||
danger: 'bg-red-900/40 text-red-200',
|
|
||||||
info: 'bg-blue-900/40 text-blue-200',
|
|
||||||
neutral: 'bg-gray-800 text-gray-300'
|
|
||||||
}
|
|
||||||
const lightClasses: Record<AdminFileInsightSeverity, string> = {
|
|
||||||
success: 'bg-emerald-100 text-emerald-700',
|
|
||||||
warning: 'bg-amber-100 text-amber-700',
|
|
||||||
danger: 'bg-red-100 text-red-700',
|
|
||||||
info: 'bg-blue-100 text-blue-700',
|
|
||||||
neutral: 'bg-gray-100 text-gray-700'
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDarkMode.value ? darkClasses[item.severity] : lightClasses[item.severity]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getRouteHealthFilter = (): AdminFileHealthFilter => {
|
const getRouteHealthFilter = (): AdminFileHealthFilter => {
|
||||||
const health = route.query.health
|
const health = route.query.health
|
||||||
if (typeof health === 'string' && isHealthFilterValue(health)) {
|
if (typeof health === 'string' && healthFilterValues.includes(health as AdminFileHealthFilter)) {
|
||||||
return health
|
return health as AdminFileHealthFilter
|
||||||
}
|
}
|
||||||
return 'all'
|
return 'all'
|
||||||
}
|
}
|
||||||
@@ -1718,16 +1552,6 @@ const handleResetFilters = async () => {
|
|||||||
await syncHealthFilterQuery('all')
|
await syncHealthFilterQuery('all')
|
||||||
}
|
}
|
||||||
|
|
||||||
const openViewSummaryItem = async (item: AdminFileViewSummaryViewItem) => {
|
|
||||||
const health = String(item.targetHealthValue || 'all')
|
|
||||||
if (health !== 'all' && isHealthFilterValue(health)) {
|
|
||||||
await handleHealthFilterChange(health)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await handleResetFilters()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleViewPresetChange = async (event: Event) => {
|
const handleViewPresetChange = async (event: Event) => {
|
||||||
const target = event.target as HTMLSelectElement
|
const target = event.target as HTMLSelectElement
|
||||||
if (!target.value) {
|
if (!target.value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user