feat: implement phases 3-5 of Arma 3 UX enhancement plan
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)
This commit is contained in:
@@ -396,6 +396,56 @@ class ServerService:
|
||||
data[field] = "***"
|
||||
return sections
|
||||
|
||||
def kick_player(self, server_id: int, slot_id: int, reason: str) -> None:
|
||||
from core.threads.thread_registry import ThreadRegistry
|
||||
ra = ThreadRegistry.get_rcon_client(server_id)
|
||||
if not ra or not ra.is_connected():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={"code": "RCON_NOT_CONNECTED", "message": "RCon not connected — server must be running"},
|
||||
)
|
||||
success = ra.kick_player(int(slot_id), reason)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={"code": "KICK_FAILED", "message": "Kick command failed"},
|
||||
)
|
||||
|
||||
def ban_from_player(
|
||||
self,
|
||||
server_id: int,
|
||||
slot_id: int,
|
||||
reason: str,
|
||||
duration_minutes: int | None,
|
||||
banned_by: str,
|
||||
) -> dict:
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from core.dal.player_repository import PlayerRepository
|
||||
from core.dal.ban_repository import BanRepository
|
||||
player = PlayerRepository(self._db).get_by_slot(server_id, slot_id)
|
||||
if not player:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail={"code": "NOT_FOUND", "message": "Player not found"},
|
||||
)
|
||||
expires_at = None
|
||||
if duration_minutes is not None and duration_minutes > 0:
|
||||
expires_at = (datetime.now(timezone.utc) + timedelta(minutes=duration_minutes)).isoformat()
|
||||
from core.threads.thread_registry import ThreadRegistry
|
||||
ra = ThreadRegistry.get_rcon_client(server_id)
|
||||
if ra and ra.is_connected():
|
||||
ra.ban_player(player["guid"], duration_minutes or 0, reason)
|
||||
ban_repo = BanRepository(self._db)
|
||||
ban_id = ban_repo.create(
|
||||
server_id=server_id,
|
||||
guid=player["guid"],
|
||||
name=player["name"],
|
||||
reason=reason,
|
||||
banned_by=banned_by,
|
||||
expires_at=expires_at,
|
||||
)
|
||||
return dict(ban_repo.get_by_id(ban_id))
|
||||
|
||||
def get_config_schema(self, server_id: int) -> dict:
|
||||
server = self.get_server(server_id)
|
||||
adapter = GameAdapterRegistry.get(server["game_type"])
|
||||
|
||||
Reference in New Issue
Block a user