Files
languard-servers-manager/docs/CHERRY_PICK.md
Khoa (Revenovich) Tran Gia 4ba199dd62 docs: add arma-server-web-admin analysis reference docs
Brings in ANALYSIS.md, HOW_IT_WORKS.md, and CHERRY_PICK.md generated from
deep analysis of the arma-server-web-admin benchmark project. These docs
inform the Arma 3 UX enhancement plan (.claude/plan/arma3-ux-enhancement.md)
and provide context for implementing agents without needing to re-read the
source project.
2026-04-17 14:55:59 +07:00

11 KiB
Raw Permalink Blame History

Cherry-Pick Candidates for languard-servers-manager

This document lists modules and functions from arma-server-web-admin that are worth adapting into languard-servers-manager. Each entry explains what the code does, why it is valuable, and a concrete adapter strategy.

Target project: E:\TestScript\languard-servers-manager (FastAPI + React stack).


1. Manager Pattern — Multi-Server Lifecycle Registry

Source: lib/manager.js Key functions: load(), save(), addServer(), removeServer(), getServer(), getServers()

What it does: Maintains an in-memory registry of Server instances (both as an ordered array and a hash for O(1) lookup), flushes state to servers.json on every mutation, and emits change events so the Socket.IO layer can broadcast diffs.

Why it is valuable: languard already has a similar concept (servers stored in SQLite), but the EventEmitter pattern that automatically triggers broadcasts on every mutation is clean and decoupled. The dual array+hash storage pattern is also worth copying for cache performance.

Adapter strategy:

Create: backend/core/servers/manager.py
  - ServerManager class with in-memory cache (dict + list)
  - on_change callback / asyncio.Event instead of EventEmitter
  - load() reads from SQLite via existing ServerRepository
  - save() writes back through repository
  - Emit WebSocket broadcasts via FastAPI WebSocket manager on every mutation

2. Process Lifecycle — Start / Stop / Kill with Graceful Fallback

Source: lib/server.jsstart(), stop() methods

What it does: Spawns a child process for the game server, captures its PID, starts a status-poll interval, pipes stdout/stderr to a log file, and on stop sends SIGTERM with a 5-second SIGKILL fallback. Headless clients are launched/killed alongside the main process.

Why it is valuable: languard's backend already launches processes, but the graceful stop with timed SIGKILL fallback and the headless client co-lifecycle are patterns not yet present.

Adapter strategy:

Adapt: backend/core/servers/process_manager.py
  - async def start_server(config) -> asyncio.subprocess.Process
  - async def stop_server(proc, timeout=5) -> SIGTERM then SIGKILL
  - Capture PID, store in DB server record
  - Pipe stdout/stderr to dated log file (matches existing log path logic)

3. Status Polling — Gamedig-style Periodic Query

Source: lib/server.jsqueryStatus() + setInterval pattern

What it does: Every 5 seconds, queries the running game server via the game's UDP query protocol (Gamedig). Stores the response (players, mission, state) in the server's in-memory state and emits it to connected clients.

Why it is valuable: languard's frontend relies on WebSocket events pushed from process stdout. A periodic external query would give accurate player counts and current mission data independent of log output.

Adapter strategy:

Create: backend/core/servers/status_poller.py
  - asyncio.Task per running server (cancel on stop)
  - Use python-a2s or opengsq-python to query UDP game port
  - QueryAdapter interface: arma3, dayz, etc. as subclasses
  - On result: update server record in DB, push via WebSocket manager
  - Poll interval configurable (default 5 s)

4. Mod Discovery — Parallel Metadata Resolution

Source: lib/mods/index.js, lib/mods/modFile.js, lib/mods/steamMeta.js, lib/mods/folderSize.js

What it does: Finds all mod directories via glob, then for each mod concurrently: parses mod.cpp for display name, parses meta.cpp for Steam Workshop ID, and recursively calculates folder size with symlink-aware deduplication.

Why it is valuable: languard's mod tab lists paths but does not extract Steam IDs, human-readable names, or folder sizes. This pattern handles all three in parallel efficiently.

Adapter strategy:

Create: backend/core/mods/scanner.py
  - async def scan_mods(game_path: str) -> list[ModMeta]
  - Use pathlib.glob for discovery
  - asyncio.gather for parallel metadata:
      parse_mod_file(path)   -> ModFileAdapter  (reads mod.cpp)
      parse_steam_meta(path) -> SteamMetaAdapter (reads meta.cpp)
      folder_size(path)      -> recursive os.scandir sum
  - ModMeta dataclass: { path, name, steam_id, size_bytes }

5. Log File Management — Platform Paths + Auto-Cleanup

Source: lib/logs.jslogsPath(), logFiles(), cleanupOldLogFiles()

What it does: Resolves the game log directory based on platform (windows / linux / wine). Lists .rpt files with metadata, auto-deletes the oldest files beyond a configurable retention limit (default 20).

Why it is valuable: languard streams real-time logs via WebSocket but has no endpoint to browse historical .rpt files on disk. Discovery and cleanup logic is directly reusable.

Adapter strategy:

