From 4bfad019d00d163edbd3449f9906cea5273cf2d1 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 14 Mar 2026 14:31:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E9=A1=B5=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=81=A5?= =?UTF-8?q?=E5=BA=B7=E4=BA=8B=E4=BB=B6=E8=BF=87=E6=BB=A4=E5=92=8C=E6=B5=81?= =?UTF-8?q?=E9=87=8F=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- atsf_server/controller/agent.go | 2 +- .../web/components/data/rank-chart.tsx | 37 ++++---- .../web/components/data/trend-chart.tsx | 16 ++-- .../nodes/components/node-detail-page.tsx | 91 ++++++++++--------- 4 files changed, 77 insertions(+), 69 deletions(-) diff --git a/atsf_server/controller/agent.go b/atsf_server/controller/agent.go index 049d171a..cdc71b8f 100644 --- a/atsf_server/controller/agent.go +++ b/atsf_server/controller/agent.go @@ -84,7 +84,7 @@ func AgentHeartbeat(c *gin.Context) { func AgentGetActiveConfig(c *gin.Context) { config, err := service.GetActiveConfigForAgent() if err != nil { - respondFailure(c, "褰撳墠娌℃湁婵€娲荤増鏈?") + respondFailure(c, "当前没有激活版本") return } respondSuccess(c, config) diff --git a/atsf_server/web/components/data/rank-chart.tsx b/atsf_server/web/components/data/rank-chart.tsx index 815fd57a..4908e7bd 100644 --- a/atsf_server/web/components/data/rank-chart.tsx +++ b/atsf_server/web/components/data/rank-chart.tsx @@ -18,20 +18,24 @@ type RankChartProps = { const defaultFormatter = (value: number) => value.toLocaleString('zh-CN'); +function getChartValue(params: unknown) { + if (typeof params !== 'object' || params === null || !('value' in params)) { + return 0; + } + const rawValue = (params as { value?: unknown }).value; + if (Array.isArray(rawValue)) { + const candidate = rawValue[0]; + return typeof candidate === 'number' ? candidate : 0; + } + return typeof rawValue === 'number' ? rawValue : 0; +} + export function RankChart({ items, color, valueFormatter = defaultFormatter, emptyMessage = '暂无分布数据', }: RankChartProps) { - if (items.length === 0) { - return ( -
- {emptyMessage} -
- ); - } - const option = useMemo( () => ({ animationDuration: 400, @@ -100,14 +104,7 @@ export function RankChart({ show: true, position: 'right', color: '#e2e8f0', - formatter: (params: any) => { - const rawValue = Array.isArray(params.value) - ? params.value[0] - : params.value; - return valueFormatter( - typeof rawValue === 'number' ? rawValue : 0, - ); - }, + formatter: (params: unknown) => valueFormatter(getChartValue(params)), }, }, ], @@ -115,6 +112,14 @@ export function RankChart({ [color, items, valueFormatter], ); + if (items.length === 0) { + return ( +
+ {emptyMessage} +
+ ); + } + return ( value.toLocaleString('zh-CN'); export function TrendChart({ labels, series, height = 220 }: TrendChartProps) { - if (labels.length === 0 || series.length === 0) { - return ( -
- 暂无趋势数据 -
- ); - } - const option = useMemo(() => { const maxValue = Math.max( @@ -120,6 +112,14 @@ export function TrendChart({ labels, series, height = 220 }: TrendChartProps) { }; }, [labels, series]); + if (labels.length === 0 || series.length === 0) { + return ( +
+ 暂无趋势数据 +
+ ); + } + return (
diff --git a/atsf_server/web/features/nodes/components/node-detail-page.tsx b/atsf_server/web/features/nodes/components/node-detail-page.tsx index 3795e82d..02df12f2 100644 --- a/atsf_server/web/features/nodes/components/node-detail-page.tsx +++ b/atsf_server/web/features/nodes/components/node-detail-page.tsx @@ -499,6 +499,53 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) { ); }, [configVersionsQuery.data]); + const observability = observabilityQuery.data ?? null; + const latestMetricSnapshot = observability?.metric_snapshots?.[0] ?? null; + const activeHealthEvents = useMemo( + () => + observability?.health_events.filter((event) => event.status === 'active') ?? + [], + [observability?.health_events], + ); + const statusCodeDistribution = useMemo( + () => + aggregateTrafficBreakdown( + observability?.traffic_reports ?? [], + 'status_codes_json', + ), + [observability?.traffic_reports], + ); + const topDomains = useMemo( + () => + aggregateTrafficBreakdown( + observability?.traffic_reports ?? [], + 'top_domains_json', + ), + [observability?.traffic_reports], + ); + const resolvedHealthEvents = useMemo( + () => + observability?.health_events.filter( + (event) => event.status === 'resolved', + ) ?? [], + [observability?.health_events], + ); + const filteredHealthEvents = useMemo(() => { + switch (healthEventFilter) { + case 'active': + return activeHealthEvents; + case 'resolved': + return resolvedHealthEvents; + default: + return observability?.health_events ?? []; + } + }, [ + activeHealthEvents, + healthEventFilter, + observability?.health_events, + resolvedHealthEvents, + ]); + if (nodesQuery.isLoading) { return ; } @@ -540,28 +587,6 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) { ? previewAgentReleaseQuery.isFetching : stableAgentReleaseQuery.isFetching; const applyLogs = applyLogsQuery.data ?? []; - const observability = observabilityQuery.data ?? null; - const latestMetricSnapshot = observability?.metric_snapshots?.[0] ?? null; - const latestTrafficReport = observability?.traffic_reports?.[0] ?? null; - const activeHealthEvents = - observability?.health_events.filter((event) => event.status === 'active') ?? - []; - const statusCodeDistribution = useMemo( - () => - aggregateTrafficBreakdown( - observability?.traffic_reports ?? [], - 'status_codes_json', - ), - [observability?.traffic_reports], - ); - const topDomains = useMemo( - () => - aggregateTrafficBreakdown( - observability?.traffic_reports ?? [], - 'top_domains_json', - ), - [observability?.traffic_reports], - ); const dominantStatusCode = statusCodeDistribution[0] ?? null; const dominantDomain = topDomains[0] ?? null; const topSourceCountry = @@ -569,28 +594,6 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) { const trafficSummary = observability?.analytics.traffic ?? null; const healthSummary = observability?.analytics.health ?? null; const latestHealthEvent = activeHealthEvents[0] ?? null; - const resolvedHealthEvents = useMemo( - () => - observability?.health_events.filter( - (event) => event.status === 'resolved', - ) ?? [], - [observability?.health_events], - ); - const filteredHealthEvents = useMemo(() => { - switch (healthEventFilter) { - case 'active': - return activeHealthEvents; - case 'resolved': - return resolvedHealthEvents; - default: - return observability?.health_events ?? []; - } - }, [ - activeHealthEvents, - healthEventFilter, - observability?.health_events, - resolvedHealthEvents, - ]); const memoryUsageRatio = formatUsageRatio( latestMetricSnapshot?.memory_used_bytes, latestMetricSnapshot?.memory_total_bytes,