Open a dashboard when serve is run at a terminal

`fluksio serve` printed a log stream and nothing else, so watching an engine
meant a second terminal running `status --watch`, and stopping or pairing it
meant a third. At a terminal it now opens a dashboard: the health and flow
overview `status` draws, the recent runs as a table, and the engine's own
output in a pane below — which is what the earlier decision against this
was protecting, and it is still all there.

The engine is a child process running `serve --plain`, not a thread, so it
outlives the dashboard: q leaves it running and says so, s and r stop and
restart it, c cancels the selected run and e pairs with a portal. An engine
already serving this directory is adopted rather than duplicated, and it can
be stopped from here only because the pidfile and the token together prove
it is this installation's.

`--plain` and no terminal both keep the old behaviour, which is what the
container and CI run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hra4ndWMCLU5F3KjUuVAc
This commit is contained in:
2026-08-29 14:21:13 +02:00
co-authored by Claude Opus 5
parent c2312e6632
commit a4ea1dd0d6
5 changed files with 456 additions and 2 deletions
+42 -2
View File
@@ -567,11 +567,51 @@ def test_run_and_sweep_take_what_to_sync() -> None:
assert parser.parse_args(["sweep", "train"]).sync == []
def test_the_dashboard_runs_the_engine_as_a_child_of_itself(monkeypatch) -> None:
"""At a terminal `serve` is a dashboard; the engine is a plain serve.
Every flag is passed through, so what the child runs with is what serve
was asked for — and `--plain` is what stops it opening a second one.
"""
import sys
from fluksio import cli
from fluksio.tui import child_argv
argv = child_argv(["serve", "--port", "8123", "--gpus", "1"])
assert argv[:3] == [sys.executable, "-m", "fluksio.cli"]
assert argv[3:] == ["serve", "--port", "8123", "--gpus", "1", "--plain"]
# Already plain: told once, not twice.
assert child_argv(["serve", "--plain"])[3:] == ["serve", "--plain"]
opened: list[str] = []
monkeypatch.setattr(
"fluksio.tui.run_tui", lambda args: opened.append("tui") or 0, raising=False
)
monkeypatch.setattr(sys.stdout, "isatty", lambda: True, raising=False)
monkeypatch.setattr(sys.stdin, "isatty", lambda: True, raising=False)
parser = cli._parser()
assert cli.cmd_serve(parser.parse_args(["serve"])) == 0
assert opened == ["tui"]
# `--plain` goes past it, which is what the child and every container does.
# Nothing else of serve runs here, so it fails on the data directory it is
# given rather than opening a dashboard.
opened.clear()
monkeypatch.setattr(
cli, "_data_dir", lambda *a, **k: (_ for _ in ()).throw(SystemExit(3))
)
with __import__("pytest").raises(SystemExit):
cli.cmd_serve(parser.parse_args(["serve", "--plain"]))
assert opened == []
def test_a_serving_engine_records_itself_until_it_stops(tmp_path) -> None:
"""A pid nobody is running is the same as no pidfile at all."""
import os
from fluksio.cli import PIDFILE, read_pidfile, write_pidfile
from fluksio.cli import read_pidfile, write_pidfile
assert read_pidfile(tmp_path) is None
@@ -586,7 +626,7 @@ def test_a_serving_engine_records_itself_until_it_stops(tmp_path) -> None:
assert read_pidfile(tmp_path) is None
def test_who_holds_the_port_is_told_apart_by_the_token(tmp_path) -> None:
def test_who_holds_the_port_is_told_apart_by_the_token() -> None:
"""Only this directory's own engine may be reported as already up.
The token is signed with this directory's secret key, so an engine that