diff --git a/.claude/plan/arma3-ux-enhancement.md b/.claude/plan/arma3-ux-enhancement.md index f319196..0d2616c 100644 --- a/.claude/plan/arma3-ux-enhancement.md +++ b/.claude/plan/arma3-ux-enhancement.md @@ -14,9 +14,9 @@ |-------|--------|------------------| | 1 — Config UI Schema | `[x] done` | TagListEditor, useServerConfigSchema, ConfigEditor widget routing, backend get_ui_schema + endpoint | | 2 — Mission Rotation | `[x] done` | terrain in list_missions, rotation GET/PUT endpoints, MissionRotationEntry type, useServerMissionRotation/useUpdateMissionRotation hooks, multi-file upload, MissionList redesigned with Available + Rotation sections | -| 3 — Mod Display Names + Split Pane | `[ ] not started` | | -| 4 — Player Kick/Ban | `[ ] not started` | | -| 5 — Log File Browser | `[ ] not started` | | +| 3 — Mod Display Names + Split Pane | `[x] done` | _parse_mod_cpp/_parse_meta_cpp in mod_manager, ModList split-pane redesign | +| 4 — Player Kick/Ban | `[x] done` | get_by_slot in PlayerRepository, get_rcon_client in ThreadRegistry, kick/ban endpoints, PlayerTable modals | +| 5 — Log File Browser | `[x] done` | list_log_files/get_log_file_path in RPTParser, logfiles_router (GET/download/DELETE), LogViewer file browser section | **How to resume:** Read this table first. Find the first phase that is not `[x] done`. Read only that phase section — do not re-read earlier phases. Run `cd frontend && npx tsc --noEmit` to confirm the build is clean before making any changes. diff --git a/API.md b/API.md index 07397e7..6161cda 100644 --- a/API.md +++ b/API.md @@ -1596,16 +1596,17 @@ Endpoints planned during the Arma 3 UX Enhancement. ✅ = implemented. | GET ✅ | `/servers/{server_id}/missions/rotation` | Bearer | Get current mission rotation list | | PUT ✅ | `/servers/{server_id}/missions/rotation` | Admin | Replace mission rotation (requires `config_version` for optimistic locking) | -### Phase 4 — Player Kick / Ban +### Phase 4 — Player Kick / Ban ✅ | Method | Path | Auth | Description | |--------|------|------|-------------| -| POST | `/servers/{server_id}/players/{slot_id}/kick` | Admin | Kick player by slot ID via RCon; requires `reason` in body | -| POST | `/servers/{server_id}/players/{slot_id}/ban` | Admin | Ban player by slot ID via RCon + DB; requires `reason` and optional `duration_minutes` (`null` = permanent) | +| POST ✅ | `/servers/{server_id}/players/{slot_id}/kick` | Admin | Kick player by slot ID via RCon; requires `reason` in body | +| POST ✅ | `/servers/{server_id}/players/{slot_id}/ban` | Admin | Ban player by slot ID via RCon + DB; requires `reason` and optional `duration_minutes` (`null` = permanent) | -### Phase 5 — Log File Browser +### Phase 5 — Log File Browser ✅ | Method | Path | Auth | Description | |--------|------|------|-------------| -| GET | `/servers/{server_id}/logfiles` | Bearer | List historical `.rpt` log files with `filename`, `size_bytes`, `modified_at` | -| GET | `/servers/{server_id}/logfiles/{filename}` | Bearer | Stream or return contents of a historical log file | \ No newline at end of file +| GET ✅ | `/servers/{server_id}/logfiles` | Bearer | List historical `.rpt` log files with `filename`, `size_bytes`, `modified_at` | +| GET ✅ | `/servers/{server_id}/logfiles/{filename}/download` | Bearer | Download a historical `.rpt` log file as `text/plain` | +| DELETE ✅ | `/servers/{server_id}/logfiles/{filename}` | Admin | Delete a historical `.rpt` log file | \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 0fef8e1..b2f3811 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,42 +42,35 @@ All routers, services, repositories, game adapter system, WebSocket, background ### Frontend Type Mapping (API → Frontend) -Types below reflect the **current** API shape. Fields marked `(planned)` will be added during the UX enhancement plan. - | 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` *(planned)* | -| Mod | `Mod` in useServerDetail.ts | `name`, `path`, `size_bytes`, `enabled`, `display_name` *(planned)*, `workshop_id` *(planned)* | +| 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` | -### Upcoming: UX Enhancement Plan +### UX Enhancement Plan — ALL PHASES COMPLETE -**Plan file:** `.claude/plan/arma3-ux-enhancement.md` — approved, ready to implement. +**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 | Pending | -| 4 | Player Kick/Ban from Players tab via RCon | Pending | -| 5 | Historical log file browser + live log level filter | Pending | +| 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** | -**New endpoints added by the plan:** -- `GET /api/servers/{id}/config/schema` — per-field widget hints (**implemented**) -- `GET|PUT /api/servers/{id}/missions/rotation` — mission rotation (**implemented**) -- `POST /api/servers/{id}/players/{slot_id}/kick` -- `POST /api/servers/{id}/players/{slot_id}/ban` -- `GET /api/servers/{id}/logfiles` -- `GET /api/servers/{id}/logfiles/{filename}/download` -- `DELETE /api/servers/{id}/logfiles/{filename}` - -**New backend additions:** -- `Arma3ConfigGenerator.get_ui_schema()` — widget schema per config field -- `PlayerRepository.get_by_slot()` — lookup player by slot_id -- `ThreadRegistry.get_rcon_client()` — expose live RCon client for kick/ban -- `RPTParser.list_log_files()` / `get_log_file_path()` — historical log access +**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 diff --git a/FRONTEND.md b/FRONTEND.md index 68d3047..a9e452d 100644 --- a/FRONTEND.md +++ b/FRONTEND.md @@ -69,8 +69,8 @@ frontend/src/ │ │ ├── PlayerTable.tsx # Current players + history with search │ │ ├── BanTable.tsx # Ban list + create/revoke form │ │ ├── MissionList.tsx # Available missions + Mission Rotation sections; multi-file upload -│ │ ├── ModList.tsx # Mod list with enable/disable checkboxes -│ │ └── LogViewer.tsx # Log display with level filter (receives logs as props) +│ │ ├── ModList.tsx # Split-pane mod selector (Available vs Selected); Apply Selection button +│ │ └── LogViewer.tsx # Log display with level filter + collapsible Log Files browser (download/delete) │ ├── settings/ │ │ ├── PasswordChange.tsx # Password change form │ │ └── UserManager.tsx # User CRUD table (admin only) @@ -134,8 +134,8 @@ App │ │ │ ├── PlayerTable (current + history with search) │ │ │ ├── BanTable (ban list + create/revoke) │ │ │ ├── MissionList (Available + Rotation sections, multi-file upload) -│ │ │ ├── ModList (enable/disable checkboxes) -│ │ │ └── LogViewer (level filter, real-time via WebSocket onEvent) +│ │ │ ├── ModList (split-pane: Available | Selected; Apply Selection) +│ │ │ └── LogViewer (level filter, real-time stream + Log Files browser) │ │ ├── /servers/new → CreateServerPage │ │ │ └── 4-step wizard (Game Type → Info → Options → Review) │ │ └── /settings → SettingsPage @@ -186,6 +186,10 @@ All server data flows through TanStack Query hooks: | `useDeleteMission(id)` | Mutation | `DELETE /api/servers/:id/missions/:filename` | Invalidates `["missions", id]` | | `useSetEnabledMods(id)` | Mutation | `PUT /api/servers/:id/mods/enabled` | Invalidates `["mods", id]` | | `useSendCommand(id)` | Mutation | `POST /api/servers/:id/rcon/command` | No invalidation | +| `useKickPlayer(id)` | Mutation | `POST /api/servers/:id/players/:slot_id/kick` | Invalidates `["players", id]` | +| `useBanPlayer(id)` | Mutation | `POST /api/servers/:id/players/:slot_id/ban` | Invalidates players + bans | +| `useServerLogFiles(id)` | Query | `GET /api/servers/:id/logfiles` | `["servers", id, "logfiles"]` (refetch 30s) | +| `useDeleteLogFile(id)` | Mutation | `DELETE /api/servers/:id/logfiles/:filename` | Invalidates logfiles | **Auth** (`useAuth.ts`): @@ -210,18 +214,10 @@ All server data flows through TanStack Query hooks: **Key type notes**: - `Server` type in `useServers.ts` uses `game_port`, `current_players`, `max_players` (matches enriched API response) - `Mission` type: `{ name, filename, size_bytes, terrain }` — terrain parsed from filename -- `Mod` type: `{ name, path, size_bytes, enabled, display_name, workshop_id }` — `display_name`/`workshop_id` from mod.cpp/meta.cpp (planned Phase 3) +- `Mod` type: `{ name, path, size_bytes, enabled, display_name, workshop_id }` — `display_name`/`workshop_id` from mod.cpp/meta.cpp - `Ban` type: `{ id, server_id, guid, name, reason, banned_by, banned_at, expires_at, is_active, game_data }` (matches API) - There is no REST endpoint for logs — logs are only pushed via WebSocket events -**Planned hooks (UX Enhancement Plan — remaining):** - -| Hook | Phase | Endpoint | -|---|---|---| -| `useKickPlayer(id)` | Phase 4 | `POST /api/servers/:id/players/:slot_id/kick` | -| `useBanPlayer(id)` | Phase 4 | `POST /api/servers/:id/players/:slot_id/ban` | -| `useLogFiles(id)` | Phase 5 | `GET /api/servers/:id/logfiles` | - QueryClient defaults: `staleTime: 10s`, `retry: 2`, `refetchOnWindowFocus: false`. ### Client State (Zustand) @@ -285,7 +281,7 @@ Dark neumorphic theme defined in `tailwind.config.js`: ## Testing -### Unit Tests (120 tests, Vitest + React Testing Library) +### Unit Tests (149 tests, Vitest + React Testing Library) | Test File | Tests | Coverage | |---|---|---| diff --git a/MODULES.md b/MODULES.md index b30e6ef..0f800d7 100644 --- a/MODULES.md +++ b/MODULES.md @@ -51,11 +51,11 @@ All 7 capabilities implemented: | `adapter.py` | `Arma3Adapter` | Composite adapter declaring all capabilities | | `config_generator.py` | `Arma3ConfigGenerator` | 5 Pydantic config models, writes server.cfg/basic.cfg/Arma3Profile/beserver.cfg, builds launch args, `get_ui_schema()` for per-field widget hints | | `process_config.py` | `Arma3ProcessConfig` | Allowed executables, port conventions (game+1/+2/+3), directory layout | -| `log_parser.py` | `RPTParser` | Regex-based .rpt log parser, log file resolver | +| `log_parser.py` | `RPTParser` | Regex-based .rpt log parser, log file resolver, `list_log_files()`, `get_log_file_path()` | | `rcon_client.py` | `BERConClient` | BattlEye RCon v2 UDP protocol implementation | | `remote_admin.py` | `Arma3RemoteAdmin` + `Arma3RemoteAdminFactory` | Implements RemoteAdmin protocol using BERConClient | | `mission_manager.py` | `Arma3MissionManager` | .pbo upload, delete, list, rotation config generation | -| `mod_manager.py` | `Arma3ModManager` | @-prefixed mod scanning, enabled-mod persistence, -mod/-serverMod args | +| `mod_manager.py` | `Arma3ModManager` | @-prefixed mod scanning, enabled-mod persistence, -mod/-serverMod args; `_parse_mod_cpp()`/`_parse_meta_cpp()` for display_name/workshop_id | | `ban_manager.py` | `Arma3BanManager` | BattlEye bans.txt file sync + DB sync | ### `core/auth/` — Authentication @@ -72,7 +72,8 @@ All 7 capabilities implemented: | Module | Purpose | |---|---| | `router.py` | Server CRUD, lifecycle (start/stop/restart/kill), config read/write/preview, RCon command | -| `players_router.py` | Player list, player history | +| `players_router.py` | Player list, player history, kick/ban by slot_id | +| `logfiles_router.py` | List, download, and delete historical `.rpt` log files | | `bans_router.py` | Ban CRUD with bans.txt file sync | | `missions_router.py` | Mission list, .pbo upload (500MB), delete, GET/PUT rotation | | `mods_router.py` | List mods, set enabled mods | @@ -105,7 +106,7 @@ All 7 capabilities implemented: | Module | Purpose | |---|---| | `base_thread.py` | `BaseServerThread` — abstract base with stop event, thread-local DB, exception backoff | -| `thread_registry.py` | `ThreadRegistry` — manages per-server thread bundles, start/stop/reattach; `get_rcon_client(server_id)` class method to be added in Phase 4 | +| `thread_registry.py` | `ThreadRegistry` — manages per-server thread bundles, start/stop/reattach; `get_rcon_client(server_id)` class method exposes live RCon client | | `log_tail.py` | `LogTailThread` — tails log files, parses lines, persists to DB, broadcasts | | `process_monitor.py` | `ProcessMonitorThread` — detects crashes, triggers auto-restart | | `metrics_collector.py` | `MetricsCollectorThread` — psutil CPU/RAM collection every 10s | @@ -125,7 +126,7 @@ All 7 capabilities implemented: | `base_repository.py` | `BaseRepository` — thin wrapper around SQLAlchemy `text()` queries | | `server_repository.py` | `ServerRepository` — CRUD, status updates, running servers, restart count | | `config_repository.py` | `ConfigRepository` — Per-section upsert with Fernet encryption and optimistic locking | -| `player_repository.py` | `PlayerRepository` — Upsert/clear players, player_history queries; `get_by_slot(server_id, slot_id)` to be added in Phase 4 | +| `player_repository.py` | `PlayerRepository` — Upsert/clear players, player_history queries, `get_by_slot(server_id, slot_id)` | | `ban_repository.py` | `BanRepository` — Ban CRUD with active/inactive flag | | `event_repository.py` | `EventRepository` — Insert server events, query, cleanup | | `log_repository.py` | `LogRepository` — Insert parsed log entries, query with filters, cleanup |