Files
languard-servers-manager/MODULES.md
Tran G. (Revernomad) Khoa 6511353b55 feat: implement full backend + frontend server detail, settings, and create server pages
Backend:
- Complete FastAPI backend with 42+ REST endpoints (auth, servers, config,
  players, bans, missions, mods, games, system)
- Game adapter architecture with Arma 3 as first-class adapter
- WebSocket real-time events for status, metrics, logs, players
- Background thread system (process monitor, metrics, log tail, RCon poller)
- Fernet encryption for sensitive config fields at rest
- JWT auth with admin/viewer roles, bcrypt password hashing
- SQLite with WAL mode, parameterized queries, migration system
- APScheduler cleanup jobs for logs, metrics, events

Frontend:
- Server Detail page with 7 tabs (overview, config, players, bans,
  missions, mods, logs)
- Settings page with password change and admin user management
- Create Server wizard (4-step; known bug: silent validation failure)
- New hooks: useServerDetail, useAuth, useGames
- New components: ServerHeader, ConfigEditor, PlayerTable, BanTable,
  MissionList, ModList, LogViewer, PasswordChange, UserManager
- WebSocket onEvent callback for real-time log accumulation
- 120 unit tests passing (Vitest + React Testing Library)

Docs:
- Added .gitignore, CLAUDE.md, README.md
- Updated FRONTEND.md, ARCHITECTURE.md with current implementation state
- Added .env.example for backend configuration

Known issues:
- Create Server form: "Next" buttons don't validate before advancing,
  causing silent submit failure when fields are invalid
- Config sub-tabs need UX redesign for non-technical users
2026-04-17 11:58:34 +07:00

220 lines
11 KiB
Markdown

