# Languard Server Manager ## Quick Start ```bash # Backend (from backend/, venv must be active) uvicorn main:app --host 0.0.0.0 --port 8000 --reload # Frontend (from frontend/) npm run dev ``` - Backend API: http://localhost:8000 (docs: http://localhost:8000/docs) - Frontend: http://localhost:5173 (Vite proxies /api and /ws to :8000) - Default admin: `admin` / (random, printed at first startup) - Full setup instructions (secrets, venv, debug configs): see README.md ## Architecture FastAPI + SQLite backend, React 19 + TypeScript + Vite frontend. See ARCHITECTURE.md for full details. ### Key Rules - Frontend types must match API response shapes, NOT database schema columns - There is no REST endpoint for logs — logs are only pushed via WebSocket events - WebSocket `onEvent` callback is the mechanism for receiving real-time log entries - Config updates use optimistic locking (config_version) — 409 on conflict - Sensitive config fields are encrypted at rest with Fernet ## Current Implementation Status ### Backend: Fully implemented (42+ endpoints) All routers, services, repositories, game adapter system, WebSocket, background threads, and scheduled cleanup are complete. ### Frontend: Fully implemented (baseline) | Route | Status | Notes | |-------|--------|-------| | `/login` | Complete | Zod + react-hook-form validation | | `/` | Complete | Dashboard with server grid + Start/Stop/Restart quick actions | | `/servers/:id` | Complete | 7-tab detail page (overview, config, players, bans, missions, mods, logs) | | `/servers/new` | Complete | 4-step wizard with per-step validation via `trigger()` | | `/settings` | Complete | Password change + admin user management | ### Frontend Type Mapping (API → Frontend) | API Resource | Frontend Type | Key Fields | |---|---|---| | Server (enriched) | `Server` in useServers.ts | `game_port`, `current_players`, `max_players`, `cpu_percent`, `ram_mb` | | Mission | `Mission` in useServerDetail.ts | `name`, `filename`, `size_bytes`, `terrain` | | Mod | `Mod` in useServerDetail.ts | `name`, `path`, `size_bytes`, `enabled`, `display_name`, `workshop_id` | | Ban | `Ban` in useServerDetail.ts | `id`, `server_id`, `guid`, `name`, `reason`, `banned_by`, `banned_at`, `expires_at`, `is_active`, `game_data` | | Player | `Player` in useServerDetail.ts | `id`, `slot_id`, `name`, `guid`, `ip`, `ping` | | LogFile | `LogFile` in useServerDetail.ts | `filename`, `size_bytes`, `modified_at` | ### UX Enhancement Plan — ALL PHASES COMPLETE **Plan file:** `.claude/plan/arma3-ux-enhancement.md` | Phase | Feature | Status | |-------|---------|--------| | 1 | Config field UI widgets (textarea/toggle/select/tag-list per field) | **Done** | | 2 | Mission rotation table + multi-file upload | **Done** | | 3 | Mod display names (mod.cpp) + split-pane selector | **Done** | | 4 | Player Kick/Ban from Players tab via RCon | **Done** | | 5 | Historical log file browser + live log level filter | **Done** | **Endpoints added:** - `GET /api/servers/{id}/config/schema` — per-field widget hints - `GET|PUT /api/servers/{id}/missions/rotation` — mission rotation with optimistic locking - `POST /api/servers/{id}/players/{slot_id}/kick` — kick via RCon - `POST /api/servers/{id}/players/{slot_id}/ban` — ban via RCon + DB record - `GET /api/servers/{id}/logfiles` — list `.rpt` log files - `GET /api/servers/{id}/logfiles/{filename}/download` — download log file - `DELETE /api/servers/{id}/logfiles/{filename}` — delete log file ## Test Commands ```bash # Frontend unit tests cd frontend && npx vitest run # Frontend type check cd frontend && npx tsc --noEmit # Backend (no test suite yet) ``` ## Key Implementation Notes - `BanRepository.create()` takes `expires_at` (ISO string), not `duration_minutes` — convert in service - `slot_id` is stored as a string in the `players` table — cast with `str(slot_id)` in queries - Config field names in `ServerConfig` Pydantic model: `password_admin` (not `admin_password`), `battleye` (not `battle_eye`), `disable_von` (not `von`) - **Arma 3 log files** are located at `{exe_path_parent}/server/*.rpt` (next to the .exe), NOT in languard's `servers/{id}/` data directory. Code that finds log files must use `Path(server["exe_path"]).parent` to resolve the log directory. - Config UI schema now covers all ~80 Arma 3 fields across 5 sections (server, basic, profile, launch, rcon) with per-field widget hints (text, toggle, select, number, password, tag-list, hidden, textarea, key-value). The `missions` field in the server section is marked `hidden` because mission rotation is managed via the dedicated Missions tab. - **Arma 3 per-mission params**: `ServerConfig.missions` is now `list[MissionRotationItem]` (adds optional `params: dict`). A new `default_mission_params` field holds server-wide defaults. Config version bumped to `"1.1.0"`. `_render_server_cfg()` now emits a `class Missions { ... }` block when the rotation is non-empty; `class Params` inside each mission uses per-mission params → global defaults → omit (in that priority order). The `MissionRotationEntry.params` is edited per-row in the Missions tab via `MissionParamsEditor`; `default_mission_params` is edited in the Config tab via the `key-value` widget. - **Config version migration**: `migrate_config("1.0.0", ...)` backfills `params: {}` on each existing rotation entry and adds `default_mission_params: {}`. `normalize_section()` does the same on reads for stored rows that pre-date the migration run. ## Mods Tab — Implementation Notes - Mods go in `{server_data_dir}/{server_id}/mods/@ModName` (e.g. `D:/ImContainer/Arma3Server/1/mods/@CBA_A3/`) - Enabled mods config schema: `{"enabled_mods": [{"name": "@CBA_A3", "is_server_mod": false}]}` - Old string-list format is auto-migrated to the dict format on read - `is_server_mod: true` → `-serverMod=` arg; `false` → `-mod=` arg - `list_available_mods()` scans `{server_dir}/mods/` for `@*` directories - `set_enabled_mods()` stores the new dict format; validates names against disk - Server start reads mods from `game_configs` via `config_repo`, NOT from the dead `server_mods` table - Directory scaffold: all 4 Arma3 subdirs (`server/`, `battleye/`, `mpmissions/`, `mods/`) are created on server create and backfilled on startup; each gets a `README.txt` if not already present