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); } +}