left4me/docs/superpowers/plans/2026-05-06-l4d2-job-pages-and-cancel.md

2.6 KiB

L4D2 Job Pages and Cancellation Follow-Up

Goal

Make queued and running lifecycle jobs easier to inspect and stop from the web UI.

Scope

  • Add job list navigation for server pages and admin pages.
  • Add a job detail page with persisted command logs streamed through the existing SSE endpoint.
  • Add cancellation for queued jobs first.
  • Add best-effort cancellation for running jobs by terminating the subprocess owned by l4d2host.process.run_command().

Slice 1: Job Browsing and Queued Cancel

Behavior

  • /servers/<server_id> shows recent jobs for that server and links to the full job history.
  • /servers/<server_id>/jobs shows all jobs for that server, newest first.
  • /jobs/<job_id> shows job metadata and live/replayed logs.
  • /admin/jobs reuses the same job table markup and links every job to its detail page.
  • POST /jobs/<job_id>/cancel cancels queued jobs only.
  • Owners can view/cancel their own jobs.
  • Admins can view/cancel any job.

Implementation Notes

  • Use one reusable Jinja partial for job tables.
  • Show cancel buttons only for queued jobs in this slice.
  • Cancelling a queued job sets state="cancelled", finished_at, updated_at, and exit_code=1.
  • Append a stderr job-log line explaining that the job was cancelled before execution.
  • Do not revert Server.desired_state; cancellation prevents execution but is not rollback.

Verification

  • pytest l4d2web/tests/test_pages.py -q
  • pytest l4d2web/tests/test_job_logs.py -q
  • pytest l4d2web/tests -q

Slice 2: Running Job Cancellation

Behavior

  • Running jobs expose the same cancel action.
  • Cancelling a running job marks it cancelling while the subprocess is being terminated.
  • Once the subprocess exits because of cancellation, the job finishes as cancelled.
  • Cancellation is best-effort and is not rollback; partial runtime state may remain.
  • Server actual state is refreshed after a cancelled server job when possible.

Implementation Notes

  • Add cancellation primitives in l4d2host.process.
  • Launch subprocesses in their own process group/session when a cancel token is supplied.
  • On cancellation, send terminate, wait briefly, then force kill.
  • Thread the cancel token through l4d2host lifecycle APIs, l4d2web.services.l4d2_facade, and l4d2web.services.job_worker.
  • Keep v1 single-process assumptions; cancellation requests are DB-backed, while process handles stay process-local.

Verification

  • pytest l4d2host/tests/test_process.py -q
  • pytest l4d2host/tests -q
  • pytest l4d2web/tests/test_job_worker.py -q
  • pytest l4d2web/tests/test_job_logs.py -q
  • pytest l4d2web/tests -q