left4me/l4d2web
mwiegand 42bdc6ad98
feat(editor-v2): files-overlay reads/writes via window.__filesEditor
Routes 8 call sites through the cm6 controller alias:
- 5 reads (byte-count, save POST, dirty checks at 306, 481, 496, 511,
  528) → getEditorValue() helper, falling back to
  editorEls.contentBox.value if window.__filesEditor isn't mounted
  (no-JS / pre-mount path).
- 3 writes (clear, "Loading…" placeholder, fetched body content at
  362, 395, 402) → setEditorValue() helper with the same fallback.

The two helpers live inline next to editorEls so the rest of the
module's call sites stay close to existing style.

Known regressions (out of scope for v2, candidate follow-ups):
- Byte-count badge updates only on file-open / setContent calls, not
  live on every keystroke. Needs a controller.onChange(cb) hook.
- Ctrl+S inside cm6 doesn't trigger the modal Save. cm6 owns the
  keymap in its editing surface; users can still click the Save
  button. Adding a custom cm6 keymap entry would restore the
  shortcut.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:08:42 +02:00
..
alembic feat(l4d2-web): add command_history table for RCON console transcript 2026-05-14 21:26:56 +02:00
l4d2web feat(editor-v2): files-overlay reads/writes via window.__filesEditor 2026-05-17 02:08:42 +02:00
scripts feat(editor-v2): build script + first bundle output 2026-05-17 01:58:46 +02:00
tests feat(editor-v2): wire data-editor-language attrs into three textareas 2026-05-17 02:06:58 +02:00
alembic.ini chore(l4d2): flatten component layout 2026-05-05 23:47:06 +02:00
pyproject.toml refactor(repo): uv workspace + hatchling + layout restructure 2026-05-15 22:04:29 +02:00
README.md refactor(repo): uv workspace + hatchling + layout restructure 2026-05-15 22:04:29 +02:00

l4d2-web-app

Flask web app for managing L4D2 servers through user-private blueprints.

Key v1 behaviors

  • Local username/password login; no public signup
  • Admin-managed overlay catalog
  • Private blueprints per user
  • Server creation from blueprints (live-linked; no per-server blueprint overrides)
  • Async job model with persisted command logs in job_logs
  • Desired vs actual state model
  • Live logs for jobs and servers via SSE endpoints
  • Host operations go through l4d2ctl via a local host command runner, not direct l4d2host imports

Frontend constraints

  • Server-rendered templates (Jinja)
  • Vendored HTMX (static/vendor/htmx.min.js)
  • Custom CSS only
  • Tokenized, consistent link and accent colors

Development

From the workspace root (../):

uv sync          # creates .venv, installs l4d2host + l4d2web editable, plus dev deps
uv run pytest l4d2web/tests -q

Configuration

The web app reads these settings from the environment:

  • DATABASE_URL: SQLAlchemy database URL, for example sqlite:////var/lib/left4me/left4me.db.
  • SECRET_KEY: Flask secret key used for sessions and CSRF-sensitive state.
  • JOB_WORKER_THREADS: number of background job worker threads.

In the systemd deployment, environment is loaded from /etc/left4me/host.env and /etc/left4me/web.env.

Admin Bootstrap

Create the first admin account with the Flask CLI. Provide the password through LEFT4ME_ADMIN_PASSWORD:

LEFT4ME_ADMIN_PASSWORD='change-me' flask create-user <username> --admin