diff --git a/hermes_cli/dashboard_auth/login_page.py b/hermes_cli/dashboard_auth/login_page.py index 2baab0fdcc6..74da4dbe2f0 100644 --- a/hermes_cli/dashboard_auth/login_page.py +++ b/hermes_cli/dashboard_auth/login_page.py @@ -3,6 +3,21 @@ No React, no JavaScript dependency. Listed providers come from the registry; clicking a provider sends a GET to ``/auth/login?provider=``. + +Visual styling mirrors the Nous Research design system (the +``@nous-research/ui`` package the React dashboard uses): the same +``Collapse`` / ``Rules Compressed`` typeface, amber-on-dark colour +tokens (``#170d02`` / ``#ffac02`` / ``#fff``), uppercase + wide-tracking +brand chrome, and the inset-bevel button shadow. Fonts are served +out of the SPA's ``/fonts/`` directory which the dashboard-auth gate +already allowlists pre-auth (see ``_GATE_PUBLIC_PREFIXES`` in +``middleware.py``), so the page renders without needing the React +bundle loaded. + +Test-stable class names: the existing test suite extracts the +``class="provider-btn"`` anchor href to walk the OAuth flow. That +class name MUST NOT change without updating +``tests/hermes_cli/test_dashboard_auth_401_reauth.py``. """ from __future__ import annotations @@ -14,6 +29,9 @@ from hermes_cli.dashboard_auth import list_providers # bundle, which we deliberately do NOT load here — the login page must # not depend on the SPA build being present or on the injected session # token. +# +# Single curly braces are placeholders for ``str.format``; CSS curlies +# are doubled (``{{`` / ``}}``). _LOGIN_HTML_TEMPLATE = """\ @@ -22,49 +40,229 @@ _LOGIN_HTML_TEMPLATE = """\ Sign in — Hermes Agent
-

Sign in to Hermes Agent

-

Choose a sign-in method to continue.

-
+
NousResearch
+
+

Sign in

+

Choose a sign-in method to continue to the Hermes Agent dashboard.

+
{provider_buttons} +
-
This dashboard is bound to a non-loopback host.
- Sign-in is required for security.
+
+ Public bind · Auth required +
@@ -75,16 +273,80 @@ _EMPTY_HTML = """\ + Sign-in unavailable — Hermes Agent + -
+ +

Sign-in unavailable

This dashboard is bound to a non-loopback host but no authentication providers are installed.

Install plugins/dashboard-auth-nous (default) or another auth provider, or restart with --insecure to bypass the auth gate (not recommended on untrusted networks).

-
+
+ + """ @@ -115,7 +377,7 @@ def render_login_html(*, next_path: str = "") -> str: buttons = [] for p in providers: buttons.append( - f' ' f'Sign in with {html.escape(p.display_name)}' )