import React, { useEffect, useMemo, useState } from 'react'; import { usePortalStore } from './store/usePortalStore'; import { TopRibbon } from './components/TopRibbon'; import { ServiceCard } from './components/ServiceCard'; import { SshShelf } from './components/SshShelf'; import { CommandPalette } from './components/CommandPalette'; import { AgyTerminalModal } from './components/AgyTerminalModal'; import { AuthGateCard } from './components/AuthGateModal'; import { StorybookWorkbench } from './components/StorybookWorkbench'; import { LAYOUT_PRESETS } from './design-system/tokens'; import type { ServiceGroup } from './types/telemetry'; export const App: React.FC = () => { const { telemetry, toast, viewMode, layoutPreset, fetchTelemetry, setShowCmdPalette, getEffectiveMode, routeMode, } = usePortalStore(); const [authRedirect, setAuthRedirect] = useState(() => { if (typeof window === 'undefined') return null; const params = new URLSearchParams(window.location.search); return params.get('auth_redirect'); }); useEffect(() => { fetchTelemetry(); const timer = setInterval(fetchTelemetry, 3000); const onKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setShowCmdPalette(true); } }; window.addEventListener('keydown', onKeyDown); return () => { clearInterval(timer); window.removeEventListener('keydown', onKeyDown); }; }, [fetchTelemetry, setShowCmdPalette]); const activeModeLabel = useMemo(() => { const m = getEffectiveMode(); if (m === 'lan') return 'LAN 局域网极速直连 (192.168.31.x)'; if (m === 'home10443') return '10443 家宽公网 HTTPS 直连'; return '443 免端口 HTTPS 直达 (*.961504.xyz)'; }, [getEffectiveMode, routeMode]); const gridClass = LAYOUT_PRESETS[layoutPreset]?.gridClass || LAYOUT_PRESETS['homepage-3col'].gridClass; const serviceGroups = useMemo(() => { const t = telemetry; return [ { title: '核心工作台与私有云存储', subtitle: 'Core Workbench & Storage', showRouteHint: true, items: [ { id: 'agy', title: 'AGY 云端工作台', subtitle: 'agy.961504.xyz · ttyd + tmux', iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/google-gemini.png', online: t.services.agy, actionType: 'drawer', 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: `${t.mini.cpu_pct}%` }, { label: '已用内存', value: `${t.mini.mem_used_gb} / 24 GB` }, { label: 'tmux 会话', value: t.mini.agy_tmux ? '常驻运行中' : '待机', highlight: true, }, ], }, { id: 'zspace', title: '极空间 Z4 私有云', subtitle: '192.168.31.185 · 20TB RAID 存储池', iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/synology-drive.png', online: t.services.nas, desc: '家庭 20TB 大容量存储中心、极影视蓝光影院与手机相册备份,已自动 SMB 挂载至 Mac mini ~/nas。', edgeUrl: 'https://z4.home.961504.xyz:10443', homeUrl: 'https://z4.home.961504.xyz:10443', lanUrl: 'http://192.168.31.185:5055', stats: [ { label: '剩余可用', value: `${t.nas.free_tb} TB`, highlight: true }, { label: '已用容量', value: `${t.nas.used_tb} TB (${t.nas.pct}%)` }, { label: 'SMB 挂载', value: t.nas.mounted ? '~/nas 已挂载' : '重连中' }, ], }, { id: 'vaultwarden', title: 'Vaultwarden 密码保险箱', subtitle: 'vw.961504.xyz · Bitwarden 兼容', iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/vaultwarden.png', online: t.services.vaultwarden, actionType: 'backup', desc: '自托管全平台加密密码库,支持 iOS / Android / 浏览器插件自动填充,每天凌晨 03:30 自动打包灾备至极空间。', edgeUrl: 'https://vw.961504.xyz', homeUrl: 'https://vw.home.961504.xyz:10443', lanUrl: 'http://192.168.31.164:8222', stats: [ { label: '容器状态', value: 'Healthy', highlight: true }, { label: '最近极空间灾备', value: t.nas.latest_backup }, { label: 'Mac SSD 占用', value: `${t.mini.disk_pct}%` }, ], }, ], }, { title: '网络网关、AI 专线与外部哨兵', subtitle: 'Network Gateway & Cloud Edge', showRouteHint: false, items: [ { id: 'clash', title: 'Mihomo 路由分流面板', subtitle: '192.168.31.1:9999 · ShellCrash', iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/clash.png', online: t.services.clash, desc: '小米主路由器透明网关控制台,实时监控全屋设备流量、分流规则与狮城 AI 算力专线锁定状态。', edgeUrl: 'https://clash.home.961504.xyz:10443/ui/', homeUrl: 'https://clash.home.961504.xyz:10443/ui/', lanUrl: 'http://192.168.31.1:9999/ui/', stats: [ { label: 'AI 锁定策略', value: '狮城节点', highlight: true }, { label: '新加坡延迟', value: t.network.sg_delay_ms > 0 ? `${t.network.sg_delay_ms} ms` : '直连', }, { label: '固定出口 IP', value: t.network.sg_egress_ip }, ], }, { id: 'router', title: '小米主路由器后台', subtitle: '192.168.31.1 · PPPoE 公网网关', iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/openwrt.png', online: t.services.router, desc: '管理家庭联通公网宽带拨号、DHCP 静态 IP 绑定、10443 与 41641 端口转发及全屋内网 Split-DNS。', edgeUrl: 'https://router.home.961504.xyz:10443', homeUrl: 'https://router.home.961504.xyz:10443', lanUrl: 'http://192.168.31.1', stats: [ { label: '联通公网 IPv4', value: t.network.home_wan_ip, highlight: true }, { label: '泛域名 DDNS', value: '*.home.961504.xyz' }, { label: '内网网关', value: '192.168.31.1' }, ], }, { id: 'uptime', title: 'Uptime Kuma 外部哨兵', subtitle: 'status.961504.xyz · SG Azure', iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/uptime-kuma.png', online: t.services.uptime, desc: '独立部署在新加坡 Azure 公有云上的 24 小时外部监控哨兵,通过 Tailscale P2P 专线实时探测家庭服务。', edgeUrl: 'https://status.961504.xyz', homeUrl: 'https://status.961504.xyz', lanUrl: 'https://status.961504.xyz', stats: [ { label: '云端公网 IP', value: '104.43.105.243' }, { label: 'Tailscale 专线', value: '100.115.204.51' }, { label: '外部探测状态', value: '100% 在线', highlight: true }, ], }, ], }, ]; }, [telemetry]); return (
{/* Toast Notification */} {toast && (
{toast}
)} {/* 1. Standard Two-Tier AppShell Header & Telemetry Subheader */} {/* 2. View Switch: Dashboard vs Storybook Workbench */} {viewMode === 'storybook' ? ( ) : (
{serviceGroups.map((group) => (

{group.title}

· {group.subtitle}
{group.showRouteHint && ( 当前路由模式: {activeModeLabel} )}
{group.items.map((card) => ( ))}
))} {/* 3. Bottom SSH Quick Copy & Maintenance Shelf */}
)} {/* 4. Global Cmd+K Command Palette */} {/* 5. Embedded AGY Cloud Terminal Modal */} {/* 6. Custom SSO Auth Gate Modal (Replaces Browser Native Basic Auth Popup!) */} {authRedirect && (
{ window.location.href = authRedirect; }} onCancel={() => { setAuthRedirect(null); window.history.replaceState({}, '', '/'); }} />
)}
); };