Adapt: backend/core/logs/log_manager.py
  - def logs_path(platform: str, game_path: str) -> Path
  - def list_logs(logs_dir: Path) -> list[LogMeta]  (stat each .rpt)
  - def cleanup_old_logs(logs_dir: Path, keep: int = 20)
  - Expose via:
      GET    /api/servers/{id}/logfiles
      GET    /api/servers/{id}/logfiles/{name}/download
      DELETE /api/servers/{id}/logfiles/{name}

6. Mission Multi-File Upload + Filename Parsing

Source: lib/missions.jsupdateMissions() + routes/missions.js upload handler

What it does: Accepts multipart upload of up to 64 .pbo files simultaneously (parallel move with limit 8), scans the mpmissions/ directory, and extracts { name, world } from the Arma filename convention missionname.worldname.pbo.

Why it is valuable: languard's missions tab lists files but does not support drag-and-drop multi-file upload or Steam Workshop download. The .pbo filename parsing convention is Arma-specific and worth encoding explicitly.

Adapter strategy:

Adapt: backend/routers/missions.py
  - POST /api/servers/{id}/missions -> UploadFile[] via FastAPI
  - Validate .pbo extension server-side (reject others)
  - asyncio.gather with semaphore (limit 8): move to mpmissions/
  - Parse filename: "name.world.pbo" -> { name, world }
  - Trigger rescan -> return updated list
  - Optional: POST /api/missions/workshop { id } -> delegate to steamcmd

7. EventEmitter → WebSocket Broadcast Bridge

Source: app.js lines 4966 (event bridge block)

What it does: Listens on EventEmitter events from Manager, Missions, and Mods, then calls io.emit() to broadcast to all connected Socket.IO clients. New connections receive a full state snapshot immediately on connect.

Why it is valuable: languard's WebSocket layer pushes real-time log lines but does not broadcast server state changes to all connected tabs/clients simultaneously. The "snapshot on connect + push diffs on change" pattern is directly applicable.

Adapter strategy:

Adapt: backend/core/websocket/broadcast_manager.py
  - WebSocketManager class (already partially exists in languard)
  - Add: publish(event: str, payload) -> broadcast_all()
  - On new connection: send snapshot of all servers, missions, mods
  - On server state change: publish("servers", get_all_servers())
  - Same for missions and mods events

8. Mission Rotation Table — Per-Mission Difficulty

Source: public/js/app/views/servers/missions/rotation/ (list + item views)

What it does: Renders a table of missions in the server's active rotation. Each row has a difficulty dropdown. Rows can be added from the discovered mission list or removed individually. The full rotation array saves with the server config.

Why it is valuable: languard's missions tab shows available missions but has no drag-to-add rotation table with per-mission difficulty. This is a key Arma workflow.

Adapter strategy:

Adapt: frontend/src/pages/ServerDetailPage.tsx (Missions tab)
  - MissionRotationTable component
  - Row type: { name: string, difficulty: '' | 'Recruit' | 'Regular' | 'Veteran' }
  - Add row: select from available missions dropdown
  - Remove row: delete button per row
  - Save: PUT /api/servers/{id}  { missions: [...] }
  - Backend: MissionRotationItem Pydantic schema as array field on Server

9. Mod Assignment UI — Split-Pane Available / Selected

Source: public/js/app/views/servers/mods/ (available list + selected list views)

What it does: Two side-by-side lists: all discovered mods on the left, server-assigned mods on the right. Clicking a mod moves it between lists. Each list has a search filter. Selection saves when the settings form submits.

Why it is valuable: languard's mods tab uses a flat checkbox list. The split-pane pattern with instant visual feedback is significantly more usable when mod counts are large (50+).

Adapter strategy:

Adapt: frontend/src/pages/ServerDetailPage.tsx (Mods tab)
  - <AvailableModsList> and <SelectedModsList> side by side
  - Shared state: selectedMods: string[] (mod paths)
  - On click: immutable transfer between lists
  - Search input on each list (client-side filter)
  - On save: PUT /api/servers/{id}  { mods: selectedMods }

10. Startup Parameter Editor — Dynamic String List

Source: public/js/app/views/servers/parameters/ (list + item views)

What it does: Renders a dynamic list of startup parameter inputs (e.g., -limitFPS=100). Rows can be added or removed. The list persists with the server config.

Why it is valuable: languard's Create Server wizard captures some fixed parameters but has no UI for arbitrary additional startup flags, which power users need.

Adapter strategy:

Create: frontend/src/components/ParameterEditor.tsx
  - Props: value: string[], onChange: (v: string[]) => void
  - Renders list of text inputs with remove buttons
  - Add button appends empty string
  - Used in: CreateServerPage wizard (step 3) and ServerDetailPage settings tab
  - Save: include in PUT /api/servers/{id}  { parameters: [...] }

Priority Ranking

Priority Candidate Effort Value
High Status Polling (asyncio task per server) Medium Accurate live player counts
High Mission Rotation Table UI Medium Key missing workflow
High Mission Multi-File Upload Low Missing feature
High Mod Discovery with Parallel Metadata Medium Rich mod metadata
Medium Startup Parameter Editor UI Low Power-user feature
Medium Mod Split-Pane Selection UI Medium UX improvement for large mod lists
Medium Log File Discovery + Cleanup Low Historical log access
Low EventEmitter Broadcast Bridge Low Already partially implemented
Low Config-Driven Optional Auth Low Dev convenience only