From 237913468c592e2d1a33a663289c43e4eac6e392 Mon Sep 17 00:00:00 2001 From: WangDL Date: Sun, 24 May 2026 13:12:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20M2-04=20admin=20=E2=80=94=20ImportMonit?= =?UTF-8?q?or=20page=20with=20detail=20drawer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- src/App.tsx | 3 +- src/pages/ImportMonitor.tsx | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/pages/ImportMonitor.tsx diff --git a/src/App.tsx b/src/App.tsx index 16ad91c..cda76bc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,6 +29,7 @@ const AuditLogPage = lazy(() => import("./pages/AuditLog")) const Dashboard = lazy(() => import('./pages/Dashboard')) const UserManagement = lazy(() => import('./pages/UserManagement')) const MemberManagement = lazy(() => import('./pages/MemberManagement')) +const ImportMonitorPage = lazy(() => import('./pages/ImportMonitor')) const HermesSettings = lazy(() => import('./pages/HermesSettings')) const TaskAssistant = lazy(() => import('./pages/TaskAssistant')) const Placeholder = lazy(() => import('./pages/Placeholder')) @@ -99,7 +100,7 @@ function App() { /> }>} /> } /> - } /> + }>} /> }>} /> (null) + + const { data, isLoading } = useQuery({ + queryKey: ['admin-imports'], + queryFn: (): Promise => api.get('/admin-api/imports?limit=50'), + staleTime: 10_000, + }) + + const { data: detail } = useQuery({ + queryKey: ['admin-imports', selectedJob?.id], + queryFn: (): Promise => api.get(`/admin-api/imports/${selectedJob?.id}`), + enabled: !!selectedJob?.id, + }) + + const handleRetry = async (id: string) => { + await api.post(`/admin-api/imports/${id}/retry`) + message.success('已重新入队') + qc.invalidateQueries({ queryKey: ['admin-imports'] }) + } + + const statusColor: Record = { + QUEUED: 'blue', PROCESSING: 'processing', COMPLETED: 'green', FAILED: 'red', CANCELLED: 'default', + } + + const columns = [ + { title: '名称', dataIndex: 'sourceName', width: 180, ellipsis: true }, + { title: '类型', dataIndex: 'sourceType', width: 80 }, + { title: '状态', dataIndex: 'status', width: 90, render: (s: string) => {s} }, + { title: '进度', dataIndex: 'progress', width: 70, render: (v: number) => `${v || 0}%` }, + { title: '重试', dataIndex: 'retryCount', width: 60, align: 'center' as const }, + { title: '步骤', dataIndex: 'step', width: 100, ellipsis: true }, + { title: '错误', dataIndex: 'errorMessage', width: 150, ellipsis: true, render: (v: string) => v ? {v} : '-' }, + { title: '时间', dataIndex: 'createdAt', width: 130, render: (d: string) => dayjs(d).format('MM-DD HH:mm') }, + { + title: '操作', width: 100, + render: (_: any, r: any) => ( + + + {r.status === 'FAILED' && } + + ), + }, + ] + + const stepCols = [ + { title: '步骤', dataIndex: 'step', width: 120 }, + { title: '状态', dataIndex: 'status', width: 80, render: (s: string) => {s} }, + { title: '详情', dataIndex: 'detail', ellipsis: true }, + { title: '开始', dataIndex: 'startedAt', width: 130, render: (d: string) => d ? dayjs(d).format('HH:mm:ss') : '-' }, + { title: '完成', dataIndex: 'completedAt', width: 130, render: (d: string) => d ? dayjs(d).format('HH:mm:ss') : '-' }, + ] + + return ( +
+
+ <ImportOutlined /> 文档导入 + +
+ + + setDrawerOpen(false)} width={700}> + {detail && ( + <> + Job: {detail.job?.id} + 状态: {detail.job?.status} + 进度: {detail.job?.progress || 0}% + 重试: {detail.job?.retryCount}/{detail.job?.maxRetries} + {detail.job?.errorMessage &&
{detail.job?.errorMessage}
} + 步骤日志 ({detail.steps?.length || 0}) +
+ + )} + + + ) +}