From 88ab8db88d4a54df89815fb706fdaa6c6e287fa3 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Mon, 7 Sep 2026 20:29:33 -0400 Subject: [PATCH] fix(admin): /admin.html retombait sur la page principale (ROADMAP #71) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le menu Admin redirigeait vers /admin.html mais aucune route backend ne le servait : le catch-all SPA /{full_path:path} renvoyait index.html, d'où le retour à la page principale. - backend/main.py: route GET /admin.html (gate require_admin) déclarée avant le catch-all SPA + correction des imports JS /frontend/js -> /static/js - frontend/admin.html: chemins d'assets alignés sur le mount /static - tests/test_admin.py: 3 tests de régression (page servie admin, refus 401/403) --- backend/main.py | 13 +++++++++++++ frontend/admin.html | 4 ++-- tests/test_admin.py | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/backend/main.py b/backend/main.py index 85b1b96..250891b 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4675,6 +4675,19 @@ if FRONTEND_DIR.exists(): return HTMLResponse(content=poc_file.read_text(encoding="utf-8")) raise HTTPException(status_code=404, detail="Editor POC not found") + @app.get("/admin.html", response_class=HTMLResponse) + async def serve_admin_page(_current_user=Depends(require_admin)): + """Serve the admin dashboard page (ROADMAP #71) — admin-gated. + + Must be declared BEFORE the SPA catch-all ``/{full_path:path}`` or the + admin page would be shadowed by ``index.html`` (the reported bug: the + Admin menu kept returning to the main page). + """ + admin_file = FRONTEND_DIR / "admin.html" + if admin_file.exists(): + return HTMLResponse(content=admin_file.read_text(encoding="utf-8")) + raise HTTPException(status_code=404, detail="Admin page not found") + @app.get("/{full_path:path}") async def serve_spa(full_path: str): """Serve the SPA index.html for all non-API routes.""" diff --git a/frontend/admin.html b/frontend/admin.html index 694ff02..56f3e80 100644 --- a/frontend/admin.html +++ b/frontend/admin.html @@ -373,8 +373,8 @@ // We import admin.js which provides init() — but the i18n module // is global (window.t), so we can use t() right away to translate // static DOM. admin.js will then call init() after DOMContentLoaded. - import { initI18n } from "/frontend/js/i18n.js"; - import * as Admin from "/frontend/js/admin.js"; + import { initI18n } from "/static/js/i18n.js"; + import * as Admin from "/static/js/admin.js"; await initI18n(); // Re-apply DOM translations now that locale strings are loaded diff --git a/tests/test_admin.py b/tests/test_admin.py index 40fa780..1a8edca 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -302,4 +302,37 @@ class TestAdminStream: def test_stream_requires_admin(self, admin_client): resp = admin_client.get("/api/admin/stream") - assert resp.status_code in (401, 403) \ No newline at end of file + assert resp.status_code in (401, 403) + + +# ═══════════════════════════════════════════════════════════════ +# Admin dashboard PAGE (/admin.html) +# ═══════════════════════════════════════════════════════════════ +class TestAdminPage: + """Regression for ROADMAP #71 — Admin menu bounced back to the main page. + + Root cause: /admin.html had no explicit route, so the SPA catch-all + ``/{full_path:path}`` served index.html. Guard the fix. + """ + + def test_page_served_as_admin(self, admin_client): + token = _login(admin_client) + resp = admin_client.get("/admin.html", headers=_bearer(token)) + assert resp.status_code == 200 + body = resp.text + # Real admin page markers (NOT the main SPA index.html) + assert "admin-main" in body or "ObsiGate — Admin" in body or "admin.js" in body + # The regression: index.html markers must be absent + assert "boot-splash" not in body + # Asset paths must point to the actual static mount (/static), not /frontend + assert "/static/js/admin.js" in body + assert "/frontend/js/admin.js" not in body + + def test_page_requires_auth(self, admin_client): + resp = admin_client.get("/admin.html") + assert resp.status_code in (401, 403) + + def test_page_requires_admin_role(self, admin_client): + token = _login(admin_client, username="normaluser", password="normal123") + resp = admin_client.get("/admin.html", headers=_bearer(token)) + assert resp.status_code == 403 \ No newline at end of file