settings: move Users + Audit under Settings tabs
This commit is contained in:
parent
2923f350f8
commit
3f225660c8
@ -35,8 +35,9 @@ window.__authReady = (async function authGate() {
|
||||
function applyAuthUi(me) {
|
||||
renderUserBadge(me);
|
||||
if (me.role !== 'admin') {
|
||||
const usersLink = document.querySelector('[data-route="users"]');
|
||||
if (usersLink) usersLink.style.display = 'none';
|
||||
document.querySelectorAll('[data-admin-only]').forEach(function (el) {
|
||||
el.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +76,6 @@ function renderUserBadge(me) {
|
||||
'scan-mailbox': 'New Mailbox Scan',
|
||||
'scan-entra': 'New Entra Group Scan',
|
||||
'tenants': 'Tenants',
|
||||
'users': 'Users',
|
||||
'settings': 'Settings',
|
||||
};
|
||||
|
||||
@ -1620,17 +1620,13 @@ function renderUserBadge(me) {
|
||||
// Users + Audit admin view
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
async function renderUsersView() {
|
||||
const root = document.getElementById('usersViewRoot');
|
||||
if (!root) return;
|
||||
root.innerHTML = '<section class="users-view"><h2>Users</h2>' +
|
||||
'<div class="users-toolbar"><button id="newUserBtn" class="btn btn-primary">New user</button></div>' +
|
||||
'<div id="usersTable"></div>' +
|
||||
'<h3 style="margin-top:24px">Audit log</h3>' +
|
||||
'<div id="auditTable"></div></section>';
|
||||
document.getElementById('newUserBtn').addEventListener('click', function () { openUserModal(null); });
|
||||
await reloadUsersTable();
|
||||
await reloadAuditTable();
|
||||
let usersTabWired = false;
|
||||
function wireUsersTabOnce() {
|
||||
if (usersTabWired) return;
|
||||
const newBtn = document.getElementById('newUserBtn');
|
||||
if (!newBtn) return;
|
||||
newBtn.addEventListener('click', function () { openUserModal(null); });
|
||||
usersTabWired = true;
|
||||
}
|
||||
|
||||
async function reloadUsersTable() {
|
||||
@ -1795,20 +1791,23 @@ function renderUserBadge(me) {
|
||||
// Hash router
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const SETTINGS_TABS = { 'general': 1, 'users': 1, 'audit': 1 };
|
||||
|
||||
function parseRoute() {
|
||||
var hash = (window.location.hash || '').replace(/^#\/?/, '');
|
||||
if (!hash) return 'dashboard';
|
||||
if (hash.indexOf('/') !== -1) {
|
||||
if (!hash) return { route: 'dashboard', sub: null };
|
||||
var parts = hash.split('/');
|
||||
if (parts[0] === 'scan' && parts[1]) return 'scan-' + parts[1];
|
||||
return parts[0];
|
||||
}
|
||||
return hash;
|
||||
if (parts[0] === 'scan' && parts[1]) return { route: 'scan-' + parts[1], sub: null };
|
||||
if (parts[0] === 'settings') return { route: 'settings', sub: parts[1] || null };
|
||||
return { route: parts[0], sub: null };
|
||||
}
|
||||
|
||||
function applyRoute(route, moveFocus) {
|
||||
function applyRoute(parsed, moveFocus) {
|
||||
var route = parsed && parsed.route;
|
||||
var sub = parsed && parsed.sub;
|
||||
if (!ROUTE_TITLES[route]) {
|
||||
route = 'dashboard';
|
||||
sub = null;
|
||||
}
|
||||
state.currentRoute = route;
|
||||
var activePage = null;
|
||||
@ -1831,8 +1830,11 @@ function renderUserBadge(me) {
|
||||
els.contentTitle.textContent = ROUTE_TITLES[route];
|
||||
}
|
||||
document.title = 'Clearview | ' + ROUTE_TITLES[route];
|
||||
if (route === 'users') {
|
||||
renderUsersView().catch(function (err) { console.error('Users view failed', err); });
|
||||
if (route === 'settings') {
|
||||
if (!SETTINGS_TABS[sub]) sub = 'general';
|
||||
var isAdmin = window.__clearviewUser && window.__clearviewUser.role === 'admin';
|
||||
if ((sub === 'users' || sub === 'audit') && !isAdmin) sub = 'general';
|
||||
applySettingsTab(sub);
|
||||
}
|
||||
// On user navigation, move focus to the new page's first heading so
|
||||
// screen-reader and keyboard users land in the freshly shown content.
|
||||
@ -1845,6 +1847,31 @@ function renderUserBadge(me) {
|
||||
}
|
||||
}
|
||||
|
||||
function applySettingsTab(sub) {
|
||||
document.querySelectorAll('.settings-tab').forEach(function (el) {
|
||||
if (el.getAttribute('data-settings-tab') === sub) {
|
||||
el.classList.add('active');
|
||||
el.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
el.classList.remove('active');
|
||||
el.setAttribute('aria-selected', 'false');
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('.settings-pane').forEach(function (el) {
|
||||
if (el.getAttribute('data-settings-pane') === sub) {
|
||||
el.removeAttribute('hidden');
|
||||
} else {
|
||||
el.setAttribute('hidden', '');
|
||||
}
|
||||
});
|
||||
if (sub === 'users') {
|
||||
wireUsersTabOnce();
|
||||
reloadUsersTable().catch(function (err) { console.error('users reload failed', err); });
|
||||
} else if (sub === 'audit') {
|
||||
reloadAuditTable().catch(function (err) { console.error('audit reload failed', err); });
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(route) {
|
||||
var hash;
|
||||
if (route === 'scan-sharepoint') hash = '#/scan/sharepoint';
|
||||
@ -1854,7 +1881,7 @@ function renderUserBadge(me) {
|
||||
if (window.location.hash !== hash) {
|
||||
window.location.hash = hash;
|
||||
} else {
|
||||
applyRoute(route, true);
|
||||
applyRoute({ route: route, sub: null }, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -32,7 +32,6 @@
|
||||
<div class="nav-section">Entra</div>
|
||||
<a href="#/scan/entra" class="nav-link" data-route="scan-entra">New Entra Scan</a>
|
||||
|
||||
<a href="#/users" class="nav-link" data-route="users">Users</a>
|
||||
<div class="nav-spacer"></div>
|
||||
<a href="#/tenants" class="nav-link" data-route="tenants">Tenants</a>
|
||||
<a href="#/settings" class="nav-link" data-route="settings">Settings</a>
|
||||
@ -572,17 +571,30 @@
|
||||
<!-- =================================================================== -->
|
||||
<!-- Route: Settings (placeholder) -->
|
||||
<!-- =================================================================== -->
|
||||
<section class="route-page" data-route-page="users" hidden>
|
||||
<div id="usersViewRoot"></div>
|
||||
</section>
|
||||
|
||||
<section class="route-page" data-route-page="settings" hidden>
|
||||
<div class="panel">
|
||||
<div class="panel-header split">
|
||||
<h2>Settings</h2>
|
||||
</div>
|
||||
<nav class="settings-tabs" role="tablist">
|
||||
<a href="#/settings/general" class="settings-tab" data-settings-tab="general" role="tab">General</a>
|
||||
<a href="#/settings/users" class="settings-tab" data-settings-tab="users" data-admin-only role="tab">Users</a>
|
||||
<a href="#/settings/audit" class="settings-tab" data-settings-tab="audit" data-admin-only role="tab">Audit log</a>
|
||||
</nav>
|
||||
|
||||
<div class="settings-pane" data-settings-pane="general">
|
||||
<p class="setup-hint">Runtime configuration is currently controlled via environment variables in <code>stack/.env</code>. See the <strong>TECHNICAL.md</strong> document for the full list (timeouts, retries, scan caps, onboarding).</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-pane" data-settings-pane="users" hidden>
|
||||
<div class="users-toolbar"><button id="newUserBtn" class="btn btn-primary" type="button">New user</button></div>
|
||||
<div id="usersTable"></div>
|
||||
</div>
|
||||
|
||||
<div class="settings-pane" data-settings-pane="audit" hidden>
|
||||
<div id="auditTable"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
||||
@ -824,6 +824,29 @@ html[data-auth-pending] body { visibility: hidden; }
|
||||
.modal input { padding: 8px 10px; background: var(--cv-white); border: 1px solid var(--cv-border); border-radius: 6px; color: var(--cv-text-primary); font: inherit; }
|
||||
.modal select { padding: 8px 10px; border: 1px solid var(--cv-border); border-radius: 6px; background: var(--cv-white); color: var(--cv-text-primary); font: inherit; }
|
||||
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
||||
.users-view table { width: 100%; border-collapse: collapse; }
|
||||
.users-view th, .users-view td { padding: 8px 10px; border-bottom: 1px solid var(--cv-border); text-align: left; color: var(--cv-text-primary); }
|
||||
.users-view table,
|
||||
.settings-pane table { width: 100%; border-collapse: collapse; }
|
||||
.users-view th, .users-view td,
|
||||
.settings-pane th, .settings-pane td { padding: 8px 10px; border-bottom: 1px solid var(--cv-border); text-align: left; color: var(--cv-text-primary); }
|
||||
.users-toolbar { margin: 12px 0; }
|
||||
|
||||
/* Settings sub-tabs */
|
||||
.settings-tabs {
|
||||
display: flex; gap: 4px; margin: 6px 0 18px;
|
||||
border-bottom: 1px solid var(--cv-border);
|
||||
}
|
||||
.settings-tab {
|
||||
padding: 8px 14px;
|
||||
color: var(--cv-text-secondary);
|
||||
text-decoration: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: color 120ms ease, border-color 120ms ease;
|
||||
}
|
||||
.settings-tab:hover { color: var(--cv-text-primary); }
|
||||
.settings-tab.active {
|
||||
color: var(--cv-accent-dark);
|
||||
border-bottom-color: var(--cv-accent);
|
||||
}
|
||||
.settings-pane { padding-top: 4px; }
|
||||
|
||||
@ -2,6 +2,13 @@
|
||||
|
||||
This file documents changes on the develop branch of this project.
|
||||
|
||||
## 2026-05-28 — Settings: move Users + Audit under Settings tabs
|
||||
|
||||
### Changed
|
||||
- Removed the top-level **Users** link from the sidebar.
|
||||
- `Settings` page is now tabbed: **General** (env-var pointer), **Users** (user management), **Audit log** (latest 100 events). Sub-routes `#/settings/general`, `#/settings/users`, `#/settings/audit` are direct-linkable.
|
||||
- Non-admins only see the **General** tab; the Users/Audit tabs are hidden client-side and the backend still enforces `require_admin`.
|
||||
|
||||
## 2026-05-28 — Authentication: fix login loop on HTTP-only deployment
|
||||
|
||||
### Changed
|
||||
|
||||
Loading…
Reference in New Issue
Block a user