Skip to content
Website

Dashboards

A dashboard is a pair of Bun services — a frontend and a backend — under ~/.adi/mono/dashboards/<id>/, authored as loose .ts files. It’s a hive service under the hood (the per-user dashboards hive supervises both halves), so it gets a real <host>.adi address the moment it’s created.

Use a dashboard for results tables or job controls. Add panels in frontend/modules/<name>.ts and endpoints in backend/routes/<name>.ts; changes hot-reload, with no build step and no deploy.

Panel: /dashboards. API: POST /api/dashboards/create ({ "name", "description", "template" }) — runs the chosen template (below) and leases ports; both services start on the first request. Leave template out for a dependency-free scaffold with no build step.

Edit frontend/modules/*.ts and backend/routes/*.ts directly; those two directories are the only ones a template or migration never touches. Read the scaffold’s own README.md for the exact shape a module or route has to export.

TemplatesExpert area

Section titled “Templates”

Creating a dashboard runs a shell command — there’s no compiled-in scaffold. Each template is defined in ~/.adi/mono/templates/<id>/template.toml:

label = "Vite + React 19 + Hono"
engine = "bash"
requires = ["bun"]
command = 'bun create adi-family/dashboard-vite-react "$ADI_DASHBOARD_DIR" && bun "$ADI_DASHBOARD_DIR/scripts/adi-init.ts"'

id is the directory’s name, never a field in the file. engine says how command runs — "bash" (bash -c '<command>') is the only one today. requires lists the CLIs command needs on PATH. Creation fails before the command runs if a required CLI is missing; the error names it. command is the literal shell command — nothing substitutes into its text, so there’s no ref or version field; a template pins whatever it wants inside command itself.

command runs with four environment variables, and nothing else is passed to it:

VariableValue
ADI_DASHBOARD_DIRthe directory the command must create
ADI_DASHBOARD_IDthe new dashboard’s id
ADI_DASHBOARD_HOSTits <id>.adi host
ADI_TEMPLATE_DIR~/.adi/mono/templates/<id>/, for a template’s own files/

It’s responsible for the whole dashboard, including its own .adi/hive.yaml — the API only writes config.toml’s name, description, and [created_from] (the template id and the exact command run) once the command succeeds. [created_from] is informational only; nothing reads it back, and nothing ever rewrites a created dashboard afterwards.

Two templates are included: plain (dependency-free) and vite-react. They run bun create against adi-family/dashboard-plain and adi-family/dashboard-vite-react, respectively. GET /api/dashboards/templates lists every template installed on this machine, with which ones this machine is actually able to run.

A create can fail three ways: an unknown template id is a 400 listing the ids that do exist; a missing requires CLI is a 400 naming it, before anything runs; a command that exits non-zero is a 500 — the half-made directory is removed, and the response carries the command’s own output.

Installing moreEnterprise interest

Section titled “Installing more”

Review template.toml as you would any script. The marketplace installs templates by copying their folders into templates/ when requested by id; nothing else about creating a dashboard changes. Point the marketplace at your own bundle repository to offer only templates you’ve written or vetted, instead of the two included ones.

One origin, relative paths onlyExpert area

Section titled “One origin, relative paths only”

A dashboard is one hostname: the frontend owns /, the backend claims /api on the same host. A panel calls ctx.api.get("/status"), never http://127.0.0.1:<port>/status and never http://<host>.adi/status — no host or port is ever written into a panel, a route, or the shell. That’s what makes the exact same dashboard work locally at <host>.adi, over the mesh at <host>.<node>.n.adi, and behind a real domain later, for every viewer, with no substitution.

POST /api/dashboards/archive, then /delete — delete refuses unless the dashboard is archived first, so removing one is always two deliberate steps.