fix(admin): /admin.html retombait sur la page principale (ROADMAP #71)
CI / lint (push) Successful in 54s
CI / security (push) Successful in 26s
CI / test (push) Successful in 50s
CI / build (push) Successful in 23s
CI / e2e (push) Successful in 6m2s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

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)
This commit is contained in:
2026-09-07 20:29:33 -04:00
parent 11406034c9
commit 88ab8db88d
3 changed files with 49 additions and 3 deletions
+13
View File
@@ -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."""
+2 -2
View File
@@ -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
+34 -1
View File
@@ -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)
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