- Add per-mission params to rotation (MissionRotationItem.params); falls back to default_mission_params, then omits entirely if both empty - Add key-value widget to ConfigEditor for default_mission_params field - Add MissionParamsEditor component for editing param key/value/type rows - Bump config schema to 1.1.0 with migration from 1.0.0 - Add normalize_section() to Protocol and ArmaConfigGenerator for read-time backfill of old stored rows - Set Arma3 BasicConfig and ProfileConfig defaults from basic.cfg / Administrator.Arma3Profile - Document 3 known Mods tab bugs in CLAUDE.md for next-session fix
7.1 KiB
Languard Server Manager
Quick Start
# Backend (from backend/)
python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# Frontend (from frontend/)
npx vite --host
- Backend API: http://localhost:8000 (docs: http://localhost:8000/docs)
- Frontend: http://localhost:5173
- Default admin:
admin/ (random, printed at first startup; reset viapython -c "from core.auth.utils import hash_password; print(hash_password('admin123'))"then update SQLite)
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
onEventcallback 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 hintsGET|PUT /api/servers/{id}/missions/rotation— mission rotation with optimistic lockingPOST /api/servers/{id}/players/{slot_id}/kick— kick via RConPOST /api/servers/{id}/players/{slot_id}/ban— ban via RCon + DB recordGET /api/servers/{id}/logfiles— list.rptlog filesGET /api/servers/{id}/logfiles/{filename}/download— download log fileDELETE /api/servers/{id}/logfiles/{filename}— delete log file
Test Commands
# 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()takesexpires_at(ISO string), notduration_minutes— convert in serviceslot_idis stored as a string in theplayerstable — cast withstr(slot_id)in queries- Config field names in
ServerConfigPydantic model:password_admin(notadmin_password),battleye(notbattle_eye),disable_von(notvon) - Arma 3 log files are located at
{exe_path_parent}/server/*.rpt(next to the .exe), NOT in languard'sservers/{id}/data directory. Code that finds log files must usePath(server["exe_path"]).parentto 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
missionsfield in the server section is markedhiddenbecause mission rotation is managed via the dedicated Missions tab. - Arma 3 per-mission params:
ServerConfig.missionsis nowlist[MissionRotationItem](adds optionalparams: dict). A newdefault_mission_paramsfield holds server-wide defaults. Config version bumped to"1.1.0"._render_server_cfg()now emits aclass Missions { ... }block when the rotation is non-empty;class Paramsinside each mission uses per-mission params → global defaults → omit (in that priority order). TheMissionRotationEntry.paramsis edited per-row in the Missions tab viaMissionParamsEditor;default_mission_paramsis edited in the Config tab via thekey-valuewidget. - Config version migration:
migrate_config("1.0.0", ...)backfillsparams: {}on each existing rotation entry and addsdefault_mission_params: {}.normalize_section()does the same on reads for stored rows that pre-date the migration run.
Known Bugs — Mods Tab (fix next session)
Three bugs prevent the Mods tab from working correctly:
Bug 1 — Save fails with TypeError (critical)
Arma3ModManager.set_enabled_mods() calls config_repo.upsert_section() with wrong keyword argument names:
- Passes
data=→ should beconfig_data= - Passes
expected_version=→ should beexpected_config_version= - Missing required
game_type=argument - Missing required
schema_version=argument
File: backend/adapters/arma3/mod_manager.py, set_enabled_mods() method (~line 127)
Bug 2 — Mods not applied on server start (critical)
service.py start_server() reads mods from a server_mods JOIN mods table (SQLAlchemy query, ~line 246) — but those tables are never populated by the Mods tab UI. The correct source is config_repo.get_section(server_id, "mods")["enabled_mods"]. The start flow needs to read from config_repo instead of the dead server_mods table join.
File: backend/core/servers/service.py, start_server() method (~line 242–255)
Bug 3 — Wrong mod folder location (UX)
list_available_mods() scans the server root (get_server_dir()) for @* folders. Mods should live in a mods/ subfolder: {server_dir}/mods/@ModName. Needs:
- Change scan path:
server_dir / "mods"instead ofserver_dir - Ensure the
mods/subdirectory is created byensure_server_dirs(add"mods"to the Arma3get_server_dir_layout()) - Update CLAUDE.md + user docs to say mods go in
D:/ImContainer/Arma3Server/{id}/mods/@ModName
File: backend/adapters/arma3/mod_manager.py, list_available_mods() and _server_dir() (~line 47–88)
Also: backend/adapters/arma3/adapter.py, get_server_dir_layout() (add "mods" entry)