Commit graph

369 commits

Author SHA1 Message Date
mwiegand
6f49efd44a
feat(l4d2-web): console panel UI on server detail page
- _console_line.html: command + reply, error variant, "(no reply)" placeholder.
- server_detail.html: console section between Live State and Files, replays
  last 50 history rows server-side; HTMX form appends new lines via hx-swap.
- console-history.js: ArrowUp/Down recall against /console/history JSON;
  scroll-to-bottom on load and after each new line.
- CSS: fixed-height scrolling transcript, terminal-ish styling, spinner via
  HTMX in-flight class.
- test_console_routes.py: update 4 assertions from legacy [ERROR] literal
  to console-error CSS class (matches new semantic markup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:39:21 +02:00
mwiegand
ecc4aa28c6
refactor(l4d2-web): tighten console route limit test and dedupe is_error
- ?limit clamp test now actually verifies the clamp instead of just
  passing through 5 rows.
- Single is_error assignment per branch, single db.add path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:35:22 +02:00
mwiegand
553b280e40
feat(l4d2-web): backend for RCON console with persisted transcript
- POST /servers/<id>/console runs a command via rcon.execute_command and
  persists every outcome (success / empty / error) to command_history.
- GET /servers/<id>/console/history returns paginated newest-first JSON
  for client-side up-arrow recall.
- server_detail() now passes the last 50 history rows as console_history
  for server-side replay on page load.
- 404 on ownership mismatch — no admin override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:32:13 +02:00
mwiegand
c4dffd471b
feat(l4d2-web): add command_history table for RCON console transcript
A row per RCON command execution: (user, server, command, reply, is_error,
created_at). Composite index on (user_id, server_id, id) supports the only
query shape — "latest N for this user+server", id DESC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:26:56 +02:00
mwiegand
9ef9ffdbde
chore(l4d2-web): clarify rcon req_id constants and helper docstring
Add comment noting _EXEC_REQ_ID/_MARKER_REQ_ID are arbitrary client-chosen
values unrelated to SERVERDATA_* packet-type constants. Update _connect_and_auth
docstring to accurately reflect that OSError/socket.timeout propagate raw from
post-connect send/recv, while only connect failure is wrapped in RconError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 21:24:41 +02:00
mwiegand
085fd714a5
feat(l4d2-web): add execute_command to rcon service with full test coverage
Extracts _connect_and_auth helper from query_status, adds execute_command
using the trailing-marker pattern for multi-packet reassembly, and covers
all paths (happy path, multi-packet, empty reply, auth failure, timeout,
input validation, marker drain) with 10 new tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:21:41 +02:00
mwiegand
1d3eb51871
docs(plan): RCON console on server detail page
Plan for adding a per-server RCON console: HTMX append-swap input form,
fixed-height scrolling transcript replayed from CommandHistory on load,
multi-packet response handling, owner-only access, 30s timeout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:14:06 +02:00
mwiegand
6cc1736f17
feat(l4d2-web): add hostname edit form to server detail page 2026-05-13 15:42:46 +02:00
mwiegand
963851c0e1
feat(l4d2-web): emit hostname in spec config with ephemeral fallback 2026-05-13 15:31:12 +02:00
mwiegand
d42383dc37
chore: add dev.db and opencode.json to gitignore 2026-05-13 14:29:57 +02:00
mwiegand
69d93dda4f
feat(l4d2-web): accept hostname on server update, default empty on create 2026-05-13 14:29:53 +02:00
mwiegand
0a7f48f174
feat(l4d2-web): add hostname column to Server model 2026-05-13 14:26:14 +02:00
mwiegand
f3f0a8927a
docs: add server hostname implementation plan 2026-05-13 14:21:30 +02:00
mwiegand
fcf3143b39
docs: add server hostname cvar design spec 2026-05-13 14:19:57 +02:00
mwiegand
fe43f67b51
feat: include password-reveal.js in base template 2026-05-13 11:37:47 +02:00
mwiegand
ab83f5fd2b
feat: add RCON password row to server detail page 2026-05-13 11:37:28 +02:00
mwiegand
d9aa6bd395
feat: add password reveal toggle JS 2026-05-13 11:36:40 +02:00
mwiegand
e75feb0649
docs: add rcon password display implementation plan 2026-05-13 11:36:08 +02:00
mwiegand
358a835d65
docs: add rcon password display design spec 2026-05-13 11:35:46 +02:00
mwiegand
d113b7821c
fix(live-state): remove loading=lazy from avatars to fix Firefox/Safari flash
Firefox and Safari defer lazy images by one paint cycle even when cached,
causing a blank frame on each innerHTML swap. These avatars are always
in-viewport and cached after the first poll, so lazy loading has no benefit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:34:53 +02:00
mwiegand
175e4e653c
fix(live-state): eliminate flash on poll by switching to innerHTML swap
outerHTML removes and re-inserts the section on each tick, causing a
blank frame. Keeping the <section> as a stable DOM container and
swapping only innerHTML means avatars and text update in-place without
any teardown/reconstruct cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:26:54 +02:00
mwiegand
096d18ac64
feat(live-state): use Steam avatarfull (184x184), downscale in CSS
64x64 avatarmedium looked soft on high-DPI screens. Switching the
GetPlayerSummaries field to avatarfull (184x184) and constraining
display size to 64px via .live-state .avatar gives sharp rendering on
retina/4k panels at the cost of a slightly larger CDN fetch (still
hot-linked, so no proxying cost).

Also adds the previously-missing CSS for the live-state player grid:
avatar+name+meta arranged in a tight 2-column grid per card, link
spans the avatar+name so the meta stays non-interactive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:17:51 +02:00
mwiegand
6cbe7dc9f2
feat(live-state): link player cards to their Steam profile
Wraps avatar + persona name in an a[href=steamcommunity.com/profiles/<id>]
in both the Current and Recent blocks. Steam auto-redirects to the user's
vanity URL on follow, so we don't need to store profileurl separately.

target=_blank + rel=noopener noreferrer to keep the dashboard page in
place when a link is followed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:51:50 +02:00
mwiegand
674c4df360
deploy: add STEAM_WEB_API_KEY to web.env template
For the live-state panel's Steam profile enrichment (persona names +
avatars). Optional: empty value disables enrichment and the panel falls
back to in-game names + placeholder avatars.

