From fe3c6312f966eff5812ac571586b85a867d3891b Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 14 Mar 2026 16:00:41 +0800 Subject: [PATCH] Refactor dashboard overview: remove active alerts and lagging nodes from summary, update tests and components accordingly - Removed active alerts and lagging nodes from DashboardSummary and related types. - Updated DashboardOverview component to reflect changes in data structure. - Adjusted tests to align with the new dashboard overview structure, ensuring no active alerts are displayed. - Simplified the dashboard metrics and risk signals, focusing on essential health and capacity metrics. - Enhanced the WorldStage component to present updated traffic and capacity information. - Removed unused alert-related functions and components to streamline the codebase. --- atsf_server/service/dashboard.go | 192 ------- atsf_server/service/node_update_test.go | 34 +- .../web/features/dashboard/api/overview.ts | 1 - .../components/dashboard-overview.tsx | 481 ++++-------------- .../dashboard/components/world-stage-map.tsx | 4 +- .../dashboard/components/world-stage.tsx | 152 +----- atsf_server/web/features/dashboard/types.ts | 58 --- .../tests/unit/dashboard-overview.test.tsx | 96 ---- 8 files changed, 118 insertions(+), 900 deletions(-) diff --git a/atsf_server/service/dashboard.go b/atsf_server/service/dashboard.go index 99ef7835..552370ea 100644 --- a/atsf_server/service/dashboard.go +++ b/atsf_server/service/dashboard.go @@ -2,11 +2,8 @@ package service import ( "atsflare/model" - "errors" "sort" "time" - - "gorm.io/gorm" ) type DashboardOverviewView struct { @@ -14,13 +11,9 @@ type DashboardOverviewView struct { Summary DashboardSummary `json:"summary"` Traffic DashboardTraffic `json:"traffic"` Capacity DashboardCapacity `json:"capacity"` - Config DashboardConfig `json:"config"` - Risk DashboardRiskSummary `json:"risk"` - Peaks DashboardPeakSummary `json:"peaks"` Distributions TrafficDistributions `json:"distributions"` Trends DashboardTrends `json:"trends"` Nodes []DashboardNodeHealth `json:"nodes"` - ActiveAlerts []DashboardAlert `json:"active_alerts"` } type DashboardSummary struct { @@ -29,8 +22,6 @@ type DashboardSummary struct { OfflineNodes int `json:"offline_nodes"` PendingNodes int `json:"pending_nodes"` UnhealthyNodes int `json:"unhealthy_nodes"` - ActiveAlerts int `json:"active_alerts"` - LaggingNodes int `json:"lagging_nodes"` } type DashboardTraffic struct { @@ -49,48 +40,6 @@ type DashboardCapacity struct { HighStorageNodes int `json:"high_storage_nodes"` } -type DashboardConfig struct { - ActiveVersion string `json:"active_version"` - LaggingNodes int `json:"lagging_nodes"` - PendingNodes int `json:"pending_nodes"` -} - -type DashboardRiskSummary struct { - CriticalAlerts int `json:"critical_alerts"` - WarningAlerts int `json:"warning_alerts"` - InfoAlerts int `json:"info_alerts"` - OfflineNodes int `json:"offline_nodes"` - UnhealthyNodes int `json:"unhealthy_nodes"` - LaggingNodes int `json:"lagging_nodes"` - HighCPUNodes int `json:"high_cpu_nodes"` - HighMemoryNodes int `json:"high_memory_nodes"` - HighStorageNodes int `json:"high_storage_nodes"` -} - -type DashboardPeakSummary struct { - PeakRequestHour DashboardPeakHour `json:"peak_request_hour"` - PeakErrorHour DashboardPeakHour `json:"peak_error_hour"` - BusiestNode *DashboardPeakNode `json:"busiest_node"` - RiskiestNode *DashboardPeakNode `json:"riskiest_node"` -} - -type DashboardPeakHour struct { - BucketStartedAt time.Time `json:"bucket_started_at"` - RequestCount int64 `json:"request_count"` - ErrorCount int64 `json:"error_count"` -} - -type DashboardPeakNode struct { - NodeID string `json:"node_id"` - NodeName string `json:"node_name"` - RequestCount int64 `json:"request_count"` - ErrorCount int64 `json:"error_count"` - CPUUsagePercent float64 `json:"cpu_usage_percent"` - ActiveEventCount int `json:"active_event_count"` - OpenrestyStatus string `json:"openresty_status"` - StorageUsagePercent float64 `json:"storage_usage_percent"` -} - type DashboardTrends struct { Traffic24h []TrafficTrendPoint `json:"traffic_24h"` Capacity24h []CapacityTrendPoint `json:"capacity_24h"` @@ -118,16 +67,6 @@ type DashboardNodeHealth struct { UniqueVisitorCount int64 `json:"unique_visitor_count"` } -type DashboardAlert struct { - NodeID string `json:"node_id"` - NodeName string `json:"node_name"` - EventType string `json:"event_type"` - Severity string `json:"severity"` - Message string `json:"message"` - LastTriggeredAt time.Time `json:"last_triggered_at"` - Status string `json:"status"` -} - func GetDashboardOverview() (*DashboardOverviewView, error) { now := time.Now() since := now.Add(-24 * time.Hour) @@ -137,13 +76,6 @@ func GetDashboardOverview() (*DashboardOverviewView, error) { return nil, err } - activeVersion := "" - if version, versionErr := model.GetActiveConfigVersion(); versionErr == nil { - activeVersion = version.Version - } else if !errors.Is(versionErr, gorm.ErrRecordNotFound) { - return nil, versionErr - } - snapshots, err := model.ListMetricSnapshotsSince(since) if err != nil { return nil, err @@ -160,7 +92,6 @@ func GetDashboardOverview() (*DashboardOverviewView, error) { view := &DashboardOverviewView{ GeneratedAt: now, Nodes: make([]DashboardNodeHealth, 0, len(nodes)), - ActiveAlerts: make([]DashboardAlert, 0), Distributions: buildTrafficDistributions(reports, 8), Trends: DashboardTrends{ Traffic24h: buildTrafficTrendPoints(now, reports), @@ -183,21 +114,11 @@ func GetDashboardOverview() (*DashboardOverviewView, error) { view.Summary.OnlineNodes++ case NodeStatusOffline: view.Summary.OfflineNodes++ - view.Risk.OfflineNodes++ case NodeStatusPending: view.Summary.PendingNodes++ } if node.OpenrestyStatus == OpenrestyStatusUnhealthy { view.Summary.UnhealthyNodes++ - view.Risk.UnhealthyNodes++ - } - if activeVersion != "" && node.CurrentVersion != "" && node.CurrentVersion != activeVersion { - view.Summary.LaggingNodes++ - view.Risk.LaggingNodes++ - } - if activeVersion != "" && node.CurrentVersion == "" && computedStatus != NodeStatusPending { - view.Summary.LaggingNodes++ - view.Risk.LaggingNodes++ } latestSnapshot := latestSnapshots[node.NodeID] @@ -218,26 +139,6 @@ func GetDashboardOverview() (*DashboardOverviewView, error) { ActiveEventCount: len(nodeActiveEvents), } - for _, event := range nodeActiveEvents { - view.ActiveAlerts = append(view.ActiveAlerts, DashboardAlert{ - NodeID: node.NodeID, - NodeName: node.Name, - EventType: event.EventType, - Severity: event.Severity, - Message: event.Message, - LastTriggeredAt: event.LastTriggeredAt, - Status: event.Status, - }) - switch event.Severity { - case NodeHealthSeverityCritical: - view.Risk.CriticalAlerts++ - case NodeHealthSeverityWarning: - view.Risk.WarningAlerts++ - default: - view.Risk.InfoAlerts++ - } - } - if latestSnapshot != nil { nodeHealth.CPUUsagePercent = latestSnapshot.CPUUsagePercent nodeHealth.MemoryUsagePercent = percentage(latestSnapshot.MemoryUsedBytes, latestSnapshot.MemoryTotalBytes) @@ -252,15 +153,12 @@ func GetDashboardOverview() (*DashboardOverviewView, error) { } if latestSnapshot.CPUUsagePercent >= 80 { view.Capacity.HighCPUNodes++ - view.Risk.HighCPUNodes++ } if nodeHealth.MemoryUsagePercent >= 85 { view.Capacity.HighMemoryNodes++ - view.Risk.HighMemoryNodes++ } if nodeHealth.StorageUsagePercent >= 85 { view.Capacity.HighStorageNodes++ - view.Risk.HighStorageNodes++ } } @@ -277,14 +175,10 @@ func GetDashboardOverview() (*DashboardOverviewView, error) { view.Traffic.ReportedNodes++ } - view.Summary.ActiveAlerts += len(nodeActiveEvents) view.Nodes = append(view.Nodes, nodeHealth) } view.Summary.TotalNodes = len(nodes) - view.Config.ActiveVersion = activeVersion - view.Config.LaggingNodes = view.Summary.LaggingNodes - view.Config.PendingNodes = view.Summary.PendingNodes if cpuNodeCount > 0 { view.Capacity.AverageCPUUsagePercent /= float64(cpuNodeCount) @@ -293,13 +187,6 @@ func GetDashboardOverview() (*DashboardOverviewView, error) { view.Capacity.AverageMemoryUsagePercent /= float64(memoryNodeCount) } - sort.Slice(view.ActiveAlerts, func(i int, j int) bool { - if severityWeight(view.ActiveAlerts[i].Severity) == severityWeight(view.ActiveAlerts[j].Severity) { - return view.ActiveAlerts[i].LastTriggeredAt.After(view.ActiveAlerts[j].LastTriggeredAt) - } - return severityWeight(view.ActiveAlerts[i].Severity) > severityWeight(view.ActiveAlerts[j].Severity) - }) - sort.Slice(view.Nodes, func(i int, j int) bool { if view.Nodes[i].ActiveEventCount == view.Nodes[j].ActiveEventCount { return view.Nodes[i].CPUUsagePercent > view.Nodes[j].CPUUsagePercent @@ -307,19 +194,6 @@ func GetDashboardOverview() (*DashboardOverviewView, error) { return view.Nodes[i].ActiveEventCount > view.Nodes[j].ActiveEventCount }) - if len(view.ActiveAlerts) > 8 { - view.ActiveAlerts = view.ActiveAlerts[:8] - } - - view.Peaks.PeakRequestHour = peakTrafficHour(view.Trends.Traffic24h, func(point TrafficTrendPoint) int64 { - return point.RequestCount - }) - view.Peaks.PeakErrorHour = peakTrafficHour(view.Trends.Traffic24h, func(point TrafficTrendPoint) int64 { - return point.ErrorCount - }) - view.Peaks.BusiestNode = busiestDashboardNode(view.Nodes) - view.Peaks.RiskiestNode = riskiestDashboardNode(view.Nodes) - return view, nil } @@ -368,69 +242,3 @@ func activeHealthEventsByNode(events []*model.NodeHealthEvent) map[string][]*mod } return result } - -func severityWeight(severity string) int { - switch severity { - case NodeHealthSeverityCritical: - return 3 - case NodeHealthSeverityWarning: - return 2 - default: - return 1 - } -} - -func peakTrafficHour(points []TrafficTrendPoint, selector func(point TrafficTrendPoint) int64) DashboardPeakHour { - var result DashboardPeakHour - var maxValue int64 = -1 - for _, point := range points { - value := selector(point) - if value <= maxValue { - continue - } - maxValue = value - result = DashboardPeakHour{ - BucketStartedAt: point.BucketStartedAt, - RequestCount: point.RequestCount, - ErrorCount: point.ErrorCount, - } - } - return result -} - -func busiestDashboardNode(nodes []DashboardNodeHealth) *DashboardPeakNode { - var selected *DashboardPeakNode - for _, node := range nodes { - candidate := &DashboardPeakNode{ - NodeID: node.NodeID, - NodeName: node.Name, - RequestCount: node.RequestCount, - ErrorCount: node.ErrorCount, - CPUUsagePercent: node.CPUUsagePercent, - ActiveEventCount: node.ActiveEventCount, - OpenrestyStatus: node.OpenrestyStatus, - StorageUsagePercent: node.StorageUsagePercent, - } - if selected == nil || candidate.RequestCount > selected.RequestCount || (candidate.RequestCount == selected.RequestCount && candidate.ErrorCount > selected.ErrorCount) { - selected = candidate - } - } - return selected -} - -func riskiestDashboardNode(nodes []DashboardNodeHealth) *DashboardPeakNode { - if len(nodes) == 0 { - return nil - } - node := nodes[0] - return &DashboardPeakNode{ - NodeID: node.NodeID, - NodeName: node.Name, - RequestCount: node.RequestCount, - ErrorCount: node.ErrorCount, - CPUUsagePercent: node.CPUUsagePercent, - ActiveEventCount: node.ActiveEventCount, - OpenrestyStatus: node.OpenrestyStatus, - StorageUsagePercent: node.StorageUsagePercent, - } -} diff --git a/atsf_server/service/node_update_test.go b/atsf_server/service/node_update_test.go index f4f96c89..febac672 100644 --- a/atsf_server/service/node_update_test.go +++ b/atsf_server/service/node_update_test.go @@ -1005,24 +1005,24 @@ func TestGetDashboardOverview(t *testing.T) { if view.Summary.TotalNodes != 2 || view.Summary.OnlineNodes != 2 { t.Fatalf("unexpected dashboard summary: %+v", view.Summary) } - if view.Summary.UnhealthyNodes != 1 || view.Summary.ActiveAlerts != 1 { - t.Fatalf("unexpected unhealthy/alert summary: %+v", view.Summary) + if view.Summary.UnhealthyNodes != 1 { + t.Fatalf("unexpected unhealthy summary: %+v", view.Summary) } if view.Traffic.RequestCount != 900 || view.Traffic.ErrorCount != 36 { t.Fatalf("unexpected dashboard traffic: %+v", view.Traffic) } - if view.Config.ActiveVersion != "20260314-001" || view.Config.LaggingNodes != 1 { - t.Fatalf("unexpected dashboard config summary: %+v", view.Config) + if view.Capacity.HighCPUNodes != 1 || view.Capacity.HighMemoryNodes != 1 { + t.Fatalf("unexpected dashboard capacity summary: %+v", view.Capacity) } - if view.Risk.CriticalAlerts != 1 || view.Risk.HighCPUNodes != 1 || view.Risk.HighMemoryNodes != 1 { - t.Fatalf("unexpected dashboard risk summary: %+v", view.Risk) - } - if len(view.Nodes) != 2 || len(view.ActiveAlerts) != 1 { - t.Fatalf("unexpected dashboard nodes/alerts: %+v %+v", view.Nodes, view.ActiveAlerts) + if len(view.Nodes) != 2 { + t.Fatalf("unexpected dashboard nodes: %+v", view.Nodes) } if view.Nodes[0].GeoName == "" && view.Nodes[1].GeoName == "" { t.Fatalf("expected dashboard nodes to expose geo metadata: %+v", view.Nodes) } + if view.Nodes[0].ActiveEventCount != 1 { + t.Fatalf("expected dashboard nodes to preserve active event counts: %+v", view.Nodes) + } if len(view.Trends.Traffic24h) != 24 || len(view.Trends.Capacity24h) != 24 || len(view.Trends.Network24h) != 24 || len(view.Trends.DiskIO24h) != 24 { t.Fatalf("expected 24-point dashboard trends, got %+v", view.Trends) } @@ -1044,25 +1044,19 @@ func TestGetDashboardOverview(t *testing.T) { if len(view.Distributions.TopDomains) == 0 || view.Distributions.TopDomains[0].Key != "app.example.com" { t.Fatalf("unexpected dashboard domain distributions: %+v", view.Distributions.TopDomains) } - if view.Peaks.BusiestNode == nil || view.Peaks.BusiestNode.NodeID != "node-dashboard-a" { - t.Fatalf("unexpected busiest node: %+v", view.Peaks.BusiestNode) - } - if view.Peaks.RiskiestNode == nil || view.Peaks.RiskiestNode.NodeID != "node-dashboard-b" { - t.Fatalf("unexpected riskiest node: %+v", view.Peaks.RiskiestNode) - } } -func TestGetDashboardOverviewReturnsEmptyAlertSlice(t *testing.T) { +func TestGetDashboardOverviewReturnsEmptyNodeSlice(t *testing.T) { setupServiceTestDB(t) view, err := GetDashboardOverview() if err != nil { t.Fatalf("GetDashboardOverview failed: %v", err) } - if view.ActiveAlerts == nil { - t.Fatalf("expected active alerts to be an empty slice, got nil") + if view.Nodes == nil { + t.Fatalf("expected nodes to be an empty slice, got nil") } - if len(view.ActiveAlerts) != 0 { - t.Fatalf("expected empty active alerts, got %+v", view.ActiveAlerts) + if len(view.Nodes) != 0 { + t.Fatalf("expected empty nodes, got %+v", view.Nodes) } } diff --git a/atsf_server/web/features/dashboard/api/overview.ts b/atsf_server/web/features/dashboard/api/overview.ts index e1c51cf7..ffba93f0 100644 --- a/atsf_server/web/features/dashboard/api/overview.ts +++ b/atsf_server/web/features/dashboard/api/overview.ts @@ -16,7 +16,6 @@ function normalizeDashboardOverview( return { ...overview, nodes: arrayOrEmpty(overview.nodes), - active_alerts: arrayOrEmpty(overview.active_alerts), distributions: { ...overview.distributions, source_countries: arrayOrEmpty(overview.distributions?.source_countries), diff --git a/atsf_server/web/features/dashboard/components/dashboard-overview.tsx b/atsf_server/web/features/dashboard/components/dashboard-overview.tsx index f9cfbab9..61f9c44d 100644 --- a/atsf_server/web/features/dashboard/components/dashboard-overview.tsx +++ b/atsf_server/web/features/dashboard/components/dashboard-overview.tsx @@ -11,11 +11,7 @@ import { LoadingState } from '@/components/feedback/loading-state'; import { AppCard } from '@/components/ui/app-card'; import { StatusBadge } from '@/components/ui/status-badge'; import { getDashboardOverview } from '@/features/dashboard/api/overview'; -import type { - DashboardAlert, - DashboardNodeHealth, - DashboardPeakNode, -} from '@/features/dashboard/types'; +import type { DashboardNodeHealth } from '@/features/dashboard/types'; import { WorldStage } from '@/features/dashboard/components/world-stage'; import { getNodeStatusLabel, @@ -59,48 +55,6 @@ function formatTrendHour(value: string) { return `${date.getHours().toString().padStart(2, '0')}:00`; } -function formatPeakHour(value: string) { - if (!value) { - return '暂无'; - } - const date = new Date(value); - if (Number.isNaN(date.getTime())) { - return '暂无'; - } - return `${formatTrendHour(value)} - ${date - .getMinutes() - .toString() - .padStart(2, '0')}`; -} - -function getAlertVariant( - alert: DashboardAlert, -): 'success' | 'warning' | 'danger' | 'info' { - if (alert.status === 'resolved') { - return 'success'; - } - if (alert.severity === 'critical') { - return 'danger'; - } - if (alert.severity === 'warning') { - return 'warning'; - } - return 'info'; -} - -function formatPeakNode(node: DashboardPeakNode | null) { - if (!node) { - return { - title: '暂无', - hint: '当前没有可用节点数据', - }; - } - return { - title: node.node_name, - hint: `请求 ${node.request_count} · 错误 ${node.error_count} · CPU ${formatPercent(node.cpu_usage_percent)}`, - }; -} - function buildNodeDetailHref(id?: number | null) { if (!id) { return '/node'; @@ -173,82 +127,6 @@ function OverviewMetric({ ); } -function RiskSignal({ - label, - value, - tone, - hint, - href, -}: { - label: string; - value: number; - tone: 'danger' | 'warning' | 'info' | 'success'; - hint: string; - href?: string; -}) { - const toneClass = - tone === 'danger' - ? 'border-rose-400/28 bg-rose-500/10 text-rose-100' - : tone === 'warning' - ? 'border-amber-400/28 bg-amber-500/10 text-amber-100' - : tone === 'success' - ? 'border-emerald-400/28 bg-emerald-500/10 text-emerald-100' - : 'border-sky-400/28 bg-sky-500/10 text-sky-100'; - - const content = ( -
-

{label}

-

{value}

-

{hint}

-
- ); - - if (!href) { - return content; - } - - return ( - - {content} - - ); -} - -function PeakCard({ - label, - value, - hint, - href, -}: { - label: string; - value: string; - hint: string; - href?: string; -}) { - const content = ( -
-

- {label} -

-

- {value} -

-

{hint}

-
- ); - if (!href) { - return content; - } - return ( - - {content} - - ); -} - function NodeHealthRow({ node }: { node: DashboardNodeHealth }) { return ( [node.node_id, node.id] as const), - ); - return (
-
- -
- - - - - - -
-
- - -
- - - - -
- -
- - - - - - -
-
-
+ +
+ + + + + + +
+
-
+
- - {overview.active_alerts.length === 0 ? ( - - ) : ( -
- {overview.active_alerts.map((alert) => ( - -
- -

- {alert.node_name} -

-
-

- {alert.message || '暂无详细消息'} -

-

- {formatRelativeTime(alert.last_triggered_at)} ·{' '} - {formatDateTime(alert.last_triggered_at)} -

- - ))} -
- )} -
-
- -
- - ({ - label: item.key, - value: item.value, - }))} - color="#38bdf8" - emptyMessage="暂无来源分布数据" - /> - - - - ({ - label: `HTTP ${item.key}`, - value: item.value, - }))} - color="#f59e0b" - emptyMessage="暂无状态码分布" - /> - - - - ({ - label: item.key, - value: item.value, - }))} - color="#34d399" - emptyMessage="暂无域名分布" - /> - -
- -
@@ -760,61 +465,49 @@ export function DashboardOverview() {
+
+ +
+ + ({ + label: item.key, + value: item.value, + }))} + color="#38bdf8" + emptyMessage="暂无来源分布数据" + /> + -
-
-

- 第一优先级 -

-

- 先处理活动异常与 OpenResty 不健康节点 -

-

- 当前有 {overview.summary.active_alerts} 个活动异常,{` `} - {overview.summary.unhealthy_nodes} 个 OpenResty 不健康节点。 -

-
+ ({ + label: `HTTP ${item.key}`, + value: item.value, + }))} + color="#f59e0b" + emptyMessage="暂无状态码分布" + /> + -
-

- 第二优先级 -

-

- 检查配置落后与容量逼近节点 -

-

- 当前有 {overview.summary.lagging_nodes} 个配置落后节点,{` `} - {overview.capacity.high_cpu_nodes + - overview.capacity.high_memory_nodes + - overview.capacity.high_storage_nodes}{' '} - 个节点出现明显容量压力。 -

-
- -
-

- 观察项 -

-

- 关注峰值时段与来源分布变化 -

-

- 请求峰值出现在 {formatPeakHour( - overview.peaks.peak_request_hour.bucket_started_at, - )} - ,当前主要来源集中在 - {overview.distributions.source_countries - .slice(0, 2) - .map((item) => item.key) - .join(' / ') || ' 暂无来源数据'} - 。 -

-
-
+ + ({ + label: item.key, + value: item.value, + }))} + color="#34d399" + emptyMessage="暂无域名分布" + />
diff --git a/atsf_server/web/features/dashboard/components/world-stage-map.tsx b/atsf_server/web/features/dashboard/components/world-stage-map.tsx index 994d47c9..01c9f3ce 100644 --- a/atsf_server/web/features/dashboard/components/world-stage-map.tsx +++ b/atsf_server/web/features/dashboard/components/world-stage-map.tsx @@ -285,8 +285,8 @@ export function WorldStageMap({ roam: false, silent: true, layoutCenter: ['50%', '50%'], - layoutSize: '132%', - zoom: 1.12, + layoutSize: '220%', + zoom: 1.2, itemStyle: { areaColor: mapPalette.areaColor, borderColor: mapPalette.borderColor, diff --git a/atsf_server/web/features/dashboard/components/world-stage.tsx b/atsf_server/web/features/dashboard/components/world-stage.tsx index 79cb323f..88099945 100644 --- a/atsf_server/web/features/dashboard/components/world-stage.tsx +++ b/atsf_server/web/features/dashboard/components/world-stage.tsx @@ -1,25 +1,17 @@ 'use client'; -import Link from 'next/link'; import dynamic from 'next/dynamic'; import { useEffect, useRef, useState } from 'react'; import { EmptyState } from '@/components/feedback/empty-state'; import { useTheme } from '@/components/providers/theme-provider'; -import { StatusBadge } from '@/components/ui/status-badge'; import type { - DashboardConfig, + DashboardCapacity, DashboardNodeHealth, - DashboardRiskSummary, DashboardSummary, + DashboardTraffic, DistributionItem, } from '@/features/dashboard/types'; -import { - getNodeStatusLabel, - getNodeStatusVariant, - getOpenrestyStatusLabel, - getOpenrestyStatusVariant, -} from '@/features/nodes/utils'; import { cn } from '@/lib/utils/cn'; import { formatDateTime } from '@/lib/utils/date'; @@ -40,13 +32,6 @@ function formatPercent(value: number) { return `${value.toFixed(value >= 100 ? 0 : 1)}%`; } -function buildNodeDetailHref(id?: number | null) { - if (!id) { - return '/node'; - } - return `/node/detail?id=${id}`; -} - function HeroMetric({ label, value, @@ -174,15 +159,15 @@ function CountrySignal({ export function WorldStage({ generatedAt, summary, - risk, - config, + traffic, + capacity, nodes, sourceCountries, }: { generatedAt: string; summary: DashboardSummary; - risk: DashboardRiskSummary; - config: DashboardConfig; + traffic: DashboardTraffic; + capacity: DashboardCapacity; nodes: DashboardNodeHealth[]; sourceCountries: DistributionItem[]; }) { @@ -223,15 +208,9 @@ export function WorldStage({ summary.total_nodes > 0 ? (summary.online_nodes / summary.total_nodes) * 100 : 0; - const syncedNodes = Math.max( - 0, - summary.total_nodes - summary.lagging_nodes - summary.pending_nodes, - ); - const syncRate = - summary.total_nodes > 0 ? (syncedNodes / summary.total_nodes) * 100 : 0; const healthyNodes = Math.max( 0, - summary.online_nodes - summary.unhealthy_nodes - risk.offline_nodes, + summary.online_nodes - summary.unhealthy_nodes, ); const healthyRate = summary.total_nodes > 0 ? (healthyNodes / summary.total_nodes) * 100 : 0; @@ -280,7 +259,7 @@ export function WorldStage({ isDark ? 'text-slate-300' : 'text-slate-600', )} > - 在同一视图汇总节点在线率、运行状态、配置一致性与全球流量来源, + 在同一视图汇总节点在线率、运行状态、资源负载与全球流量来源, 让首屏可以直接承担值守与研判入口。

@@ -396,7 +375,7 @@ export function WorldStage({
-
+
- -
-
-
-

- 节点风险清单 -

-

- 优先处置节点 -

-
- - 查看全部节点 - -
-
- {nodes.slice(0, 4).map((node) => ( - -
-
-

- {node.name} -

-

- 请求量 {node.request_count.toLocaleString('zh-CN')} · 错误数{' '} - {node.error_count.toLocaleString('zh-CN')} -

-
-
- - -
-
- - ))} - {nodes.length === 0 ? ( -
- 当前暂无节点运行数据。节点开始上报 heartbeat 后,这里会展示待优先处理的节点。 -
- ) : null} -
-
diff --git a/atsf_server/web/features/dashboard/types.ts b/atsf_server/web/features/dashboard/types.ts index a4dee4df..944cf24a 100644 --- a/atsf_server/web/features/dashboard/types.ts +++ b/atsf_server/web/features/dashboard/types.ts @@ -4,8 +4,6 @@ export interface DashboardSummary { offline_nodes: number; pending_nodes: number; unhealthy_nodes: number; - active_alerts: number; - lagging_nodes: number; } export interface DashboardTraffic { @@ -24,48 +22,6 @@ export interface DashboardCapacity { high_storage_nodes: number; } -export interface DashboardConfig { - active_version: string; - lagging_nodes: number; - pending_nodes: number; -} - -export interface DashboardRiskSummary { - critical_alerts: number; - warning_alerts: number; - info_alerts: number; - offline_nodes: number; - unhealthy_nodes: number; - lagging_nodes: number; - high_cpu_nodes: number; - high_memory_nodes: number; - high_storage_nodes: number; -} - -export interface DashboardPeakHour { - bucket_started_at: string; - request_count: number; - error_count: number; -} - -export interface DashboardPeakNode { - node_id: string; - node_name: string; - request_count: number; - error_count: number; - cpu_usage_percent: number; - active_event_count: number; - openresty_status: 'healthy' | 'unhealthy' | 'unknown'; - storage_usage_percent: number; -} - -export interface DashboardPeakSummary { - peak_request_hour: DashboardPeakHour; - peak_error_hour: DashboardPeakHour; - busiest_node: DashboardPeakNode | null; - riskiest_node: DashboardPeakNode | null; -} - export interface DistributionItem { key: string; value: number; @@ -134,26 +90,12 @@ export interface DashboardNodeHealth { unique_visitor_count: number; } -export interface DashboardAlert { - node_id: string; - node_name: string; - event_type: string; - severity: 'info' | 'warning' | 'critical'; - message: string; - last_triggered_at: string; - status: 'active' | 'resolved'; -} - export interface DashboardOverview { generated_at: string; summary: DashboardSummary; traffic: DashboardTraffic; capacity: DashboardCapacity; - config: DashboardConfig; - risk: DashboardRiskSummary; - peaks: DashboardPeakSummary; distributions: TrafficDistributions; trends: DashboardTrends; nodes: DashboardNodeHealth[]; - active_alerts: DashboardAlert[]; } diff --git a/atsf_server/web/tests/unit/dashboard-overview.test.tsx b/atsf_server/web/tests/unit/dashboard-overview.test.tsx index 9ad8c405..de80acad 100644 --- a/atsf_server/web/tests/unit/dashboard-overview.test.tsx +++ b/atsf_server/web/tests/unit/dashboard-overview.test.tsx @@ -71,8 +71,6 @@ describe('DashboardOverview', () => { offline_nodes: 0, pending_nodes: 0, unhealthy_nodes: 1, - active_alerts: 1, - lagging_nodes: 1, }, traffic: { request_count: 900, @@ -88,54 +86,6 @@ describe('DashboardOverview', () => { high_memory_nodes: 1, high_storage_nodes: 1, }, - config: { - active_version: '20260314-001', - lagging_nodes: 1, - pending_nodes: 0, - }, - risk: { - critical_alerts: 1, - warning_alerts: 2, - info_alerts: 0, - offline_nodes: 0, - unhealthy_nodes: 1, - lagging_nodes: 1, - high_cpu_nodes: 1, - high_memory_nodes: 1, - high_storage_nodes: 1, - }, - peaks: { - peak_request_hour: { - bucket_started_at: '2026-03-14T08:00:00Z', - request_count: 900, - error_count: 36, - }, - peak_error_hour: { - bucket_started_at: '2026-03-14T09:00:00Z', - request_count: 400, - error_count: 60, - }, - busiest_node: { - node_id: 'node-a', - node_name: 'edge-a', - request_count: 600, - error_count: 6, - cpu_usage_percent: 45, - active_event_count: 0, - openresty_status: 'healthy', - storage_usage_percent: 60, - }, - riskiest_node: { - node_id: 'node-b', - node_name: 'edge-b', - request_count: 300, - error_count: 30, - cpu_usage_percent: 92, - active_event_count: 2, - openresty_status: 'unhealthy', - storage_usage_percent: 95, - }, - }, distributions: { source_countries: [ { key: 'CN', value: 440 }, @@ -220,17 +170,6 @@ describe('DashboardOverview', () => { unique_visitor_count: 80, }, ], - active_alerts: [ - { - node_id: 'node-b', - node_name: 'edge-b', - event_type: 'openresty_unhealthy', - severity: 'critical', - message: 'reload failed', - last_triggered_at: '2026-03-14T07:59:00Z', - status: 'active', - }, - ], }, }), ), @@ -249,7 +188,6 @@ describe('DashboardOverview', () => { expect(await screen.findByText('来源分布')).toBeInTheDocument(); expect(await screen.findByText('Top Domain')).toBeInTheDocument(); expect(await screen.findByText('Top 节点榜单')).toBeInTheDocument(); - expect(await screen.findByText('处置建议')).toBeInTheDocument(); expect(await screen.findByText('节点健康列表')).toBeInTheDocument(); }); @@ -275,8 +213,6 @@ describe('DashboardOverview', () => { offline_nodes: 0, pending_nodes: 0, unhealthy_nodes: 0, - active_alerts: 0, - lagging_nodes: 0, }, traffic: { request_count: 0, @@ -292,36 +228,6 @@ describe('DashboardOverview', () => { high_memory_nodes: 0, high_storage_nodes: 0, }, - config: { - active_version: '', - lagging_nodes: 0, - pending_nodes: 0, - }, - risk: { - critical_alerts: 0, - warning_alerts: 0, - info_alerts: 0, - offline_nodes: 0, - unhealthy_nodes: 0, - lagging_nodes: 0, - high_cpu_nodes: 0, - high_memory_nodes: 0, - high_storage_nodes: 0, - }, - peaks: { - peak_request_hour: { - bucket_started_at: '', - request_count: 0, - error_count: 0, - }, - peak_error_hour: { - bucket_started_at: '', - request_count: 0, - error_count: 0, - }, - busiest_node: null, - riskiest_node: null, - }, distributions: { source_countries: null, status_codes: null, @@ -334,7 +240,6 @@ describe('DashboardOverview', () => { disk_io_24h: null, }, nodes: null, - active_alerts: null, }, }), ), @@ -347,7 +252,6 @@ describe('DashboardOverview', () => { renderDashboardOverview(); - expect(await screen.findByText('暂无活动异常')).toBeInTheDocument(); expect(await screen.findByText('暂无节点')).toBeInTheDocument(); expect(await screen.findByText('暂无来源分布数据')).toBeInTheDocument(); });