// The ONE console navigation. // // Shape and grouping are lux.cloud's: flat labelled groups, text rows, no // icons and no expand/collapse. A nested tree hides half the product behind a // disclosure triangle, and it made this app's own nav incomplete — // /dashboard/settings, /dashboard/assets/{tokens,nfts}, // /dashboard/infrastructure/{lux,services,observability}, /chains and /agents // were all reachable by URL and by nothing else. Every one of them is a row // now. // // This list is the single source: the sidebar renders it, and the command // palette indexes it (buildCommandItems). Adding a section = one entry, and it // appears in both. export type NavItem = { name: string href: string description?: string external?: boolean /** Admin-only row — rendered only when the signed-in user holds `admin`. */ admin?: boolean } export type NavGroup = { label: string items: NavItem[] } export const DASHBOARD_NAVIGATION: NavGroup[] = [ { label: "Overview", items: [ { name: "Overview", href: "/dashboard", description: "Command centre and quick actions" }, { name: "Analytics", href: "/dashboard/analytics", description: "Usage, performance and chain telemetry" }, { name: "Usage", href: "/dashboard/usage", description: "Compute units and quota consumption" }, { name: "Markets", href: "/dashboard/markets", description: "Token markets and pricing" }, { name: "Gas", href: "/dashboard/gas", description: "Gas prices across supported chains" }, ], }, { label: "Infrastructure", items: [ { name: "Infrastructure", href: "/dashboard/infrastructure", description: "Everything you run, in one view" }, { name: "Nodes", href: "/dashboard/infrastructure/nodes", description: "Deployed nodes and their health" }, { name: "Networks", href: "/dashboard/infrastructure/networks", description: "Networks you participate in" }, { name: "Clusters", href: "/dashboard/infrastructure/clusters", description: "Node clusters and regions" }, { name: "Services", href: "/dashboard/infrastructure/services", description: "Managed services and endpoints" }, { name: "Fleet Map", href: "/dashboard/infrastructure/lux", description: "Validator fleet by region" }, { name: "Storage", href: "/dashboard/infrastructure/storage", description: "Object storage and buckets" }, { name: "Volumes", href: "/dashboard/infrastructure/volumes", description: "Block volumes attached to nodes" }, ], }, { label: "Observability", items: [ { name: "Monitoring", href: "/dashboard/monitoring", description: "Health checks, alerts and incidents" }, { name: "Service Health", href: "/dashboard/infrastructure/monitoring", description: "Per-service uptime and latency", }, { name: "Observability", href: "/dashboard/infrastructure/observability", description: "Logs, traces and metrics", }, ], }, { label: "Assets", items: [ { name: "Assets", href: "/dashboard/assets", description: "Portfolio across every chain" }, { name: "Tokens", href: "/dashboard/assets/tokens", description: "Fungible token balances" }, { name: "NFTs", href: "/dashboard/assets/nfts", description: "Non-fungible holdings" }, { name: "Wallets", href: "/dashboard/wallets", description: "Custody and signing wallets" }, { name: "Staking", href: "/dashboard/staking", description: "Delegations and validator rewards" }, ], }, { label: "Developer", items: [ { name: "API Keys", href: "/dashboard/api-keys", description: "Access credentials for the RPC gateway" }, { name: "Webhooks", href: "/dashboard/webhooks", description: "Event delivery endpoints" }, { name: "Chains", href: "/chains", description: "Every supported chain and its endpoints" }, { name: "Agents", href: "/agents", description: "Autonomous agents and their runs" }, { name: "Docs", href: "/docs", description: "SDKs, quickstarts and API reference" }, ], }, { label: "Organization", items: [ { name: "Organization", href: "/dashboard/organization", description: "Your organization at a glance" }, { name: "Team", href: "/dashboard/organization/team", description: "Members, roles and invitations" }, { name: "Org Settings", href: "/dashboard/organization/settings", description: "Organization-wide settings" }, { name: "Billing", href: "/dashboard/organization/billing", description: "Plans, invoices, tax and compliance" }, { name: "Preferences", href: "/dashboard/settings", description: "Your personal console preferences" }, { name: "Admin", href: "/admin", description: "Platform administration", admin: true }, ], }, ] /** * Which single nav row is current for a given pathname. * * Two things this has to get right, both of which the obvious * `pathname.startsWith(href)` per row gets wrong: * * 1. TRAILING SLASHES. The image serves clean URLs by materialising a * directory index per page (see web/Dockerfile), so the browser's pathname * is `/dashboard/`, not `/dashboard`. An equality test against the href * never matched and the console rendered with nothing highlighted at all. * 2. NESTING. `/dashboard/infrastructure/nodes` also starts with * `/dashboard/infrastructure`, so a per-row predicate lights BOTH rows and * the reader cannot tell where they are. Only the most specific match — * the longest href — is current. * * Which makes this a selection over the whole nav, not a test per row. */ export function currentHref(pathname: string, hrefs: string[]): string | null { const trim = (p: string) => (p.length > 1 ? p.replace(/\/+$/, "") : p) const path = trim(pathname) let best: string | null = null for (const href of hrefs) { const h = trim(href) if (path === h || path.startsWith(h + "/")) { if (best === null || h.length > trim(best).length) best = href } } return best } /** Every href in the nav, in order. */ export function navHrefs(isAdmin = false): string[] { return DASHBOARD_NAVIGATION.flatMap((g) => g.items.filter((i) => !i.admin || isAdmin).map((i) => i.href)) } export interface DashboardCommandItem { id: string title: string description: string href: string section: "Navigation" | "Actions" keywords: string[] } export function buildCommandItems(): DashboardCommandItem[] { const items: DashboardCommandItem[] = DASHBOARD_NAVIGATION.flatMap((group) => group.items.map((item) => ({ id: item.href, title: group.label === item.name ? item.name : `${group.label} · ${item.name}`, description: item.description ?? "", href: item.href, section: "Navigation" as const, keywords: [group.label.toLowerCase(), item.name.toLowerCase(), item.href], })) ) items.push( { id: "deploy-node", title: "Deploy New Node", description: "Open node deploy experience with chain onboarding", href: "/dashboard/infrastructure/nodes?deploy=1", section: "Actions", keywords: ["deploy", "node", "chain", "infrastructure"], }, { id: "open-billing", title: "Open Billing Workspace", description: "Business info, payment methods, invoices, tax and compliance", href: "/dashboard/organization/billing", section: "Actions", keywords: ["billing", "invoice", "tax", "compliance", "payment"], }, { id: "new-api-key", title: "Create API Key", description: "Manage and rotate API credentials", href: "/dashboard/api-keys?new=1", section: "Actions", keywords: ["api", "key", "credential"], }, { id: "new-webhook", title: "Create Webhook", description: "Add webhook destination for chain events", href: "/dashboard/webhooks?new=1", section: "Actions", keywords: ["webhook", "event", "integration"], } ) return items }