The actual web.env is materialized by the ckn-bw bundle's Mako; the
template here documents the operator-facing shape.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:25:03 +02:00
mwiegand
37a9ad68a2
fix(live-state): cast poll_seconds to int for HTMX hx-trigger
HTMX's hx-trigger="every Ns" syntax does not accept fractional seconds —
a config override like 7.5 would render every 7.5s and silently break
auto-refresh. Floor to int with a 1s minimum.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:23:15 +02:00
mwiegand
9aaa26d9a9
feat(servers): add live-state panel with current and recent players
HTMX-refreshed /servers/<id>/live-state fragment renders snapshot
summary, current players with avatars/ping, and recent-player history;
server_detail.html bootstraps it via hx-trigger="load".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:20:01 +02:00
mwiegand
b00a3cceea
test(live-state): assert stale server's map is not rendered in the badge
Closes the negative-assertion gap from the Task 10 review: without this
check, a regression that drops the freshness guard would still pass the
positive 2/4 + c1m2_streets assertions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:17:02 +02:00
mwiegand
072d9f78e7
feat(servers): show live counts + map badge in server list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:14:57 +02:00
mwiegand
0dc61d5de4
feat(live-state): start daemon poller, prune history, close stuck sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:10:55 +02:00
mwiegand
be476112ee
feat(live-state): enrich roster with cached Steam profiles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:02:58 +02:00
mwiegand
33899f8c17
feat(live-state): reconcile player sessions on each poll
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:58:30 +02:00
mwiegand
c9cd2557fd
style(live-state): drop unused imports staged for later tasks
threading, time, Callable were imported in anticipation of Task 9's
daemon-thread startup. Task 9 will re-add them when actually needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:55:36 +02:00
mwiegand
f48d624dcc
feat(live-state): poller writes RLE snapshots to server_live_state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:53:58 +02:00
mwiegand
f88d07a473
feat(steam): add GetPlayerSummaries client
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:48:02 +02:00
mwiegand
465a103c3a
feat(servers): generate rcon_password on server create
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:43:56 +02:00
mwiegand
2a440dae45
feat(facade): append rcon_password as final server.cfg line
Source cvar semantics are last-wins; appending the rcon_password after
all overlay exec lines and blueprint config ensures no overlay or user
config line can silently override it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:40:56 +02:00
mwiegand
83d2a9932c
refactor(rcon): harden _parse_duration; surface fixture handler errors
- _parse_duration wraps int() in try/except so malformed connected
  durations raise RconError (not ValueError leaking past the poller's
  except RconError).
