# 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, `get_ui_schema()` returns per-field widget hints for all ~80 fields across 5 sections (text, toggle, select, number, password, tag-list, hidden, textarea); `missions` field marked hidden | | `process_config.py` | `Arma3ProcessConfig` | Allowed executables, port conventions (game+1/+2/+3), directory layout | | `log_parser.py` | `RPTParser` | Regex-based .rpt log parser, resolves log path using `Path(server["exe_path"]).parent / "server"` (not languard data dir), `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; `_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 | 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, kick/ban by slot_id | | `logfiles_router.py` | List, download, and delete historical `.rpt` log files from `Path(server["exe_path"]).parent / "server"` (not languard data dir) | | `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 | | `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; `get_rcon_client(server_id)` class method exposes live RCon client | | `log_tail.py` | `LogTailThread` — resolves log path from `Path(server["exe_path"]).parent`, tails .rpt 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, `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 | | `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 `` 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` 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 9 TanStack Query hooks: `useServers`, `useServer`, `useStartServer`, `useStopServer`, `useRestartServer`, `useCreateServer`, `useDeleteServer`, `useUpdateServer`, `useKillServer` - `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