- Fix logfiles_router and thread_registry to resolve .rpt log files from Path(server["exe_path"]).parent/server/ instead of the languard data dir, which never contained log files — log list and live tail both now work correctly - Rewrite get_ui_schema() in config_generator to cover all ~80 fields across all 5 sections (server/basic/profile/launch/rcon) with proper toggle/select/number/password/tag-list/hidden widgets and labels; missions field is hidden (managed by Missions tab) - Add formatSelectDisplay() to ConfigEditor so select fields show descriptive text (e.g. "0 - Never") instead of raw numbers in view mode - Add ToggleDisplay for boolean fields (Enabled/Disabled with indicator dot) - Add section tab labels and descriptions to ConfigEditor - Add MissionList UX hints and dynamic Add/In Rotation button labels - Add "hidden" to FieldSchema widget union type - Update API.md, ARCHITECTURE.md, CLAUDE.md, FRONTEND.md, MODULES.md, THREADING.md to document log path fix and schema coverage
12 KiB
12 KiB
Module Reference
Backend Modules
main.py — Application Factory
create_app()— FastAPI app with lifespan, CORS middleware, rate limiter, exception handlerlifespan()— 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
Settingsclass (Pydantic BaseSettings) withLANGUARD_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 keysrun_migrations(engine)— Applies numbered.sqlmigration files fromcore/migrations/get_db()— FastAPI dependency yielding a DB connectionget_thread_db()— Thread-local DB connection for background threads
dependencies.py — FastAPI Dependencies
get_current_user(token)— Decodes JWT, validates user existsrequire_admin(user)— Returns 403 if user role is not adminget_server_or_404(server_id, db)— Loads server row or raises 404get_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 argsProcessConfig— Allowed executables, port conventions, directory layoutLogParser— Parse log lines, resolve latest log fileRemoteAdmin— Connect, send commands, get/kick/ban playersMissionManager— List, upload, delete mission filesModManager— Scan mods, set enabled mods, build CLI argsBanManager— Sync bans between DB and file- Composite
GameAdapter— Aggregates all protocols, plushas_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 scansimportlib.metadataentry 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 <App /> into #root with React StrictMode.
src/App.tsx — Root Component
- Creates
QueryClientwith stale time 10s, retry 2, refetchOnWindowFocus false - Wraps app in
QueryClientProvider,BrowserRouter,ReactQueryDevtools - Routes:
/login→LoginPage,/*→ProtectedLayout(auth guard) ProtectedLayoutchecksisAuthenticatedfrom Zustand, redirects to/loginif false
src/lib/api.ts — API Client
- Axios instance with
baseURLfromVITE_API_URLenv, 30s timeout - Request interceptor: reads
languard_tokenfrom localStorage, addsAuthorization: Bearerheader - 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
localStorageunder keylanguard-auth onRehydrateStoragesetsisAuthenticated: trueif token existspartializestores onlytokenandusersetAuth(token, user)also writeslanguard_tokento localStorageclearAuth()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
Serverinterface with all fieldsuseServersrefetches 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 submitsetAuth(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
ServerCardinsideLinkcomponents - "X servers configured" counter
src/components/layout/Sidebar.tsx — Sidebar
- "Languard Server Manager" branding
- Dashboard link with LayoutDashboard icon
- Dynamic server list from
useServers()withStatusLeddots - 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 parentLinknavigation- 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
showLabelprop - Uses
clsxfor conditional class merging