Files
bootnode/console/lib/dashboard-navigation.ts
zeekayandHanzo Dev 7d293212cb refactor: web/ is now console/ — bootnode/bootnode is the backend, bootnode/console the frontend
Names the two halves for what they are. The repo already holds both (12407e9 merged
bootnode/bootnode into this history); this makes the frontend directory say so, so the layout
reads the way the owner describes the system: backend and frontend separate, one repo.

git mv throughout — 187 renames, history preserved. Every path reference follows: hanzo.yml
build context and dockerfile, compose.yml, compose.prod.yml, start.sh, LLM.md,
infra/k8s/web-deployments.yaml, infra/cloud/deploy.sh, and the Dockerfile's own COPY paths.
Image and container names (bootnode-web, the -web tag suffix) are deliberately NOT renamed here:
those are a deploy contract the App CR and ingress bind to on port 3001, and breaking them is a
separate, staged change.

Console builds green after the move; the Go tree builds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 17:47:51 -07:00

195 lines
7.8 KiB
TypeScript

// 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
}