From 42c5e9dbc24e4eeacad4dbba0690fc0c94ebdcba Mon Sep 17 00:00:00 2001 From: Test User Date: Thu, 23 Jul 2026 18:27:13 +0200 Subject: [PATCH] Web client: add a top-bar log in / log out button Relying on the proactive prompt alone was not enough: it only fires on first connect, only when no credentials are stored, and only against an auth-enabled server, so a manual affordance was missing. The top bar now shows a "log in" button whenever the server reports auth is enabled (Init.auth_enabled, kept in a store signal), opening the same dismissible credentials dialog. Once credentials are stored it becomes "log out", which clears them and reloads to drop back to the guest role. The button is hidden on servers with no auth, where sending credentials would only earn an UNAUTHENTICATED lock-out. Co-Authored-By: Claude Opus 4.8 (1M context) --- cbd-web/src/app.rs | 55 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/cbd-web/src/app.rs b/cbd-web/src/app.rs index 61c6ff9..2db02bc 100644 --- a/cbd-web/src/app.rs +++ b/cbd-web/src/app.rs @@ -80,6 +80,9 @@ struct Store { /// Set once the proactive "auth is enabled" prompt has been shown, so /// stream reconnects do not re-open it after the user dismissed it. login_prompted: RwSignal, + /// Whether the server has auth configured (from `Init.auth_enabled`), + /// so the top bar can offer a login only where it is meaningful. + auth_enabled: RwSignal, queue: RwSignal>, queue_pos: RwSignal, resolving: RwSignal, @@ -110,6 +113,7 @@ impl Store { needs_login: RwSignal::new(false), guest_ok: RwSignal::new(false), login_prompted: RwSignal::new(false), + auth_enabled: RwSignal::new(false), queue: RwSignal::new(Vec::new()), queue_pos: RwSignal::new(0), resolving: RwSignal::new(false), @@ -530,10 +534,12 @@ fn run_stream(store: Store) { store.apply(StreamUpdate::Position(position)); } // We connected — as the unauthenticated fallback - // role if we sent no credentials. When the server - // has auth configured and the user has none stored, - // offer a login once, but let them dismiss it to - // stay on the fallback role. + // role if we sent no credentials. Record whether + // the server has auth at all (drives the top-bar + // login button), then, when it does and the user + // has none stored, offer a login once — dismissible + // so they can stay on the fallback role. + store.auth_enabled.set(init.auth_enabled); let no_stored_creds = load_pref("user").unwrap_or_default().is_empty(); if init.auth_enabled && no_stored_creds @@ -666,6 +672,47 @@ fn TopBar(store: Store) -> impl IntoView { {move || if store.connected.get() { "" } else { "disconnected — reconnecting…" }} + {move || { + // A login is only meaningful where the server has auth + // (offer "log in"); once credentials are stored, offer + // "log out" to drop back to the guest/fallback role. + let logged_in = !load_pref("user").unwrap_or_default().is_empty(); + (store.auth_enabled.get() || logged_in) + .then(|| { + if logged_in { + view! { + + } + .into_any() + } else { + view! { + + } + .into_any() + } + }) + }}