controlroom
Recipes

A full-screen dashboard

The reference dashboard: tabs, a checklist with progress, a log tail and a status line that collapses, in the alternate screen — and clean lines in a pipe and NDJSON under --json, from the same program.

examples/dashboard is a boilerplate in the shape of a setup wizard: two tabs, a checklist with progress, a log tail and a status line that collapses, in the alternate screen. Copy it to start your own:

npx degit ofri-peretz/burgee/examples/dashboard my-dashboard
cd my-dashboard && npm install && node dashboard.mjs
KeyWhat it does
← →switch tab
scollapse or expand the status line
q, Ctrl+Cquit, and hand the terminal back as it was

How it is built

One layout per tab. layout is a function of the screen's state, so the status tab shows the learn panel beside the checklist and the logs tab shows the tail:

function layout(state) {
  const body =
    state.active === 0
      ? { direction: 'row', parts: [{ size: { fr: 1, min: 20 }, content: 'learn' }, { size: { fr: 2 }, content: 'tasks' }] }
      : 'log';
  return {
    direction: 'column',
    parts: [
      { size: 1, content: 'tabs' },
      { size: 'fit', content: 'status' },
      { content: body },
      { size: 1, content: 'hints' },
    ],
  };
}

Keys are data. The keymap binds the arrows to the reducer's tab.prev and tab.next, s to toggle:status, which collapses the status pane, and q to the program's own quit. The hint line at the bottom is hints(KEYMAP, LABELS), so it lists exactly those keys:

const KEYMAP = { left: 'tab.prev', right: 'tab.next', s: 'toggle:status', q: 'quit', 'ctrl+c': 'quit' };
const LABELS = { 'tab.prev': 'switch tab', 'tab.next': 'switch tab', 'toggle:status': 'toggle status', quit: 'quit' };

Panes are flagstaff components. tabBar(), tasks() and logTail() are flagstaff's; the status and learn panes are a one-line text component. The tab bar and the hint line are liveOnly, because tabs and keys mean nothing off a terminal.

The work drives the screen. Each step marks itself running, pushes its log lines, and updates three panes; the last one commits a summary and closes. In the alternate screen the commit is held and printed to the main screen as the program exits, so the summary is what is left in the terminal.

Every caller

On a terminal it is a full screen. Piped, each pane prints under its label, and then each change is one line:

node dashboard.mjs
Status
Progress: 0/4 completed
Learn
The wizard installs the SDK
and checks the first event.
Tasks

Log

◆ Detect the framework
┊ found package.json
┊ framework: next
✔ Detect the framework
◆ Install the SDK
┊ npm install posthog-js
┊ added 1 package
Progress: 1/4 completed
✔ Install the SDK
◆ Write the config
┊ wrote posthog.config.js
Progress: 2/4 completed
✔ Write the config
◆ Check the setup
┊ sent a test event
┊ event received
Progress: 3/4 completed
✔ Check the setup
Progress: 4/4 completed
Done: Progress: 4/4 completed

node dashboard.mjs --json writes one NDJSON event per update to stderr, {"event":"tasks", "state":{…}} and so on, ending with the commit and each pane's last state. The example's own npm test checks the pipe, --json, and the keys on a simulated terminal: → to the log tab, s to collapse the status line, q to leave the alternate screen.

What to change first

  1. STEPS, the work: a title and the log lines each step produces. In your program, run the real work where advance() marks a step running, and push its output into lines.
  2. layout(), the arrangement per tab (Layout).
  3. KEYMAP and LABELS, the keys (Tabs, focus and keys).

On this page