Files
languard-servers-manager/backend/core/websocket/router.py
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

90 lines
2.4 KiB
Python

"""
WebSocket endpoint.
URL: /ws
/ws?server_id=1
/ws?server_id=1&server_id=2
Authentication: JWT passed as a query parameter `token` because
browser WebSocket API does not support custom headers.
If the token is missing or invalid, the connection is closed with code 4001.
After authentication, the client receives:
- A "connected" welcome message with the list of subscribed server IDs
- All events for subscribed servers pushed by BroadcastThread
"""
from __future__ import annotations
import logging
from typing import Optional
from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect
from core.auth.utils import decode_access_token
logger = logging.getLogger(__name__)
router = APIRouter(tags=["websocket"])
@router.websocket("/ws")
async def websocket_endpoint(
ws: WebSocket,
token: Optional[str] = Query(default=None),
server_id: Optional[list[int]] = Query(default=None),
) -> None:
"""
WebSocket endpoint for real-time server events.
Query parameters:
token: JWT access token (required)
server_id: One or more server IDs to subscribe to (optional, default=all)
"""
# Authenticate before accepting
if not token:
await ws.close(code=4001, reason="Missing token")
return
try:
user = decode_access_token(token)
except Exception as exc:
logger.warning("WebSocket: token decode failed: %s", exc)
user = None
if user is None:
await ws.close(code=4001, reason="Invalid or expired token")
return
# Get WebSocketManager from app state
ws_manager = ws.app.state.ws_manager
await ws_manager.connect(ws, server_ids=server_id)
logger.info(
"WebSocket: user '%s' connected, subscribed to servers=%s",
user.get("sub"),
server_id,
)
try:
# Send welcome message
await ws_manager.send_to_connection(ws, {
"type": "connected",
"data": {
"user": user.get("sub"),
"subscriptions": server_id or "all",
},
})
# Keep connection alive — wait for client to disconnect
while True:
data = await ws.receive_text()
except WebSocketDisconnect:
logger.info(
"WebSocket: user '%s' disconnected",
user.get("sub"),
)
except Exception as exc:
logger.error("WebSocket: unexpected error: %s", exc)
finally:
await ws_manager.disconnect(ws)