Upload 30 files
Browse files- routes.py +23 -9
- templates_setup.py +1 -0
routes.py
CHANGED
|
@@ -5,7 +5,7 @@ import json
|
|
| 5 |
import logging
|
| 6 |
import asyncio
|
| 7 |
from datetime import datetime, timedelta
|
| 8 |
-
from typing import Optional
|
| 9 |
|
| 10 |
from fastapi import APIRouter, Depends, File, Form, Query, Request, HTTPException, status
|
| 11 |
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse, Response
|
|
@@ -178,15 +178,22 @@ def _clear_session(response: RedirectResponse) -> None:
|
|
| 178 |
async def _audit(
|
| 179 |
db: AsyncSession,
|
| 180 |
action: str,
|
| 181 |
-
user: Optional[
|
| 182 |
target_type: Optional[str] = None,
|
| 183 |
target_id: Optional[int] = None,
|
| 184 |
details: Optional[dict] = None,
|
| 185 |
ip: Optional[str] = None,
|
| 186 |
ua: Optional[str] = None,
|
| 187 |
) -> None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
log_entry = AuditLog(
|
| 189 |
-
user_id=
|
| 190 |
action=action,
|
| 191 |
target_type=target_type,
|
| 192 |
target_id=target_id,
|
|
@@ -1461,7 +1468,7 @@ async def admin_control_delete_user(
|
|
| 1461 |
|
| 1462 |
@router.get("/admin/users", response_class=HTMLResponse)
|
| 1463 |
async def admin_users(request: Request, db: AsyncSession = Depends(get_db)):
|
| 1464 |
-
require_role(UserRole.ADMIN)(request)
|
| 1465 |
result = await db.execute(select(User).order_by(User.created_at.desc()))
|
| 1466 |
users = result.scalars().all()
|
| 1467 |
# Per-user usage — for the % utilisation columns. We do this as
|
|
@@ -1474,6 +1481,7 @@ async def admin_users(request: Request, db: AsyncSession = Depends(get_db)):
|
|
| 1474 |
resp = templates.TemplateResponse(request, "admin_users.html", {
|
| 1475 |
"request": request,
|
| 1476 |
"title": "Users",
|
|
|
|
| 1477 |
"users": users,
|
| 1478 |
"usage": usage,
|
| 1479 |
"presets": {
|
|
@@ -1496,7 +1504,7 @@ async def admin_users(request: Request, db: AsyncSession = Depends(get_db)):
|
|
| 1496 |
|
| 1497 |
@router.get("/admin/invites", response_class=HTMLResponse)
|
| 1498 |
async def admin_invites(request: Request, db: AsyncSession = Depends(get_db)):
|
| 1499 |
-
require_role(UserRole.ADMIN)(request)
|
| 1500 |
result = await db.execute(select(InviteCode).order_by(InviteCode.created_at.desc()))
|
| 1501 |
invites = list(result.scalars().all())
|
| 1502 |
|
|
@@ -1519,6 +1527,7 @@ async def admin_invites(request: Request, db: AsyncSession = Depends(get_db)):
|
|
| 1519 |
resp = templates.TemplateResponse(request, "admin_invites.html", {
|
| 1520 |
"request": request,
|
| 1521 |
"title": "Invite codes",
|
|
|
|
| 1522 |
"invites": invites,
|
| 1523 |
"redemptions_by_code": redemptions_by_code,
|
| 1524 |
"invite_required": PANEL_CONFIG.require_invite,
|
|
@@ -1628,7 +1637,7 @@ async def admin_invites_delete(
|
|
| 1628 |
|
| 1629 |
@router.get("/admin/stats", response_class=HTMLResponse)
|
| 1630 |
async def admin_stats(request: Request, db: AsyncSession = Depends(get_db)):
|
| 1631 |
-
require_role(UserRole.ADMIN)(request)
|
| 1632 |
|
| 1633 |
snapshot = await collect_admin_stats(db)
|
| 1634 |
|
|
@@ -1652,6 +1661,7 @@ async def admin_stats(request: Request, db: AsyncSession = Depends(get_db)):
|
|
| 1652 |
resp = templates.TemplateResponse(request, "admin_stats.html", {
|
| 1653 |
"request": request,
|
| 1654 |
"title": gettext("System stats"),
|
|
|
|
| 1655 |
"host": snapshot["host"],
|
| 1656 |
"rows": rows,
|
| 1657 |
"totals": snapshot["totals"],
|
|
@@ -1904,6 +1914,7 @@ async def admin_secrets(
|
|
| 1904 |
resp = templates.TemplateResponse(request, "admin_secrets.html", {
|
| 1905 |
"request": request,
|
| 1906 |
"title": "Admin control panel — magic secrets",
|
|
|
|
| 1907 |
"rows": rows_decrypted,
|
| 1908 |
"q": q,
|
| 1909 |
})
|
|
@@ -2046,7 +2057,7 @@ async def admin_audit(
|
|
| 2046 |
q: Optional[str] = Query(None),
|
| 2047 |
db: AsyncSession = Depends(get_db),
|
| 2048 |
):
|
| 2049 |
-
require_role(UserRole.ADMIN)(request)
|
| 2050 |
|
| 2051 |
base = select(AuditLog)
|
| 2052 |
if user_id:
|
|
@@ -2087,6 +2098,7 @@ async def admin_audit(
|
|
| 2087 |
resp = templates.TemplateResponse(request, "admin_audit.html", {
|
| 2088 |
"request": request,
|
| 2089 |
"title": gettext("Audit Log"),
|
|
|
|
| 2090 |
"logs": logs,
|
| 2091 |
"actions_list": actions_list,
|
| 2092 |
"targets_list": targets_list,
|
|
@@ -3293,7 +3305,7 @@ async def admin_metrics(
|
|
| 3293 |
db: AsyncSession = Depends(get_db),
|
| 3294 |
):
|
| 3295 |
"""Analytics dashboard — proxy traffic and bot upload/download per hour."""
|
| 3296 |
-
require_role(UserRole.ADMIN)(request)
|
| 3297 |
|
| 3298 |
# Resolve the bot filter (admin-only — they see everything).
|
| 3299 |
bots = (await db.execute(
|
|
@@ -3306,6 +3318,7 @@ async def admin_metrics(
|
|
| 3306 |
resp = templates.TemplateResponse(request, "admin_metrics.html", {
|
| 3307 |
"request": request,
|
| 3308 |
"title": gettext("Metrics"),
|
|
|
|
| 3309 |
"hours": hours,
|
| 3310 |
"bot_id": bot_id,
|
| 3311 |
"bots": bots,
|
|
@@ -3449,7 +3462,7 @@ def _serialize_event(event: AlertEvent) -> dict[str, Any]:
|
|
| 3449 |
@router.get("/admin/alerts", response_class=HTMLResponse)
|
| 3450 |
async def admin_alerts(request: Request, db: AsyncSession = Depends(get_db)):
|
| 3451 |
"""Alert rules management page."""
|
| 3452 |
-
require_role(UserRole.ADMIN)(request)
|
| 3453 |
|
| 3454 |
rules = (await db.execute(
|
| 3455 |
select(AlertRule).order_by(AlertRule.created_at.desc())
|
|
@@ -3468,6 +3481,7 @@ async def admin_alerts(request: Request, db: AsyncSession = Depends(get_db)):
|
|
| 3468 |
resp = templates.TemplateResponse(request, "admin_alerts.html", {
|
| 3469 |
"request": request,
|
| 3470 |
"title": gettext("Alerts"),
|
|
|
|
| 3471 |
"rules": [_serialize_rule(r) for r in rules],
|
| 3472 |
"events": [_serialize_event(e) for e in events],
|
| 3473 |
"bots": bots,
|
|
|
|
| 5 |
import logging
|
| 6 |
import asyncio
|
| 7 |
from datetime import datetime, timedelta
|
| 8 |
+
from typing import Optional, Any
|
| 9 |
|
| 10 |
from fastapi import APIRouter, Depends, File, Form, Query, Request, HTTPException, status
|
| 11 |
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse, Response
|
|
|
|
| 178 |
async def _audit(
|
| 179 |
db: AsyncSession,
|
| 180 |
action: str,
|
| 181 |
+
user: Optional[Any] = None,
|
| 182 |
target_type: Optional[str] = None,
|
| 183 |
target_id: Optional[int] = None,
|
| 184 |
details: Optional[dict] = None,
|
| 185 |
ip: Optional[str] = None,
|
| 186 |
ua: Optional[str] = None,
|
| 187 |
) -> None:
|
| 188 |
+
# `user` may be either a ``models.User`` (with ``.id``) or a
|
| 189 |
+
# ``SessionUser`` dataclass (with ``.user_id``) depending on which
|
| 190 |
+
# auth path the caller went through. Resolve both transparently
|
| 191 |
+
# so callers don't have to care.
|
| 192 |
+
user_id = None
|
| 193 |
+
if user is not None:
|
| 194 |
+
user_id = getattr(user, "id", None) or getattr(user, "user_id", None)
|
| 195 |
log_entry = AuditLog(
|
| 196 |
+
user_id=user_id,
|
| 197 |
action=action,
|
| 198 |
target_type=target_type,
|
| 199 |
target_id=target_id,
|
|
|
|
| 1468 |
|
| 1469 |
@router.get("/admin/users", response_class=HTMLResponse)
|
| 1470 |
async def admin_users(request: Request, db: AsyncSession = Depends(get_db)):
|
| 1471 |
+
user = require_role(UserRole.ADMIN)(request)
|
| 1472 |
result = await db.execute(select(User).order_by(User.created_at.desc()))
|
| 1473 |
users = result.scalars().all()
|
| 1474 |
# Per-user usage — for the % utilisation columns. We do this as
|
|
|
|
| 1481 |
resp = templates.TemplateResponse(request, "admin_users.html", {
|
| 1482 |
"request": request,
|
| 1483 |
"title": "Users",
|
| 1484 |
+
"user": user,
|
| 1485 |
"users": users,
|
| 1486 |
"usage": usage,
|
| 1487 |
"presets": {
|
|
|
|
| 1504 |
|
| 1505 |
@router.get("/admin/invites", response_class=HTMLResponse)
|
| 1506 |
async def admin_invites(request: Request, db: AsyncSession = Depends(get_db)):
|
| 1507 |
+
user = require_role(UserRole.ADMIN)(request)
|
| 1508 |
result = await db.execute(select(InviteCode).order_by(InviteCode.created_at.desc()))
|
| 1509 |
invites = list(result.scalars().all())
|
| 1510 |
|
|
|
|
| 1527 |
resp = templates.TemplateResponse(request, "admin_invites.html", {
|
| 1528 |
"request": request,
|
| 1529 |
"title": "Invite codes",
|
| 1530 |
+
"user": user,
|
| 1531 |
"invites": invites,
|
| 1532 |
"redemptions_by_code": redemptions_by_code,
|
| 1533 |
"invite_required": PANEL_CONFIG.require_invite,
|
|
|
|
| 1637 |
|
| 1638 |
@router.get("/admin/stats", response_class=HTMLResponse)
|
| 1639 |
async def admin_stats(request: Request, db: AsyncSession = Depends(get_db)):
|
| 1640 |
+
user = require_role(UserRole.ADMIN)(request)
|
| 1641 |
|
| 1642 |
snapshot = await collect_admin_stats(db)
|
| 1643 |
|
|
|
|
| 1661 |
resp = templates.TemplateResponse(request, "admin_stats.html", {
|
| 1662 |
"request": request,
|
| 1663 |
"title": gettext("System stats"),
|
| 1664 |
+
"user": user,
|
| 1665 |
"host": snapshot["host"],
|
| 1666 |
"rows": rows,
|
| 1667 |
"totals": snapshot["totals"],
|
|
|
|
| 1914 |
resp = templates.TemplateResponse(request, "admin_secrets.html", {
|
| 1915 |
"request": request,
|
| 1916 |
"title": "Admin control panel — magic secrets",
|
| 1917 |
+
"user": user,
|
| 1918 |
"rows": rows_decrypted,
|
| 1919 |
"q": q,
|
| 1920 |
})
|
|
|
|
| 2057 |
q: Optional[str] = Query(None),
|
| 2058 |
db: AsyncSession = Depends(get_db),
|
| 2059 |
):
|
| 2060 |
+
user = require_role(UserRole.ADMIN)(request)
|
| 2061 |
|
| 2062 |
base = select(AuditLog)
|
| 2063 |
if user_id:
|
|
|
|
| 2098 |
resp = templates.TemplateResponse(request, "admin_audit.html", {
|
| 2099 |
"request": request,
|
| 2100 |
"title": gettext("Audit Log"),
|
| 2101 |
+
"user": user,
|
| 2102 |
"logs": logs,
|
| 2103 |
"actions_list": actions_list,
|
| 2104 |
"targets_list": targets_list,
|
|
|
|
| 3305 |
db: AsyncSession = Depends(get_db),
|
| 3306 |
):
|
| 3307 |
"""Analytics dashboard — proxy traffic and bot upload/download per hour."""
|
| 3308 |
+
user = require_role(UserRole.ADMIN)(request)
|
| 3309 |
|
| 3310 |
# Resolve the bot filter (admin-only — they see everything).
|
| 3311 |
bots = (await db.execute(
|
|
|
|
| 3318 |
resp = templates.TemplateResponse(request, "admin_metrics.html", {
|
| 3319 |
"request": request,
|
| 3320 |
"title": gettext("Metrics"),
|
| 3321 |
+
"user": user,
|
| 3322 |
"hours": hours,
|
| 3323 |
"bot_id": bot_id,
|
| 3324 |
"bots": bots,
|
|
|
|
| 3462 |
@router.get("/admin/alerts", response_class=HTMLResponse)
|
| 3463 |
async def admin_alerts(request: Request, db: AsyncSession = Depends(get_db)):
|
| 3464 |
"""Alert rules management page."""
|
| 3465 |
+
user = require_role(UserRole.ADMIN)(request)
|
| 3466 |
|
| 3467 |
rules = (await db.execute(
|
| 3468 |
select(AlertRule).order_by(AlertRule.created_at.desc())
|
|
|
|
| 3481 |
resp = templates.TemplateResponse(request, "admin_alerts.html", {
|
| 3482 |
"request": request,
|
| 3483 |
"title": gettext("Alerts"),
|
| 3484 |
+
"user": user,
|
| 3485 |
"rules": [_serialize_rule(r) for r in rules],
|
| 3486 |
"events": [_serialize_event(e) for e in events],
|
| 3487 |
"bots": bots,
|
templates_setup.py
CHANGED
|
@@ -109,3 +109,4 @@ templates.env.globals["csrf_token"] = _csrf_token
|
|
| 109 |
templates.env.globals["_"] = _gettext_context
|
| 110 |
templates.env.globals["ngettext"] = _ngettext_context
|
| 111 |
templates.env.globals["available_languages"] = available_languages
|
|
|
|
|
|
| 109 |
templates.env.globals["_"] = _gettext_context
|
| 110 |
templates.env.globals["ngettext"] = _ngettext_context
|
| 111 |
templates.env.globals["available_languages"] = available_languages
|
| 112 |
+
templates.env.globals["_detect_lang"] = _detect_lang
|