1.2.20
This release gives the web dashboard a terminal twin: cronstable tui
opens the same board in a terminal -- an SSH session, a tmux pane, a box where
a browser is one window too many. It is a pure client of the daemon's
existing HTTP control API (the daemon itself is untouched by this release),
it adds zero new dependencies (the standard library plus the already-core
aiohttp), and it keeps the web page's muscle memory: the shortcut table is
the same sixteen keys, now enforced by a test that parses the rows out of the
web page's own source. The final cut also reflects an adversarial review of
the whole surface -- the notable outcomes are documented below because they
are load-bearing guarantees, not incidental polish.
The terminal dashboard (cronstable tui)
-
The whole board makes the trip. The jobs table (status glyphs,
next-fire countdowns, duration sparklines, live CPU/memory chips, the owner
column under a spread cluster, filter/sort/status segments); the job drawer
with the live SSE log tail (search withn/N, follow/wrap/timestamps
toggles, save-to-file), run history with success rate and per-run bars,
resources for monitored jobs, and the schedule tab; the fuzzy
command palette; the verdict bar, incident timeline, and
mitigate console with its Markdown writeup; the multi-tail merging
up to four live logs; the DAG drawer (runs, an ASCII task graph,
per-task states, approval gates decided witha/R, XCom, task logs,
trigger and backfill); the cluster panel, fleet matrix, node
resources, activity heatmap, and next-fire radar; the durable-state
inspector; the cron sandbox; the wallboard with itsNO SIGNAL
banner and zen screensaver; and the BIOS-style boot self-test, probing
the daemon for real (at most every 12 hours; any key,--no-boot, or a
settings toggle skips it). -
The schedule tab cannot disagree with the scheduler. The next-fire
preview runs the daemon's ownCronTabengine, stepping in absolute time
so a window that crosses a DST transition shows the true fire instants (no
phantom or hour-shifted entries), and the plain-English description states
the engine's deliberate rule that a day must satisfy both day-of-month
and day-of-week when both are restricted ("on the 13th, and only on
Friday") -- the daemon's documented parse-crontab semantics, not standard
cron's OR. An expression the engine would reject (month13, weekday8)
degrades to prose in the sandbox instead of raising. -
Hostile bytes die before the frame. A TUI paints raw bytes into the
operator's terminal, so everything remote is scrubbed in layers: log
content keeps only SGR colour (re-inked per theme) while every other
escape family -- CSI with private parameters, OSC (title, clipboard),
DCS/SOS/PM/APC strings, single-character escapes like a hard reset, bare
trailing ESC -- is stripped; every other API-derived string (job and DAG
names, node/peer names arriving over cluster gossip, XCom keys, server
error text in toasts) passes the same scrub in the text helpers; and the
final row assembly drops any non-SGR escape as a last line of defense. A
malicious peer advertising an OSC 52 node name cannot write the operator's
clipboard; a job that runsresetcannot tear down the board from inside
its own log pane. -
Tails do not repeat themselves. The daemon replays a finished run's
retained buffer on every SSE re-attach, and the stream carries no run
identity -- so the tail holds a replay aside until it diverges from what is
already on screen: an identical replay that simply ends again is the old
run repeated and is dropped whole, while divergence is the next run's
output and flushes through (runs stack up behind their end markers, like
the page). Idle re-attaches back off geometrically, so an open drawer on
a finished job stops re-downloading its log every five seconds. -
Responsive from the first frame. The input and paint loops start
before the first data load, boot-probe API calls race the skip key, and
every HTTP call carries a bounded connect timeout (with a read timeout
above the daemon's SSE keep-alive cadence on streams) -- so against an
unreachable daemon the header says "disconnected" andq/Ctrl-C work,
rather than a blank, un-quittable screen while probes time out. Manual
refresh (g, and the refresh after every action or token entry) performs
a fetch even with polling paused (--poll 0), and Ctrl-K on the wallboard
stays inert rather than opening an invisible palette that could fire
unseen actions. -
Same themes, same accessibility, same keys. The five hues in phosphor
(dark) and paper (light) variants,t/Tcycling, the colour-vision-safe
remaps, and an--asciiglyph mode for limited fonts; preferences persist
in a small JSON file (%APPDATA%\cronstable\tui.jsonon Windows,
$XDG_CONFIG_HOME/cronstable/tui.jsonelsewhere). Flags mirror the
page's hash routes:--tv(the wallboard),--job NAME(deep-link a
drawer), plus--url,--token/--token-env(default
CRONSTABLE_WEB_TOKEN; a401opens the token prompt, and the token is
kept for the session only, never written to the prefs file),--theme,
--poll,--boot/--no-boot. Works on Linux, macOS, and Windows (a
msvcrt reader thread and VT-mode enablement stand in for termios), and
ships in the same package and binaries as the daemon. -
The CLI stays light.
cronstable.tuidefers itsaiohttpimport
until the app actually starts, so registering the subcommand costs every
othercronstableinvocation nothing.
Documentation and tests
-
A README section and a Terminal-Dashboard wiki page (options, every
key, the panel tour), with screenshots captured from the real TUI driven
against the running grand-tour fleet by a new
docs/screenshots/capture_tui.py, alongside the web dashboard's existing
pipeline. -
A headless, tty-free test harness boots the real app against a fake
daemon on a loopback port, drives it with a scripted key queue, and
asserts on painted frames -- the same suite runs on POSIX CI and a Windows
checkout. The keyboard-parity test extracts the shortcut table from
fillHelp()in the web page's source (failing loudly if the parse finds
nothing), so the two frontends cannot drift apart silently.
Full Changelog: 1.2.19...1.2.20