- fake_rcon_server captures handler exceptions and re-raises at context
  exit, so a buggy test handler surfaces as a real failure instead of
  silently degrading into a client-side timeout.
- Two new parser tests: HH:MM:SS duration parsing and malformed input
  coverage.
- Fix Steam ID formula typo in the spec doc (Z*2 + Y, not Y*2 + Z; Y is
  the low bit). Code was already correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:39:32 +02:00
mwiegand
b95a82b8a4
feat(rcon): add Source RCON client + status parser
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:31:32 +02:00
mwiegand
e25e7098f6
refactor(live-state): drop redundant ix_sps_server_recent index
The two indexes ix_sps_server_open and ix_sps_server_recent were
byte-identical because SQLAlchemy's Index(name, *cols) form drops the
DESC ordering the spec intended. Rather than reach for text("left_at
DESC"), drop the second index entirely — SQLite scans the ASC index
backwards at no measurable cost. Spec and plan updated to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:27:01 +02:00
mwiegand
0f825686c6
feat(live-state): add schema for snapshots, sessions, steam profiles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:18:24 +02:00
mwiegand
a5f7b736a2
docs/plan: server live-state display implementation plan
Thirteen TDD-structured tasks covering schema migration, RCON client,
spec injection, password generation, Steam Web API client, live-state
poller (RLE snapshots + session reconciliation + profile enrichment +
retention + thread startup), server list badge, server detail
fragment, deploy env, and end-to-end smoke.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:10:33 +02:00
mwiegand
202026e11a
docs/spec: add server live-state display design
RCON-based polling with run-length-encoded snapshots, session intervals
with min/max ping, Steam profile cache, and a server-detail roster of
current + recent players hot-linked from Steam CDN avatars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:03:26 +02:00
mwiegand
e52219b1e9
deploy: weaken refresh-timer dep on web.service from Requires to Wants 2026-05-11 23:22:42 +02:00
mwiegand
429fee3868
docs/plan: trim retry-backoff tuple to match attempts-1 2026-05-11 23:21:10 +02:00
mwiegand
8cc7f84801
deploy: schedule daily workshop refresh via systemd timer
Adds left4me-workshop-refresh.service (oneshot, triggers flask
workshop-refresh) and left4me-workshop-refresh.timer (04:00 daily,
Persistent=true, 15 min jitter). Wires both into the deploy script's
cp and enable blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:20:13 +02:00
mwiegand
f614ac05f0
tests/cli: cover running+cancelling idempotency, tighten app-context scope 2026-05-11 23:18:54 +02:00
mwiegand
0ab54b4a7d
cli: add workshop-refresh subcommand for scheduled global refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:15:05 +02:00
mwiegand
653e3212b9
tests: harden refresh-hidden-during-build with positive assertion 2026-05-11 23:13:39 +02:00
mwiegand
25b38e633d
overlay_detail: add 'Refresh from Steam' button for workshop overlays
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:10:27 +02:00
mwiegand
e1b189ad3c
workshop_routes: narrow refresh's steam exception handler
Catch only requests.RequestException in refresh_overlay so that
server-side data errors (e.g., ValueError) bubble up as 500 rather
than being disguised as a 502 "steam api error". Update the 502 test to
use a real requests exception, add a sibling test that verifies
non-requests exceptions propagate, and explicitly assert that refresh
enqueues a build_overlay job even when Steam returns no entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:08:41 +02:00