220 lines
6.9 KiB
TypeScript
220 lines
6.9 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { Table, Tag, Card, Row, Col, Statistic, Button, Modal, Space, Descriptions } from 'antd'
|
|
import { AlertOutlined, InfoCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
|
import { useAuthStore } from '../../stores/auth'
|
|
|
|
interface Alert {
|
|
id: number
|
|
severity: 'critical' | 'warning' | 'info'
|
|
status: 'active' | 'acknowledged' | 'resolved'
|
|
datasource_name: string
|
|
message: string
|
|
created_at: string
|
|
acknowledged_at?: string
|
|
resolved_at?: string
|
|
}
|
|
|
|
function Alerts() {
|
|
const { token } = useAuthStore()
|
|
const [alerts, setAlerts] = useState<Alert[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
const [selectedAlert, setSelectedAlert] = useState<Alert | null>(null)
|
|
const [detailVisible, setDetailVisible] = useState(false)
|
|
|
|
const fetchAlerts = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const res = await fetch('/api/v1/alerts', {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
const data = await res.json()
|
|
setAlerts(data.data || [])
|
|
} catch (error) {
|
|
console.error('Failed to fetch alerts:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchAlerts()
|
|
}, [token])
|
|
|
|
const handleAcknowledge = async (alertId: number) => {
|
|
try {
|
|
await fetch(`/api/v1/alerts/${alertId}/acknowledge`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
fetchAlerts()
|
|
} catch (error) {
|
|
console.error('Failed to acknowledge alert:', error)
|
|
}
|
|
}
|
|
|
|
const handleResolve = async (alertId: number) => {
|
|
try {
|
|
await fetch(`/api/v1/alerts/${alertId}/resolve`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify({ resolution: '已处理' }),
|
|
})
|
|
fetchAlerts()
|
|
} catch (error) {
|
|
console.error('Failed to resolve alert:', error)
|
|
}
|
|
}
|
|
|
|
const columns = [
|
|
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
|
|
{
|
|
title: '级别',
|
|
dataIndex: 'severity',
|
|
key: 'severity',
|
|
render: (s: string) => {
|
|
const colors: Record<string, string> = { critical: 'error', warning: 'warning', info: 'blue' }
|
|
const icons: Record<string, JSX.Element> = {
|
|
critical: <AlertOutlined />,
|
|
warning: <AlertOutlined />,
|
|
info: <InfoCircleOutlined />,
|
|
}
|
|
return (
|
|
<Tag color={colors[s]} icon={icons[s]}>
|
|
{s === 'critical' ? '严重' : s === 'warning' ? '警告' : '信息'}
|
|
</Tag>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
key: 'status',
|
|
render: (s: string) => {
|
|
const colors: Record<string, string> = { active: 'red', acknowledged: 'orange', resolved: 'green' }
|
|
return (
|
|
<Tag color={colors[s]}>
|
|
{s === 'active' ? '待处理' : s === 'acknowledged' ? '已确认' : '已解决'}
|
|
</Tag>
|
|
)
|
|
},
|
|
},
|
|
{ title: '数据源', dataIndex: 'datasource_name', key: 'datasource_name' },
|
|
{ title: '消息', dataIndex: 'message', key: 'message', ellipsis: true },
|
|
{
|
|
title: '时间',
|
|
dataIndex: 'created_at',
|
|
key: 'created_at',
|
|
render: (t: string) => new Date(t).toLocaleString('zh-CN'),
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
render: (_: unknown, record: Alert) => (
|
|
<Space>
|
|
{record.status === 'active' && (
|
|
<Button type="link" size="small" onClick={() => handleAcknowledge(record.id)}>
|
|
确认
|
|
</Button>
|
|
)}
|
|
{record.status !== 'resolved' && (
|
|
<Button type="link" size="small" onClick={() => handleResolve(record.id)}>
|
|
解决
|
|
</Button>
|
|
)}
|
|
<Button type="link" size="small" onClick={() => { setSelectedAlert(record); setDetailVisible(true); }}>
|
|
详情
|
|
</Button>
|
|
</Space>
|
|
),
|
|
},
|
|
]
|
|
|
|
const stats = alerts.reduce(
|
|
(acc, alert) => {
|
|
if (alert.status === 'active') {
|
|
acc[alert.severity]++
|
|
}
|
|
return acc
|
|
},
|
|
{ critical: 0, warning: 0, info: 0 } as Record<string, number>
|
|
)
|
|
|
|
return (
|
|
<div>
|
|
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
|
<Col span={8}>
|
|
<Card>
|
|
<Statistic
|
|
title="严重告警"
|
|
value={stats.critical}
|
|
valueStyle={{ color: '#ff4d4f' }}
|
|
prefix={<AlertOutlined />}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Card>
|
|
<Statistic
|
|
title="警告"
|
|
value={stats.warning}
|
|
valueStyle={{ color: '#faad14' }}
|
|
prefix={<AlertOutlined />}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Card>
|
|
<Statistic title="信息" value={stats.info} valueStyle={{ color: '#1890ff' }} prefix={<InfoCircleOutlined />} />
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
<Card
|
|
title="告警列表"
|
|
extra={<Button icon={<ReloadOutlined />} onClick={fetchAlerts}>刷新</Button>}
|
|
>
|
|
<Table columns={columns} dataSource={alerts} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} />
|
|
</Card>
|
|
|
|
<Modal
|
|
title="告警详情"
|
|
open={detailVisible}
|
|
onCancel={() => setDetailVisible(false)}
|
|
footer={null}
|
|
width={600}
|
|
>
|
|
{selectedAlert && (
|
|
<Descriptions column={1} bordered>
|
|
<Descriptions.Item label="ID">{selectedAlert.id}</Descriptions.Item>
|
|
<Descriptions.Item label="级别">
|
|
<Tag color={selectedAlert.severity === 'critical' ? 'error' : selectedAlert.severity === 'warning' ? 'warning' : 'blue'}>
|
|
{selectedAlert.severity}
|
|
</Tag>
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="状态">
|
|
<Tag color={selectedAlert.status === 'active' ? 'red' : selectedAlert.status === 'acknowledged' ? 'orange' : 'green'}>
|
|
{selectedAlert.status}
|
|
</Tag>
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="数据源">{selectedAlert.datasource_name}</Descriptions.Item>
|
|
<Descriptions.Item label="消息">{selectedAlert.message}</Descriptions.Item>
|
|
<Descriptions.Item label="创建时间">{new Date(selectedAlert.created_at).toLocaleString('zh-CN')}</Descriptions.Item>
|
|
{selectedAlert.acknowledged_at && (
|
|
<Descriptions.Item label="确认时间">
|
|
{new Date(selectedAlert.acknowledged_at).toLocaleString('zh-CN')}
|
|
</Descriptions.Item>
|
|
)}
|
|
{selectedAlert.resolved_at && (
|
|
<Descriptions.Item label="解决时间">
|
|
{new Date(selectedAlert.resolved_at).toLocaleString('zh-CN')}
|
|
</Descriptions.Item>
|
|
)}
|
|
</Descriptions>
|
|
)}
|
|
</Modal>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default Alerts
|