From a508ee3d2ed374000777b65934bbc4174e76f088 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Mon, 25 May 2026 14:56:01 -0400 Subject: [PATCH] feat(web): regroup settings nav under General and Administration Group sidebar items under General and Administration section labels; default Settings landing page to Models; rename General page to Server (now at /settings/server); rename Resources to Monitoring (now at /settings/monitoring) with ChartBarSquare icon. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/fabro-web/app/router.test.tsx | 4 +- apps/fabro-web/app/router.tsx | 11 ++-- .../fabro-web/app/routes/settings-general.tsx | 2 +- ....test.tsx => settings-monitoring.test.tsx} | 16 ++--- ...-resources.tsx => settings-monitoring.tsx} | 4 +- apps/fabro-web/app/routes/settings.tsx | 66 +++++++++++-------- 6 files changed, 59 insertions(+), 44 deletions(-) rename apps/fabro-web/app/routes/{settings-resources.test.tsx => settings-monitoring.test.tsx} (93%) rename apps/fabro-web/app/routes/{settings-resources.tsx => settings-monitoring.tsx} (98%) diff --git a/apps/fabro-web/app/router.test.tsx b/apps/fabro-web/app/router.test.tsx index 2901413f5..89def916e 100644 --- a/apps/fabro-web/app/router.test.tsx +++ b/apps/fabro-web/app/router.test.tsx @@ -33,9 +33,9 @@ describe("browser router", () => { expect(paths).not.toContain("/setup/complete"); }); - test("exposes the resources settings page", () => { + test("exposes the monitoring settings page", () => { const paths = collectPaths(routes); - expect(paths).toContain("/settings/resources"); + expect(paths).toContain("/settings/monitoring"); }); }); diff --git a/apps/fabro-web/app/router.tsx b/apps/fabro-web/app/router.tsx index c3c13b9b5..fd41edaba 100644 --- a/apps/fabro-web/app/router.tsx +++ b/apps/fabro-web/app/router.tsx @@ -40,7 +40,7 @@ import * as SettingsModels from "./routes/settings-models"; import * as SettingsSandboxes from "./routes/settings-sandboxes"; import * as SettingsSecrets from "./routes/settings-secrets"; import * as SettingsSecretsNew from "./routes/settings-secrets-new"; -import * as SettingsResources from "./routes/settings-resources"; +import * as SettingsMonitoring from "./routes/settings-monitoring"; import * as SettingsSecurity from "./routes/settings-security"; import * as SettingsStorage from "./routes/settings-storage"; import * as SettingsLiveEvents from "./routes/settings-live-events"; @@ -142,15 +142,16 @@ export const routes: RouteObject[] = [ }), route("settings", Settings, { children: [ - indexRoute(SettingsGeneral), - route("integrations", SettingsIntegrations), + indexRoute(SettingsModels), route("models", SettingsModels), + route("integrations", SettingsIntegrations), route("sandboxes", SettingsSandboxes), + route("security", SettingsSecurity), route("secrets", SettingsSecrets), route("secrets/new", SettingsSecretsNew), - route("security", SettingsSecurity), + route("server", SettingsGeneral), route("storage", SettingsStorage), - route("resources", SettingsResources), + route("monitoring", SettingsMonitoring), route("live-events", SettingsLiveEvents), ], }), diff --git a/apps/fabro-web/app/routes/settings-general.tsx b/apps/fabro-web/app/routes/settings-general.tsx index 1d4838683..470a7e7fb 100644 --- a/apps/fabro-web/app/routes/settings-general.tsx +++ b/apps/fabro-web/app/routes/settings-general.tsx @@ -21,7 +21,7 @@ import { } from "../components/settings-panel"; export function meta() { - return [{ title: "General settings — Fabro" }]; + return [{ title: "Server settings — Fabro" }]; } const DESCRIPTION = ( diff --git a/apps/fabro-web/app/routes/settings-resources.test.tsx b/apps/fabro-web/app/routes/settings-monitoring.test.tsx similarity index 93% rename from apps/fabro-web/app/routes/settings-resources.test.tsx rename to apps/fabro-web/app/routes/settings-monitoring.test.tsx index f8c225bc8..10227b98a 100644 --- a/apps/fabro-web/app/routes/settings-resources.test.tsx +++ b/apps/fabro-web/app/routes/settings-monitoring.test.tsx @@ -21,14 +21,14 @@ mock.module("../lib/queries", () => ({ useSystemResources: () => ({ data: systemResources }), })); -const { default: SettingsResources } = await import("./settings-resources"); +const { default: SettingsMonitoring } = await import("./settings-monitoring"); const mountedRenderers: TestRenderer.ReactTestRenderer[] = []; -function renderSettingsResources() { +function renderSettingsMonitoring() { let renderer: TestRenderer.ReactTestRenderer | undefined; act(() => { - renderer = TestRenderer.create(); + renderer = TestRenderer.create(); }); mountedRenderers.push(renderer!); return renderer!; @@ -103,7 +103,7 @@ function sampleServerSettings(maxConcurrentRuns = 8): ServerSettings { } as unknown as ServerSettings; } -describe("SettingsResources route", () => { +describe("SettingsMonitoring route", () => { beforeEach(() => { teardownReactTestEnv = setupReactTestEnv(); systemInfo = { runs: { active: 3, total: 12 } }; @@ -126,7 +126,7 @@ describe("SettingsResources route", () => { test("renders loaded resource data", () => { systemResources = sampleResources(); - const renderer = renderSettingsResources(); + const renderer = renderSettingsMonitoring(); const text = textContent(renderer.toJSON()); expect(text).toContain("18%"); @@ -148,7 +148,7 @@ describe("SettingsResources route", () => { }, }); - const renderer = renderSettingsResources(); + const renderer = renderSettingsMonitoring(); expect(textContent(renderer.toJSON())).toContain("Collecting sample"); }); @@ -179,7 +179,7 @@ describe("SettingsResources route", () => { }, }); - const renderer = renderSettingsResources(); + const renderer = renderSettingsMonitoring(); const text = textContent(renderer.toJSON()); expect(text).toContain("Unsupported"); @@ -192,7 +192,7 @@ describe("SettingsResources route", () => { notes: ["Memory is scoped to the current container."], }); - const renderer = renderSettingsResources(); + const renderer = renderSettingsMonitoring(); expect(textContent(renderer.toJSON())).toContain( "Memory is scoped to the current container.", diff --git a/apps/fabro-web/app/routes/settings-resources.tsx b/apps/fabro-web/app/routes/settings-monitoring.tsx similarity index 98% rename from apps/fabro-web/app/routes/settings-resources.tsx rename to apps/fabro-web/app/routes/settings-monitoring.tsx index 8d6409adc..b058e6d27 100644 --- a/apps/fabro-web/app/routes/settings-resources.tsx +++ b/apps/fabro-web/app/routes/settings-monitoring.tsx @@ -17,13 +17,13 @@ import { } from "../components/settings-panel"; export function meta() { - return [{ title: "Resources — Fabro" }]; + return [{ title: "Monitoring — Fabro" }]; } const DESCRIPTION = "Server-visible run concurrency, CPU, memory, and storage filesystem usage for this Fabro process."; -export default function SettingsResources() { +export default function SettingsMonitoring() { const resourcesQuery = useSystemResources(); const resources = resourcesQuery.data; diff --git a/apps/fabro-web/app/routes/settings.tsx b/apps/fabro-web/app/routes/settings.tsx index f4c0e1758..c577262c7 100644 --- a/apps/fabro-web/app/routes/settings.tsx +++ b/apps/fabro-web/app/routes/settings.tsx @@ -1,12 +1,12 @@ import { BoltIcon, + ChartBarSquareIcon, CircleStackIcon, Cog6ToothIcon, CpuChipIcon, CubeTransparentIcon, KeyIcon, PuzzlePieceIcon, - ServerStackIcon, ShieldCheckIcon, } from "@heroicons/react/24/outline"; import { Link, Outlet, useLocation, useMatches } from "react-router"; @@ -25,28 +25,19 @@ type NavItem = { match: (pathname: string) => boolean; }; +type NavSection = { type: "section"; key: string; label: string }; + type NavDivider = { type: "divider"; key: string }; -type NavEntry = NavItem | NavDivider; +type NavEntry = NavItem | NavSection | NavDivider; const navItems: NavEntry[] = [ - { - name: "General", - href: "/settings", - icon: Cog6ToothIcon, - match: (p) => p === "/settings", - }, + { type: "section", key: "general", label: "General" }, { name: "Models", href: "/settings/models", icon: CpuChipIcon, - match: (p) => p.startsWith("/settings/models"), - }, - { - name: "Sandboxes", - href: "/settings/sandboxes", - icon: CubeTransparentIcon, - match: (p) => p.startsWith("/settings/sandboxes"), + match: (p) => p === "/settings" || p.startsWith("/settings/models"), }, { name: "Integrations", @@ -55,10 +46,10 @@ const navItems: NavEntry[] = [ match: (p) => p.startsWith("/settings/integrations"), }, { - name: "Secrets", - href: "/settings/secrets", - icon: KeyIcon, - match: (p) => p.startsWith("/settings/secrets"), + name: "Sandboxes", + href: "/settings/sandboxes", + icon: CubeTransparentIcon, + match: (p) => p.startsWith("/settings/sandboxes"), }, { name: "Security", @@ -66,6 +57,19 @@ const navItems: NavEntry[] = [ icon: ShieldCheckIcon, match: (p) => p.startsWith("/settings/security"), }, + { + name: "Secrets", + href: "/settings/secrets", + icon: KeyIcon, + match: (p) => p.startsWith("/settings/secrets"), + }, + { type: "section", key: "administration", label: "Administration" }, + { + name: "Server", + href: "/settings/server", + icon: Cog6ToothIcon, + match: (p) => p.startsWith("/settings/server"), + }, { name: "Storage", href: "/settings/storage", @@ -73,12 +77,12 @@ const navItems: NavEntry[] = [ match: (p) => p.startsWith("/settings/storage"), }, { - name: "Resources", - href: "/settings/resources", - icon: ServerStackIcon, - match: (p) => p.startsWith("/settings/resources"), + name: "Monitoring", + href: "/settings/monitoring", + icon: ChartBarSquareIcon, + match: (p) => p.startsWith("/settings/monitoring"), }, - { type: "divider", key: "after-storage" }, + { type: "divider", key: "after-administration" }, { name: "Live Events", href: "/settings/live-events", @@ -88,7 +92,7 @@ const navItems: NavEntry[] = [ ]; function isLink(entry: NavEntry): entry is NavItem { - return entry.type !== "divider"; + return entry.type !== "divider" && entry.type !== "section"; } function classNames(...classes: Array) { @@ -115,7 +119,7 @@ export default function SettingsLayout() {