# Module Reference
## Backend Modules
### `main.py` — Application Factory
- `create_app()` — FastAPI app with lifespan, CORS middleware, rate limiter, exception handler
- `lifespan()` — Startup: init DB, register adapters, create WebSocket manager, create broadcast thread, create ThreadRegistry, recover processes, reattach threads, seed admin, start scheduler. Shutdown: stop threads, stop broadcast, stop scheduler.
### `config.py` — Settings
- `Settings` class (Pydantic BaseSettings) with `LANGUARD_` env prefix
- 13 configurable settings: SECRET_KEY, ENCRYPTION_KEY, DB_PATH, SERVERS_DIR, HOST, PORT, CORS_ORIGINS, log/metrics/player retention days, JWT expiry, Arma3 default exe
### `database.py` — Database Engine
- `get_engine()` — Creates SQLite engine with WAL mode and foreign keys
- `run_migrations(engine)` — Applies numbered `.sql` migration files from `core/migrations/`
- `get_db()` — FastAPI dependency yielding a DB connection
- `get_thread_db()` — Thread-local DB connection for background threads
### `dependencies.py` — FastAPI Dependencies
- `get_current_user(token)` — Decodes JWT, validates user exists
- `require_admin(user)` — Returns 403 if user role is not admin
- `get_server_or_404(server_id, db)` — Loads server row or raises 404
- `get_adapter_for_server(server_id, db)` — Loads server + resolves its game adapter
### `adapters/protocols.py` — Adapter Protocols
7 runtime-checkable Protocol classes:
- `ConfigGenerator` — Render config files, build launch args
- `ProcessConfig` — Allowed executables, port conventions, directory layout
- `LogParser` — Parse log lines, resolve latest log file
- `RemoteAdmin` — Connect, send commands, get/kick/ban players
- `MissionManager` — List, upload, delete mission files
- `ModManager` — Scan mods, set enabled mods, build CLI args
- `BanManager` — Sync bans between DB and file
- Composite `GameAdapter` — Aggregates all protocols, plus `has_capability()`, `get_additional_routers()`, `get_custom_thread_factories()`
### `adapters/registry.py` — Game Adapter Registry
- `GameAdapterRegistry` — Class-level singleton dict (`game_type` → adapter instance)
- Methods: `register()`, `get()`, `all()`, `list_game_types()`, `is_registered()`
### `adapters/exceptions.py` — Typed Exceptions
- `AdapterError`, `ConfigWriteError`, `ConfigValidationError`, `ConfigMigrationError`, `LaunchArgsError`, `RemoteAdminError`, `ExeNotAllowedError`
### `adapters/__init__.py` — Adapter Loading
- `initialize_adapters()` — Imports built-in adapters (Arma3), then scans `importlib.metadata` entry points under `"languard.adapters"` for third-party plugins
### `adapters/arma3/` — Arma 3 Adapter
All 7 capabilities implemented:
| Module | Class | Purpose |
|---|---|---|
| `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 |
| `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 |
| `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 |
| `ban_manager.py` | `Arma3BanManager` | BattlEye bans.txt file sync + DB sync |
### `core/auth/` — Authentication
| Module | Purpose |
|---|---|
| `router.py` | 7 endpoints: login, logout, me, change password, user CRUD |
| `service.py` | `AuthService` — login, create_user, change_password, seed_admin_if_empty |
| `schemas.py` | Pydantic models: LoginRequest, CreateUserRequest, ChangePasswordRequest |
| `utils.py` | JWT creation/validation (HS256), bcrypt password hashing |
### `core/servers/` — Server Management
| Module | Purpose |
|---|---|
| `router.py` | Server CRUD, lifecycle (start/stop/restart/kill), config read/write/preview, RCon command |
| `players_router.py` | Player list, player history |
| `bans_router.py` | Ban CRUD with bans.txt file sync |
| `missions_router.py` | Mission list, .pbo upload (500MB), delete |
| `mods_router.py` | List mods, set enabled mods |
| `service.py` | `ServerService` — orchestrates all lifecycle operations, config writes, thread management |
| `schemas.py` | Pydantic models: CreateServerRequest, UpdateServerRequest, StopServerRequest |
| `process_manager.py` | `ProcessManager` singleton — subprocess.Popen lifecycle, PID recovery via psutil |
### `core/games/` — Game Type Discovery
| Module | Purpose |
|---|---|
| `router.py` | 4 endpoints: list game types, get game details, config schema, defaults |
### `core/system/` — System Health
| Module | Purpose |
|---|---|
| `router.py` | `/health` (public) + `/status` (authed) |
### `core/websocket/` — Real-Time Events
| Module | Purpose |
|---|---|
| `router.py` | `/ws` endpoint with JWT auth via query param |
| `manager.py` | `WebSocketManager` — asyncio connection registry with server_id subscriptions |
| `broadcast_thread.py` | `BroadcastThread` — bridges `queue.Queue` to asyncio event loop |
### `core/threads/` — Background Threads
| 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 |
| `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 |
| `remote_admin_poller.py` | `RemoteAdminPollerThread` — polls player list via RCon, syncs join/leave events |
### `core/jobs/` — Scheduled Tasks
| Module | Purpose |
|---|---|
| `scheduler.py` | APScheduler `BackgroundScheduler` singleton |
| `cleanup_jobs.py` | 3 cron jobs: old logs (daily 03:00), old metrics (6h), old events (weekly Sun 04:00) |
### `core/dal/` — Data Access Layer
| Module | Purpose |
|---|---|
| `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 |
| `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 |
| `metrics_repository.py` | `MetricsRepository` — Insert CPU/RAM metrics, query by time range, cleanup |
### `core/utils/` — Utilities
| Module | Purpose |
|---|---|
| `crypto.py` | Fernet field-level encryption: `encrypt()`, `decrypt()`, `is_encrypted()` |
| `file_utils.py` | `get_server_dir()`, `ensure_server_dirs()`, `sanitize_filename()`, `safe_delete_file()` |
| `port_checker.py` | Port availability checks with cross-server conflict detection |
---
## Frontend Modules
### `src/main.tsx` — Entry Point
Renders `<App />` into `#root` with React StrictMode.
### `src/App.tsx` — Root Component
- Creates `QueryClient` with stale time 10s, retry 2, refetchOnWindowFocus false
- Wraps app in `QueryClientProvider`, `BrowserRouter`, `ReactQueryDevtools`
- Routes: `/login``LoginPage`, `/*``ProtectedLayout` (auth guard)
- `ProtectedLayout` checks `isAuthenticated` from Zustand, redirects to `/login` if false
### `src/lib/api.ts` — API Client
- Axios instance with `baseURL` from `VITE_API_URL` env, 30s timeout
- Request interceptor: reads `languard_token` from localStorage, adds `Authorization: Bearer` header
- Response interceptor: on 401, checks URL prefix — non-auth 401s clear token and redirect to `/login`; auth 401s (login) are left for the component to handle
- Exports `ApiResponse<T>` type: `{ success, data, error? }`
### `src/store/auth.store.ts` — Auth Store (Zustand + persist)
- Persisted to `localStorage` under key `languard-auth`
- `onRehydrateStorage` sets `isAuthenticated: true` if token exists
- `partialize` stores only `token` and `user`
- `setAuth(token, user)` also writes `languard_token` to localStorage
- `clearAuth()` removes both storage keys
### `src/store/ui.store.ts` — UI Store (Zustand, in-memory)
- `sidebarOpen`, `activeServerId`, `notifications[]`
- `addNotification(type, message)` creates toast with auto-remove (5s timeout)
- `removeNotification(id)` for manual dismiss
### `src/hooks/useServers.ts` — Server Data Hooks
7 TanStack Query hooks: `useServers`, `useServer`, `useStartServer`, `useStopServer`, `useRestartServer`, `useCreateServer`, `useDeleteServer`
- `Server` interface with all fields
- `useServers` refetches every 30s
- Mutations invalidate relevant cache keys on success
### `src/hooks/useWebSocket.ts` — WebSocket Hook
- Connects to `ws://localhost:8000/ws?token=...&server_id=...`
- Exponential backoff reconnect (2s → 30s max)
- Close code 4001 = explicit logout (no reconnect)
- Invalidates TanStack Query caches on server_status, metrics, log, players events
- Cleans up on unmount (closes WebSocket, clears reconnect timeout)
### `src/pages/LoginPage.tsx` — Login Page
- React Hook Form + Zod validation (username/password required)
- `apiClient.post("/api/auth/login")` on submit
- `setAuth(token, user)` + navigate to `/` on success
- Error display from catch block
- Loading state: button changes to "Signing in..."
### `src/pages/DashboardPage.tsx` — Dashboard Page
- `useServers()` for data, `useWebSocket()` for real-time updates
- Loading state, error state, empty state, content with server grid
- Server cards rendered via `ServerCard` inside `Link` components
- "X servers configured" counter
### `src/components/layout/Sidebar.tsx` — Sidebar
- "Languard Server Manager" branding
- Dashboard link with LayoutDashboard icon
- Dynamic server list from `useServers()` with `StatusLed` dots
- Settings link at bottom
- Active server highlighting via URL params
### `src/components/servers/ServerCard.tsx` — Server Card
- Displays: server name, `StatusLed`, game type badge
- 3-column stat grid: Players (current/max), Port, Restarts
- Action buttons based on server status:
- **Stopped**: Start button only
- **Running**: Stop + Restart buttons
- **Starting/Restarting**: Stop + Restart (disabled)
- `e.preventDefault()` + `e.stopPropagation()` on buttons to prevent parent `Link` navigation
- Success/error notifications via `useUIStore`
### `src/components/ui/StatusLed.tsx` — Status LED
- Colored dot with CSS class `status-led-{status}` (5 statuses)
- Sizes: `sm` (6px), `md` (8px)
- Optional label text via `showLabel` prop
- Uses `clsx` for conditional class merging