Dashboard
Mount the operator interface behind application authorization, or run it standalone with a built-in administrator login.
@stablemates/workhorse-dashboard gives your operators a full interface — jobs, attempts, checkpoints,
schedules, queues, workers, failures, health — without you building a single admin page. It reads
everything from PostgreSQL, so a dashboard mounted anywhere can observe workers running on
machines it has never met.
What it shows
Every view comes from the same read models as Queries and timelines: payload-free job listings, per-job timelines with redacted payloads, dead letters, the worker registry, and the health verdict with its reasons. With host-authorized controllers wired in, it can also cancel jobs, run schedules now, pause queues and workers, and edit maintenance and retention policy.
Mount it in your server
createDashboardHost builds a framework-neutral handler over standard Request/Response
objects. It answers only for requests under its configured path and returns null for
everything else.
import { createDashboardHost } from "@stablemates/workhorse-dashboard/server";
const host = createDashboardHost({
path: "/workhorse",
database: pool,
environment: "production",
authorize: (request) => isOperator(request),
});Fetch-native frameworks — Hono, Next.js route handlers, SvelteKit, Nitro — call
host.handle(request) and fall through when it resolves to null:
app.all("/workhorse/*", async (c) => {
const response = await host.handle(c.req.raw);
return response ?? c.notFound();
});Connect-style frameworks — Express, Connect, Fastify via @fastify/middie — wrap the same host:
import { dashboardNodeMiddleware } from "@stablemates/workhorse-dashboard/server";
app.use(dashboardNodeMiddleware(host));The middleware passes unowned requests to next() untouched, so the dashboard never captures
unrelated routes. On every owned request, the host checks schema compatibility and refuses with a
clear error rather than serving against a mismatched database. It never installs or migrates
anything.
Serve several databases as workspaces
One dashboard can serve several Workhorse databases as named workspaces, switchable from the
header. Configure workspaces instead of database; each entry owns its connection and may
override environment, operator, and the controllers, falling back to the host-level options:
const host = createDashboardHost({
path: "/workhorse",
workspaces: {
production: { database: productionPool, environment: "production" },
staging: { database: stagingPool, environment: "staging" },
},
defaultWorkspace: "production",
authorize: (request, workspace) => isOperator(request, workspace),
});Each workspace lives under its own path — /workhorse/production, /workhorse/staging — so
links deep into one workspace stay shareable and two browser tabs can watch two workspaces. The
authorize callback receives the resolved workspace name, letting the application grant access
per workspace. The host still never owns pool sizing, shutdown, or credentials for any of the
connections.
The standalone CLI serves workspaces too: repeat --workspace name=url, or point --config at a
JSON file whose entries name a url directly or a urlEnv environment variable that keeps
credentials out of the file.
Choose the authentication boundary
An embedded dashboard uses your application's authentication through authorize. The callback
runs before the interface, assets, and every RPC method. Return a verified principal for an
authenticated operator, false for a 403, or your own Response to redirect to a login flow.
Mutations go further. The operator, queueController, taskController, workerController,
scheduleController, and settingsController options let each mutation pass through your own
authorization and audit services. auditActor and the requestedBy fields provide attribution
after authorization — they record who acted, they never decide who may.
The settings page edits maintenance and retention policy through DashboardSettingsController,
previews destructive retention changes before applying them, and shows process-owned worker
values read-only, because changing those requires a deployment.
The standalone CLI
For a quick local console, skip the mounting entirely:
workhorse dashboard --port 3000It binds 127.0.0.1 and serves read-only by default. Without credentials, that loopback listener
is an explicit development bypass and cannot bind to a remotely reachable address.
Set WORKHORSE_DASHBOARD_USERNAME and WORKHORSE_DASHBOARD_PASSWORD_HASH to enable the built-in
administrator login. The standalone server owns session expiry, logout, password rotation, origin
checks, and login throttling. A remote listener also requires an HTTPS public origin. Installations
that need multiple users, roles, SSO, or tenant isolation should use an embedded host and its
authorize callback.
The login page follows the browser's color scheme. The dashboard header shows the authenticated administrator and provides sign-out; if the session expires while the page is open, the next private request returns the browser to login.
Customize without forking
Hosts that want the components inside their own React tree can render Dashboard and
WorkhorseThemeProvider directly, connected through createDashboardClient, importing the
packaged stylesheet for tokens and layout. configuredWorkers describes expected worker
processes before they first register, and projectDurability translates checkpoint evidence
into domain-specific step names without touching stored queue data.
Refresh model
The browser polls only the active page at the selected cadence, so job volume never multiplies browser requests; manual refresh covers the rest. Remember what you are looking at: an operator view, not a transactional snapshot. PostgreSQL statistics and worker registry rows can lag the processes behind them by design.
Next
- Queries and timelines — the same read models from application code
- Installation — add only the optional packages you use
- Operations — fleet controls behind the dashboard's buttons
Exact operator projection, worker registry, and read-model behavior: architecture reference.