From d9cda0c31c5458afb4eadb08921c43a494b0530d Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Thu, 11 Jun 2026 21:10:02 -0700 Subject: [PATCH] Manage Profiles: make the login-password state visible (clarity) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-member login password was easy to miss — the lock button had no dedicated styling and nothing showed who was actually stranded. Now, when login mode is on: - a banner explains every member needs a login password (+ to use the lock button) - each member row shows a status pill: "⚠ No login password" (red) or "🔒 Login ready" (green) — so you can see at a glance who can't sign in yet - the lock button is properly styled (it had none) and pulses red when that member has no password, so the action you need is obvious When login mode is off, none of this shows (no noise). Pure UI/clarity — no behavior change. --- webui/static/init.js | 26 +++++++++++++++++++- webui/static/style.css | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/webui/static/init.js b/webui/static/init.js index e5855d2d..d339cb18 100644 --- a/webui/static/init.js +++ b/webui/static/init.js @@ -1873,6 +1873,22 @@ async function loadProfileManageList() { const data = await res.json(); const profiles = data.profiles || []; + // Login-mode aware: when it's on, surface which members can't sign in yet + // (no login password) so the lock button's purpose is obvious. + let loginMode = false; + try { loginMode = !!(await (await fetch('/api/profiles/current')).json()).login_mode; } catch (e) { /* ignore */ } + + // Banner when login mode is on (explains the password requirement up front). + const banner = document.getElementById('profile-manage-login-banner'); + if (banner) banner.remove(); + if (loginMode) { + const b = document.createElement('div'); + b.id = 'profile-manage-login-banner'; + b.className = 'profile-manage-login-banner'; + b.textContent = '🔐 Login mode is on — every member needs a login password to sign in. Use the lock button to set one.'; + list.parentNode.insertBefore(b, list); + } + list.innerHTML = ''; profiles.forEach(p => { const item = document.createElement('div'); @@ -1896,6 +1912,12 @@ async function loadProfileManageList() { if (p.is_admin) pills.push({ text: 'Admin', cls: 'profile-role-pill--admin' }); if (p.can_download === false) pills.push({ text: 'No Downloads', cls: '' }); if (p.allowed_pages) pills.push({ text: `${p.allowed_pages.length} pages`, cls: '' }); + // Login-password status (only meaningful while login mode is on). + if (loginMode && !p.is_admin) { + pills.push(p.has_password + ? { text: '🔒 Login ready', cls: 'profile-role-pill--ok' } + : { text: '⚠ No login password', cls: 'profile-role-pill--warn' }); + } if (pills.length) { const roleDiv = document.createElement('div'); roleDiv.className = 'role'; @@ -1931,7 +1953,9 @@ async function loadProfileManageList() { // used when "Require login" is on). A member with no password can't // sign in and can't self-bootstrap one, so the admin sets it here. const pwBtn = document.createElement('button'); - pwBtn.className = 'profile-password-btn' + (p.has_password ? ' has-password' : ''); + // Pulse the button when login's on and this member can't sign in yet. + const needsPw = loginMode && !p.has_password; + pwBtn.className = 'profile-password-btn' + (p.has_password ? ' has-password' : '') + (needsPw ? ' needs-password' : ''); pwBtn.dataset.id = p.id; pwBtn.dataset.name = p.name; pwBtn.dataset.hasPassword = p.has_password ? '1' : '0'; diff --git a/webui/static/style.css b/webui/static/style.css index 954ff067..94c1f058 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -68187,3 +68187,59 @@ body.em-scroll-lock { overflow: hidden; } body.app-locked > *:not(#launch-pin-overlay):not(#login-overlay):not(script):not(style):not(noscript) { display: none !important; } + + +/* Login-password clarity in Manage Profiles (#login-mode) */ +.profile-manage-login-banner { + margin: 0 0 12px; + padding: 9px 12px; + border-radius: 10px; + font-size: 12px; + line-height: 1.4; + color: #fcd9a8; + background: rgba(245, 158, 11, 0.10); + border: 1px solid rgba(245, 158, 11, 0.30); +} +.profile-role-pill--warn { + background: rgba(239, 68, 68, 0.16); + color: #fca5a5; + border-color: rgba(239, 68, 68, 0.40); + text-transform: none; +} +.profile-role-pill--ok { + background: rgba(16, 185, 129, 0.14); + color: #6ee7b7; + border-color: rgba(16, 185, 129, 0.30); + text-transform: none; +} +/* The set-login-password button (lock icon) + an attention pulse when a member + can't sign in yet (login on, no password). */ +.profile-manage-item .profile-password-btn { + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.07); + color: #9ca3af; + border-radius: 8px; + padding: 6px; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all .15s ease; +} +.profile-manage-item .profile-password-btn svg { width: 15px; height: 15px; } +.profile-manage-item .profile-password-btn:hover { + background: rgba(99, 102, 241, 0.18); + border-color: rgba(99, 102, 241, 0.4); + color: #c7d2fe; +} +.profile-manage-item .profile-password-btn.has-password { color: #6ee7b7; } +.profile-manage-item .profile-password-btn.needs-password { + color: #fca5a5; + border-color: rgba(239, 68, 68, 0.45); + background: rgba(239, 68, 68, 0.12); + animation: arecPwPulse 1.8s ease-in-out infinite; +} +@keyframes arecPwPulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.0); } + 50% { box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.18); } +}