mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-20 03:40:26 +00:00
976 lines
32 KiB
Python
976 lines
32 KiB
Python
"""Admin routes for Ban System monitoring in cabinet."""
|
||
|
||
import logging
|
||
from typing import Optional, List, Any
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||
|
||
from app.config import settings
|
||
from app.database.models import User
|
||
from app.external.ban_system_api import BanSystemAPI, BanSystemAPIError
|
||
|
||
from ..dependencies import get_current_admin_user
|
||
from ..schemas.ban_system import (
|
||
BanSystemStatusResponse,
|
||
BanSystemStatsResponse,
|
||
BanUsersListResponse,
|
||
BanUserListItem,
|
||
BanUserDetailResponse,
|
||
BanUserIPInfo,
|
||
BanUserRequestLog,
|
||
BanPunishmentsListResponse,
|
||
BanPunishmentItem,
|
||
BanHistoryResponse,
|
||
BanUserRequest,
|
||
UnbanResponse,
|
||
BanNodesListResponse,
|
||
BanNodeItem,
|
||
BanAgentsListResponse,
|
||
BanAgentItem,
|
||
BanAgentsSummary,
|
||
BanTrafficViolationsResponse,
|
||
BanTrafficViolationItem,
|
||
BanTrafficResponse,
|
||
BanTrafficTopItem,
|
||
BanSettingsResponse,
|
||
BanSettingDefinition,
|
||
BanWhitelistRequest,
|
||
BanReportResponse,
|
||
BanReportTopViolator,
|
||
BanHealthResponse,
|
||
BanHealthComponent,
|
||
BanHealthDetailedResponse,
|
||
BanAgentHistoryResponse,
|
||
BanAgentHistoryItem,
|
||
)
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter(prefix="/admin/ban-system", tags=["Cabinet Admin Ban System"])
|
||
|
||
|
||
def _get_ban_api() -> BanSystemAPI:
|
||
"""Get Ban System API instance."""
|
||
logger.debug(f"Ban System check - enabled: {settings.is_ban_system_enabled()}, configured: {settings.is_ban_system_configured()}")
|
||
logger.debug(f"Ban System URL: {settings.get_ban_system_api_url()}")
|
||
|
||
if not settings.is_ban_system_enabled():
|
||
raise HTTPException(
|
||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||
detail="Ban System integration is disabled",
|
||
)
|
||
|
||
if not settings.is_ban_system_configured():
|
||
raise HTTPException(
|
||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||
detail="Ban System is not configured",
|
||
)
|
||
|
||
return BanSystemAPI(
|
||
base_url=settings.get_ban_system_api_url(),
|
||
api_token=settings.get_ban_system_api_token(),
|
||
timeout=settings.get_ban_system_request_timeout(),
|
||
)
|
||
|
||
|
||
async def _api_request(api: BanSystemAPI, method: str, *args, **kwargs) -> Any:
|
||
"""Execute API request with error handling."""
|
||
try:
|
||
async with api:
|
||
func = getattr(api, method)
|
||
return await func(*args, **kwargs)
|
||
except BanSystemAPIError as e:
|
||
logger.error(f"Ban System API error: {e}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||
detail=f"Ban System API error: {e.message}",
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Ban System unexpected error: {e}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Internal error: {str(e)}",
|
||
)
|
||
|
||
|
||
# === Status ===
|
||
|
||
@router.get("/status", response_model=BanSystemStatusResponse)
|
||
async def get_ban_system_status(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanSystemStatusResponse:
|
||
"""Get Ban System integration status."""
|
||
return BanSystemStatusResponse(
|
||
enabled=settings.is_ban_system_enabled(),
|
||
configured=settings.is_ban_system_configured(),
|
||
)
|
||
|
||
|
||
# === Stats ===
|
||
|
||
@router.get("/stats/raw")
|
||
async def get_stats_raw(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> dict:
|
||
"""Get raw stats from Ban System API for debugging."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_stats")
|
||
return {"raw_response": data}
|
||
|
||
|
||
@router.get("/stats", response_model=BanSystemStatsResponse)
|
||
async def get_stats(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanSystemStatsResponse:
|
||
"""Get overall Ban System statistics."""
|
||
from datetime import datetime
|
||
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_stats")
|
||
|
||
logger.debug(f"Ban System raw stats: {data}")
|
||
|
||
# Extract punishment stats
|
||
punishment_stats = data.get("punishment_stats") or {}
|
||
|
||
# Extract connected nodes info
|
||
connected_nodes = data.get("connected_nodes", [])
|
||
|
||
# Count online nodes/agents
|
||
nodes_online = sum(1 for n in connected_nodes if n.get("is_online", False))
|
||
|
||
# Extract tcp_metrics for uptime
|
||
tcp_metrics = data.get("tcp_metrics") or {}
|
||
uptime_seconds = None
|
||
intake_started = tcp_metrics.get("intake_started_at")
|
||
if intake_started:
|
||
try:
|
||
start_time = datetime.fromisoformat(intake_started.replace("Z", "+00:00"))
|
||
uptime_seconds = int((datetime.now(start_time.tzinfo) - start_time).total_seconds())
|
||
except Exception:
|
||
pass
|
||
|
||
return BanSystemStatsResponse(
|
||
total_users=data.get("total_users", 0),
|
||
active_users=data.get("users_with_limit", 0),
|
||
users_over_limit=data.get("users_over_limit", 0),
|
||
total_requests=data.get("total_requests", 0),
|
||
total_punishments=punishment_stats.get("total_punishments", 0),
|
||
active_punishments=punishment_stats.get("active_punishments", 0),
|
||
nodes_online=nodes_online,
|
||
nodes_total=len(connected_nodes),
|
||
agents_online=nodes_online, # Agents = connected nodes with stats
|
||
agents_total=len(connected_nodes),
|
||
panel_connected=data.get("panel_loaded", False),
|
||
uptime_seconds=uptime_seconds,
|
||
)
|
||
|
||
|
||
# === Users ===
|
||
|
||
@router.get("/users", response_model=BanUsersListResponse)
|
||
async def get_users(
|
||
offset: int = Query(0, ge=0),
|
||
limit: int = Query(50, ge=1, le=100),
|
||
status: Optional[str] = Query(None, description="Filter: over_limit, with_limit, unlimited"),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanUsersListResponse:
|
||
"""Get list of users from Ban System."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_users", offset=offset, limit=limit, status=status)
|
||
|
||
users = []
|
||
for user_data in data.get("users", []):
|
||
users.append(BanUserListItem(
|
||
email=user_data.get("email", ""),
|
||
unique_ip_count=user_data.get("unique_ip_count", 0),
|
||
total_requests=user_data.get("total_requests", 0),
|
||
limit=user_data.get("limit"),
|
||
is_over_limit=user_data.get("is_over_limit", False),
|
||
blocked_count=user_data.get("blocked_count", 0),
|
||
))
|
||
|
||
return BanUsersListResponse(
|
||
users=users,
|
||
total=data.get("total", len(users)),
|
||
offset=offset,
|
||
limit=limit,
|
||
)
|
||
|
||
|
||
@router.get("/users/over-limit", response_model=BanUsersListResponse)
|
||
async def get_users_over_limit(
|
||
limit: int = Query(50, ge=1, le=100),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanUsersListResponse:
|
||
"""Get users who exceeded their device limit."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_users_over_limit", limit=limit)
|
||
|
||
users = []
|
||
for user_data in data.get("users", []):
|
||
users.append(BanUserListItem(
|
||
email=user_data.get("email", ""),
|
||
unique_ip_count=user_data.get("unique_ip_count", 0),
|
||
total_requests=user_data.get("total_requests", 0),
|
||
limit=user_data.get("limit"),
|
||
is_over_limit=True,
|
||
blocked_count=user_data.get("blocked_count", 0),
|
||
))
|
||
|
||
return BanUsersListResponse(
|
||
users=users,
|
||
total=len(users),
|
||
offset=0,
|
||
limit=limit,
|
||
)
|
||
|
||
|
||
@router.get("/users/search/{query}")
|
||
async def search_users(
|
||
query: str,
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanUsersListResponse:
|
||
"""Search for users."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "search_users", query=query)
|
||
|
||
users = []
|
||
users_data = data.get("users", []) if isinstance(data, dict) else data
|
||
for user_data in users_data:
|
||
users.append(BanUserListItem(
|
||
email=user_data.get("email", ""),
|
||
unique_ip_count=user_data.get("unique_ip_count", 0),
|
||
total_requests=user_data.get("total_requests", 0),
|
||
limit=user_data.get("limit"),
|
||
is_over_limit=user_data.get("is_over_limit", False),
|
||
blocked_count=user_data.get("blocked_count", 0),
|
||
))
|
||
|
||
return BanUsersListResponse(
|
||
users=users,
|
||
total=len(users),
|
||
offset=0,
|
||
limit=100,
|
||
)
|
||
|
||
|
||
@router.get("/users/{email}", response_model=BanUserDetailResponse)
|
||
async def get_user_detail(
|
||
email: str,
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanUserDetailResponse:
|
||
"""Get detailed user information."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_user", email=email)
|
||
|
||
ips = []
|
||
for ip_data in data.get("ips", {}).values() if isinstance(data.get("ips"), dict) else data.get("ips", []):
|
||
ips.append(BanUserIPInfo(
|
||
ip=ip_data.get("ip", ""),
|
||
first_seen=ip_data.get("first_seen"),
|
||
last_seen=ip_data.get("last_seen"),
|
||
node=ip_data.get("node"),
|
||
request_count=ip_data.get("request_count", 0),
|
||
country_code=ip_data.get("country_code"),
|
||
country_name=ip_data.get("country_name"),
|
||
city=ip_data.get("city"),
|
||
))
|
||
|
||
recent_requests = []
|
||
for req_data in data.get("recent_requests", []):
|
||
recent_requests.append(BanUserRequestLog(
|
||
timestamp=req_data.get("timestamp"),
|
||
source_ip=req_data.get("source_ip", ""),
|
||
destination=req_data.get("destination"),
|
||
dest_port=req_data.get("dest_port"),
|
||
protocol=req_data.get("protocol"),
|
||
action=req_data.get("action"),
|
||
node=req_data.get("node"),
|
||
))
|
||
|
||
return BanUserDetailResponse(
|
||
email=data.get("email", email),
|
||
unique_ip_count=data.get("unique_ip_count", 0),
|
||
total_requests=data.get("total_requests", 0),
|
||
limit=data.get("limit"),
|
||
is_over_limit=data.get("is_over_limit", False),
|
||
blocked_count=data.get("blocked_count", 0),
|
||
ips=ips,
|
||
recent_requests=recent_requests,
|
||
network_type=data.get("network_type"),
|
||
)
|
||
|
||
|
||
# === Punishments ===
|
||
|
||
@router.get("/punishments", response_model=BanPunishmentsListResponse)
|
||
async def get_punishments(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanPunishmentsListResponse:
|
||
"""Get list of active punishments (bans)."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_punishments")
|
||
|
||
punishments = []
|
||
punishments_data = data if isinstance(data, list) else data.get("punishments", [])
|
||
for p in punishments_data:
|
||
punishments.append(BanPunishmentItem(
|
||
id=p.get("id"),
|
||
user_id=p.get("user_id", ""),
|
||
uuid=p.get("uuid"),
|
||
username=p.get("username", ""),
|
||
reason=p.get("reason"),
|
||
punished_at=p.get("punished_at"),
|
||
enable_at=p.get("enable_at"),
|
||
ip_count=p.get("ip_count", 0),
|
||
limit=p.get("limit", 0),
|
||
enabled=p.get("enabled", False),
|
||
enabled_at=p.get("enabled_at"),
|
||
node_name=p.get("node_name"),
|
||
))
|
||
|
||
return BanPunishmentsListResponse(
|
||
punishments=punishments,
|
||
total=len(punishments),
|
||
)
|
||
|
||
|
||
@router.post("/punishments/{user_id}/unban", response_model=UnbanResponse)
|
||
async def unban_user(
|
||
user_id: str,
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> UnbanResponse:
|
||
"""Unban (enable) a user."""
|
||
api = _get_ban_api()
|
||
try:
|
||
await _api_request(api, "enable_user", user_id=user_id)
|
||
logger.info(f"Admin {admin.id} unbanned user {user_id} in Ban System")
|
||
return UnbanResponse(success=True, message="User unbanned successfully")
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
return UnbanResponse(success=False, message=str(e))
|
||
|
||
|
||
@router.post("/ban", response_model=UnbanResponse)
|
||
async def ban_user(
|
||
request: BanUserRequest,
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> UnbanResponse:
|
||
"""Manually ban a user."""
|
||
api = _get_ban_api()
|
||
try:
|
||
await _api_request(
|
||
api,
|
||
"ban_user",
|
||
username=request.username,
|
||
minutes=request.minutes,
|
||
reason=request.reason,
|
||
)
|
||
logger.info(f"Admin {admin.id} banned user {request.username}: {request.reason}")
|
||
return UnbanResponse(success=True, message="User banned successfully")
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
return UnbanResponse(success=False, message=str(e))
|
||
|
||
|
||
@router.get("/history/{query}", response_model=BanHistoryResponse)
|
||
async def get_punishment_history(
|
||
query: str,
|
||
limit: int = Query(20, ge=1, le=100),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanHistoryResponse:
|
||
"""Get punishment history for a user."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_punishment_history", query=query, limit=limit)
|
||
|
||
items = []
|
||
history_data = data if isinstance(data, list) else data.get("items", [])
|
||
for p in history_data:
|
||
items.append(BanPunishmentItem(
|
||
id=p.get("id"),
|
||
user_id=p.get("user_id", ""),
|
||
uuid=p.get("uuid"),
|
||
username=p.get("username", ""),
|
||
reason=p.get("reason"),
|
||
punished_at=p.get("punished_at"),
|
||
enable_at=p.get("enable_at"),
|
||
ip_count=p.get("ip_count", 0),
|
||
limit=p.get("limit", 0),
|
||
enabled=p.get("enabled", False),
|
||
enabled_at=p.get("enabled_at"),
|
||
node_name=p.get("node_name"),
|
||
))
|
||
|
||
return BanHistoryResponse(
|
||
items=items,
|
||
total=len(items),
|
||
)
|
||
|
||
|
||
# === Nodes ===
|
||
|
||
@router.get("/nodes", response_model=BanNodesListResponse)
|
||
async def get_nodes(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanNodesListResponse:
|
||
"""Get list of connected nodes."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_nodes")
|
||
|
||
nodes = []
|
||
nodes_data = data if isinstance(data, list) else data.get("nodes", [])
|
||
online_count = 0
|
||
for n in nodes_data:
|
||
# API returns is_online, not is_connected
|
||
is_connected = n.get("is_online", n.get("is_connected", False))
|
||
if is_connected:
|
||
online_count += 1
|
||
nodes.append(BanNodeItem(
|
||
name=n.get("name", ""),
|
||
address=n.get("address"),
|
||
is_connected=is_connected,
|
||
# API returns last_heartbeat, not last_seen
|
||
last_seen=n.get("last_heartbeat", n.get("last_seen")),
|
||
# API returns unique_users, not users_count
|
||
users_count=n.get("unique_users", n.get("users_count", 0)),
|
||
agent_stats=n.get("agent_stats"),
|
||
))
|
||
|
||
return BanNodesListResponse(
|
||
nodes=nodes,
|
||
total=len(nodes),
|
||
online=online_count,
|
||
)
|
||
|
||
|
||
# === Agents ===
|
||
|
||
@router.get("/agents", response_model=BanAgentsListResponse)
|
||
async def get_agents(
|
||
search: Optional[str] = Query(None),
|
||
health: Optional[str] = Query(None, description="healthy, warning, critical"),
|
||
agent_status: Optional[str] = Query(None, alias="status", description="online, offline"),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanAgentsListResponse:
|
||
"""Get list of monitoring agents."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(
|
||
api,
|
||
"get_agents",
|
||
search=search,
|
||
health=health,
|
||
status=agent_status,
|
||
)
|
||
|
||
agents = []
|
||
agents_data = data.get("agents", {}) if isinstance(data, dict) else data
|
||
online_count = 0
|
||
|
||
# API returns agents as dict: {"node_name": {stats...}, ...}
|
||
if isinstance(agents_data, dict):
|
||
for node_name, agent_info in agents_data.items():
|
||
# Extract metrics from nested structure
|
||
stats = agent_info.get("stats", {}) or {}
|
||
metrics = stats.get("metrics", {}) or {}
|
||
sent_info = metrics.get("sent", {}) or {}
|
||
queue_info = metrics.get("queue", {}) or {}
|
||
conn_info = metrics.get("connection", {}) or {}
|
||
|
||
is_online = agent_info.get("is_online", False)
|
||
if is_online:
|
||
online_count += 1
|
||
|
||
agents.append(BanAgentItem(
|
||
node_name=node_name,
|
||
sent_total=sent_info.get("total", 0),
|
||
dropped_total=sent_info.get("dropped", 0),
|
||
batches_total=sent_info.get("batches", 0),
|
||
reconnects=conn_info.get("reconnects", 0),
|
||
failures=conn_info.get("failures", sent_info.get("failed", 0)),
|
||
queue_size=queue_info.get("current", 0),
|
||
queue_max=queue_info.get("high_watermark", 0),
|
||
dedup_checked=0,
|
||
dedup_skipped=0,
|
||
filter_checked=0,
|
||
filter_filtered=0,
|
||
health=agent_info.get("health", "unknown"),
|
||
is_online=is_online,
|
||
last_report=agent_info.get("updated_at"),
|
||
))
|
||
else:
|
||
# Fallback for list format
|
||
for a in agents_data:
|
||
is_online = a.get("is_online", False)
|
||
if is_online:
|
||
online_count += 1
|
||
agents.append(BanAgentItem(
|
||
node_name=a.get("node_name", ""),
|
||
sent_total=a.get("sent_total", 0),
|
||
dropped_total=a.get("dropped_total", 0),
|
||
batches_total=a.get("batches_total", 0),
|
||
reconnects=a.get("reconnects", 0),
|
||
failures=a.get("failures", 0),
|
||
queue_size=a.get("queue_size", 0),
|
||
queue_max=a.get("queue_max", 0),
|
||
dedup_checked=a.get("dedup_checked", 0),
|
||
dedup_skipped=a.get("dedup_skipped", 0),
|
||
filter_checked=a.get("filter_checked", 0),
|
||
filter_filtered=a.get("filter_filtered", 0),
|
||
health=a.get("health", "unknown"),
|
||
is_online=is_online,
|
||
last_report=a.get("last_report"),
|
||
))
|
||
|
||
summary = None
|
||
if isinstance(data, dict) and "summary" in data:
|
||
s = data["summary"]
|
||
summary = BanAgentsSummary(
|
||
total_agents=s.get("total_agents", len(agents)),
|
||
online_agents=s.get("online_agents", online_count),
|
||
total_sent=s.get("total_sent", 0),
|
||
total_dropped=s.get("total_dropped", 0),
|
||
avg_queue_size=s.get("avg_queue_size", 0.0),
|
||
healthy_count=s.get("healthy_count", 0),
|
||
warning_count=s.get("warning_count", 0),
|
||
critical_count=s.get("critical_count", 0),
|
||
)
|
||
|
||
return BanAgentsListResponse(
|
||
agents=agents,
|
||
summary=summary,
|
||
total=len(agents),
|
||
online=online_count,
|
||
)
|
||
|
||
|
||
@router.get("/agents/summary", response_model=BanAgentsSummary)
|
||
async def get_agents_summary(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanAgentsSummary:
|
||
"""Get agents summary statistics."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_agents_summary")
|
||
|
||
return BanAgentsSummary(
|
||
total_agents=data.get("total_agents", 0),
|
||
online_agents=data.get("online_agents", 0),
|
||
total_sent=data.get("total_sent", 0),
|
||
total_dropped=data.get("total_dropped", 0),
|
||
avg_queue_size=data.get("avg_queue_size", 0.0),
|
||
healthy_count=data.get("healthy_count", 0),
|
||
warning_count=data.get("warning_count", 0),
|
||
critical_count=data.get("critical_count", 0),
|
||
)
|
||
|
||
|
||
# === Traffic Violations ===
|
||
|
||
@router.get("/traffic/violations", response_model=BanTrafficViolationsResponse)
|
||
async def get_traffic_violations(
|
||
limit: int = Query(50, ge=1, le=100),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanTrafficViolationsResponse:
|
||
"""Get list of traffic limit violations."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_traffic_violations", limit=limit)
|
||
|
||
violations = []
|
||
violations_data = data if isinstance(data, list) else data.get("violations", [])
|
||
for v in violations_data:
|
||
violations.append(BanTrafficViolationItem(
|
||
id=v.get("id"),
|
||
username=v.get("username", ""),
|
||
email=v.get("email"),
|
||
violation_type=v.get("violation_type", v.get("type", "")),
|
||
description=v.get("description"),
|
||
bytes_used=v.get("bytes_used", 0),
|
||
bytes_limit=v.get("bytes_limit", 0),
|
||
detected_at=v.get("detected_at"),
|
||
resolved=v.get("resolved", False),
|
||
))
|
||
|
||
return BanTrafficViolationsResponse(
|
||
violations=violations,
|
||
total=len(violations),
|
||
)
|
||
|
||
|
||
# === Full Traffic Stats ===
|
||
|
||
@router.get("/traffic", response_model=BanTrafficResponse)
|
||
async def get_traffic(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanTrafficResponse:
|
||
"""Get full traffic statistics including top users."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_traffic")
|
||
|
||
top_users = []
|
||
for u in data.get("top_users", []):
|
||
top_users.append(BanTrafficTopItem(
|
||
username=u.get("username", ""),
|
||
bytes_total=u.get("bytes_total", u.get("total_bytes", 0)),
|
||
bytes_limit=u.get("bytes_limit"),
|
||
over_limit=u.get("over_limit", False),
|
||
))
|
||
|
||
violations = []
|
||
for v in data.get("recent_violations", []):
|
||
violations.append(BanTrafficViolationItem(
|
||
id=v.get("id"),
|
||
username=v.get("username", ""),
|
||
email=v.get("email"),
|
||
violation_type=v.get("violation_type", v.get("type", "")),
|
||
description=v.get("description"),
|
||
bytes_used=v.get("bytes_used", 0),
|
||
bytes_limit=v.get("bytes_limit", 0),
|
||
detected_at=v.get("detected_at"),
|
||
resolved=v.get("resolved", False),
|
||
))
|
||
|
||
return BanTrafficResponse(
|
||
enabled=data.get("enabled", False),
|
||
stats=data.get("stats"),
|
||
top_users=top_users,
|
||
recent_violations=violations,
|
||
)
|
||
|
||
|
||
@router.get("/traffic/top")
|
||
async def get_traffic_top(
|
||
limit: int = Query(20, ge=1, le=100),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> List[BanTrafficTopItem]:
|
||
"""Get top users by traffic."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_traffic_top", limit=limit)
|
||
|
||
top_users = []
|
||
users_data = data if isinstance(data, list) else data.get("users", [])
|
||
for u in users_data:
|
||
top_users.append(BanTrafficTopItem(
|
||
username=u.get("username", ""),
|
||
bytes_total=u.get("bytes_total", u.get("total_bytes", 0)),
|
||
bytes_limit=u.get("bytes_limit"),
|
||
over_limit=u.get("over_limit", False),
|
||
))
|
||
|
||
return top_users
|
||
|
||
|
||
# === Settings ===
|
||
|
||
def _parse_setting_response(key: str, data: Any, default_type: str = "str") -> BanSettingDefinition:
|
||
"""Parse setting response from API."""
|
||
if isinstance(data, dict) and "value" in data:
|
||
return BanSettingDefinition(
|
||
key=key,
|
||
value=data.get("value"),
|
||
type=data.get("type", default_type),
|
||
min_value=data.get("min"),
|
||
max_value=data.get("max"),
|
||
editable=data.get("editable", True),
|
||
description=data.get("description"),
|
||
category=data.get("category"),
|
||
)
|
||
else:
|
||
# Простое значение или dict без "value"
|
||
value = data.get("value", data) if isinstance(data, dict) else data
|
||
value_type = default_type
|
||
if isinstance(value, bool):
|
||
value_type = "bool"
|
||
elif isinstance(value, int):
|
||
value_type = "int"
|
||
elif isinstance(value, float):
|
||
value_type = "float"
|
||
elif isinstance(value, list):
|
||
value_type = "list"
|
||
|
||
return BanSettingDefinition(
|
||
key=key,
|
||
value=value,
|
||
type=value_type,
|
||
min_value=None,
|
||
max_value=None,
|
||
editable=True,
|
||
description=None,
|
||
category=None,
|
||
)
|
||
|
||
|
||
@router.get("/settings", response_model=BanSettingsResponse)
|
||
async def get_settings(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanSettingsResponse:
|
||
"""Get all Ban System settings."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_settings")
|
||
|
||
settings_list = []
|
||
settings_data = data.get("settings", {}) if isinstance(data, dict) else {}
|
||
|
||
for key, info in settings_data.items():
|
||
# API может возвращать настройки в двух форматах:
|
||
# 1. {"key": {"value": ..., "type": ...}} - с метаданными
|
||
# 2. {"key": value} - просто значение
|
||
if isinstance(info, dict) and "value" in info:
|
||
# Формат с метаданными
|
||
settings_list.append(BanSettingDefinition(
|
||
key=key,
|
||
value=info.get("value"),
|
||
type=info.get("type", "str"),
|
||
min_value=info.get("min"),
|
||
max_value=info.get("max"),
|
||
editable=info.get("editable", True),
|
||
description=info.get("description"),
|
||
category=info.get("category"),
|
||
))
|
||
else:
|
||
# Простой формат - определяем тип по значению
|
||
value_type = "str"
|
||
if isinstance(info, bool):
|
||
value_type = "bool"
|
||
elif isinstance(info, int):
|
||
value_type = "int"
|
||
elif isinstance(info, float):
|
||
value_type = "float"
|
||
elif isinstance(info, list):
|
||
value_type = "list"
|
||
|
||
settings_list.append(BanSettingDefinition(
|
||
key=key,
|
||
value=info,
|
||
type=value_type,
|
||
min_value=None,
|
||
max_value=None,
|
||
editable=True,
|
||
description=None,
|
||
category=None,
|
||
))
|
||
|
||
return BanSettingsResponse(settings=settings_list)
|
||
|
||
|
||
@router.get("/settings/{key}")
|
||
async def get_setting(
|
||
key: str,
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanSettingDefinition:
|
||
"""Get a specific setting."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_setting", key=key)
|
||
|
||
return _parse_setting_response(key, data)
|
||
|
||
|
||
@router.post("/settings/{key}")
|
||
async def set_setting(
|
||
key: str,
|
||
value: str = Query(...),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanSettingDefinition:
|
||
"""Set a setting value."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "set_setting", key=key, value=value)
|
||
|
||
logger.info(f"Admin {admin.id} changed Ban System setting {key} to {value}")
|
||
|
||
return _parse_setting_response(key, data)
|
||
|
||
|
||
@router.post("/settings/{key}/toggle")
|
||
async def toggle_setting(
|
||
key: str,
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanSettingDefinition:
|
||
"""Toggle a boolean setting."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "toggle_setting", key=key)
|
||
|
||
logger.info(f"Admin {admin.id} toggled Ban System setting {key}")
|
||
|
||
return _parse_setting_response(key, data, default_type="bool")
|
||
|
||
|
||
# === Whitelist ===
|
||
|
||
@router.post("/settings/whitelist/add", response_model=UnbanResponse)
|
||
async def whitelist_add(
|
||
request: BanWhitelistRequest,
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> UnbanResponse:
|
||
"""Add user to whitelist."""
|
||
api = _get_ban_api()
|
||
try:
|
||
await _api_request(api, "whitelist_add", username=request.username)
|
||
logger.info(f"Admin {admin.id} added {request.username} to Ban System whitelist")
|
||
return UnbanResponse(success=True, message=f"User {request.username} added to whitelist")
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
return UnbanResponse(success=False, message=str(e))
|
||
|
||
|
||
@router.post("/settings/whitelist/remove", response_model=UnbanResponse)
|
||
async def whitelist_remove(
|
||
request: BanWhitelistRequest,
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> UnbanResponse:
|
||
"""Remove user from whitelist."""
|
||
api = _get_ban_api()
|
||
try:
|
||
await _api_request(api, "whitelist_remove", username=request.username)
|
||
logger.info(f"Admin {admin.id} removed {request.username} from Ban System whitelist")
|
||
return UnbanResponse(success=True, message=f"User {request.username} removed from whitelist")
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
return UnbanResponse(success=False, message=str(e))
|
||
|
||
|
||
# === Reports ===
|
||
|
||
@router.get("/report", response_model=BanReportResponse)
|
||
async def get_report(
|
||
hours: int = Query(24, ge=1, le=168),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanReportResponse:
|
||
"""Get period report."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_stats_period", hours=hours)
|
||
|
||
top_violators = []
|
||
punishment_stats = data.get("punishment_stats", {}) or {}
|
||
for v in punishment_stats.get("top_violators", []):
|
||
top_violators.append(BanReportTopViolator(
|
||
username=v.get("username", ""),
|
||
count=v.get("count", 0),
|
||
))
|
||
|
||
return BanReportResponse(
|
||
period_hours=hours,
|
||
current_users=data.get("current_users", 0),
|
||
current_ips=data.get("current_ips", 0),
|
||
punishment_stats=punishment_stats,
|
||
top_violators=top_violators,
|
||
)
|
||
|
||
|
||
# === Health ===
|
||
|
||
@router.get("/health", response_model=BanHealthResponse)
|
||
async def get_health(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanHealthResponse:
|
||
"""Get Ban System health status."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "health_check")
|
||
|
||
components = []
|
||
for name, info in data.get("components", {}).items():
|
||
if isinstance(info, dict):
|
||
components.append(BanHealthComponent(
|
||
name=name,
|
||
status=info.get("status", "unknown"),
|
||
message=info.get("message"),
|
||
details=info.get("details"),
|
||
))
|
||
else:
|
||
components.append(BanHealthComponent(
|
||
name=name,
|
||
status=str(info) if info else "unknown",
|
||
))
|
||
|
||
return BanHealthResponse(
|
||
status=data.get("status", "unknown"),
|
||
uptime=data.get("uptime"),
|
||
components=components,
|
||
)
|
||
|
||
|
||
@router.get("/health/detailed", response_model=BanHealthDetailedResponse)
|
||
async def get_health_detailed(
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanHealthDetailedResponse:
|
||
"""Get detailed health information."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "health_detailed")
|
||
|
||
return BanHealthDetailedResponse(
|
||
status=data.get("status", "unknown"),
|
||
uptime=data.get("uptime"),
|
||
components=data.get("components", {}),
|
||
)
|
||
|
||
|
||
# === Agent History ===
|
||
|
||
@router.get("/agents/{node_name}/history", response_model=BanAgentHistoryResponse)
|
||
async def get_agent_history(
|
||
node_name: str,
|
||
hours: int = Query(24, ge=1, le=168),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanAgentHistoryResponse:
|
||
"""Get agent statistics history."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_agent_history", node_name=node_name, hours=hours)
|
||
|
||
history = []
|
||
for item in data.get("history", []):
|
||
history.append(BanAgentHistoryItem(
|
||
timestamp=item.get("timestamp"),
|
||
sent_total=item.get("sent_total", 0),
|
||
dropped_total=item.get("dropped_total", 0),
|
||
queue_size=item.get("queue_size", 0),
|
||
batches_total=item.get("batches_total", 0),
|
||
))
|
||
|
||
return BanAgentHistoryResponse(
|
||
node=data.get("node", node_name),
|
||
hours=data.get("hours", hours),
|
||
records=data.get("records", len(history)),
|
||
delta=data.get("delta"),
|
||
first=data.get("first"),
|
||
last=data.get("last"),
|
||
history=history,
|
||
)
|
||
|
||
|
||
# === User Punishment History ===
|
||
|
||
@router.get("/users/{email}/history", response_model=BanHistoryResponse)
|
||
async def get_user_punishment_history(
|
||
email: str,
|
||
limit: int = Query(20, ge=1, le=100),
|
||
admin: User = Depends(get_current_admin_user),
|
||
) -> BanHistoryResponse:
|
||
"""Get punishment history for a specific user."""
|
||
api = _get_ban_api()
|
||
data = await _api_request(api, "get_punishment_history", query=email, limit=limit)
|
||
|
||
items = []
|
||
history_data = data if isinstance(data, list) else data.get("items", [])
|
||
for p in history_data:
|
||
items.append(BanPunishmentItem(
|
||
id=p.get("id"),
|
||
user_id=p.get("user_id", ""),
|
||
uuid=p.get("uuid"),
|
||
username=p.get("username", ""),
|
||
reason=p.get("reason"),
|
||
punished_at=p.get("punished_at"),
|
||
enable_at=p.get("enable_at"),
|
||
ip_count=p.get("ip_count", 0),
|
||
limit=p.get("limit", 0),
|
||
enabled=p.get("enabled", False),
|
||
enabled_at=p.get("enabled_at"),
|
||
node_name=p.get("node_name"),
|
||
))
|
||
|
||
return BanHistoryResponse(
|
||
items=items,
|
||
total=len(items),
|
||
)
|