feat: M4-04 — backup & cleanup admin page
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 11s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 11s
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
8145279626
commit
b36924d9b3
@ -42,6 +42,7 @@ const ReviewAdmin = lazy(() => import('./pages/ReviewAdmin'))
|
|||||||
const NotificationAdmin = lazy(() => import('./pages/NotificationAdmin'))
|
const NotificationAdmin = lazy(() => import('./pages/NotificationAdmin'))
|
||||||
const CacheAdmin = lazy(() => import('./pages/CacheAdmin'))
|
const CacheAdmin = lazy(() => import('./pages/CacheAdmin'))
|
||||||
const LearningData = lazy(() => import('./pages/LearningData'))
|
const LearningData = lazy(() => import('./pages/LearningData'))
|
||||||
|
const BackupAdmin = lazy(() => import('./pages/BackupAdmin'))
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
@ -178,6 +179,7 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
<Route path="reviews" element={<Suspense fallback={<PageLoading />}><ReviewAdmin /></Suspense>} />
|
<Route path="reviews" element={<Suspense fallback={<PageLoading />}><ReviewAdmin /></Suspense>} />
|
||||||
<Route path="learning-data" element={<Suspense fallback={<PageLoading />}><LearningData /></Suspense>} />
|
<Route path="learning-data" element={<Suspense fallback={<PageLoading />}><LearningData /></Suspense>} />
|
||||||
|
<Route path="backup" element={<Suspense fallback={<PageLoading />}><BackupAdmin /></Suspense>} />
|
||||||
<Route path="notification-admin" element={<Suspense fallback={<PageLoading />}><NotificationAdmin /></Suspense>} />
|
<Route path="notification-admin" element={<Suspense fallback={<PageLoading />}><NotificationAdmin /></Suspense>} />
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export const adminMenuItems: AdminMenuItem[] = [
|
|||||||
{ path: '/events', name: '事件队列' },
|
{ path: '/events', name: '事件队列' },
|
||||||
{ path: '/vector', name: '向量检索' },
|
{ path: '/vector', name: '向量检索' },
|
||||||
{ path: '/config', name: '配置管理' },
|
{ path: '/config', name: '配置管理' },
|
||||||
|
{ path: '/backup', name: '备份清理' },
|
||||||
{ path: '/cache', name: '缓存管理' },
|
{ path: '/cache', name: '缓存管理' },
|
||||||
{ path: '/notification-admin', name: '通知管理' },
|
{ path: '/notification-admin', name: '通知管理' },
|
||||||
{ path: '/safety', name: '内容安全' },
|
{ path: '/safety', name: '内容安全' },
|
||||||
|
|||||||
91
src/pages/BackupAdmin.tsx
Normal file
91
src/pages/BackupAdmin.tsx
Normal file
@ -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<string, string> = { 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<any>('/admin-api/backup/jobs'),
|
||||||
|
enabled: tab === 'backups',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: cleanups, isLoading: cLoading } = useQuery({
|
||||||
|
queryKey: ['admin', 'cleanup-jobs'],
|
||||||
|
queryFn: () => api.get<any>('/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) => <Tag color={statusColors[s] || 'default'}>{s}</Tag> },
|
||||||
|
{ 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) => <Tag color={statusColors[s] || 'default'}>{s}</Tag> },
|
||||||
|
{ 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 (
|
||||||
|
<div>
|
||||||
|
<Title level={4}>备份与清理</Title>
|
||||||
|
<Tabs activeKey={tab} onChange={setTab} items={[
|
||||||
|
{
|
||||||
|
key: 'backups', label: '备份任务',
|
||||||
|
children: (
|
||||||
|
<div>
|
||||||
|
<Space style={{ marginBottom: 16 }}>
|
||||||
|
<Button icon={<CloudUploadOutlined />} onClick={() => triggerBackup.mutate('mysql')} loading={triggerBackup.isPending}>MySQL 备份</Button>
|
||||||
|
<Button icon={<CloudUploadOutlined />} onClick={() => triggerBackup.mutate('qdrant')} loading={triggerBackup.isPending}>Qdrant Snapshot</Button>
|
||||||
|
<Button icon={<CloudUploadOutlined />} onClick={() => triggerBackup.mutate('files')} loading={triggerBackup.isPending}>文件备份</Button>
|
||||||
|
</Space>
|
||||||
|
<Table dataSource={backups?.items || []} columns={backupColumns} rowKey="id" loading={bLoading} pagination={{ total: backups?.total || 0 }} size="small" scroll={{ x: 1100 }} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'cleanup', label: '清理任务',
|
||||||
|
children: (
|
||||||
|
<div>
|
||||||
|
<Space style={{ marginBottom: 16 }}>
|
||||||
|
<Button icon={<DeleteOutlined />} onClick={() => triggerCleanup.mutate('soft-delete')} loading={triggerCleanup.isPending}>软删除清理</Button>
|
||||||
|
<Button icon={<DeleteOutlined />} onClick={() => triggerCleanup.mutate('api-metrics')} loading={triggerCleanup.isPending}>指标清理</Button>
|
||||||
|
<Button icon={<DeleteOutlined />} onClick={() => triggerCleanup.mutate('task-logs')} loading={triggerCleanup.isPending}>任务日志清理</Button>
|
||||||
|
</Space>
|
||||||
|
<Table dataSource={cleanups?.items || []} columns={cleanupColumns} rowKey="id" loading={cLoading} pagination={{ total: cleanups?.total || 0 }} size="small" scroll={{ x: 900 }} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user