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

64 lines
2.6 KiB
Markdown

# 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`