first commit
This commit is contained in:
153
frontend/src/pages/Tasks/Tasks.tsx
Normal file
153
frontend/src/pages/Tasks/Tasks.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Table, Tag, Card, Row, Col, Statistic, Button } from 'antd'
|
||||
import { ReloadOutlined, CheckCircleOutlined, CloseCircleOutlined, SyncOutlined } from '@ant-design/icons'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
|
||||
interface Task {
|
||||
id: number
|
||||
collector: string
|
||||
status: 'success' | 'failed' | 'running' | 'pending'
|
||||
records_processed: number
|
||||
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: (c: string) => <Tag color="blue">{c}</Tag>,
|
||||
},
|
||||
{
|
||||
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) => t ? new Date(t).toLocaleString('zh-CN') : '-',
|
||||
},
|
||||
]
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic title="今日任务" value={tasks.length} prefix={<ReloadOutlined />} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="成功率"
|
||||
value={successRate.toFixed(1)}
|
||||
suffix="%"
|
||||
valueStyle={{ color: successRate >= 90 ? '#52c41a' : '#faad14' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic title="成功" value={statusCounts.success} valueStyle={{ color: '#52c41a' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic title="失败" value={statusCounts.failed} valueStyle={{ color: '#ff4d4f' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Card
|
||||
title="任务历史"
|
||||
extra={
|
||||
<Button type="primary" icon={<ReloadOutlined />} onClick={fetchTasks}>
|
||||
刷新
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table columns={columns} dataSource={tasks} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} />
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tasks
|
||||
Reference in New Issue
Block a user