Новые вкладки в AdminBanSystem:

1. Traffic (Трафик) - статистика трафика, топ пользователей по трафику, последние нарушения
  2. Reports (Отчёты) - отчёты за период (6h, 12h, 24h, 48h, 72h), статистика активных пользователей и IP, топ нарушителей
  3. Settings (Настройки) - управление настройками системы банов, группировка по категориям, переключатели для bool, ввод для int
  4. Health (Здоровье) - статус системы (healthy/degraded/unhealthy), аптайм, статус компонентов
This commit is contained in:
PEDZEO
2026-01-16 20:51:28 +03:00
parent 723a49f1f1
commit b0f83f3534
3 changed files with 519 additions and 2 deletions

View File

@@ -30,6 +30,18 @@ from ..schemas.ban_system import (
BanAgentsSummary,
BanTrafficViolationsResponse,
BanTrafficViolationItem,
BanTrafficResponse,
BanTrafficTopItem,
BanSettingsResponse,
BanSettingDefinition,
BanWhitelistRequest,
BanReportResponse,
BanReportTopViolator,
BanHealthResponse,
BanHealthComponent,
BanHealthDetailedResponse,
BanAgentHistoryResponse,
BanAgentHistoryItem,
)
logger = logging.getLogger(__name__)
@@ -584,3 +596,342 @@ async def get_traffic_violations(
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 ===
@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():
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"),
))
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 BanSettingDefinition(
key=key,
value=data.get("value"),
type=data.get("type", "str"),
min_value=data.get("min"),
max_value=data.get("max"),
editable=data.get("editable", True),
description=data.get("description"),
category=data.get("category"),
)
@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 BanSettingDefinition(
key=key,
value=data.get("value"),
type=data.get("type", "str"),
min_value=data.get("min"),
max_value=data.get("max"),
editable=data.get("editable", True),
description=data.get("description"),
category=data.get("category"),
)
@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 BanSettingDefinition(
key=key,
value=data.get("value"),
type=data.get("type", "bool"),
min_value=data.get("min"),
max_value=data.get("max"),
editable=data.get("editable", True),
description=data.get("description"),
category=data.get("category"),
)
# === 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),
)

View File

@@ -230,3 +230,111 @@ class BanTrafficViolationsResponse(BaseModel):
"""List of traffic violations."""
violations: List[BanTrafficViolationItem] = []
total: int = 0
class BanTrafficTopItem(BaseModel):
"""Top user by traffic."""
username: str
bytes_total: int = 0
bytes_limit: Optional[int] = None
over_limit: bool = False
class BanTrafficResponse(BaseModel):
"""Full traffic statistics response."""
enabled: bool = False
stats: Optional[Dict[str, Any]] = None
top_users: List[BanTrafficTopItem] = []
recent_violations: List[BanTrafficViolationItem] = []
# === Settings ===
class BanSettingDefinition(BaseModel):
"""Setting definition with value."""
key: str
value: Any
type: str # bool, int, str, list
min_value: Optional[int] = None
max_value: Optional[int] = None
editable: bool = True
description: Optional[str] = None
category: Optional[str] = None
class BanSettingsResponse(BaseModel):
"""All settings response."""
settings: List[BanSettingDefinition] = []
class BanSettingUpdateRequest(BaseModel):
"""Request to update a setting."""
value: Any
class BanWhitelistRequest(BaseModel):
"""Request to add/remove from whitelist."""
username: str = Field(..., min_length=1)
# === Reports ===
class BanReportTopViolator(BaseModel):
"""Top violator in report."""
username: str
count: int = 0
class BanReportResponse(BaseModel):
"""Period report response."""
period_hours: int = 24
current_users: int = 0
current_ips: int = 0
punishment_stats: Optional[Dict[str, Any]] = None
top_violators: List[BanReportTopViolator] = []
# === Health ===
class BanHealthComponent(BaseModel):
"""Health component status."""
name: str
status: str # healthy, degraded, unhealthy
message: Optional[str] = None
details: Optional[Dict[str, Any]] = None
class BanHealthResponse(BaseModel):
"""Health status response."""
status: str # healthy, degraded, unhealthy
uptime: Optional[int] = None
components: List[BanHealthComponent] = []
class BanHealthDetailedResponse(BaseModel):
"""Detailed health response."""
status: str
uptime: Optional[int] = None
components: Dict[str, Any] = {}
# === Agent History ===
class BanAgentHistoryItem(BaseModel):
"""Agent history item."""
timestamp: datetime
sent_total: int = 0
dropped_total: int = 0
queue_size: int = 0
batches_total: int = 0
class BanAgentHistoryResponse(BaseModel):
"""Agent history response."""
node: str
hours: int = 24
records: int = 0
delta: Optional[Dict[str, Any]] = None
first: Optional[Dict[str, Any]] = None
last: Optional[Dict[str, Any]] = None
history: List[BanAgentHistoryItem] = []

View File

@@ -352,6 +352,64 @@ class BanSystemAPI:
"""
Check API health.
GET /api/health
GET /health
"""
return await self._request("GET", "/api/health")
return await self._request("GET", "/health")
async def health_detailed(self) -> Dict[str, Any]:
"""
Get detailed health information.
GET /health/detailed
"""
return await self._request("GET", "/health/detailed")
# === Settings ===
async def get_settings(self) -> Dict[str, Any]:
"""
Get all settings with their definitions.
GET /api/settings
"""
return await self._request("GET", "/api/settings")
async def get_setting(self, key: str) -> Dict[str, Any]:
"""
Get a specific setting value.
GET /api/settings/{key}
"""
return await self._request("GET", f"/api/settings/{key}")
async def set_setting(self, key: str, value: Any) -> Dict[str, Any]:
"""
Set a setting value.
POST /api/settings/{key}?value={value}
"""
return await self._request("POST", f"/api/settings/{key}", params={"value": value})
async def toggle_setting(self, key: str) -> Dict[str, Any]:
"""
Toggle a boolean setting.
POST /api/settings/{key}/toggle
"""
return await self._request("POST", f"/api/settings/{key}/toggle")
async def whitelist_add(self, username: str) -> Dict[str, Any]:
"""
Add user to whitelist.
POST /api/settings/whitelist/add?username={username}
"""
return await self._request("POST", "/api/settings/whitelist/add", params={"username": username})
async def whitelist_remove(self, username: str) -> Dict[str, Any]:
"""
Remove user from whitelist.
POST /api/settings/whitelist/remove?username={username}
"""
return await self._request("POST", "/api/settings/whitelist/remove", params={"username": username})