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` 等)。