All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 13s
## 类型安全 - tsconfig: 开启 strict: true(豁免 noImplicitAny + strictNullChecks,待后续收紧) - 新建 15 个领域类型文件 (src/types/),共 ~80 个类型定义 - 统一 barrel export (src/types/index.ts) - 消除 9 个 [key: string]: any 接口 - 消除 22 个 API data: any 参数 → 替换为具体 DTO - 消除 36 个 Promise<any> → 使用具体泛型 - 消除重复 PaginatedResponse,统一使用 PaginatedResult - API 内联类型全部迁至 types/ ## 状态管理 - 引入 Zustand (zustand + persist) - 新建 src/stores/ui-store.ts — 侧边栏折叠状态持久化 - AdminLayout 接入 useUIStore ## 环境配置 - 新建 .env.example — 环境变量模板 - 新建 .env.production — 生产架构文档 - 新建 src/vite-env.d.ts — IDE 类型声明 ## 通用 UI 类型 - 新建 src/types/ui.ts — TypedColumn<T>, SortState, FilterState 等 Co-Authored-By: Claude <noreply@anthropic.com>
120 lines
7.1 KiB
TypeScript
120 lines
7.1 KiB
TypeScript
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
import { Table, Card, Row, Col, Statistic, Button, Typography, Tabs } from 'antd'
|
|
import { ReloadOutlined, DashboardOutlined, CloudOutlined, NodeIndexOutlined } from '@ant-design/icons'
|
|
import { metricsAPI } from '@/api'
|
|
import dayjs from 'dayjs'
|
|
|
|
const { Title } = Typography
|
|
|
|
function MetricsPage() {
|
|
const qc = useQueryClient()
|
|
|
|
const { data: overview } = useQuery({ queryKey: ['metrics', 'overview'], queryFn: () => metricsAPI.overview(), staleTime: 10_000 })
|
|
const { data: top } = useQuery({ queryKey: ['metrics', 'top'], queryFn: () => metricsAPI.top(), staleTime: 10_000 })
|
|
const { data: recent } = useQuery({ queryKey: ['metrics', 'recent'], queryFn: () => metricsAPI.recent(), staleTime: 5_000 })
|
|
const { data: ai } = useQuery({ queryKey: ['metrics', 'ai'], queryFn: () => metricsAPI.ai(), staleTime: 10_000 })
|
|
const { data: worker } = useQuery({ queryKey: ['metrics', 'worker'], queryFn: () => metricsAPI.worker(), staleTime: 10_000 })
|
|
|
|
const topCols = [
|
|
{ title: '接口', dataIndex: 'path', width: 300, ellipsis: true },
|
|
{ title: '方法', dataIndex: 'method', width: 70 },
|
|
{ title: '调用', dataIndex: 'calls', width: 70, align: 'center' as const },
|
|
{ title: '平均耗时', dataIndex: 'avgDuration', width: 100, align: 'center' as const, render: (v: number) => <span style={{ color: v > 1000 ? '#ff4d4f' : v > 500 ? '#faad14' : '#52c41a' }}>{v}ms</span> },
|
|
]
|
|
|
|
const recentCols = [
|
|
{ title: '时间', dataIndex: 'createdAt', width: 140, render: (d: string) => dayjs(d).format('HH:mm:ss') },
|
|
{ title: '接口', dataIndex: 'path', ellipsis: true },
|
|
{ title: '方法', dataIndex: 'method', width: 60 },
|
|
{ title: '状态', dataIndex: 'statusCode', width: 60, render: (v: number) => <span style={{ color: v >= 400 ? '#ff4d4f' : '#52c41a' }}>{v}</span> },
|
|
{ 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 (
|
|
<div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
|
<Title level={5} style={{ margin: 0 }}><DashboardOutlined /> 接口监控</Title>
|
|
<Button icon={<ReloadOutlined />} onClick={() => qc.invalidateQueries({ queryKey: ['metrics'] })}>刷新</Button>
|
|
</div>
|
|
|
|
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
|
<Col span={6}><Card size="small"><Statistic title="今日调用" value={overview?.todayCalls || 0} /></Card></Col>
|
|
<Col span={6}><Card size="small"><Statistic title="平均耗时" value={overview?.avgDuration || 0} suffix="ms" valueStyle={{ color: (overview?.avgDuration || 0) > 500 ? '#ff4d4f' : '#52c41a' }} /></Card></Col>
|
|
<Col span={6}><Card size="small"><Statistic title="错误数" value={overview?.errorCount || 0} valueStyle={{ color: (overview?.errorCount || 0) > 0 ? '#ff4d4f' : '#52c41a' }} /></Card></Col>
|
|
<Col span={6}><Card size="small"><Statistic title="总调用" value={overview?.total || 0} /></Card></Col>
|
|
</Row>
|
|
|
|
<Tabs items={[
|
|
{
|
|
key: 'api', label: 'API 性能',
|
|
children: (
|
|
<Row gutter={[16, 16]}>
|
|
<Col span={12}>
|
|
<Card size="small" title="耗时排行"><Table dataSource={top || []} columns={topCols} rowKey="path" pagination={false} size="small" /></Card>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Card size="small" title="最近请求"><Table dataSource={recent || []} columns={recentCols} rowKey="id" pagination={false} size="small" /></Card>
|
|
</Col>
|
|
</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>
|
|
)
|
|
}
|
|
|
|
export default MetricsPage
|