- 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
122 lines
7.1 KiB
Markdown
122 lines
7.1 KiB
Markdown
# Languard Server Manager
|
||
|
||
## Quick Start
|
||
|
||
```bash
|
||
# 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 via `python -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 `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.
|
||
|
||
## 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 be `config_data=`
|
||
- Passes `expected_version=` → should be `expected_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:
|
||
1. Change scan path: `server_dir / "mods"` instead of `server_dir`
|
||
2. Ensure the `mods/` subdirectory is created by `ensure_server_dirs` (add `"mods"` to the Arma3 `get_server_dir_layout()`)
|
||
3. 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) |