From 8ecbb13f52f55b026c7be90c9428c0ca9ab06d87 Mon Sep 17 00:00:00 2001 From: oliver Date: Tue, 13 Oct 2026 01:51:37 +0800 Subject: [PATCH] Redesign the shell and console with a liquid-glass look. - Title bar: floating glass capsule aligned to each page's content edge, new NETX mark (links-as-X), task badge, user menu with sign-out, globe locale. - Console: resource strip on top, module launcher as the main area with fixed-size tiles that fill rows as modules are added, task rail sized to the left column; dropped health pill, refresh button and filler copy. - Glass layer (theme/glass.css, loaded last): black-blue wallpaper with a faint dot grid, clear panels with specular rims, translucent controls and table headers; sidebar pages float their nav and work area on a 16px gutter. - Removed ~280 lines of duplicated header CSS from primitives/theme. Co-Authored-By: Claude Opus 5.5 --- web/public/favicon.svg | 30 +- web/src/features/workbench/WorkbenchPage.tsx | 514 ++++++++---------- .../workbench/components/WorkbenchModules.tsx | 67 +-- web/src/i18n/locales/en/layout.ts | 1 + web/src/i18n/locales/zh/layout.ts | 1 + web/src/index.css | 1 + web/src/layout/AppLayout.tsx | 84 ++- web/src/layout/BrandMark.tsx | 23 + web/src/layout/HeaderMenu.tsx | 5 +- web/src/styles/components/primitives.css | 224 -------- web/src/styles/layout/app-shell.css | 344 +++++++++--- web/src/styles/pages/workbench.css | 320 ++++++----- web/src/styles/theme/glass.css | 235 ++++++++ web/src/styles/theme/theme.css | 62 --- web/src/styles/theme/tokens.css | 19 + 15 files changed, 1035 insertions(+), 895 deletions(-) create mode 100644 web/src/layout/BrandMark.tsx create mode 100644 web/src/styles/theme/glass.css diff --git a/web/public/favicon.svg b/web/public/favicon.svg index 7097b3e..75b4ccc 100644 --- a/web/public/favicon.svg +++ b/web/public/favicon.svg @@ -1,16 +1,16 @@ - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/web/src/features/workbench/WorkbenchPage.tsx b/web/src/features/workbench/WorkbenchPage.tsx index ba9762d..b7fb4fb 100644 --- a/web/src/features/workbench/WorkbenchPage.tsx +++ b/web/src/features/workbench/WorkbenchPage.tsx @@ -48,11 +48,11 @@ const FLEET_TONE_COLOR: Record<"ok" | "mid" | "warn" | "bad" | "muted", string> const REGION_PALETTE = [ "#38bdf8", "#22d3ee", - "#818cf8", + "#60a5fa", "#34d399", "#fbbf24", "#fb7185", - "#a78bfa", + "#93c5fd", "#2dd4bf", ]; @@ -109,10 +109,10 @@ function FleetDonut({ return (
+
{center}
{unit ?
{unit}
: null} @@ -373,17 +373,13 @@ export function WorkbenchPage() { const webMax = Math.max(1, Number(metrics?.webcrt?.max_sessions || 20)); const webPct = ratioPct(webUsed, webMax); - const hostBusy = cpuPct >= 75 || memPct >= 75; - const platformBusy = cliPct >= 75 || dbPct >= 75 || webPct >= 75; const schedulers = metrics?.device_schedulers; const schedConfigOn = Boolean(schedulers?.config_sync?.running ?? metrics?.config_sync?.running); const schedLldpOn = Boolean(schedulers?.lldp_collect?.running ?? metrics?.lldp_collect?.running); const schedPortOn = Boolean(schedulers?.port_traffic?.running ?? metrics?.port_traffic?.running); const schedNeOn = Boolean(schedulers?.ne_collect?.running); const schedStale = Boolean(schedulers?.stale); - const schedMode = String(schedulers?.mode || ""); const schedAge = typeof schedulers?.age_sec === "number" ? schedulers.age_sec : null; - const healthBusy = hostBusy || platformBusy || schedStale; const canReadFleet = isAdmin || hasScope("ne:read"); const neTotal = neStatsQuery.data?.total ?? 0; @@ -392,13 +388,6 @@ export function WorkbenchPage() { const neFail = Number(neByStatus.fail || 0); const neTesting = Number(neByStatus.testing || 0); const neUnknown = Number(neByStatus.unknown || 0); - const neNoTag = Number(neStatsQuery.data?.no_tag_count || 0); - const neKnownPct = neTotal > 0 ? clampPct(((neTotal - neUnknown) / neTotal) * 100) : 0; - const neTopTags = Object.entries(neStatsQuery.data?.tag_counts ?? {}) - .filter(([tag, n]) => tag && tag !== "__no_tag__" && Number(n) > 0) - .sort((a, b) => Number(b[1]) - Number(a[1])) - .slice(0, 2) - .map(([tag, n]) => ({ tag, n: Number(n) })); const neValue = !canReadFleet ? "—" : neStatsQuery.isLoading @@ -409,8 +398,6 @@ export function WorkbenchPage() { const fabricNodes = Number(fabricQuery.data?.node_count || 0); const fabricEdges = Number(fabricQuery.data?.edge_count || 0); - const fabricDiscover = formatCompactTime(fabricQuery.data?.last_discover_at ?? null); - const fabricUpdated = formatCompactTime(fabricQuery.data?.updated_at ?? null); const regionSlices = useMemo((): FleetSlice[] => { const regions = topLevelRegions(topoTreeQuery.data?.root) @@ -493,307 +480,234 @@ export function WorkbenchPage() { }, ]; const schedOnCount = schedRows.filter((r) => r.on).length; - const refreshing = [metricsQuery, opsQuery, neStatsQuery, fabricQuery, topoTreeQuery].some((query) => query.isFetching); - const refreshConsole = () => { - void metricsQuery.refetch(); - void opsQuery.refetch(); - if (canReadFleet) { - void neStatsQuery.refetch(); - void fabricQuery.refetch(); - void topoTreeQuery.refetch(); - } - }; - const healthKnown = Boolean(metrics && hostOk && !metricsQuery.isError); - const healthTone = !healthKnown ? "is-unknown" : healthBusy ? "is-busy" : "is-ok"; - const healthLabel = !healthKnown ? t("workbench.kpi.healthUnknown") : healthBusy ? t("workbench.kpi.healthWarn") : t("workbench.kpi.healthOk"); const taskValue = opsQuery.isLoading ? "…" : opsQuery.isError ? "—" : formatCount(activeTasks); const schedValue = metricsQuery.isLoading ? "…" : !schedulers || metricsQuery.isError ? "—" : `${schedOnCount}/${schedRows.length}`; return (
-
-
-
-

{t("workbench.consoleTitle")}

- -
-
-
- {metricsQuery.dataUpdatedAt ? t("workbench.refreshedAt", { time: new Date(metricsQuery.dataUpdatedAt).toLocaleTimeString(undefined, { hour12: false }) }) : t("workbench.awaitingData")} - -
-
- -
-
- - -
-
-

{t("workbench.fleet")}

- -
-
-