diff --git a/Wavelet/frontend/app/(main)/components/dashboard/world-stage-map.tsx b/Wavelet/frontend/app/(main)/components/dashboard/world-stage-map.tsx index 9d5ef174..9fd75510 100644 --- a/Wavelet/frontend/app/(main)/components/dashboard/world-stage-map.tsx +++ b/Wavelet/frontend/app/(main)/components/dashboard/world-stage-map.tsx @@ -1,11 +1,11 @@ 'use client'; import ReactEChartsCore from 'echarts-for-react/lib/core'; -import type { EChartsCoreOption } from 'echarts/core'; +import type {EChartsCoreOption} from 'echarts/core'; import * as echarts from 'echarts/core'; -import { MapChart, ScatterChart } from 'echarts/charts'; -import { GeoComponent, TooltipComponent } from 'echarts/components'; -import { CanvasRenderer } from 'echarts/renderers'; +import {MapChart, ScatterChart} from 'echarts/charts'; +import {GeoComponent, TooltipComponent} from 'echarts/components'; +import {CanvasRenderer} from 'echarts/renderers'; import {useTheme} from 'next-themes'; import {useRouter} from 'next/navigation'; import {useEffect, useMemo, useRef, useState} from 'react'; @@ -13,10 +13,7 @@ import {useEffect, useMemo, useRef, useState} from 'react'; import {EmptyState} from '@/components/layout/empty'; import type {DashboardNodeHealth, DistributionItem} from '@/lib/services/openflare'; -import { - getNodeStatusLabel, - getOpenrestyStatusLabel, -} from '../../nodes/components/node-utils'; +import {getNodeStatusLabel, getOpenrestyStatusLabel,} from '../../nodes/components/node-utils'; import worldGeoJson from './data/world-geo.json'; echarts.use([ @@ -100,9 +97,9 @@ type CountryRegionDatum = { let worldMapRegistrationAttempted = false; let worldMapRegistrationSucceeded = false; -const baseWorldMapLayoutSizePercent = 108; +const baseWorldMapLayoutSizePercent = 118; const baseWorldMapZoom = 1; -const worldMapAspectRatio = 2 / 3; +const worldMapAspectRatio = 5 / 3; function buildNodeDetailHref(id?: number | null) { if (!id) { diff --git a/Wavelet/frontend/app/(main)/components/dashboard/world-stage.tsx b/Wavelet/frontend/app/(main)/components/dashboard/world-stage.tsx index 1112ef75..9a6b7868 100644 --- a/Wavelet/frontend/app/(main)/components/dashboard/world-stage.tsx +++ b/Wavelet/frontend/app/(main)/components/dashboard/world-stage.tsx @@ -1,26 +1,12 @@ 'use client'; import dynamic from 'next/dynamic'; -import {useEffect, useRef, useState, type ComponentType} from 'react'; -import { - Activity, - Cpu, - Globe2, - HardDrive, - MemoryStick, - Server, - ShieldCheck, -} from 'lucide-react'; +import {type ComponentType, useEffect, useRef, useState} from 'react'; +import {Activity, Cpu, Globe2, HardDrive, MemoryStick, Network, Server, ShieldCheck,} from 'lucide-react'; import {EmptyState} from '@/components/layout/empty'; import {Badge} from '@/components/ui/badge'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; +import {Card, CardContent, CardDescription, CardHeader, CardTitle,} from '@/components/ui/card'; import {Progress} from '@/components/ui/progress'; import type { DashboardCapacity, @@ -45,7 +31,7 @@ const LEGEND_ITEMS = [ {dot: 'bg-destructive', label: '异常'}, ] as const; -function CompactMetric({ +function SummaryMetric({ label, value, hint, @@ -63,25 +49,57 @@ function CompactMetric({ return (
-
- {label} - +
+ {label} +
- + {value} {typeof progress === 'number' ? ( - + ) : null} {hint}
); } +function DetailMetric({ + label, + value, + hint, + icon: Icon, + progress, +}: { + label: string; + value: string; + hint: string; + icon: ComponentType<{className?: string}>; + progress?: number; +}) { + return ( +
+
+
+

{label}

+

+ {value} +

+
+ +
+ {typeof progress === 'number' ? ( + + ) : null} +

{hint}

+
+ ); +} + export function WorldStage({ summary, traffic, @@ -149,7 +167,7 @@ export function WorldStage({ ? '节点坐标' : '覆盖信号'; - const metrics = [ + const headlineMetrics = [ { label: '在线覆盖', value: formatPercent(onlineRate), @@ -165,11 +183,23 @@ export function WorldStage({ progress: healthyRate, }, { - label: '窗口请求', + label: '最近窗口请求', value: formatCompactNumber(traffic.request_count), - hint: `QPS ${traffic.estimated_qps.toFixed(1)}`, + hint: `QPS ${traffic.estimated_qps.toFixed(1)} · ${traffic.reported_nodes} 个节点上报`, icon: Activity, }, + { + label: '节点坐标', + value: formatCompactNumber(geoConfiguredNodes), + hint: + geoConfiguredNodes > 0 + ? `${sourceCountries.length} 个来源国家` + : '未配置时使用预设落点', + icon: Network, + }, + ] as const; + + const detailMetrics = [ { label: '平均 CPU', value: formatPercent(capacity.average_cpu_usage_percent), @@ -193,9 +223,9 @@ export function WorldStage({ ] as const; return ( - - -
+ + +
@@ -207,11 +237,11 @@ export function WorldStage({ {mapModeLabel} - - 节点分布与来源热度 + + 汇总节点分布、来源国家热度与最近窗口运行状态。
-
+
{LEGEND_ITEMS.map((item) => ( - -
-
+ +
+ {headlineMetrics.map((metric) => ( + + ))} +
+ +
+
{shouldRenderMap ? ( @@ -254,13 +290,13 @@ export function WorldStage({
-
- {metrics.map((metric) => ( - +
+ {detailMetrics.map((metric) => ( + ))}
); -} \ No newline at end of file +} diff --git a/Wavelet/frontend/app/(main)/nodes/components/node-detail-shell.tsx b/Wavelet/frontend/app/(main)/nodes/components/node-detail-shell.tsx index a712c8bf..e1c2054a 100644 --- a/Wavelet/frontend/app/(main)/nodes/components/node-detail-shell.tsx +++ b/Wavelet/frontend/app/(main)/nodes/components/node-detail-shell.tsx @@ -2,7 +2,7 @@ import Link from 'next/link'; import {usePathname, useRouter, useSearchParams} from 'next/navigation'; -import {useCallback, useMemo, type ReactNode} from 'react'; +import {type ReactNode, useCallback, useMemo} from 'react'; import type {LucideIcon} from 'lucide-react'; import {ArrowLeft} from 'lucide-react'; @@ -19,7 +19,6 @@ export type NodeDetailTabId = 'overview' | 'dashboard' | 'manage'; export type NodeDetailTabConfig = { id: NodeDetailTabId; label: string; - description: string; icon?: LucideIcon; }; @@ -33,17 +32,14 @@ const TAB_CONFIGS: NodeDetailTabConfig[] = [ { id: 'overview', label: '概览', - description: '快速查看节点身份、运行状态与关键指标。', }, { id: 'dashboard', label: '数据看板', - description: '系统画像、资源快照、流量趋势与健康事件时间线。', }, { id: 'manage', label: '配置与部署', - description: '版本同步、部署命令与节点运维配置。', }, ]; @@ -150,7 +146,7 @@ export function NodeDetailShell({ }} className="w-full gap-0" > -
+
{TAB_CONFIGS.map((tab) => ( ))} -

{activeTabMeta.description}

@@ -180,4 +175,4 @@ export function NodeDetailShell({
); -} \ No newline at end of file +} diff --git a/Wavelet/frontend/app/(main)/page.tsx b/Wavelet/frontend/app/(main)/page.tsx index a7f1f925..bfa207d1 100644 --- a/Wavelet/frontend/app/(main)/page.tsx +++ b/Wavelet/frontend/app/(main)/page.tsx @@ -109,4 +109,4 @@ export default function OpenFlareDashboardPage() { )}
); -} \ No newline at end of file +} diff --git a/docs/changelog/index.md b/docs/changelog/index.md index e08c7855..bb8d6427 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -21,9 +21,9 @@ sidebar: false - Wavelet 总览仪表盘图表对齐旧前端 ECharts 样式:24 小时请求/容量趋势补充请求量与错误量展示,新增网络与磁盘趋势、Top 节点榜单、来源分布、状态码分布与 Top Domain 板块。 - Wavelet 总览仪表盘迁移旧前端「全球态势板」:世界地图展示节点健康与来源国家热度,并保留在线覆盖、流量与容量摘要指标。 - Wavelet 全球态势板视觉对齐系统风格:改用 `Card` / `Badge` / `Progress` 与虚线边框指标卡,地图配色与 tooltip 跟随主题 token。 -- Wavelet 全球态势板布局紧凑化:地图高度降至约 260px,指标改为侧栏密集单元格,图例改为行内色点标注。 +- Wavelet 全球态势板布局重构:关键指标前置为摘要栅格,地图与容量诊断并排展示,移动端自动堆叠。 - 修复 Wavelet 全球态势板 ECharts 地图在紧凑布局下容器尺寸为 0 导致无法渲染的问题。 -- Wavelet 全球态势板地图容器调整为约 2:3(宽:高)比例,避免世界地图横向过度拉伸。 +- Wavelet 全球态势板地图容器调整为横向看板比例,避免世界地图被压缩成窄竖卡片。 - 将 `openflare-server` 后端业务域迁移至 `Wavelet/internal/apps/openflare/`,通过 `/api/*` legacy 兼容层保持旧前端 API 路径不变。 - 新增 OpenFlare 业务表 goose 迁移(`of_options`、`of_origins`、`of_proxy_routes`、`of_nodes`、`of_waf_*`、`of_tls_*`、`of_config_versions`、`of_pages_*`、`of_apply_logs` 等)。