Phase 3 - Mod display names + split-pane selector:
- Parse mod.cpp/meta.cpp for display_name and workshop_id
- Rewrite ModList as two-pane available/selected interface
Phase 4 - Player kick/ban from Players tab:
- Add get_by_slot() to PlayerRepository
- Add get_rcon_client() class method to ThreadRegistry
- Add /players/{slot_id}/kick and /ban endpoints
- Rewrite PlayerTable with kick/ban modals and ban presets
Phase 5 - Historical log file browser:
- Add list_log_files() and get_log_file_path() to RPTParser
- Add logfiles_router with GET/download/DELETE endpoints
- Update LogViewer with collapsible log files section (download + delete)
94 lines
2.7 KiB
Python
94 lines
2.7 KiB
Python
"""Player endpoints — list current players for a running server."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.engine import Connection
|
|
|
|
from core.dal.player_repository import PlayerRepository
|
|
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}/players", tags=["players"])
|
|
|
|
|
|
class KickRequest(BaseModel):
|
|
reason: str = "Kicked by admin"
|
|
|
|
|
|
class BanFromPlayerRequest(BaseModel):
|
|
reason: str = "Banned by admin"
|
|
duration_minutes: int | None = None
|
|
|
|
|
|
def _ok(data):
|
|
return {"success": True, "data": data, "error": None}
|
|
|
|
|
|
@router.get("")
|
|
def list_players(
|
|
server_id: int,
|
|
db: Annotated[Connection, Depends(get_db)],
|
|
_user: Annotated[dict, Depends(get_current_user)],
|
|
) -> dict:
|
|
"""List current players (cached from RemoteAdminPollerThread)."""
|
|
ServerService(db).get_server(server_id) # raises 404 if not found
|
|
player_repo = PlayerRepository(db)
|
|
players = player_repo.get_all(server_id=server_id)
|
|
count = player_repo.count(server_id=server_id)
|
|
return _ok({
|
|
"server_id": server_id,
|
|
"player_count": count,
|
|
"players": players,
|
|
})
|
|
|
|
|
|
@router.get("/history")
|
|
def player_history(
|
|
server_id: int,
|
|
db: Annotated[Connection, Depends(get_db)],
|
|
_user: Annotated[dict, Depends(get_current_user)],
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
search: str | None = None,
|
|
) -> dict:
|
|
"""Get historical player sessions."""
|
|
ServerService(db).get_server(server_id) # raises 404 if not found
|
|
player_repo = PlayerRepository(db)
|
|
total, rows = player_repo.get_history(
|
|
server_id=server_id, limit=limit, offset=offset, search=search,
|
|
)
|
|
return _ok({"total": total, "items": rows})
|
|
|
|
|
|
@router.post("/{slot_id}/kick")
|
|
def kick_player(
|
|
server_id: int,
|
|
slot_id: int,
|
|
body: KickRequest,
|
|
db: Annotated[Connection, Depends(get_db)],
|
|
_admin: Annotated[dict, Depends(require_admin)],
|
|
) -> dict:
|
|
ServerService(db).kick_player(server_id, slot_id, body.reason)
|
|
return _ok({"message": f"Player {slot_id} kicked"})
|
|
|
|
|
|
@router.post("/{slot_id}/ban")
|
|
def ban_player_from_list(
|
|
server_id: int,
|
|
slot_id: int,
|
|
body: BanFromPlayerRequest,
|
|
db: Annotated[Connection, Depends(get_db)],
|
|
admin: Annotated[dict, Depends(require_admin)],
|
|
) -> dict:
|
|
ban = ServerService(db).ban_from_player(
|
|
server_id, slot_id, body.reason, body.duration_minutes,
|
|
banned_by=admin["username"],
|
|
)
|
|
return _ok(ban) |