diff --git a/containers/clearview/site/app.js b/containers/clearview/site/app.js
index 6664986..c451e2c 100644
--- a/containers/clearview/site/app.js
+++ b/containers/clearview/site/app.js
@@ -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 = 'Users ' +
- 'New user
' +
- '
' +
- 'Audit log ' +
- '
';
- 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) {
- var parts = hash.split('/');
- if (parts[0] === 'scan' && parts[1]) return 'scan-' + parts[1];
- return parts[0];
- }
- return hash;
+ if (!hash) return { route: 'dashboard', sub: null };
+ var parts = hash.split('/');
+ 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);
}
}
diff --git a/containers/clearview/site/index.html b/containers/clearview/site/index.html
index 7caf5c6..d3c37cc 100644
--- a/containers/clearview/site/index.html
+++ b/containers/clearview/site/index.html
@@ -32,7 +32,6 @@
Entra
New Entra Scan
- Users
Tenants
Settings
@@ -572,16 +571,29 @@
-
-
-
Runtime configuration is currently controlled via environment variables in stack/.env. See the TECHNICAL.md document for the full list (timeouts, retries, scan caps, onboarding).
+
+ General
+ Users
+ Audit log
+
+
+
+
Runtime configuration is currently controlled via environment variables in stack/.env. See the TECHNICAL.md document for the full list (timeouts, retries, scan caps, onboarding).
+
+
+
+
+
diff --git a/containers/clearview/site/styles.css b/containers/clearview/site/styles.css
index fb5fe4f..41cecdb 100644
--- a/containers/clearview/site/styles.css
+++ b/containers/clearview/site/styles.css
@@ -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; }
diff --git a/docs/changelog-develop.md b/docs/changelog-develop.md
index 196abcf..df174ae 100644
--- a/docs/changelog-develop.md
+++ b/docs/changelog-develop.md
@@ -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