first commit

This commit is contained in:
rayd1o
2026-03-05 11:46:58 +08:00
commit e7033775d8
20657 changed files with 1988940 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
import { useEffect, useState } from 'react'
import { Layout, Menu, Card, Row, Col, Statistic, Typography, Button, Tag, Spin } from 'antd'
import {
DashboardOutlined,
DatabaseOutlined,
UserOutlined,
SettingOutlined,
BarChartOutlined,
AlertOutlined,
WifiOutlined,
DisconnectOutlined,
ReloadOutlined,
} from '@ant-design/icons'
import { Link } from 'react-router-dom'
import { useAuthStore } from '../../stores/auth'
const { Header, Sider, Content } = Layout
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 { user, logout, 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])
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 handleLogout = () => {
logout()
window.location.href = '/'
}
const handleClearAuth = () => {
clearAuth()
window.location.href = '/'
}
const handleRetry = () => {
window.location.reload()
}
const menuItems = [
{ key: '/', icon: <DashboardOutlined />, label: <Link to="/"></Link> },
{ key: '/datasources', icon: <DatabaseOutlined />, label: <Link to="/datasources"></Link> },
{ key: '/data', icon: <BarChartOutlined />, label: <Link to="/data"></Link> },
{ key: '/users', icon: <UserOutlined />, label: <Link to="/users"></Link> },
{ key: '/settings', icon: <SettingOutlined />, label: '系统配置' },
]
if (loading && !stats) {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<Spin size="large" tip="加载中..." />
</div>
)
}
return (
<Layout className="dashboard-layout">
<Sider width={240} className="dashboard-sider">
<div style={{ height: 64, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Title level={4} style={{ color: 'white', margin: 0 }}></Title>
</div>
<Menu theme="dark" mode="inline" defaultSelectedKeys={['/']} items={menuItems} />
</Sider>
<Layout>
<Header className="dashboard-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Text strong>, {user?.username}</Text>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{wsConnected ? (
<Tag icon={<WifiOutlined />} color="success"></Tag>
) : (
<Tag icon={<DisconnectOutlined />} color="default">线</Tag>
)}
<Button type="link" danger onClick={handleLogout}>退</Button>
<Button type="link" onClick={handleClearAuth}></Button>
<Button type="link" icon={<ReloadOutlined />} onClick={handleRetry}></Button>
</div>
</Header>
<Content className="dashboard-content">
{error && (
<Card style={{ marginBottom: 16, borderColor: '#ff4d4f' }}>
<Text style={{ color: '#ff4d4f' }}>{error}</Text>
</Card>
)}
<Row gutter={[16, 16]}>
<Col span={6}>
<Card>
<Statistic title="数据源总数" value={stats?.total_datasources || 0} prefix={<DatabaseOutlined />} />
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic title="活跃数据源" value={stats?.active_datasources || 0} valueStyle={{ color: '#52c41a' }} />
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic title="今日任务" value={stats?.tasks_today || 0} prefix={<BarChartOutlined />} />
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic title="成功率" value={stats?.success_rate || 0} suffix="%" valueStyle={{ color: '#1890ff' }} />
</Card>
</Col>
</Row>
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
<Col span={8}>
<Card>
<Statistic title="严重告警" value={stats?.alerts?.critical || 0} valueStyle={{ color: '#ff4d4f' }} prefix={<AlertOutlined />} />
</Card>
</Col>
<Col span={8}>
<Card>
<Statistic title="警告" value={stats?.alerts?.warning || 0} valueStyle={{ color: '#faad14' }} prefix={<AlertOutlined />} />
</Card>
</Col>
<Col span={8}>
<Card>
<Statistic title="提示" value={stats?.alerts?.info || 0} valueStyle={{ color: '#1890ff' }} prefix={<AlertOutlined />} />
</Card>
</Col>
</Row>
{stats?.last_updated && (
<div style={{ marginTop: 16, textAlign: 'center', color: '#8c8c8c' }}>
: {new Date(stats.last_updated).toLocaleString('zh-CN')}
{wsConnected && <Tag color="green" style={{ marginLeft: 8 }}></Tag>}
</div>
)}
</Content>
</Layout>
</Layout>
)
}
export default Dashboard