diff --git a/src/central/gui/__init__.py b/src/central/gui/__init__.py index 87f1269..20d79aa 100644 --- a/src/central/gui/__init__.py +++ b/src/central/gui/__init__.py @@ -136,6 +136,50 @@ def _create_app() -> FastAPI: # Include routes app.include_router(router) + # CSRF exception handler - return friendly error instead of 500 + from fastapi_csrf_protect.exceptions import CsrfProtectError + from fastapi.responses import RedirectResponse + + @app.exception_handler(CsrfProtectError) + async def csrf_exception_handler(request, exc: CsrfProtectError): + from fastapi_csrf_protect import CsrfProtect + + csrf_protect = CsrfProtect() + csrf_token, signed_token = csrf_protect.generate_csrf_tokens() + + if request.url.path == "/login": + response = templates.TemplateResponse( + request=request, + name="login.html", + context={"csrf_token": csrf_token, "error": "Your session expired. Please try again."}, + ) + csrf_protect.set_csrf_cookie(signed_token, response) + return response + elif request.url.path == "/setup": + response = templates.TemplateResponse( + request=request, + name="setup.html", + context={"csrf_token": csrf_token, "error": "Your session expired. Please try again."}, + ) + csrf_protect.set_csrf_cookie(signed_token, response) + return response + elif request.url.path == "/logout": + return RedirectResponse("/login", status_code=302) + elif request.url.path == "/change-password": + response = templates.TemplateResponse( + request=request, + name="change_password.html", + context={"csrf_token": csrf_token, "error": "Your session expired. Please try again."}, + ) + csrf_protect.set_csrf_cookie(signed_token, response) + return response + elif request.url.path.startswith("/adapters/"): + # Redirect back to adapters list + return RedirectResponse("/adapters", status_code=302) + else: + # Fallback: redirect to login + return RedirectResponse("/login", status_code=302) + return app diff --git a/src/central/gui/routes.py b/src/central/gui/routes.py index 755cb80..73543c1 100644 --- a/src/central/gui/routes.py +++ b/src/central/gui/routes.py @@ -182,6 +182,7 @@ async def dashboard_streams(request: Request) -> HTMLResponse: async def dashboard_polls(request: Request) -> HTMLResponse: """Get last poll times for each adapter.""" from central.gui.nats import get_js + from nats.js.errors import NotFoundError templates = _get_templates() pool = get_pool() @@ -210,43 +211,31 @@ async def dashboard_polls(request: Request) -> HTMLResponse: else: for name in adapter_names: try: - # Get last message from CENTRAL_META for this adapter - sub = await js.pull_subscribe( - f"central.meta.{name}.status", - durable=f"dashboard-poll-{name}", - stream="CENTRAL_META", + msg = await js.get_last_msg( + "CENTRAL_META", + f"central.meta.adapter.{name}.status", ) - try: - msgs = await sub.fetch(1, timeout=1.0) - if msgs: - data = json.loads(msgs[0].data.decode()) - last_poll = data.get("data", {}).get("time", "—") - adapters.append({ - "name": name, - "last_poll": last_poll, - "status": "✓", - "error": None, - }) - else: - adapters.append({ - "name": name, - "last_poll": None, - "status": None, - "error": None, - }) - except Exception: - adapters.append({ - "name": name, - "last_poll": None, - "status": None, - "error": None, - }) - except Exception: + data = json.loads(msg.data.decode()) + adapters.append({ + "name": name, + "last_poll": data.get("ts"), + "status": "✓" if data.get("ok") else "✗", + "error": data.get("error") if not data.get("ok") else None, + }) + except NotFoundError: + # No status message for this adapter yet adapters.append({ "name": name, "last_poll": None, "status": None, - "error": "unavailable", + "error": None, + }) + except Exception as e: + adapters.append({ + "name": name, + "last_poll": None, + "status": "?", + "error": str(e), }) return templates.TemplateResponse( diff --git a/src/central/gui/templates/change_password.html b/src/central/gui/templates/change_password.html index c353c60..ad91796 100644 --- a/src/central/gui/templates/change_password.html +++ b/src/central/gui/templates/change_password.html @@ -8,6 +8,10 @@
{{ error }}
+ {% endif %} +