diff --git a/src/routes/api.py b/src/routes/api.py index 9830af9..276537f 100644 --- a/src/routes/api.py +++ b/src/routes/api.py @@ -137,6 +137,41 @@ async def auth_check(request: Request): return JSONResponse(content={"authenticated": False}, status_code=401) +# ── Protected Ban Management API ───────────────────────────────────── + + +class BanOverrideRequest(BaseModel): + ip: str + action: str # "ban", "unban", or "reset" + + +@router.post("/api/ban-override") +async def ban_override(request: Request, body: BanOverrideRequest): + if not verify_auth(request): + return JSONResponse(content={"error": "Unauthorized"}, status_code=401) + + db = get_db() + action_map = {"ban": True, "unban": False, "reset": None} + override_value = action_map.get(body.action) + if body.action not in action_map: + return JSONResponse( + content={"error": "Invalid action. Use: ban, unban, reset"}, + status_code=400, + ) + + if body.action == "ban": + success = db.force_ban_ip(body.ip) + else: + success = db.set_ban_override(body.ip, override_value) + + if success: + get_app_logger().info(f"Ban override: {body.action} on IP {body.ip}") + return JSONResponse( + content={"success": True, "ip": body.ip, "action": body.action} + ) + return JSONResponse(content={"error": "IP not found"}, status_code=404) + + @router.get("/api/all-ip-stats") async def all_ip_stats(request: Request): db = get_db() diff --git a/src/routes/htmx.py b/src/routes/htmx.py index 366c4a0..592b372 100644 --- a/src/routes/htmx.py +++ b/src/routes/htmx.py @@ -434,3 +434,56 @@ async def htmx_admin(request: Request): "dashboard_path": _dashboard_path(request), }, ) + + +# ── Ban Management HTMX Endpoints ─────────────────────────────────── + + +@router.get("/htmx/ban/attackers") +async def htmx_ban_attackers( + request: Request, + page: int = Query(1), + page_size: int = Query(25), +): + if not verify_auth(request): + return HTMLResponse( + "
Unauthorized
", status_code=200 + ) + + db = get_db() + result = db.get_attackers_paginated(page=max(1, page), page_size=page_size) + templates = get_templates() + return templates.TemplateResponse( + "dashboard/partials/ban_attackers_table.html", + { + "request": request, + "dashboard_path": _dashboard_path(request), + "items": result["attackers"], + "pagination": result["pagination"], + }, + ) + + +@router.get("/htmx/ban/overrides") +async def htmx_ban_overrides( + request: Request, + page: int = Query(1), + page_size: int = Query(25), +): + if not verify_auth(request): + return HTMLResponse( + "Unauthorized
", status_code=200 + ) + + db = get_db() + result = db.get_ban_overrides_paginated(page=max(1, page), page_size=page_size) + templates = get_templates() + return templates.TemplateResponse( + "dashboard/partials/ban_overrides_table.html", + { + "request": request, + "dashboard_path": _dashboard_path(request), + "items": result["overrides"], + "pagination": result["pagination"], + }, + ) diff --git a/src/templates/jinja2/base.html b/src/templates/jinja2/base.html index 22105c4..8359e42 100644 --- a/src/templates/jinja2/base.html +++ b/src/templates/jinja2/base.html @@ -8,6 +8,7 @@ + diff --git a/src/templates/jinja2/dashboard/index.html b/src/templates/jinja2/dashboard/index.html index 9a551dc..e3cc6c6 100644 --- a/src/templates/jinja2/dashboard/index.html +++ b/src/templates/jinja2/dashboard/index.html @@ -59,7 +59,7 @@ IP Insight - Admin + IP Banlist {# Lock icon (not authenticated) #}