box
docs /Dashboard

Dashboard

ply works entirely from the terminal. The dashboard is the optional web face on top: apps with live CPU/mem sparklines, log tailing, an events journal, a deploy wizard for GitHub repos, update indicators with a one-click deploy, and a real shell into any container — served by a single static Go binary that is itself just a ply app.

The honest premise: they sell a dashboard that needs a server; ply is a server that happens to have a dashboard.

#Install

On a host prepared with sudo ply setup --edge, the dashboard is one deployment file — it installs, updates, and supervises itself through the same machinery it displays:

# /var/lib/ply/deployments/dashboard.toml
github = "iluxav/ply-dashboard"
publish = ["internal:7070"]
domain = ["dash.example.com"]      # point DNS here; the edge does HTTPS
grant_links = true

First boot prints a setup token to the app's log (ply logs dashboard); the create-account page requires it, which closes the first-visitor-owns-the-box race. Credentials live in one auth.json in the app's volume — deleting that file is the documented password reset. The filesystem is the admin API.

#Permissions ARE the ACL

The dashboard reads ply's state through explicitly granted bind mounts (the [requests] links in its manifest, mounted because the operator said grant_links = true). What it may do follows from what was granted:

No roles, no tokens, no API surface. Granting the apps dir read-write means granting a shell — that is stated here plainly, not hidden.

#Deploying from the dashboard

Paste a GitHub URL. Public repos are inspected: a release carrying a ply image recommends the pull CI image lane; a package.json with next prefills the known-good Next.js build; ply.toml in the repo means the repo rules. Private repos work identically once you paste a fine-grained token (Contents: read, that one repo) — one credential for cloning, images, and update checks. The wizard previews the exact TOML it will write, because the file is the truth — and every deployment's spec is editable in place afterward.

Each GitHub-backed deployment shows freshness — update available: <version or commit> — with a deploy now button. The button just touches the spec file; inotify and ply reconcile do the rest. With the timer installed, updates flow with no clicks at all (see Deployments & CD); auto = false keeps an app manual, and the button still works, because a touch is explicit intent.

#Logs, events, post-mortems

Every instance writes a rotating log ring; the dashboard tails it live. The events journal records deploys, scales, restarts, crash respawns, and terminal opens — each row links to the logs that explain it, in a resizable side pane. Build failures link to the builder's ring, which outlives the builder: the post-mortem for a dead build is one click.

#The terminal

Every live instance gets a terminal button. How it works is the whole security story: the dashboard holds no privileges — it writes an exec command file, and the app's own run parent (already root for its own app) answers by serving a PTY on a unix socket inside the control dir. The socket crosses the bind mount; the browser speaks to it through a WebSocket bridge; every open lands in the events journal. Everything is a file, including the shell.

#Designing without a server

MOCK=true ply-dashboard runs the entire UI against a fabricated state tree — fake apps, wandering sparklines, a crash-looping worker, every deployment state at once — for UI work with no ply install at all (login mock / mockmock).