1
0

feat: 前端指标体系增强 — Dashboard/Metrics API、2×4 统计区、趋势图面积+异常标记、连续状态列

- 新增 GET /api/dashboard 合并原 summary+targets 首屏接口
- 新增 GET /api/targets/:id/metrics 合并原 stats+trend 概览接口
- 后端指标纯函数:可用率、百分位、故障段分析、连续状态、UTC 小时分桶
- ProbeStore 窗口取数方法替代全量历史查询
- SummaryCards 扩展为 4 卡片(新增异常事件数)+ 数据新鲜度展示
- 表格新增「连续」列(Tag 渲染 capped 状态)
- OverviewTab 重构为 2×4 Statistic 多维度布局
- TrendChart 改为延迟范围面积图 + 红色异常标记点
- 删除旧路由(summary/targets/trend)和 computeTrendStats
- 同步 delta specs 到主 specs 并归档变更
This commit is contained in:
2026-05-14 12:32:41 +08:00
parent e983e5d75d
commit 1c5cfafda6
47 changed files with 1768 additions and 1231 deletions

View File

@@ -1,11 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import type { MetaResponse, SummaryResponse, TargetStatus } from "../../shared/api";
import type { DashboardResponse, MetaResponse, TargetMetricsResponse } from "../../shared/api";
const queryKeys = {
dashboard: () => ["dashboard", "24h", 30] as const,
meta: () => ["meta"] as const,
summary: () => ["summary"] as const,
targets: () => ["targets"] as const,
metrics: (targetId: number, from: string, to: string, bucket: "1h") =>
["metrics", targetId, from, to, bucket] as const,
};
export async function fetchJson<T>(url: string): Promise<T> {
@@ -14,6 +15,15 @@ export async function fetchJson<T>(url: string): Promise<T> {
return response.json() as Promise<T>;
}
export function useDashboard() {
return useQuery({
queryFn: () => fetchJson<DashboardResponse>("/api/dashboard?window=24h&recentLimit=30"),
queryKey: queryKeys.dashboard(),
refetchInterval: 8000,
refetchIntervalInBackground: false,
});
}
export function useMeta() {
return useQuery({
queryFn: () => fetchJson<MetaResponse>("/api/meta"),
@@ -22,20 +32,15 @@ export function useMeta() {
});
}
export function useSummary() {
export function useTargetMetrics(targetId: null | number, from: string, to: string, bucket: "1h") {
return useQuery({
queryFn: () => fetchJson<SummaryResponse>("/api/summary"),
queryKey: queryKeys.summary(),
refetchInterval: 8000,
refetchIntervalInBackground: false,
});
}
export function useTargets() {
return useQuery({
queryFn: () => fetchJson<TargetStatus[]>("/api/targets"),
queryKey: queryKeys.targets(),
refetchInterval: 8000,
refetchIntervalInBackground: false,
enabled: targetId !== null && !!from && !!to,
queryFn: () => {
if (targetId === null) throw new Error("未选择目标");
return fetchJson<TargetMetricsResponse>(
`/api/targets/${targetId}/metrics?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&bucket=${bucket}`,
);
},
queryKey: targetId !== null && from && to ? queryKeys.metrics(targetId, from, to, bucket) : ["metrics", "disabled"],
});
}