Control-plane dashboard
Architecture of the HostSSH SaaS dashboard — stack, design system, RBAC, and how to add a page.
Control-plane dashboard
The hostssh.com SaaS dashboard — the "supreme" control surface for the whole fleet.
- Location:
control-plane/web/ - Stack: Next.js 16 (App Router, RSC) · React 19 · TypeScript (strict) · Tailwind CSS v4 · lucide-react
- Run:
cd control-plane/web && npm install && npm run dev→ http://localhost:3000
Layout
control-plane/web/
app/
layout.tsx root: <html> data-density/data-launch, fonts, ToastProvider
globals.css design tokens (@theme) + base styles + keyframes
(god)/
layout.tsx shell: Sidebar + Topbar + Impersonation banner + ⌘K + shortcuts
loading.tsx premium route-level skeleton
page.tsx Command Center (home)
<section>/page.tsx one folder per IA section
components/
shell/ sidebar, topbar, command-palette, shortcuts, launch-mode-switcher, simulator-pill, impersonation-banner
ui/ the ONE component system (card, stat, badge, table, button, drawer, meter, toast, spinner, page-header, section, empty-state)
<section>/ co-located client components for a section
lib/
types.ts domain contracts (Server, Image, Connection, License, Tenant, …)
data.ts async mock accessors (swap bodies for the real API later)
nav.ts information architecture + keyboard SHORTCUTS (single source of truth)
rbac.ts permission catalog + userHasPermission (fail-closed)
session.ts god-mode session: launch-mode, impersonation, simulator (cookie-backed)
actions.ts server actions that mutate the control state
access.ts visibleNavKeys() + guard(permission)
format.ts bytes/money/relativeTime/… (deterministic)
cn.ts class merge
Design language
Ultra-refined, command-first, calm density. Encoded as Tailwind v4 @theme tokens in
app/globals.css: a near-black surface ramp, one accent (infra cyan) plus the
dark-red god chrome (--color-god) reserved for elevation, impersonation, and
destructive actions. Two density modes via data-density. The shell tints crimson in
God launch-mode via [data-launch='god'].
Use only the primitives in components/ui/*. Numbers use the tabular class. Never
introduce new colors — extend the token set if a new semantic is truly needed.
God-mode layer
- RBAC (
lib/rbac.ts): granular permission catalog;super_admin→ all, sub-admins → granted subset;admin.manage_admins+hardware.manageare super-only. - Launch mode (
god/beta/production): switched from the top bar; recolors chrome and gates god-only sections (e.g. Hardware). - Impersonation: cookie-carried, expiring, read-only/read-write; a crimson banner is always present while active, with one-click exit.
- Simulator: preview the product as any tier/tenant without leaving your identity.
Add a new section page
-
Add the entry to
lib/nav.ts(NAVgroup + aSHORTCUTS[key]chord). -
Create
app/(god)/<key>/page.tsxas a server component:import { guard } from '@/lib/access' import { PageHeader } from '@/components/ui/page-header' import { LockedState } from '@/components/ui/empty-state' import { getThings } from '@/lib/data' export default async function ThingsPage() { const { allowed } = await guard('things.view') if (!allowed) return (<><PageHeader title="Things" /><LockedState /></>) const things = await getThings() return ( <> <PageHeader title="Things" hint="…" /> {/* compose ui/* primitives */} </> ) } -
For interactivity (filters, drawers, action calls, toasts), add a
'use client'component undercomponents/<key>/and pass it serializable props only. Call server actions viauseTransition; fireuseToast()on success.
See app/(god)/fleet/page.tsx +
components/fleet/fleet-table.tsx
as the canonical example.
From mock to real
lib/data.ts returns realistic fixtures today (the live Girard Media fleet + sample
tenants). Each accessor is async with the final signature, so wiring the real
control-plane API / agent telemetry is a body swap — pages don't change. Likewise
lib/session.ts seeds the immutable root super-admin (sgirard@girardmedia.com);
replace getSession() with the real auth lookup, keeping the Session shape.