242 lines
9.6 KiB
TypeScript
242 lines
9.6 KiB
TypeScript
import React from 'react';
|
|
import { BookOpen, Bot, LayoutGrid, MessageSquare, Moon, Search, Sun, Terminal } from 'lucide-react';
|
|
import { usePortalStore } from '../store/usePortalStore';
|
|
import type { RouteModeId } from '../types/telemetry';
|
|
import { Button } from './ui/button';
|
|
import { Tabs, TabsList, TabsTrigger } from './ui/tabs';
|
|
|
|
const MODES: Array<{ id: RouteModeId; label: string }> = [
|
|
{ id: 'auto', label: '智能路由' },
|
|
{ id: 'edge443', label: '443 免端口' },
|
|
{ id: 'home10443', label: '10443 家宽' },
|
|
{ id: 'lan', label: 'LAN 局域网' },
|
|
];
|
|
|
|
export const TopRibbon: React.FC = () => {
|
|
const {
|
|
theme,
|
|
resolvedTheme,
|
|
routeMode,
|
|
viewMode,
|
|
telemetry,
|
|
setTheme,
|
|
toggleTheme,
|
|
setRouteMode,
|
|
setViewMode,
|
|
setShowAgyModal,
|
|
setShowWechatModal,
|
|
setShowCmdPalette,
|
|
} = usePortalStore();
|
|
|
|
return (
|
|
<>
|
|
{/* =====================================================================
|
|
Tier 1: Standard AppShell Global Top Navbar (h-14, NEVER wraps!)
|
|
Left: Brand + Radix Tabs (中控台 / Storybook)
|
|
Far Right: Search (Cmd+K) + AGY Terminal + Theme Switcher
|
|
===================================================================== */}
|
|
<header
|
|
className="border-b hl-border sticky top-0 z-30 backdrop-blur-md"
|
|
style={{ backgroundColor: 'color-mix(in srgb, var(--bg-card) 88%, transparent)' }}
|
|
>
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between gap-4">
|
|
{/* Left Group: Brand Logo + Official Radix Navigation Tabs */}
|
|
<div className="flex items-center gap-4 shrink-0">
|
|
<div
|
|
onClick={() => setViewMode('dashboard')}
|
|
className="flex items-center gap-2.5 cursor-pointer"
|
|
>
|
|
<img
|
|
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/apple.png"
|
|
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="h-4 w-[1px] hl-muted" />
|
|
|
|
{/* View Switcher Tabs via @radix-ui/react-tabs */}
|
|
<Tabs
|
|
value={viewMode}
|
|
onValueChange={(v) => setViewMode(v as 'dashboard' | 'agents' | 'storybook')}
|
|
>
|
|
<TabsList>
|
|
<TabsTrigger value="dashboard">
|
|
<LayoutGrid className="w-3.5 h-3.5" />
|
|
<span>中控台</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="agents">
|
|
<Bot className="w-3.5 h-3.5" />
|
|
<span>Agent 编排</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="storybook">
|
|
<BookOpen className="w-3.5 h-3.5" />
|
|
<span>Storybook 设计规范</span>
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
</div>
|
|
|
|
{/* Far Right Group: Pinned to Right Edge (Search + AGY + Theme Switcher) */}
|
|
<div className="flex items-center gap-2.5 shrink-0 ml-auto">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setShowCmdPalette(true)}
|
|
className="gap-3"
|
|
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
|
|
</kbd>
|
|
</Button>
|
|
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setShowWechatModal(true)}
|
|
title="连接微信 ClawBot,在微信内直接收发消息与控制 Mac mini"
|
|
>
|
|
<span
|
|
className="w-2 h-2 rounded-full"
|
|
style={{
|
|
backgroundColor: telemetry.services.clawbot ? 'var(--success)' : 'var(--accent)',
|
|
}}
|
|
/>
|
|
<MessageSquare className="w-3.5 h-3.5" />
|
|
<span>微信 ClawBot</span>
|
|
</Button>
|
|
|
|
<Button onClick={() => setShowAgyModal(true)}>
|
|
<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>
|
|
{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>
|
|
</header>
|
|
|
|
{/* =====================================================================
|
|
Tier 2: Subheader Telemetry & Route Mode Bar (Aligned with Grid)
|
|
Left: 4 Hardware & Network Gauges
|
|
Right: Route Mode Segmented Switcher via @radix-ui/react-tabs
|
|
===================================================================== */}
|
|
{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 via @radix-ui/react-tabs */}
|
|
<Tabs
|
|
value={routeMode}
|
|
onValueChange={(v) => setRouteMode(v as RouteModeId)}
|
|
className="ml-auto"
|
|
>
|
|
<TabsList>
|
|
{MODES.map((m) => (
|
|
<TabsTrigger key={m.id} value={m.id}>
|
|
{m.label}
|
|
</TabsTrigger>
|
|
))}
|
|
</TabsList>
|
|
</Tabs>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
};
|