admin-projects/src/pages/BackupAdmin.tsx
wangdl 8663ceba66
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 10s
feat: HTTP 层重构 + API 抽离 + 状态管理优化 + 路由修复
- HTTP 层:安装 axios,创建 lib/api-client.ts 统一拦截器
- API 层:创建 33 个 api/*.ts 文件,所有页面迁移完成
- DataPages.tsx 拆分为 10 个独立文件
- message 导入统一为 App.useApp()
- 新增 StaticAntdProvider 全局消息/通知
- 全局 HTTP 错误/成功提示(notification.error/success)
- learning 路由 /learning → /learning/dashboard 修复选中 bug
- 学习会话页面优化(移除 ID 列、加批量删除、后端排序)
- 文档导入页面重构(知识库筛选、批量重新解析)
- 删除旧 service 文件 10 个(admin-api、billing-api 等)
- antd App 包裹根组件

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-01 22:44:51 +08:00

93 lines
5.0 KiB
TypeScript

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 { backupAPI } from '@/api'
import { backupStatusLabels } from '@/constants/labels'
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: () => backupAPI.jobs(),
enabled: tab === 'backups',
})
const { data: cleanups, isLoading: cLoading } = useQuery({
queryKey: ['admin', 'cleanup-jobs'],
queryFn: () => backupAPI.cleanups(),
enabled: tab === 'cleanup',
})
const triggerBackup = useMutation({
mutationFn: (type: string) => backupAPI.triggerBackup(type),
onSuccess: () => { message.success('备份任务已触发'); qc.invalidateQueries({ queryKey: ['admin', 'backup-jobs'] }) },
})
const triggerCleanup = useMutation({
mutationFn: (type: string) => backupAPI.triggerCleanup(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'}>{backupStatusLabels[s] || 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'}>{backupStatusLabels[s] || 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={{ defaultPageSize: 20, 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={{ defaultPageSize: 20, total: cleanups?.total || 0 }} size="small" scroll={{ x: 900 }} />
</div>
),
},
]} />
</div>
)
}