oxid/ docs

// dashboard

Web dashboard

Served by the daemon itself at / — a handful of static files embedded at compile time. No build step, no bundler, and no request that leaves the host: Alpine.js is vendored, there is no webfont and no icon font. Deep links like /ui/projects/1 or /ui/environments/5?tab=logs survive a hard refresh.

Everything the panel does goes through the same HTTP API the CLI uses. There is no privileged back channel; anything you can do here you can script.

The views

SectionWhat you'll find
/ui/onboardingSetup wizard — auto-opens on first visit. Five steps, all of them also available from the CLI. Re-run anytime from Setup in the top bar.
/ui/environmentsEvery environment across projects — filter by project, state or branch; pause, wake and destroy inline; auto-refresh.
/ui/environments/{id}Detail: address, commit, timestamps, plus tabs for live logs (SSE) and that environment's history.
/ui/projectsRegistered projects, their live branches and their detected stack tag.
/ui/projects/{id}Settings (idle/lifetime policy, private-repo git token), a deploy form, per-branch environments, and for a monorepo the list of detected services with the active one marked.
/ui/secretsGlobal and project-scoped secrets. Write-only — values are never shown back.
/ui/queueDeploys waiting for host capacity, oldest and highest-priority first.
/ui/auditCross-project audit trail with search and deploy durations.
/ui/diagnosticsThe daemon's own health: Docker capacity, network and Traefik wiring, what is missing and how to fix it.
/ui/adminBackup download, master-key rotation, named API tokens.

First five minutes — the wizard

The first visit redirects to /ui/onboarding. Every step has a CLI equivalent; the wizard exists so a first install does not require reading these docs.

#StepWhat it doesBehind the scenes
1TokenPaste the OXID_API_TOKEN — the installer printed it, or the daemon can hand it over directly depending on OXID_BOOTSTRAP_TOKEN_ACCESS.GET /api/v1/setup/token, verified with GET /api/v1/stats
2InfrastructureChecks the Docker network, Traefik and the wake catch-all router. One click fixes whatever is missing.GET /api/v1/infra/status → POST /api/v1/infra/bootstrap (idempotent)
3First projectRegister by Git URL (https://… or scp-style git@host:org/repo.git; private repos take an encrypted PAT), then deploy main with live polling.POST /api/v1/projects → POST /api/v1/projects/{id}/deploy
4WebhooksPick a provider and copy the URL and secret into your Git host.GET /api/v1/setup/webhook-secret (master token only)
5CLIA copy-paste oxid context add line and a curl snippet for programmatic registration.—

Direct-publish mode (no OXID_DOCKER_NETWORK) reports “nothing to bootstrap” at step 2. That is a valid topology — each environment gets its own host port and no DNS is needed — with scale-to-zero off by design.

Installable, and it works offline

The panel is a PWA: manifest.webmanifest, a service worker and two SVG icons are served from the same binary. Installed, it opens like an app; on a phone with no connection it still opens.

What the service worker caches is the shell — HTML, CSS, JS, icons. It never caches /api/. A cached environment list is a lie about live cluster state, and a lie about whether something is running is worse than an error, so API requests go to the network and fail honestly when it is not there. Tests assert that exclusion rather than trusting the comment.

AssetSize
index.html57.7 KB
app.js54.5 KB
vendor/alpine.min.js53.2 KB
i18n.js46.2 KB
style.css24.0 KB
Whole shell240 KB uncompressed

Service-worker registration is best-effort: it needs a secure context, which a daemon on a plain-HTTP LAN address does not have. The panel works either way; only offline opening depends on it.

Small screens, coarse pointers, weak hardware

The layout is fluid from 320px to ultrawide, and verified there rather than assumed.

  • Below 760px the tables become cards. Each <td> carries a data-label holding its column header, so the labels have exactly one source and cannot drift from the table.
  • The nav becomes a thumb-scrollable strip instead of stacking into a wall of links.
  • @media (pointer: coarse) raises controls to a 44px target. A checkbox needs a wrapping label.check for this, since padding does not enlarge a replaced element.
  • No webfont, no icon font, no CDN. Nothing to download and nothing to fail on a bad connection.

Language

English and Spanish. The switcher re-renders every binding without a reload, because t() reads the active locale on each call. Language is chosen as: previous choice (localStorage), else navigator.languages, else English.

Three things are deliberately not translated, each for a reason worth keeping: --json output and API field names (scripts parse them), log lines (aggregators match on their text), and anything wrapping a git2/bollard/sqlx error — those strings come from those libraries and are what an operator searches for.

Catalogs are guarded by tests that fail on a missing key, a dropped placeholder, an invented one, or a key the UI asks for that no catalog defines.

Authentication

If the daemon has a bearer token, paste it into the token field in the top bar. It is kept in the browser's local storage and sent as Authorization: Bearer … on every request — it is never written to the daemon, and clearing site data removes it.

A project-scoped token works here too: other projects' routes answer 404 and node-wide pages (admin, diagnostics) answer 403, so the panel shows a scoped operator only their own projects. See token scoping.