From ec5dc7f31e9af0d6428db070bade26174b0b47ba Mon Sep 17 00:00:00 2001 From: eric Date: Sat, 28 Mar 2026 12:32:20 -0500 Subject: [PATCH] feat(ui): auto-refresh overview/modules, quick config, HW re-probe, link-tile layout; deploy: npm ci+build, restart live-console Made-with: Cursor --- tools/deploy_live_paramiko.py | 29 ++++-- web-console/app/globals.css | 155 ++++++++++++++++++++++++++++ web-console/app/page.tsx | 189 +++++++++++++++++----------------- 3 files changed, 272 insertions(+), 101 deletions(-) diff --git a/tools/deploy_live_paramiko.py b/tools/deploy_live_paramiko.py index 0a81013..4b2818a 100644 --- a/tools/deploy_live_paramiko.py +++ b/tools/deploy_live_paramiko.py @@ -6,6 +6,7 @@ from __future__ import annotations import os import posixpath import sys +import time from datetime import datetime from pathlib import Path @@ -23,13 +24,19 @@ REMOTE_BASE = os.environ.get("LIVE_DEPLOY_PATH", "/home/live/douyinyoutube").rst REPO_ROOT = Path(__file__).resolve().parent.parent FILES = [ + "src/control_plane.py", "src/web_process_backend.py", "web.py", "main.py", "scripts/hardware_probe.py", "douyin_youtube_ffplay.py", + "web-console/package.json", + "web-console/package-lock.json", + "web-console/app/globals.css", + "web-console/app/layout.tsx", "web-console/app/page.tsx", "web-console/middleware.ts", + "web-console/components/OverviewCharts.tsx", ] LOG_DIR = REPO_ROOT / ".deploy_logs" @@ -182,7 +189,8 @@ def main() -> int: py_compile = ( f"cd {REMOTE_BASE} && python3 -m py_compile web.py " - "src/web_process_backend.py scripts/hardware_probe.py douyin_youtube_ffplay.py main.py" + "src/control_plane.py src/web_process_backend.py " + "scripts/hardware_probe.py douyin_youtube_ffplay.py main.py" ) code_py, _ = run_logged(client, log_path, "python_py_compile", py_compile, timeout=90) if code_py != 0: @@ -191,6 +199,7 @@ def main() -> int: build_cmd = ( f"cd {REMOTE_BASE}/web-console && " + "(npm ci 2>/dev/null || npm install) && " "(command -v pnpm >/dev/null 2>&1 && pnpm run build || npm run build)" ) code_b, _ = run_logged(client, log_path, "web_console_build", build_cmd, timeout=900) @@ -212,15 +221,23 @@ def main() -> int: "echo try_restart_done'" ) run_logged(client, log_path, "sudo_try_restart_units", restart_cmd, timeout=60) - run_logged( client, log_path, - "curl_health", - "curl -sS -m 10 http://127.0.0.1:8001/health 2>&1 || " - "curl -sS -m 10 http://127.0.0.1:8101/health 2>&1 || " + "sudo_restart_live_console", + f"echo '{PASS}' | sudo -S systemctl restart live-console.service", + timeout=120, + ) + _safe_print("等待服务监听…") + time.sleep(6) + run_logged( + client, + log_path, + "curl_health_after_restart", + "curl -sS -m 15 http://127.0.0.1:8001/health 2>&1 || " + "curl -sS -m 15 http://127.0.0.1:8101/health 2>&1 || " "echo 'curl_health_failed'", - timeout=20, + timeout=25, ) run_logged( diff --git a/web-console/app/globals.css b/web-console/app/globals.css index 7ffb47a..58aebcd 100644 --- a/web-console/app/globals.css +++ b/web-console/app/globals.css @@ -64,6 +64,161 @@ html[data-theme="light"] .chart-surface { background: var(--bg-card); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); + transition: border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease; + } + + html[data-theme="light"] .glass { + @apply border-slate-200/90 shadow-slate-400/15; + } + + .section-title { + @apply text-lg font-semibold tracking-tight; + color: var(--text); + } + + .section-lead { + @apply mt-2 text-xs leading-relaxed; + color: var(--muted); + } + + .meta-chip { + @apply rounded-2xl border px-4 py-3 text-xs font-mono leading-relaxed; + border-color: rgb(255 255 255 / 0.1); + background: rgb(0 0 0 / 0.22); + color: var(--muted); + } + + html[data-theme="light"] .meta-chip { + border-color: rgb(226 232 240 / 0.95); + background: rgb(255 255 255 / 0.72); + color: var(--muted); + } + + .tab-strip { + @apply -mx-1 flex gap-2 overflow-x-auto pb-1.5 pt-0.5 md:flex-wrap md:overflow-visible; + scroll-snap-type: x proximity; + -webkit-overflow-scrolling: touch; + } + + .tab-btn { + @apply btn-focus shrink-0 snap-start rounded-2xl border px-4 py-2.5 text-sm font-medium transition-colors duration-200; + border-color: rgb(255 255 255 / 0.12); + background: rgb(0 0 0 / 0.22); + color: rgb(203 213 225); + } + + .tab-btn:hover { + border-color: rgb(34 211 238 / 0.25); + color: rgb(226 232 240); + } + + .tab-btn-active { + border-color: rgb(34 211 238 / 0.45) !important; + background: rgb(34 211 238 / 0.12) !important; + color: rgb(207 250 254) !important; + } + + html[data-theme="light"] .tab-btn { + border-color: rgb(226 232 240); + background: rgb(255 255 255 / 0.75); + color: rgb(71 85 105); + } + + html[data-theme="light"] .tab-btn:hover { + border-color: rgb(6 182 212 / 0.45); + color: rgb(15 118 110); + } + + html[data-theme="light"] .tab-btn-active { + border-color: rgb(6 182 212 / 0.55) !important; + background: rgb(207 250 254 / 0.55) !important; + color: rgb(14 116 144) !important; + } + + .link-tile { + @apply btn-focus block rounded-2xl border px-4 py-2.5 text-sm font-medium transition-colors duration-200; + border-color: rgb(255 255 255 / 0.1); + background: rgb(0 0 0 / 0.2); + color: rgb(241 245 249); + } + + .link-tile:hover { + border-color: rgb(34 211 238 / 0.35); + } + + html[data-theme="light"] .link-tile { + border-color: rgb(226 232 240); + background: rgb(255 255 255 / 0.65); + color: rgb(30 41 59); + } + + .link-tile-accent { + border-color: rgb(34 211 238 / 0.28); + background: rgb(34 211 238 / 0.1); + color: rgb(236 254 255); + } + + .link-tile-accent:hover { + border-color: rgb(34 211 238 / 0.55); + } + + html[data-theme="light"] .link-tile-accent { + border-color: rgb(6 182 212 / 0.35); + background: rgb(207 250 254 / 0.65); + color: rgb(14 116 144); + } + + .link-tile-warn { + border-color: rgb(251 191 36 / 0.25); + background: rgb(251 191 36 / 0.1); + color: rgb(254 252 232); + } + + html[data-theme="light"] .link-tile-warn { + border-color: rgb(251 191 36 / 0.45); + background: rgb(254 252 232 / 0.9); + color: rgb(120 53 15); + } + + .service-card { + @apply rounded-3xl border p-5 transition-shadow duration-200; + border-color: rgb(255 255 255 / 0.08); + background: rgb(0 0 0 / 0.2); + } + + .service-card:hover { + box-shadow: 0 12px 40px -24px rgb(34 211 238 / 0.35); + } + + html[data-theme="light"] .service-card { + border-color: rgb(226 232 240 / 0.95); + background: rgb(255 255 255 / 0.55); + } + + .panel-inset { + @apply rounded-2xl border p-4 text-sm; + border-color: rgb(255 255 255 / 0.08); + background: rgb(0 0 0 / 0.2); + color: rgb(203 213 225); + } + + html[data-theme="light"] .panel-inset { + border-color: rgb(226 232 240 / 0.85); + background: rgb(255 255 255 / 0.5); + color: rgb(51 65 85); + } + + .field-select { + @apply w-full rounded-2xl border px-4 py-3 text-sm outline-none transition; + border-color: rgb(255 255 255 / 0.12); + background: rgb(2 6 23 / 0.55); + color: rgb(248 250 252); + } + + html[data-theme="light"] .field-select { + border-color: rgb(226 232 240); + background: rgb(255 255 255 / 0.9); + color: rgb(15 23 42); } .glow-text { diff --git a/web-console/app/page.tsx b/web-console/app/page.tsx index 32c9830..d6f3572 100644 --- a/web-console/app/page.tsx +++ b/web-console/app/page.tsx @@ -383,6 +383,29 @@ export default function Home() { [refreshFiles], ); + const jumpToConfig = useCallback( + (root: string, path: string) => { + setTab("config"); + window.setTimeout(() => { + void openManagedFile(root, path); + }, 0); + }, + [openManagedFile], + ); + + const probeHardware = useCallback(async () => { + setBusy("hardware:probe"); + try { + const data = await fetchJson("/hardware_profile?refresh=true"); + setHardware(data); + notify(tr("硬件探测已更新", "Hardware profile refreshed")); + } catch (reason) { + notify((reason as Error).message); + } finally { + setBusy(null); + } + }, [fetchJson, notify, tr]); + const boot = useCallback(async () => { setBooting(true); setError(null); @@ -439,6 +462,14 @@ export default function Home() { return () => window.clearInterval(timer); }, [currentProcess, refreshProcess]); + useEffect(() => { + if (booting || error) return; + if (tab !== "overview" && tab !== "modules") return; + void refreshServices(); + const timer = window.setInterval(() => void refreshServices(), 10000); + return () => clearInterval(timer); + }, [booting, error, refreshServices, tab]); + useEffect(() => { const visibleRoots = tab === "shellcrash" ? shellRoots : tab === "config" ? otherRoots : roots; if (!visibleRoots.length) return; @@ -703,7 +734,7 @@ export default function Home() { <>
{toast ? ( -
+
{toast}
) : null} @@ -725,11 +756,11 @@ export default function Home() {

-
+
{(stack?.mdns_host || "live.local") + - " | " + + " · " + (stack?.machine || "unknown") + - " | " + + " · " + (stack?.kernel || "unknown")}
-
+
-

{tr("业务运行时", "Business Runtime")}

-

{tr("这里只放业务脚本本身,Hub 基座可单独运行。", "Only business processes live here.")}

+

{tr("业务运行时", "Business Runtime")}

+

{tr("这里只放业务脚本本身,Hub 基座可单独运行。", "Only business processes live here.")}

{entries.length > 0 ? ( <> - setCurrentProcess(event.target.value)}> {entries.map((item) => ( ))} @@ -1006,7 +998,7 @@ export default function Home() { )}
-

{tr("业务日志", "Process Logs")}

+

{tr("业务日志", "Process Logs")}

{recentLog || tr("暂无输出", "No stdout yet.")}
{recentError || tr("暂无报错", "No stderr yet.")}
@@ -1018,16 +1010,23 @@ export default function Home() { {tab === "modules" ? (
-
+
-

{tr("系统模块", "System Modules")}

-

{tr("每个模块都独立启停和降级,避免一个服务异常拖垮整套业务。", "Each module starts and degrades independently.")}

+

{tr("系统模块", "System Modules")}

+

{tr("每个模块都独立启停和降级,避免一个服务异常拖垮整套业务。", "Each module starts and degrades independently.")}

+
+
+ +
-
{services.map((item) => ( -
+

{item.label}