feat: replace settings modal with full-page sidebar layout
Convert the users/admin modal into a dedicated settings page with left sidebar navigation and spacious content panels. Hash-based routing (#settings/users, #settings/smtp, etc.) enables browser back-button support and direct URL access. Admin-only tabs are hidden for non-admin users.
This commit is contained in:
+122
-2
@@ -22,11 +22,17 @@ body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#main-app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
#main-app[hidden] { display: none; }
|
||||
|
||||
/* ── Header ── */
|
||||
header {
|
||||
background: var(--header-bg);
|
||||
@@ -879,3 +885,117 @@ input[type="file"] {
|
||||
@media (max-width: 768px), (orientation: portrait) {
|
||||
#btn-layout-editor { display: none !important; }
|
||||
}
|
||||
|
||||
/* ── Settings Page ── */
|
||||
.settings-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.settings-page[hidden] { display: none; }
|
||||
|
||||
.settings-header {
|
||||
background: var(--header-bg);
|
||||
color: var(--header-fg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
height: 44px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.settings-header-left { display: flex; align-items: center; gap: 12px; }
|
||||
.settings-header-right { display: flex; align-items: center; gap: 10px; }
|
||||
.settings-back-link {
|
||||
color: rgba(255,255,255,0.7);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.settings-back-link:hover { color: #fff; }
|
||||
|
||||
.settings-layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-sidebar {
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
padding: 20px 0;
|
||||
border-right: 1px solid var(--border);
|
||||
overflow-y: auto;
|
||||
background: var(--surface);
|
||||
}
|
||||
.settings-sidebar-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
padding: 4px 20px 16px;
|
||||
color: var(--text);
|
||||
}
|
||||
.settings-nav-group { margin-bottom: 8px; }
|
||||
.settings-nav-label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-muted);
|
||||
padding: 12px 20px 6px;
|
||||
}
|
||||
.settings-nav-item {
|
||||
display: block;
|
||||
padding: 7px 20px;
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
margin: 1px 8px;
|
||||
transition: background 0.12s;
|
||||
}
|
||||
.settings-nav-item:hover { background: var(--bg); }
|
||||
.settings-nav-item.active {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
flex: 1;
|
||||
padding: 32px 48px;
|
||||
overflow-y: auto;
|
||||
max-width: 780px;
|
||||
}
|
||||
|
||||
.settings-panel h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
color: var(--text);
|
||||
}
|
||||
.settings-panel h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: var(--text);
|
||||
}
|
||||
.settings-desc {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.settings-section {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.settings-section .form-row { gap: 16px; }
|
||||
.settings-section .form-group { margin-bottom: 4px; }
|
||||
|
||||
+164
-121
@@ -87,6 +87,169 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings page (full-page, hidden by default) -->
|
||||
<div id="settings-page" class="settings-page" hidden>
|
||||
<header class="settings-header">
|
||||
<div class="settings-header-left">
|
||||
<a href="#" id="settings-back-link" class="settings-back-link">← Back</a>
|
||||
</div>
|
||||
<div class="settings-header-right">
|
||||
<span id="settings-username" class="header-username"></span>
|
||||
<button id="btn-settings-logout" class="btn-header-icon" title="Sign out">↩</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="settings-layout">
|
||||
<nav class="settings-sidebar">
|
||||
<div class="settings-sidebar-title">Settings</div>
|
||||
<div class="settings-nav-group" data-admin-only>
|
||||
<div class="settings-nav-label">Administration</div>
|
||||
<a href="#settings/users" class="settings-nav-item" data-settings-tab="users">Users</a>
|
||||
<a href="#settings/smtp" class="settings-nav-item" data-settings-tab="smtp">Email (SMTP)</a>
|
||||
</div>
|
||||
<div class="settings-nav-group">
|
||||
<div class="settings-nav-label">Account</div>
|
||||
<a href="#settings/password" class="settings-nav-item" data-settings-tab="password">Password</a>
|
||||
<a href="#settings/sso" class="settings-nav-item" data-settings-tab="sso">Single Sign-On</a>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="settings-content">
|
||||
|
||||
<!-- Users panel (admin only) -->
|
||||
<div id="settings-panel-users" class="settings-panel" hidden>
|
||||
<h2>Users</h2>
|
||||
<p class="settings-desc">Manage user accounts and access permissions.</p>
|
||||
<div class="settings-section">
|
||||
<div id="users-list"></div>
|
||||
</div>
|
||||
<div class="settings-section" id="user-form-section">
|
||||
<h3 id="user-form-title">Add User</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group required">
|
||||
<label for="uf-username">Username</label>
|
||||
<input type="text" id="uf-username" autocapitalize="none">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uf-email">Email <span class="field-hint">(for password reset)</span></label>
|
||||
<input type="email" id="uf-email" autocomplete="email">
|
||||
</div>
|
||||
<div class="form-group required">
|
||||
<label for="uf-password">Password <span class="field-hint" id="uf-password-hint">(min 10 chars, include a digit or symbol)</span></label>
|
||||
<input type="password" id="uf-password" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-group required">
|
||||
<label for="uf-role">Role</label>
|
||||
<select id="uf-role">
|
||||
<option value="viewer">Viewer</option>
|
||||
<option value="editor">Editor</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="uf-accounts-group">
|
||||
<label>Account Access <span class="field-hint">(admins see all — no selection needed)</span></label>
|
||||
<div id="uf-accounts-checkboxes" class="account-checkboxes"></div>
|
||||
</div>
|
||||
<div id="uf-oidc-group" class="form-row" hidden>
|
||||
<div class="form-group">
|
||||
<label for="uf-oidc-sub">OIDC Subject <span class="field-hint">(sub claim from provider)</span></label>
|
||||
<input type="text" id="uf-oidc-sub" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uf-oidc-issuer">OIDC Issuer <span class="field-hint">(provider URL)</span></label>
|
||||
<input type="text" id="uf-oidc-issuer" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-form-error" class="wizard-error" hidden></div>
|
||||
<div style="display:flex;gap:8px;margin-top:12px">
|
||||
<button id="btn-save-user" class="btn-primary">Add User</button>
|
||||
<button id="btn-cancel-user-edit" class="btn-ghost" hidden>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SMTP panel (admin only) -->
|
||||
<div id="settings-panel-smtp" class="settings-panel" hidden>
|
||||
<h2>Email Settings</h2>
|
||||
<p class="settings-desc">Configure SMTP for password reset emails.</p>
|
||||
<div class="settings-section" id="smtp-settings-section">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="smtp-host">SMTP Host</label>
|
||||
<input type="text" id="smtp-host" placeholder="smtp.example.com">
|
||||
</div>
|
||||
<div class="form-group" style="max-width:100px">
|
||||
<label for="smtp-port">Port</label>
|
||||
<input type="number" id="smtp-port" value="587" min="1" max="65535">
|
||||
</div>
|
||||
<div class="form-group" style="max-width:160px">
|
||||
<label for="smtp-secure">Encryption</label>
|
||||
<select id="smtp-secure">
|
||||
<option value="0">STARTTLS (587)</option>
|
||||
<option value="1">SSL/TLS (465)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="smtp-user">Username</label>
|
||||
<input type="text" id="smtp-user" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="smtp-pass">Password <span class="field-hint" id="smtp-pass-hint"></span></label>
|
||||
<input type="password" id="smtp-pass" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="smtp-from">From Address</label>
|
||||
<input type="email" id="smtp-from" placeholder="ezcheck@example.com">
|
||||
</div>
|
||||
</div>
|
||||
<div id="smtp-error" class="wizard-error" hidden></div>
|
||||
<div id="smtp-success" class="import-result" hidden></div>
|
||||
<button id="btn-save-smtp" class="btn-secondary" style="margin-top:12px">Save Email Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password panel (all users) -->
|
||||
<div id="settings-panel-password" class="settings-panel" hidden>
|
||||
<h2>Change Password</h2>
|
||||
<p class="settings-desc">Update your login password.</p>
|
||||
<div class="settings-section">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="cp-current">Current Password</label>
|
||||
<input type="password" id="cp-current" autocomplete="current-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cp-new">New Password <span class="field-hint">(min 10 chars, include a digit or symbol)</span></label>
|
||||
<input type="password" id="cp-new" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cp-confirm">Confirm New</label>
|
||||
<input type="password" id="cp-confirm" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
<div id="cp-error" class="wizard-error" hidden></div>
|
||||
<div id="cp-success" class="import-result" hidden>Password changed.</div>
|
||||
<button id="btn-change-password" class="btn-secondary" style="margin-top:12px">Change Password</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSO panel (all users, shown when OIDC enabled) -->
|
||||
<div id="settings-panel-sso" class="settings-panel" hidden>
|
||||
<h2>Single Sign-On</h2>
|
||||
<p class="settings-desc">Link your account to an external identity provider.</p>
|
||||
<div class="settings-section" id="oidc-link-section">
|
||||
<p id="oidc-link-status" class="settings-desc" style="margin-bottom:12px"></p>
|
||||
<a id="btn-oidc-link" href="/api/auth/oidc/link" class="btn-secondary" style="display:inline-block;text-decoration:none">Link My Account</a>
|
||||
<button id="btn-oidc-unlink" class="btn-ghost" style="margin-left:8px" hidden>Unlink</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-app">
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<span class="header-brand" id="company-name">ezcheck</span>
|
||||
@@ -657,128 +820,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div><!-- /main-app -->
|
||||
|
||||
<!-- User management modal (admin only) -->
|
||||
<div id="users-overlay" class="modal-overlay"></div>
|
||||
<div id="users-modal" class="modal modal-wide" role="dialog" aria-labelledby="users-title">
|
||||
<div class="modal-header">
|
||||
<h2 id="users-title">Manage Users</h2>
|
||||
<button id="btn-close-users" class="btn-icon" title="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="users-list"></div>
|
||||
<div id="user-form-section" style="margin-top:16px;border-top:1px solid var(--border);padding-top:16px">
|
||||
<h3 style="font-size:13px;font-weight:600;margin-bottom:10px" id="user-form-title">Add User</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group required">
|
||||
<label for="uf-username">Username</label>
|
||||
<input type="text" id="uf-username" autocapitalize="none">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uf-email">Email <span class="field-hint">(for password reset)</span></label>
|
||||
<input type="email" id="uf-email" autocomplete="email">
|
||||
</div>
|
||||
<div class="form-group required">
|
||||
<label for="uf-password">Password <span class="field-hint" id="uf-password-hint">(min 10 chars, include a digit or symbol)</span></label>
|
||||
<input type="password" id="uf-password" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-group required">
|
||||
<label for="uf-role">Role</label>
|
||||
<select id="uf-role">
|
||||
<option value="viewer">Viewer</option>
|
||||
<option value="editor">Editor</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="uf-accounts-group">
|
||||
<label>Account Access <span class="field-hint">(admins see all — no selection needed)</span></label>
|
||||
<div id="uf-accounts-checkboxes" class="account-checkboxes"></div>
|
||||
</div>
|
||||
<div id="uf-oidc-group" class="form-row" hidden>
|
||||
<div class="form-group">
|
||||
<label for="uf-oidc-sub">OIDC Subject <span class="field-hint">(sub claim from provider)</span></label>
|
||||
<input type="text" id="uf-oidc-sub" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uf-oidc-issuer">OIDC Issuer <span class="field-hint">(provider URL)</span></label>
|
||||
<input type="text" id="uf-oidc-issuer" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-form-error" class="wizard-error" hidden></div>
|
||||
<div style="display:flex;gap:8px;margin-top:8px">
|
||||
<button id="btn-save-user" class="btn-primary">Add User</button>
|
||||
<button id="btn-cancel-user-edit" class="btn-ghost" hidden>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- SMTP settings (admin only) -->
|
||||
<div id="smtp-settings-section" style="margin-top:16px;border-top:1px solid var(--border);padding-top:16px">
|
||||
<h3 style="font-size:13px;font-weight:600;margin-bottom:10px">Email Settings (SMTP)</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="smtp-host">SMTP Host</label>
|
||||
<input type="text" id="smtp-host" placeholder="smtp.example.com">
|
||||
</div>
|
||||
<div class="form-group" style="max-width:90px">
|
||||
<label for="smtp-port">Port</label>
|
||||
<input type="number" id="smtp-port" value="587" min="1" max="65535">
|
||||
</div>
|
||||
<div class="form-group" style="max-width:140px">
|
||||
<label for="smtp-secure">Encryption</label>
|
||||
<select id="smtp-secure">
|
||||
<option value="0">STARTTLS (587)</option>
|
||||
<option value="1">SSL/TLS (465)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="smtp-user">Username</label>
|
||||
<input type="text" id="smtp-user" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="smtp-pass">Password <span class="field-hint" id="smtp-pass-hint"></span></label>
|
||||
<input type="password" id="smtp-pass" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="smtp-from">From Address</label>
|
||||
<input type="email" id="smtp-from" placeholder="ezcheck@example.com">
|
||||
</div>
|
||||
</div>
|
||||
<div id="smtp-error" class="wizard-error" hidden></div>
|
||||
<div id="smtp-success" class="import-result" hidden></div>
|
||||
<button id="btn-save-smtp" class="btn-secondary" style="margin-top:8px">Save Email Settings</button>
|
||||
</div>
|
||||
<!-- Change own password -->
|
||||
<div style="margin-top:16px;border-top:1px solid var(--border);padding-top:16px">
|
||||
<h3 style="font-size:13px;font-weight:600;margin-bottom:10px">Change My Password</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="cp-current">Current Password</label>
|
||||
<input type="password" id="cp-current" autocomplete="current-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cp-new">New Password <span class="field-hint">(min 10 chars, include a digit or symbol)</span></label>
|
||||
<input type="password" id="cp-new" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cp-confirm">Confirm New</label>
|
||||
<input type="password" id="cp-confirm" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
<div id="cp-error" class="wizard-error" hidden></div>
|
||||
<div id="cp-success" class="import-result" hidden>Password changed.</div>
|
||||
<button id="btn-change-password" class="btn-secondary" style="margin-top:8px">Change Password</button>
|
||||
</div>
|
||||
<!-- Link my OIDC identity (self-service, shown when OIDC is enabled) -->
|
||||
<div id="oidc-link-section" style="margin-top:16px;border-top:1px solid var(--border);padding-top:16px" hidden>
|
||||
<h3 style="font-size:13px;font-weight:600;margin-bottom:10px">Single Sign-On</h3>
|
||||
<p id="oidc-link-status" style="font-size:12px;color:var(--text-muted);margin-bottom:8px"></p>
|
||||
<a id="btn-oidc-link" href="/api/auth/oidc/link" class="btn-secondary" style="display:inline-block;text-decoration:none">Link My Account</a>
|
||||
<button id="btn-oidc-unlink" class="btn-ghost" style="margin-left:8px" hidden>Unlink</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layout Editor Modal -->
|
||||
<div id="layout-editor-overlay" class="modal-overlay"></div>
|
||||
|
||||
+89
-29
@@ -65,7 +65,8 @@ async function checkAuth() {
|
||||
return false;
|
||||
}
|
||||
if (location.hash === '#oidc-linked') {
|
||||
history.replaceState(null, '', location.pathname);
|
||||
// After OIDC link callback, navigate to SSO settings panel
|
||||
location.hash = '#settings/sso';
|
||||
// Fall through to normal auth check — user is still logged in
|
||||
}
|
||||
|
||||
@@ -75,6 +76,8 @@ async function checkAuth() {
|
||||
state.user = await res.json();
|
||||
hideLoginOverlay();
|
||||
applyRoleUI();
|
||||
// Route to settings page if hash says so
|
||||
if (location.hash.startsWith('#settings')) handleHashRoute();
|
||||
return true;
|
||||
}
|
||||
// No session — check if this is first-run (no users at all)
|
||||
@@ -165,6 +168,9 @@ async function logout() {
|
||||
document.getElementById('login-error').hidden = true;
|
||||
document.getElementById('login-setup-section').hidden = true;
|
||||
document.getElementById('login-form-section').hidden = false;
|
||||
// Ensure settings page is hidden and main app restored
|
||||
document.getElementById('settings-page').hidden = true;
|
||||
document.getElementById('main-app').hidden = false;
|
||||
showLoginOverlay();
|
||||
}
|
||||
|
||||
@@ -176,8 +182,9 @@ function applyRoleUI() {
|
||||
const isEditor = state.accountRole === 'editor' || (!state.accountRole && (role === 'admin' || role === 'editor'));
|
||||
|
||||
document.getElementById('header-username').textContent = state.user ? state.user.username : '';
|
||||
document.getElementById('settings-username').textContent = state.user ? state.user.username : '';
|
||||
|
||||
// Admin-only elements
|
||||
// Admin-only elements (main app + settings sidebar)
|
||||
document.querySelectorAll('[data-admin-only]').forEach(el => { el.hidden = !isAdmin; });
|
||||
|
||||
// Editor+ elements (hide for viewers)
|
||||
@@ -191,28 +198,62 @@ function applyRoleUI() {
|
||||
|
||||
let usersState = { users: [], editingId: null };
|
||||
|
||||
function openUsersModal() {
|
||||
// ── Settings page navigation ────────────────────────────────────────────────
|
||||
|
||||
function navigateToSettings(tab) {
|
||||
const isAdmin = state.user && state.user.role === 'admin';
|
||||
document.getElementById('user-form-error').hidden = true;
|
||||
document.getElementById('users-title').textContent = isAdmin ? 'Manage Users' : 'My Account';
|
||||
document.getElementById('users-overlay').classList.add('open');
|
||||
document.getElementById('users-modal').classList.add('open');
|
||||
// Admin-only sections
|
||||
document.getElementById('users-list').hidden = !isAdmin;
|
||||
document.getElementById('user-form-section').hidden = !isAdmin;
|
||||
document.getElementById('smtp-settings-section').hidden = !isAdmin;
|
||||
if (isAdmin) {
|
||||
loadUsers();
|
||||
renderUfAccountCheckboxes();
|
||||
loadSmtpSettings();
|
||||
}
|
||||
loadOidcLinkStatus();
|
||||
const defaultTab = isAdmin ? 'users' : 'password';
|
||||
const resolved = tab || defaultTab;
|
||||
|
||||
// Guard non-admin from admin tabs
|
||||
if (!isAdmin && (resolved === 'users' || resolved === 'smtp')) {
|
||||
location.hash = '#settings/password';
|
||||
return;
|
||||
}
|
||||
|
||||
function closeUsersModal() {
|
||||
document.getElementById('users-overlay').classList.remove('open');
|
||||
document.getElementById('users-modal').classList.remove('open');
|
||||
cancelUserEdit();
|
||||
document.getElementById('main-app').hidden = true;
|
||||
const sp = document.getElementById('settings-page');
|
||||
sp.hidden = false;
|
||||
document.getElementById('settings-username').textContent = state.user ? state.user.username : '';
|
||||
|
||||
// Check OIDC status to show/hide SSO tab
|
||||
loadOidcLinkStatus();
|
||||
|
||||
activateSettingsTab(resolved);
|
||||
}
|
||||
|
||||
function activateSettingsTab(tab) {
|
||||
// Hide all panels, show the target
|
||||
document.querySelectorAll('.settings-panel').forEach(p => { p.hidden = true; });
|
||||
const panel = document.getElementById('settings-panel-' + tab);
|
||||
if (panel) panel.hidden = false;
|
||||
|
||||
// Update sidebar active state
|
||||
document.querySelectorAll('.settings-nav-item').forEach(a => {
|
||||
a.classList.toggle('active', a.dataset.settingsTab === tab);
|
||||
});
|
||||
|
||||
// Load data for the activated tab
|
||||
if (tab === 'users') { loadUsers(); renderUfAccountCheckboxes(); }
|
||||
if (tab === 'smtp') { loadSmtpSettings(); }
|
||||
if (tab === 'sso') { loadOidcLinkStatus(); }
|
||||
}
|
||||
|
||||
function showMainApp() {
|
||||
document.getElementById('settings-page').hidden = true;
|
||||
document.getElementById('main-app').hidden = false;
|
||||
}
|
||||
|
||||
function handleHashRoute() {
|
||||
const hash = location.hash;
|
||||
if (hash.startsWith('#settings/')) {
|
||||
const tab = hash.split('/')[1];
|
||||
navigateToSettings(tab);
|
||||
} else if (hash.startsWith('#settings')) {
|
||||
navigateToSettings();
|
||||
} else {
|
||||
showMainApp();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
@@ -1664,9 +1705,12 @@ async function saveSmtpSettings() {
|
||||
async function loadOidcLinkStatus() {
|
||||
try {
|
||||
const cfg = await fetch('/api/auth/oidc/config').then(r => r.json());
|
||||
const section = document.getElementById('oidc-link-section');
|
||||
if (!cfg.enabled) { section.hidden = true; return; }
|
||||
section.hidden = false;
|
||||
const ssoNavItem = document.querySelector('[data-settings-tab="sso"]');
|
||||
if (!cfg.enabled) {
|
||||
if (ssoNavItem) ssoNavItem.hidden = true;
|
||||
return;
|
||||
}
|
||||
if (ssoNavItem) ssoNavItem.hidden = false;
|
||||
|
||||
const me = await apiFetch('GET', '/api/auth/me');
|
||||
const statusEl = document.getElementById('oidc-link-status');
|
||||
@@ -1886,11 +1930,27 @@ async function init() {
|
||||
document.getElementById('btn-reset-submit').addEventListener('click', submitResetPassword);
|
||||
document.getElementById('reset-password2').addEventListener('keydown', e => { if (e.key === 'Enter') submitResetPassword(); });
|
||||
|
||||
// User management
|
||||
document.getElementById('btn-users').addEventListener('click', openUsersModal);
|
||||
document.getElementById('header-username').addEventListener('click', openUsersModal);
|
||||
document.getElementById('btn-close-users').addEventListener('click', closeUsersModal);
|
||||
document.getElementById('users-overlay').addEventListener('click', closeUsersModal);
|
||||
// User management / settings page
|
||||
document.getElementById('btn-users').addEventListener('click', () => { location.hash = '#settings/users'; });
|
||||
document.getElementById('header-username').addEventListener('click', () => {
|
||||
const isAdmin = state.user && state.user.role === 'admin';
|
||||
location.hash = isAdmin ? '#settings/users' : '#settings/password';
|
||||
});
|
||||
document.getElementById('settings-back-link').addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
location.hash = '';
|
||||
});
|
||||
document.getElementById('btn-settings-logout').addEventListener('click', () => {
|
||||
location.hash = '';
|
||||
logout();
|
||||
});
|
||||
// Sidebar tab navigation
|
||||
document.querySelectorAll('.settings-nav-item').forEach(a => {
|
||||
a.addEventListener('click', e => { e.preventDefault(); location.hash = a.getAttribute('href'); });
|
||||
});
|
||||
window.addEventListener('hashchange', () => {
|
||||
if (state.user) handleHashRoute();
|
||||
});
|
||||
document.getElementById('users-list').addEventListener('click', e => {
|
||||
const editBtn = e.target.closest('.user-btn-edit');
|
||||
const deleteBtn = e.target.closest('.user-btn-delete');
|
||||
|
||||
Reference in New Issue
Block a user