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}
-- {label} -
-- {value} -
-{hint}
-- {alert.node_name} -
-- {alert.message || '暂无详细消息'} -
-- {formatRelativeTime(alert.last_triggered_at)} ·{' '} - {formatDateTime(alert.last_triggered_at)} -
- - ))} -- 第一优先级 -
-- 先处理活动异常与 OpenResty 不健康节点 -
-- 当前有 {overview.summary.active_alerts} 个活动异常,{` `} - {overview.summary.unhealthy_nodes} 个 OpenResty 不健康节点。 -
-- 第二优先级 -
-- 检查配置落后与容量逼近节点 -
-- 当前有 {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(' / ') || ' 暂无来源数据'} - 。 -
-- 节点风险清单 -
-- 优先处置节点 -
-- {node.name} -
-- 请求量 {node.request_count.toLocaleString('zh-CN')} · 错误数{' '} - {node.error_count.toLocaleString('zh-CN')} -
-