feat(portal): implement two-tier AppShell header (fix top-left wrapping) and interactive Storybook workbench

Browse Source
This commit is contained in:
Yin Kou committed 2026-11-07 00:17:06 +08:00
1 parent 4ca86da55f
commit 6b589fa62a
11 files changed
+1025 -268

No files matched your search

@@ -0,0 +1,388 @@
import React, { useState } from 'react';
import { BookOpen, Check, Layers, Layout, Palette, Sparkles, Terminal } from 'lucide-react';
import { LAYOUT_PRESETS, THEME_PALETTES, type LayoutPresetId } from '../design-system/tokens';
import { usePortalStore } from '../store/usePortalStore';
import { ServiceCard } from './ServiceCard';
import { SshShelf } from './SshShelf';
type StoryId = 'layout-architecture' | 'theme-tokens' | 'service-card' | 'ssh-shelf';
export const StorybookWorkbench: React.FC = () => {
const [activeStory, setActiveStory] = useState<StoryId>('layout-architecture');
const {
theme,
resolvedTheme,
layoutPreset,
setTheme,
setLayoutPreset,
setViewMode,
telemetry,
} = usePortalStore();
// Interactive Playground Props for ServiceCard story
const [demoOnline, setDemoOnline] = useState(true);
const [demoAction, setDemoAction] = useState<'drawer' | 'backup' | undefined>('drawer');
const stories: Array<{ id: StoryId; group: string; title: string; icon: React.ReactNode }> = [
{
id: 'layout-architecture',
group: 'FOUNDATIONS / 架构与布局',
title: '01. AppShell 双层顶栏与网格布局',
icon: <Layout className="w-3.5 h-3.5" />,
},
{
id: 'theme-tokens',
group: 'FOUNDATIONS / 架构与布局',
title: '02. 双主题色板与自动切换规范',
icon: <Palette className="w-3.5 h-3.5" />,
},
{
id: 'service-card',
group: 'COMPONENTS / 核心组件',
title: '03. ServiceCard 内联遥测卡片',
icon: <Layers className="w-3.5 h-3.5" />,
},
{
id: 'ssh-shelf',
group: 'COMPONENTS / 核心组件',
title: '04. SshShelf 快捷指令栏',
icon: <Terminal className="w-3.5 h-3.5" />,
},
];
return (
<div className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 items-start">
{/* Left Sidebar: Storybook Navigation Tree */}
<aside className="hl-card p-4 space-y-4 lg:sticky lg:top-20">
<div className="flex items-center justify-between border-b hl-border pb-3">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4" style={{ color: 'var(--accent)' }} />
<span className="text-xs font-bold uppercase tracking-wider">Storybook 组件库</span>
</div>
<span className="text-[10px] font-mono-num px-1.5 py-0.5 rounded hl-muted">v3.1</span>
</div>
<div className="space-y-1">
{stories.map((s) => (
<button
key={s.id}
onClick={() => setActiveStory(s.id)}
className="w-full px-3 py-2 rounded-lg text-left text-xs font-medium flex items-center gap-2.5 transition-all cursor-pointer"
style={
activeStory === s.id
? {
backgroundColor: 'var(--accent-soft)',
color: 'var(--accent)',
}
: { color: 'var(--text-muted)' }
}
>
{s.icon}
<span className="truncate">{s.title}</span>
</button>
))}
</div>
<div className="pt-3 border-t hl-border space-y-2 text-xs">
<div className="text-[11px] font-semibold" style={{ color: 'var(--text-muted)' }}>
全局外观状态预览
</div>
<div className="grid grid-cols-3 gap-1 p-1 rounded-lg hl-muted border hl-border">
{(['system', 'light', 'dark'] as const).map((t) => (
<button
key={t}
onClick={() => setTheme(t)}
className="py-1 rounded text-[11px] font-medium cursor-pointer"
style={
theme === t
? { backgroundColor: 'var(--bg-card)', color: 'var(--text-main)' }
: { color: 'var(--text-muted)' }
}
>
{t === 'system' ? '自动' : t === 'light' ? '浅色' : '深色'}
</button>
))}
</div>
<button
onClick={() => setViewMode('dashboard')}
className="w-full hl-btn-primary py-2 text-xs text-center mt-2 cursor-pointer"
>
返回中控台主页
</button>
</div>
</aside>
{/* Right Canvas: Interactive Story Preview & Controls */}
<div className="lg:col-span-3 space-y-6">
{/* STORY 1: Layout Architecture */}
{activeStory === 'layout-architecture' && (
<div className="space-y-6">
<div className="hl-card p-6 space-y-4">
<div className="flex items-center justify-between border-b hl-border pb-4">
<div>
<span
className="text-[11px] font-mono-num uppercase tracking-wider"
style={{ color: 'var(--accent)' }}
>
Foundations / AppShell Layout
</span>
<h2 className="text-lg font-bold mt-0.5">
标准双层顶栏架构 (Two-Tier AppShell) & 响应式网格切换
</h2>
</div>
</div>
<p className="text-xs leading-relaxed" style={{ color: 'var(--text-muted)' }}>
参考 <strong>Vercel Geist / shadcn/ui Dashboard-01 / GitHub AppShell</strong>{' '}
标准规范,将原本容易挤压折行的单层顶栏拆分为职责清晰的双层结构:
</p>
{/* Visual Blueprint Diagram of Two-Tier Header */}
<div className="p-4 rounded-xl hl-muted border hl-border space-y-2.5 font-mono-num text-xs">
<div className="p-3 rounded-lg hl-card flex items-center justify-between">
<span>[左] 🍎 HomeLab · 导航切换 (中控台 / Storybook)</span>
<span style={{ color: 'var(--accent)' }}>
[最右固定] 🔍 搜索 ⌘K · &gt;_ AGY 终端 · ☀️/🌙 主题切换
</span>
</div>
<div className="p-3 rounded-lg hl-card flex items-center justify-between">
<span style={{ color: 'var(--success)' }}>
[左] 实时硬件遥测条 (M6 CPU · RAM · Z4 20TB · SG 专线)
</span>
<span>[右对齐] 路由模式切换器 (智能路由 / 443 / 10443 / LAN)</span>
</div>
</div>
{/* Interactive Grid Preset Selector */}
<div className="pt-2 space-y-3">
<div className="text-xs font-semibold">
选择主页卡片网格布局预设(点击立即应用到主页):
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3.5">
{(Object.keys(LAYOUT_PRESETS) as LayoutPresetId[]).map((key) => {
const preset = LAYOUT_PRESETS[key];
const active = layoutPreset === key;
return (
<div
key={key}
onClick={() => setLayoutPreset(key)}
className="hl-card hl-card-interactive p-4 cursor-pointer space-y-2"
style={active ? { borderColor: 'var(--accent)' } : {}}
>
<div className="flex items-center justify-between">
<span className="text-xs font-bold">{preset.name}</span>
{active && (
<span
className="w-4 h-4 rounded-full flex items-center justify-center"
style={{
backgroundColor: 'var(--accent)',
color: '#fff',
}}
>
<Check className="w-2.5 h-2.5" />
</span>
)}
</div>
<p className="text-[11px]" style={{ color: 'var(--text-muted)' }}>
{preset.description}
</p>
<code
className="text-[10px] font-mono-num block pt-1"
style={{ color: 'var(--accent)' }}
>
{preset.gridClass}
</code>
</div>
);
})}
</div>
</div>
</div>
</div>
)}
{/* STORY 2: Theme Tokens */}
{activeStory === 'theme-tokens' && (
<div className="hl-card p-6 space-y-6">
<div className="border-b hl-border pb-4 flex items-center justify-between">
<div>
<span
className="text-[11px] font-mono-num uppercase tracking-wider"
style={{ color: 'var(--accent)' }}
>
Foundations / Color System
</span>
<h2 className="text-lg font-bold mt-0.5">
主流双主题色板规范(当前生效:{resolvedTheme === 'dark' ? '深色极客' : '浅色纯净'})
</h2>
</div>
<span className="text-xs font-mono-num px-2.5 py-1 rounded hl-muted">
模式: {theme === 'system' ? '跟随设备自动切换' : '手动锁定'}
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
{(['light', 'dark'] as const).map((modeKey) => {
const p = THEME_PALETTES[modeKey];
return (
<div
key={p.id}
className="rounded-xl p-5 space-y-4 border"
style={{
backgroundColor: p.tokens['--bg-page'],
color: p.tokens['--text-main'],
borderColor: p.tokens['--border-color'],
}}
>
<div
className="flex items-center justify-between border-b pb-3"
style={{ borderColor: p.tokens['--border-color'] }}
>
<div>
<div className="text-sm font-bold">{p.name}</div>
<div className="text-xs mt-0.5" style={{ color: p.tokens['--text-muted'] }}>
{p.description}
</div>
</div>
<button
onClick={() => setTheme(modeKey)}
className="px-2.5 py-1 rounded-md text-xs font-mono-num font-semibold cursor-pointer"
style={{
backgroundColor: p.tokens['--primary'],
color: p.tokens['--primary-fg'],
}}
>
预览此主题
</button>
</div>
<div className="grid grid-cols-2 gap-2">
{Object.entries(p.tokens).map(([tokenName, hex]) => (
<div
key={tokenName}
className="p-2 rounded-lg border flex items-center gap-2"
style={{
backgroundColor: p.tokens['--bg-card'],
borderColor: p.tokens['--border-color'],
}}
>
<span
className="w-5 h-5 rounded border shrink-0"
style={{
backgroundColor: hex,
borderColor: p.tokens['--border-hover'],
}}
/>
<div className="min-w-0">
<div className="text-[10px] font-mono-num font-semibold truncate">
{tokenName}
</div>
<div
className="text-[10px] font-mono-num truncate"
style={{ color: p.tokens['--text-muted'] }}
>
{hex}
</div>
</div>
</div>
))}
</div>
</div>
);
})}
</div>
</div>
)}
{/* STORY 3: ServiceCard Interactive Playground */}
{activeStory === 'service-card' && (
<div className="hl-card p-6 space-y-6">
<div className="border-b hl-border pb-4 flex items-center justify-between">
<div>
<span
className="text-[11px] font-mono-num uppercase tracking-wider"
style={{ color: 'var(--accent)' }}
>
Components / ServiceCard
</span>
<h2 className="text-lg font-bold mt-0.5">
三段式内联遥测卡片 (ServiceCard Interactive Story)
</h2>
</div>
<Sparkles className="w-4 h-4" style={{ color: 'var(--accent)' }} />
</div>
{/* Interactive Controls */}
<div className="p-4 rounded-xl hl-muted border hl-border flex flex-wrap items-center gap-6 text-xs">
<div className="flex items-center gap-2">
<span className="font-semibold">在线状态 (online):</span>
<button
onClick={() => setDemoOnline(!demoOnline)}
className="px-2.5 py-1 rounded hl-card font-mono-num cursor-pointer"
>
{demoOnline ? 'true (绿灯)' : 'false (离线红灯)'}
</button>
</div>
<div className="flex items-center gap-2">
<span className="font-semibold">右上角动作按钮 (actionType):</span>
{(['drawer', 'backup', undefined] as const).map((act, i) => (
<button
key={i}
onClick={() => setDemoAction(act)}
className="px-2.5 py-1 rounded hl-card font-mono-num cursor-pointer"
style={demoAction === act ? { borderColor: 'var(--accent)' } : {}}
>
{act || 'route-badge'}
</button>
))}
</div>
</div>
{/* Live Component Canvas */}
<div className="max-w-md mx-auto py-4">
<ServiceCard
card={{
id: 'agy',
title: 'AGY 云端工作台',
subtitle: 'agy.961504.xyz · ttyd + tmux',
iconUrl:
'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/google-gemini.png',
online: demoOnline,
actionType: demoAction,
desc: 'M6 Mac mini 上的 24 小时常驻 AI 编程终端,已同步 Google 登录态并锁定新加坡出口,关掉网页后台继续运行。',
edgeUrl: 'https://agy.961504.xyz',
homeUrl: 'https://agy.home.961504.xyz:10443',
lanUrl: 'http://192.168.31.164:7681',
stats: [
{ label: 'M6 CPU 负载', value: `${telemetry.mini.cpu_pct}%` },
{ label: '已用内存', value: `${telemetry.mini.mem_used_gb} / 24 GB` },
{ label: 'tmux 会话', value: '常驻运行中', highlight: true },
],
}}
/>
</div>
</div>
)}
{/* STORY 4: SshShelf */}
{activeStory === 'ssh-shelf' && (
<div className="hl-card p-6 space-y-6">
<div className="border-b hl-border pb-4">
<span
className="text-[11px] font-mono-num uppercase tracking-wider"
style={{ color: 'var(--accent)' }}
>
Components / SshShelf
</span>
<h2 className="text-lg font-bold mt-0.5">底部 SSH 快捷复制与运维操作栏</h2>
</div>
<SshShelf />
</div>
)}
</div>
</div>
</div>
);
};
+199 -138
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Moon, Search, Sun, Terminal } from 'lucide-react'; import { BookOpen, LayoutGrid, Moon, Search, Sun, Terminal } from 'lucide-react';
import { usePortalStore } from '../store/usePortalStore'; import { usePortalStore } from '../store/usePortalStore';
import type { RouteModeId } from '../types/telemetry'; import type { RouteModeId } from '../types/telemetry';
@@ -15,171 +15,232 @@ export const TopRibbon: React.FC = () => {
theme, theme,
resolvedTheme, resolvedTheme,
routeMode, routeMode,
viewMode,
telemetry, telemetry,
setTheme, setTheme,
toggleTheme, toggleTheme,
setRouteMode, setRouteMode,
setViewMode,
setShowAgyModal, setShowAgyModal,
setShowCmdPalette, setShowCmdPalette,
} = usePortalStore(); } = usePortalStore();
return ( return (
<header <>
className="border-b hl-border sticky top-0 z-30 backdrop-blur-md" {/* =====================================================================
style={{ backgroundColor: 'color-mix(in srgb, var(--bg-page) 86%, transparent)' }} Tier 1: Standard AppShell Global Top Navbar (h-14, NEVER wraps!)
> Left: Brand + View Tabs (中控台 / Storybook)
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3 flex flex-wrap items-center justify-between gap-3"> Far Right (Red Box): Search (Cmd+K) + AGY Terminal + Theme Switcher
{/* Left: Brand + Live System Micro-Gauges Ribbon */} ===================================================================== */}
<div className="flex flex-wrap items-center gap-2.5 sm:gap-3"> <header
<div className="flex items-center gap-2.5 pr-1"> className="border-b hl-border sticky top-0 z-30 backdrop-blur-md"
<img style={{ backgroundColor: 'color-mix(in srgb, var(--bg-card) 88%, transparent)' }}
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/apple.png" >
alt="Mac mini" <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between gap-4">
className="w-6 h-6 rounded" {/* Left Group: Brand Logo + Navigation Tabs */}
/> <div className="flex items-center gap-4 shrink-0">
<span className="font-semibold text-sm tracking-tight">HomeLab</span> <div
<span onClick={() => setViewMode('dashboard')}
className="w-2 h-2 rounded-full" className="flex items-center gap-2.5 cursor-pointer"
style={{ backgroundColor: 'var(--success)' }}
title="全屋 4 节点在线"
/>
</div>
<div className="hidden xl:flex items-center gap-2 text-xs font-mono-num">
{/* CPU Gauge */}
<div className="hl-pill px-2.5 py-1 flex items-center gap-2" title="Apple M6 12核处理器负载">
<span style={{ color: 'var(--text-muted)' }}>M6 CPU</span>
<div className="w-12 h-1.5 rounded-full hl-muted overflow-hidden">
<div
className="h-full hl-bar-fill"
style={{ width: `${Math.max(5, telemetry.mini.cpu_pct)}%` }}
/>
</div>
<span className="font-medium">{telemetry.mini.cpu_pct}%</span>
</div>
{/* RAM Gauge */}
<div className="hl-pill px-2.5 py-1 flex items-center gap-2" title="Mac mini 24GB 统一内存">
<span style={{ color: 'var(--text-muted)' }}>RAM</span>
<div className="w-12 h-1.5 rounded-full hl-muted overflow-hidden">
<div
className="h-full hl-bar-fill"
style={{ width: `${Math.max(5, telemetry.mini.mem_pct)}%` }}
/>
</div>
<span className="font-medium">{telemetry.mini.mem_used_gb}G</span>
</div>
{/* ZSpace 20TB Gauge */}
<div className="hl-pill px-2.5 py-1 flex items-center gap-2" title="极空间 Z4 20TB 存储阵列">
<span style={{ color: 'var(--text-muted)' }}>NAS 余量</span>
<div className="w-12 h-1.5 rounded-full hl-muted overflow-hidden">
<div
className="h-full hl-bar-fill-green"
style={{ width: `${Math.max(5, telemetry.nas.pct)}%` }}
/>
</div>
<span className="font-medium" style={{ color: 'var(--success)' }}>
{telemetry.nas.free_tb} TB
</span>
</div>
{/* SG AI Line Gauge */}
<div className="hl-pill px-2.5 py-1 flex items-center gap-1.5" title="AGY 锁定新加坡 Azure 专线出口">
<span className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: 'var(--success)' }} />
<span style={{ color: 'var(--text-muted)' }}>SG 专线</span>
<span className="font-medium">
{telemetry.network.sg_delay_ms > 0 ? `${telemetry.network.sg_delay_ms}ms` : '直连'}
</span>
</div>
</div>
</div>
{/* Right: Search (Cmd+K), Route Switcher, AGY Terminal, Top-Right Theme Switcher */}
<div className="flex items-center gap-2">
<button
onClick={() => setShowCmdPalette(true)}
className="hl-btn-outline px-2.5 py-1 text-xs flex items-center gap-3 cursor-pointer"
title="按 Cmd+K 唤起全局命令面板"
>
<span className="flex items-center gap-1.5" style={{ color: 'var(--text-muted)' }}>
<Search className="w-3.5 h-3.5" />
<span className="hidden sm:inline">搜索...</span>
</span>
<kbd
className="px-1.5 py-0.5 text-[10px] font-mono-num rounded hl-muted"
style={{ color: 'var(--text-muted)' }}
> >
⌘K <img
</kbd> src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/apple.png"
</button> alt="Mac mini"
className="w-6 h-6 rounded"
/>
<span className="font-semibold text-sm tracking-tight">HomeLab</span>
<span
className="w-2 h-2 rounded-full"
style={{ backgroundColor: 'var(--success)' }}
title="全屋 4 节点在线"
/>
</div>
<div className="hidden md:flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs"> <div className="h-4 w-[1px] hl-muted" />
{MODES.map((m) => (
{/* View Switcher Tabs: Dashboard vs Storybook */}
<nav className="flex items-center gap-1 text-xs">
<button <button
key={m.id} onClick={() => setViewMode('dashboard')}
onClick={() => setRouteMode(m.id)} className="px-2.5 py-1.5 rounded-md font-medium flex items-center gap-1.5 transition-all cursor-pointer"
className="px-2 py-1 rounded-md font-medium transition-all cursor-pointer"
style={ style={
routeMode === m.id viewMode === 'dashboard'
? { ? {
backgroundColor: 'var(--bg-card)', backgroundColor: 'var(--bg-muted)',
color: 'var(--text-main)', color: 'var(--text-main)',
boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
} }
: { color: 'var(--text-muted)' } : { color: 'var(--text-muted)' }
} }
> >
{m.label} <LayoutGrid className="w-3.5 h-3.5" />
<span>中控台</span>
</button> </button>
))}
<button
onClick={() => setViewMode('storybook')}
className="px-2.5 py-1.5 rounded-md font-medium flex items-center gap-1.5 transition-all cursor-pointer"
style={
viewMode === 'storybook'
? {
backgroundColor: 'var(--bg-muted)',
color: 'var(--text-main)',
}
: { color: 'var(--text-muted)' }
}
>
<BookOpen className="w-3.5 h-3.5" />
<span>Storybook 设计规范</span>
</button>
</nav>
</div> </div>
<button {/* Far Right Group: Pinned to Right Edge (Search + AGY + Theme Switcher) */}
onClick={() => setShowAgyModal(true)} <div className="flex items-center gap-2.5 shrink-0 ml-auto">
className="hl-btn-primary px-3 py-1.5 text-xs flex items-center gap-1.5 cursor-pointer"
>
<Terminal className="w-3.5 h-3.5" />
<span>AGY 终端</span>
</button>
{/* Top-Right Mainstream Theme Switcher (Auto System + Light/Dark 2 Mainstream Themes) */}
<div className="flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs">
<button <button
onClick={toggleTheme} onClick={() => setShowCmdPalette(true)}
className="px-2.5 py-1 rounded-md font-medium transition-all flex items-center gap-1.5 cursor-pointer" className="hl-btn-outline px-3 py-1.5 text-xs flex items-center gap-3 cursor-pointer"
style={{ title="按 Cmd+K 唤起全局命令面板"
backgroundColor: 'var(--bg-card)',
color: 'var(--text-main)',
boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
}}
title="点击切换深色 / 浅色主题"
> >
{resolvedTheme === 'dark' ? ( <span className="flex items-center gap-1.5" style={{ color: 'var(--text-muted)' }}>
<> <Search className="w-3.5 h-3.5" />
<Moon className="w-3.5 h-3.5" /> <span className="hidden sm:inline">搜索服务或指令...</span>
<span className="hidden sm:inline">深色</span> </span>
</> <kbd
) : ( className="px-1.5 py-0.5 text-[10px] font-mono-num rounded hl-muted"
<>
<Sun className="w-3.5 h-3.5" />
<span className="hidden sm:inline">浅色</span>
</>
)}
</button>
{theme !== 'system' && (
<button
onClick={() => setTheme('system')}
className="px-2 py-1 rounded-md text-[11px] transition-all cursor-pointer"
style={{ color: 'var(--text-muted)' }} style={{ color: 'var(--text-muted)' }}
title="恢复跟随设备系统深浅色自动切换"
> >
自动 ⌘K
</kbd>
</button>
<button
onClick={() => setShowAgyModal(true)}
className="hl-btn-primary px-3 py-1.5 text-xs flex items-center gap-1.5 cursor-pointer"
>
<Terminal className="w-3.5 h-3.5" />
<span>AGY 终端</span>
</button>
{/* Top-Right Theme Toggle (Pinned at Far Right Corner!) */}
<div className="flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs">
<button
onClick={toggleTheme}
className="px-2.5 py-1 rounded-md font-medium transition-all flex items-center gap-1.5 cursor-pointer"
style={{
backgroundColor: 'var(--bg-card)',
color: 'var(--text-main)',
boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
}}
title="点击切换深色 / 浅色主题(默认跟随系统)"
>
{resolvedTheme === 'dark' ? (
<>
<Moon className="w-3.5 h-3.5" />
<span>深色</span>
</>
) : (
<>
<Sun className="w-3.5 h-3.5" />
<span>浅色</span>
</>
)}
</button> </button>
)} {theme !== 'system' && (
<button
onClick={() => setTheme('system')}
className="px-2 py-1 rounded-md text-[11px] transition-all cursor-pointer"
style={{ color: 'var(--text-muted)' }}
title="恢复跟随设备系统深浅色自动切换"
>
自动
</button>
)}
</div>
</div> </div>
</div> </div>
</div> </header>
</header>
{/* =====================================================================
Tier 2: Subheader Telemetry & Route Mode Bar (Aligned with Grid)
Left: 4 Hardware & Network Gauges
Right: Route Mode Segmented Switcher (智能路由 / 443 / 10443 / LAN)
===================================================================== */}
{viewMode === 'dashboard' && (
<div className="border-b hl-border" style={{ backgroundColor: 'var(--bg-card)' }}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex flex-wrap items-center justify-between gap-3">
{/* Left: System Telemetry Strip */}
<div className="flex flex-wrap items-center gap-2 text-xs font-mono-num">
<div className="hl-pill px-2.5 py-1 flex items-center gap-2">
<span style={{ color: 'var(--text-muted)' }}>M6 CPU</span>
<div className="w-12 h-1.5 rounded-full hl-muted overflow-hidden">
<div
className="h-full hl-bar-fill"
style={{ width: `${Math.max(5, telemetry.mini.cpu_pct)}%` }}
/>
</div>
<span className="font-medium">{telemetry.mini.cpu_pct}%</span>
</div>
<div className="hl-pill px-2.5 py-1 flex items-center gap-2">
<span style={{ color: 'var(--text-muted)' }}>RAM</span>
<div className="w-12 h-1.5 rounded-full hl-muted overflow-hidden">
<div
className="h-full hl-bar-fill"
style={{ width: `${Math.max(5, telemetry.mini.mem_pct)}%` }}
/>
</div>
<span className="font-medium">{telemetry.mini.mem_used_gb}G</span>
</div>
<div className="hl-pill px-2.5 py-1 flex items-center gap-2">
<span style={{ color: 'var(--text-muted)' }}>NAS 余量</span>
<div className="w-12 h-1.5 rounded-full hl-muted overflow-hidden">
<div
className="h-full hl-bar-fill-green"
style={{ width: `${Math.max(5, telemetry.nas.pct)}%` }}
/>
</div>
<span className="font-medium" style={{ color: 'var(--success)' }}>
{telemetry.nas.free_tb} TB
</span>
</div>
<div className="hl-pill px-2.5 py-1 flex items-center gap-1.5">
<span
className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: 'var(--success)' }}
/>
<span style={{ color: 'var(--text-muted)' }}>SG 专线</span>
<span className="font-medium">
{telemetry.network.sg_delay_ms > 0 ? `${telemetry.network.sg_delay_ms}ms` : '直连'}
</span>
</div>
</div>
{/* Right: Route Mode Switcher (Pinned to Right Edge of Subheader) */}
<div className="flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs ml-auto">
{MODES.map((m) => (
<button
key={m.id}
onClick={() => setRouteMode(m.id)}
className="px-2.5 py-1 rounded-md font-medium transition-all cursor-pointer"
style={
routeMode === m.id
? {
backgroundColor: 'var(--bg-card)',
color: 'var(--text-main)',
boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
}
: { color: 'var(--text-muted)' }
}
>
{m.label}
</button>
))}
</div>
</div>
</div>
)}
</>
); );
}; };