Files
planet/frontend/src/pages/Tasks/Tasks.tsx
linkong f14ff6ec0f
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
release: bump version to 0.56.0
2026-05-13 18:21:03 +08:00

185 lines
5.5 KiB
TypeScript

import { useEffect, useState } from 'react'
import { Table, Tag, Card, Row, Col, Statistic, Button, Tooltip } from 'antd'
import { ReloadOutlined, CheckCircleOutlined, CloseCircleOutlined, SyncOutlined } from '@ant-design/icons'
import { useAuthStore } from '../../stores/auth'
import AppLayout from '../../components/AppLayout/AppLayout'
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
import { formatDateTimeZhCN } from '../../utils/datetime'
import { getPhaseDisplay, getPhaseSummary } from '../../utils/phaseProgress'
interface Task {
id: number
collector?: string
datasource_name?: string
status: 'success' | 'failed' | 'running' | 'pending'
phase?: string | null
phase_progress?: number | null
phase_message?: string | null
phase_current?: number | null
phase_total?: number | null
phase_unit?: string | null
records_processed: number
total_records?: number | null
progress?: number | null
started_at: string
completed_at: string
duration_seconds: number
}
function Tasks() {
const { token } = useAuthStore()
const [tasks, setTasks] = useState<Task[]>([])
const [loading, setLoading] = useState(false)
const [stats, setStats] = useState({
total_today: 0,
success: 0,
failed: 0,
running: 0,
})
const fetchTasks = async () => {
setLoading(true)
try {
const res = await fetch('/api/v1/tasks', {
headers: { Authorization: `Bearer ${token}` },
})
const data = await res.json()
setTasks(data.data || [])
setStats(data.stats || stats)
} catch (error) {
console.error('Failed to fetch tasks:', error)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchTasks()
}, [token])
const columns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
{
title: '收集器',
dataIndex: 'collector',
key: 'collector',
render: (_: string, task: Task) => <Tag color="blue">{task.collector || task.datasource_name || '-'}</Tag>,
},
{
title: '阶段',
dataIndex: 'phase',
key: 'phase',
render: (_: string, task: Task) => {
const detail = task.phase ? getPhaseDisplay(task) : null
return detail ? (
<Tooltip title={detail}>
<Tag color={task.status === 'running' ? 'processing' : 'default'}>{getPhaseSummary(task)}</Tag>
</Tooltip>
) : '-'
},
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => {
const colors: Record<string, string> = {
success: 'success',
failed: 'error',
running: 'processing',
pending: 'default',
}
const icons: Record<string, JSX.Element> = {
success: <CheckCircleOutlined />,
failed: <CloseCircleOutlined />,
running: <SyncOutlined spin />,
pending: <SyncOutlined />,
}
return (
<Tag color={colors[status] || 'default'} icon={icons[status]}>
{status === 'success' ? '成功' : status === 'failed' ? '失败' : status === 'running' ? '运行中' : '等待中'}
</Tag>
)
},
},
{
title: '处理记录',
dataIndex: 'records_processed',
key: 'records_processed',
render: (n: number) => n.toLocaleString(),
},
{
title: '耗时',
dataIndex: 'duration_seconds',
key: 'duration_seconds',
render: (s: number) => s ? `${s.toFixed(2)}s` : '-',
},
{
title: '开始时间',
dataIndex: 'started_at',
key: 'started_at',
render: (t: string) => formatDateTimeZhCN(t),
},
]
const statusCounts = tasks.reduce(
(acc, task) => {
acc[task.status]++
return acc
},
{ success: 0, failed: 0, running: 0, pending: 0 } as Record<string, number>
)
const successRate = tasks.length > 0 ? (statusCounts.success / tasks.length) * 100 : 0
return (
<AppLayout>
<div className="page-shell tasks-page">
<Row gutter={[16, 16]}>
<Col xs={24} md={6}>
<Card>
<Statistic title="今日任务" value={tasks.length} prefix={<ReloadOutlined />} />
</Card>
</Col>
<Col xs={24} md={6}>
<Card>
<Statistic
title="成功率"
value={successRate.toFixed(1)}
suffix="%"
valueStyle={{ color: successRate >= 90 ? '#52c41a' : '#faad14' }}
/>
</Card>
</Col>
<Col xs={24} md={6}>
<Card>
<Statistic title="成功" value={statusCounts.success} valueStyle={{ color: '#52c41a' }} />
</Card>
</Col>
<Col xs={24} md={6}>
<Card>
<Statistic title="失败" value={statusCounts.failed} valueStyle={{ color: '#ff4d4f' }} />
</Card>
</Col>
</Row>
<Card
className="tasks-page__table-card"
title="任务历史"
extra={
<Button type="primary" icon={<ReloadOutlined />} onClick={fetchTasks}>
</Button>
}
>
<TableScrollRegion className="data-source-table-region">
<Table columns={columns} dataSource={tasks} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 'max-content', y: 480 }} tableLayout="fixed" />
</TableScrollRegion>
</Card>
</div>
</AppLayout>
)
}
export default Tasks