feat: M1-05 metrics page — AI + Worker performance tabs
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 7s

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
WangDL 2026-05-24 10:56:45 +08:00
parent d71bb5f4d8
commit 6812d8038d

View File

@ -1,6 +1,6 @@
import { useQuery, useQueryClient } from '@tanstack/react-query' import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Table, Card, Row, Col, Statistic, Button, Typography } from 'antd' import { Table, Card, Row, Col, Statistic, Button, Typography, Tabs } from 'antd'
import { ReloadOutlined, DashboardOutlined } from '@ant-design/icons' import { ReloadOutlined, DashboardOutlined, CloudOutlined, NodeIndexOutlined } from '@ant-design/icons'
import { api } from '@/services/http-client' import { api } from '@/services/http-client'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -12,6 +12,8 @@ function MetricsPage() {
const { data: overview } = useQuery({ queryKey: ['metrics', 'overview'], queryFn: (): Promise<any> => api.get('/admin-api/metrics/overview'), staleTime: 10_000 }) const { data: overview } = useQuery({ queryKey: ['metrics', 'overview'], queryFn: (): Promise<any> => api.get('/admin-api/metrics/overview'), staleTime: 10_000 })
const { data: top } = useQuery({ queryKey: ['metrics', 'top'], queryFn: (): Promise<any> => api.get('/admin-api/metrics/top?limit=15'), staleTime: 10_000 }) const { data: top } = useQuery({ queryKey: ['metrics', 'top'], queryFn: (): Promise<any> => api.get('/admin-api/metrics/top?limit=15'), staleTime: 10_000 })
const { data: recent } = useQuery({ queryKey: ['metrics', 'recent'], queryFn: (): Promise<any> => api.get('/admin-api/metrics/recent?limit=30'), staleTime: 5_000 }) const { data: recent } = useQuery({ queryKey: ['metrics', 'recent'], queryFn: (): Promise<any> => api.get('/admin-api/metrics/recent?limit=30'), staleTime: 5_000 })
const { data: ai } = useQuery({ queryKey: ['metrics', 'ai'], queryFn: (): Promise<any> => api.get('/admin-api/metrics/ai?days=7'), staleTime: 10_000 })
const { data: worker } = useQuery({ queryKey: ['metrics', 'worker'], queryFn: (): Promise<any> => api.get('/admin-api/metrics/worker?days=7'), staleTime: 10_000 })
const topCols = [ const topCols = [
{ title: '接口', dataIndex: 'path', width: 300, ellipsis: true }, { title: '接口', dataIndex: 'path', width: 300, ellipsis: true },
@ -28,6 +30,22 @@ function MetricsPage() {
{ title: '耗时', dataIndex: 'duration', width: 70, align: 'center' as const, render: (v: number) => `${v}ms` }, { title: '耗时', dataIndex: 'duration', width: 70, align: 'center' as const, render: (v: number) => `${v}ms` },
] ]
const aiProviderCols = [
{ title: 'Provider', dataIndex: 'name', width: 120 },
{ title: '调用量', dataIndex: 'calls', width: 80, align: 'center' as const },
{ title: '平均耗时', dataIndex: 'avgLatencyMs', width: 100, align: 'center' as const, render: (v: number) => <span style={{ color: v > 5000 ? '#ff4d4f' : v > 2000 ? '#faad14' : '#52c41a' }}>{v}ms</span> },
{ title: '失败率', dataIndex: 'failureRate', width: 80, align: 'center' as const },
{ title: '成本', dataIndex: 'totalCost', width: 80, align: 'center' as const, render: (v: string) => `$${v}` },
]
const workerCols = [
{ title: '队列', dataIndex: 'name', width: 160 },
{ title: '总任务', dataIndex: 'total', width: 80, align: 'center' as const },
{ title: '完成', dataIndex: 'completed', width: 80, align: 'center' as const },
{ title: '失败', dataIndex: 'failed', width: 80, align: 'center' as const, render: (v: number) => v > 0 ? <span style={{ color: '#ff4d4f' }}>{v}</span> : <span>0</span> },
{ title: '成功率', dataIndex: 'successRate', width: 80, align: 'center' as const },
]
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
@ -42,6 +60,10 @@ function MetricsPage() {
<Col span={6}><Card size="small"><Statistic title="总调用" value={overview?.total || 0} /></Card></Col> <Col span={6}><Card size="small"><Statistic title="总调用" value={overview?.total || 0} /></Card></Col>
</Row> </Row>
<Tabs items={[
{
key: 'api', label: 'API 性能',
children: (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={12}> <Col span={12}>
<Card size="small" title="耗时排行"><Table dataSource={top || []} columns={topCols} rowKey="path" pagination={false} size="small" /></Card> <Card size="small" title="耗时排行"><Table dataSource={top || []} columns={topCols} rowKey="path" pagination={false} size="small" /></Card>
@ -50,6 +72,46 @@ function MetricsPage() {
<Card size="small" title="最近请求"><Table dataSource={recent || []} columns={recentCols} rowKey="id" pagination={false} size="small" /></Card> <Card size="small" title="最近请求"><Table dataSource={recent || []} columns={recentCols} rowKey="id" pagination={false} size="small" /></Card>
</Col> </Col>
</Row> </Row>
),
},
{
key: 'ai', label: <><CloudOutlined /> AI </>,
children: (
<>
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
<Col span={6}><Card size="small"><Statistic title="AI 总调用" value={ai?.totalCalls || 0} suffix={` / ${ai?.days || 7}d`} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="Provider 数" value={ai?.providers?.length || 0} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="模型数" value={ai?.models?.length || 0} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="平均耗时" value={ai?.providers?.[0]?.avgLatencyMs || 0} suffix="ms" /></Card></Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Card size="small" title="按 Provider"><Table dataSource={ai?.providers || []} columns={aiProviderCols} rowKey="name" pagination={false} size="small" /></Card>
</Col>
<Col span={12}>
<Card size="small" title="按模型"><Table dataSource={ai?.models || []} columns={aiProviderCols} rowKey="name" pagination={false} size="small" /></Card>
</Col>
</Row>
</>
),
},
{
key: 'worker', label: <><NodeIndexOutlined /> Worker </>,
children: (
<>
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
<Col span={6}><Card size="small"><Statistic title="任务总数" value={worker?.totalTasks || 0} suffix={` / ${worker?.days || 7}d`} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="队列数" value={worker?.queues?.length || 0} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="成功率" value={worker?.queues?.[0]?.successRate || '0%'} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="失败" value={worker?.queues?.reduce((s: number, q: any) => s + q.failed, 0) || 0} valueStyle={{ color: '#ff4d4f' }} /></Card></Col>
</Row>
<Card size="small" title="按队列">
<Table dataSource={worker?.queues || []} columns={workerCols} rowKey="name" pagination={false} size="small" />
</Card>
</>
),
},
]} />
</div> </div>
) )
} }