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

' + - '
' + - '
' + - '

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 @@ New Entra Scan - Users Tenants Settings @@ -572,16 +571,29 @@ - - 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