From b36924d9b335ed9d9b98794db0282b99d5f88101 Mon Sep 17 00:00:00 2001 From: WangDL Date: Sun, 24 May 2026 17:57:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20M4-04=20=E2=80=94=20backup=20&=20cleanu?= =?UTF-8?q?p=20admin=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add BackupAdmin page with backup/cleanup tabs - Trigger buttons for mysql/qdrant/files backup and soft-delete/api-metrics/task-logs cleanup Co-Authored-By: Claude Opus 4.7 --- src/App.tsx | 2 + src/config/menu.tsx | 1 + src/pages/BackupAdmin.tsx | 91 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/pages/BackupAdmin.tsx diff --git a/src/App.tsx b/src/App.tsx index ecf0f1d..e7503bb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,6 +42,7 @@ const ReviewAdmin = lazy(() => import('./pages/ReviewAdmin')) const NotificationAdmin = lazy(() => import('./pages/NotificationAdmin')) const CacheAdmin = lazy(() => import('./pages/CacheAdmin')) const LearningData = lazy(() => import('./pages/LearningData')) +const BackupAdmin = lazy(() => import('./pages/BackupAdmin')) const queryClient = new QueryClient() @@ -178,6 +179,7 @@ function App() { /> }>} /> }>} /> + }>} /> }>} /> } /> diff --git a/src/config/menu.tsx b/src/config/menu.tsx index 7c92e82..8df465b 100644 --- a/src/config/menu.tsx +++ b/src/config/menu.tsx @@ -41,6 +41,7 @@ export const adminMenuItems: AdminMenuItem[] = [ { path: '/events', name: '事件队列' }, { path: '/vector', name: '向量检索' }, { path: '/config', name: '配置管理' }, + { path: '/backup', name: '备份清理' }, { path: '/cache', name: '缓存管理' }, { path: '/notification-admin', name: '通知管理' }, { path: '/safety', name: '内容安全' }, diff --git a/src/pages/BackupAdmin.tsx b/src/pages/BackupAdmin.tsx new file mode 100644 index 0000000..75f8f8c --- /dev/null +++ b/src/pages/BackupAdmin.tsx @@ -0,0 +1,91 @@ +import { useState } from 'react' +import { Table, Button, Tag, Space, Typography, Tabs, message } from 'antd' +import { CloudUploadOutlined, DeleteOutlined } from '@ant-design/icons' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { api } from '@/services/http-client' + +const { Title } = Typography + +const statusColors: Record = { RUNNING: 'blue', COMPLETED: 'green', FAILED: 'red' } + +export default function BackupAdmin() { + const [tab, setTab] = useState('backups') + const qc = useQueryClient() + + const { data: backups, isLoading: bLoading } = useQuery({ + queryKey: ['admin', 'backup-jobs'], + queryFn: () => api.get('/admin-api/backup/jobs'), + enabled: tab === 'backups', + }) + + const { data: cleanups, isLoading: cLoading } = useQuery({ + queryKey: ['admin', 'cleanup-jobs'], + queryFn: () => api.get('/admin-api/backup/cleanup'), + enabled: tab === 'cleanup', + }) + + const triggerBackup = useMutation({ + mutationFn: (type: string) => api.post(`/admin-api/backup/trigger/${type}`), + onSuccess: () => { message.success('备份任务已触发'); qc.invalidateQueries({ queryKey: ['admin', 'backup-jobs'] }) }, + }) + + const triggerCleanup = useMutation({ + mutationFn: (type: string) => api.post(`/admin-api/backup/cleanup/${type}`), + onSuccess: () => { message.success('清理任务已触发'); qc.invalidateQueries({ queryKey: ['admin', 'cleanup-jobs'] }) }, + }) + + const backupColumns = [ + { title: 'ID', dataIndex: 'id', width: 100, ellipsis: true }, + { title: '类型', dataIndex: 'type', width: 80 }, + { title: '状态', dataIndex: 'status', width: 80, render: (s: string) => {s} }, + { title: '本地路径', dataIndex: 'localPath', width: 200, ellipsis: true, render: (p: string | null) => p || '-' }, + { title: 'COS Key', dataIndex: 'cosObjectKey', width: 180, ellipsis: true, render: (k: string | null) => k || '-' }, + { title: '大小', dataIndex: 'fileSizeBytes', width: 80, render: (s: number) => s ? `${(Number(s) / 1048576).toFixed(1)} MB` : '-' }, + { title: '开始时间', dataIndex: 'startedAt', width: 120, render: (d: string) => new Date(d).toLocaleString() }, + { title: '完成时间', dataIndex: 'completedAt', width: 120, render: (d: string | null) => d ? new Date(d).toLocaleString() : '-' }, + ] + + const cleanupColumns = [ + { title: 'ID', dataIndex: 'id', width: 100, ellipsis: true }, + { title: '类型', dataIndex: 'type', width: 100 }, + { title: '状态', dataIndex: 'status', width: 80, render: (s: string) => {s} }, + { title: '目标', dataIndex: 'target', width: 120, ellipsis: true, render: (t: string | null) => t || '-' }, + { title: '影响行数', dataIndex: 'rowsAffected', width: 80 }, + { title: '开始时间', dataIndex: 'startedAt', width: 120, render: (d: string) => new Date(d).toLocaleString() }, + { title: '完成时间', dataIndex: 'completedAt', width: 120, render: (d: string | null) => d ? new Date(d).toLocaleString() : '-' }, + ] + + return ( +
+ 备份与清理 + + + + + + + + + ), + }, + { + key: 'cleanup', label: '清理任务', + children: ( +
+ + + + + +
+ + ), + }, + ]} /> + + ) +}