Files
planet/frontend/src/pages/Dashboard/Dashboard.tsx
2026-03-25 17:19:10 +08:00

200 lines
5.9 KiB
TypeScript

import { useEffect, useState } from 'react'
import { Card, Row, Col, Statistic, Typography, Button, Tag, Spin, Space } from 'antd'
import {
DatabaseOutlined,
BarChartOutlined,
AlertOutlined,
WifiOutlined,
DisconnectOutlined,
ReloadOutlined,
} from '@ant-design/icons'
import { useAuthStore } from '../../stores/auth'
import AppLayout from '../../components/AppLayout/AppLayout'
const { Title, Text } = Typography
interface Stats {
total_datasources: number
active_datasources: number
tasks_today: number
success_rate: number
last_updated: string
alerts: {
critical: number
warning: number
info: number
}
}
function Dashboard() {
const { token, clearAuth } = useAuthStore()
const [stats, setStats] = useState<Stats | null>(null)
const [loading, setLoading] = useState(true)
const [wsConnected, setWsConnected] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!token) return
const fetchStats = async () => {
try {
setLoading(true)
const res = await fetch('/api/v1/dashboard/stats', {
headers: { Authorization: `Bearer ${token}` },
})
if (res.status === 401) {
clearAuth()
window.location.href = '/'
return
}
const data = await res.json()
setStats(data)
setError(null)
} catch (err) {
setError('获取数据失败')
console.error(err)
} finally {
setLoading(false)
}
}
fetchStats()
}, [token, clearAuth])
useEffect(() => {
if (!token) return
let ws: WebSocket | null = null
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
const connectWs = () => {
try {
ws = new WebSocket(`ws://localhost:8000/ws?token=${token}`)
ws.onopen = () => {
setWsConnected(true)
ws?.send(JSON.stringify({ type: 'subscribe', data: { channels: ['dashboard'] } }))
}
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data)
if (msg.type === 'data_frame' && msg.channel === 'dashboard') {
setStats(msg.payload?.stats as Stats)
}
} catch (e) {
console.error('Parse WS message error:', e)
}
}
ws.onclose = () => {
setWsConnected(false)
reconnectTimer = setTimeout(connectWs, 3000)
}
ws.onerror = () => {
setWsConnected(false)
}
} catch (e) {
console.error('WS connect error:', e)
}
}
connectWs()
return () => {
ws?.close()
if (reconnectTimer) clearTimeout(reconnectTimer)
}
}, [token])
const handleRetry = () => {
window.location.reload()
}
if (loading && !stats) {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<Spin size="large" tip="加载中..." />
</div>
)
}
return (
<AppLayout>
<div className="dashboard-page">
<div className="dashboard-page__header">
<div>
<Title level={4} style={{ margin: 0 }}></Title>
<Text type="secondary"></Text>
</div>
<Space wrap className="dashboard-page__actions">
{wsConnected ? (
<Tag className="dashboard-status-tag" icon={<WifiOutlined />} color="success"></Tag>
) : (
<Tag className="dashboard-status-tag" icon={<DisconnectOutlined />} color="default">线</Tag>
)}
<Button className="dashboard-refresh-button" icon={<ReloadOutlined />} onClick={handleRetry}></Button>
</Space>
</div>
{error && (
<Card style={{ borderColor: '#ff4d4f' }}>
<Text style={{ color: '#ff4d4f' }}>{error}</Text>
</Card>
)}
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} xl={6}>
<Card>
<Statistic title="数据源总数" value={stats?.total_datasources || 0} prefix={<DatabaseOutlined />} />
</Card>
</Col>
<Col xs={24} sm={12} xl={6}>
<Card>
<Statistic title="活跃数据源" value={stats?.active_datasources || 0} valueStyle={{ color: '#52c41a' }} />
</Card>
</Col>
<Col xs={24} sm={12} xl={6}>
<Card>
<Statistic title="今日任务" value={stats?.tasks_today || 0} prefix={<BarChartOutlined />} />
</Card>
</Col>
<Col xs={24} sm={12} xl={6}>
<Card>
<Statistic title="成功率" value={stats?.success_rate || 0} suffix="%" valueStyle={{ color: '#1890ff' }} />
</Card>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col xs={24} md={8}>
<Card>
<Statistic title="严重告警" value={stats?.alerts?.critical || 0} valueStyle={{ color: '#ff4d4f' }} prefix={<AlertOutlined />} />
</Card>
</Col>
<Col xs={24} md={8}>
<Card>
<Statistic title="警告" value={stats?.alerts?.warning || 0} valueStyle={{ color: '#faad14' }} prefix={<AlertOutlined />} />
</Card>
</Col>
<Col xs={24} md={8}>
<Card>
<Statistic title="提示" value={stats?.alerts?.info || 0} valueStyle={{ color: '#1890ff' }} prefix={<AlertOutlined />} />
</Card>
</Col>
</Row>
{stats?.last_updated && (
<div style={{ textAlign: 'center', color: '#8c8c8c' }}>
: {new Date(stats.last_updated).toLocaleString('zh-CN')}
{wsConnected && <Tag className="dashboard-status-tag" color="green" style={{ marginLeft: 8 }}></Tag>}
</div>
)}
</div>
</AppLayout>
)
}
export default Dashboard