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
This commit is contained in:
Tran G. (Revernomad) Khoa
2026-04-17 11:58:34 +07:00
parent 620429c9b8
commit 6511353b55
119 changed files with 13752 additions and 5000 deletions

View File

@@ -0,0 +1,101 @@
"""Mod management endpoints — list available mods, set enabled mods."""
from __future__ import annotations
import logging
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from sqlalchemy.engine import Connection
from adapters.exceptions import AdapterError
from adapters.registry import GameAdapterRegistry
from core.dal.config_repository import ConfigRepository
from core.servers.service import ServerService
from database import get_db
from dependencies import get_current_user, require_admin
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/servers/{server_id}/mods", tags=["mods"])
def _ok(data):
return {"success": True, "data": data, "error": None}
class SetEnabledModsRequest(BaseModel):
mods: list[str]
def _get_mod_manager(server_id: int, game_type: str):
"""Get ModManager for the server's game type."""
adapter = GameAdapterRegistry.get(game_type)
if not adapter.has_capability("mod_manager"):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={"code": "NOT_SUPPORTED", "message": f"Game type '{game_type}' does not support mod management"},
)
return adapter.get_mod_manager(server_id)
@router.get("")
def list_mods(
server_id: int,
db: Annotated[Connection, Depends(get_db)],
_user: Annotated[dict, Depends(get_current_user)],
) -> dict:
"""List all available mods and which are enabled."""
server = ServerService(db).get_server(server_id) # raises 404 if not found
mgr = _get_mod_manager(server_id, server["game_type"])
config_repo = ConfigRepository(db)
try:
available = mgr.list_available_mods()
enabled = set(mgr.get_enabled_mods(config_repo))
except AdapterError as exc:
raise HTTPException(status_code=500, detail={"code": "ADAPTER_ERROR", "message": str(exc)})
for mod in available:
mod["enabled"] = mod["name"] in enabled
return _ok({
"server_id": server_id,
"mods": available,
"enabled_count": len(enabled),
})
@router.put("/enabled")
def set_enabled_mods(
server_id: int,
body: SetEnabledModsRequest,
db: Annotated[Connection, Depends(get_db)],
_admin: Annotated[dict, Depends(require_admin)],
) -> dict:
"""
Set the list of enabled mods.
Replaces the current enabled list entirely.
Server must be restarted for changes to take effect.
"""
server = ServerService(db).get_server(server_id) # raises 404 if not found
mgr = _get_mod_manager(server_id, server["game_type"])
config_repo = ConfigRepository(db)
try:
mgr.set_enabled_mods(body.mods, config_repo)
except AdapterError as exc:
raise HTTPException(status_code=400, detail={"code": "ADAPTER_ERROR", "message": str(exc)})
except ValueError as exc:
if "CONFIG_VERSION_CONFLICT" in str(exc):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail={"code": "VERSION_CONFLICT", "message": "Config was modified by another request. Please retry."},
)
raise
db.commit()
return _ok({
"message": "Enabled mods updated. Restart the server for changes to take effect.",
"enabled_mods": body.mods,
})