clavitor/clavis/clavis-vault/cmd/clavitor/web/topbar.js

122 lines
5.0 KiB
JavaScript

// Theme management (before anything else so it applies immediately, no flash)
var _themes = ['', 'theme-dark', 'theme-midnight'];
var _currentTheme = localStorage.getItem('clavitor_theme') || '';
if (_currentTheme) document.body.className = _currentTheme;
function cycleTheme() {
var idx = _themes.indexOf(_currentTheme);
_currentTheme = _themes[(idx + 1) % _themes.length];
document.body.className = _currentTheme;
localStorage.setItem('clavitor_theme', _currentTheme);
}
// Stateless auth: L1 Bearer from master key in sessionStorage.
function getL1Bearer() {
var masterB64 = sessionStorage.getItem('clavitor_master');
if (!masterB64) return null;
var bin = atob(masterB64);
var l1 = new Uint8Array(8);
for (var i = 0; i < 8; i++) l1[i] = bin.charCodeAt(i);
var b64 = btoa(String.fromCharCode.apply(null, l1));
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
// Global API helper. Every request sends L1 as Bearer.
async function api(method, path, body) {
var opts = { method: method, headers: { 'Content-Type': 'application/json' } };
var bearer = getL1Bearer();
if (bearer) opts.headers['Authorization'] = 'Bearer ' + bearer;
if (body) opts.body = JSON.stringify(body);
var res = await fetch(path, opts);
if (res.status === 401) {
sessionStorage.removeItem('clavitor_master');
location.href = '/app/';
throw new Error('Unauthorized');
}
return res.json();
}
// --- Idle timer state (module-level, survives re-init) ---
var _idleTimer = null;
var _countdownTimer = null;
var _countdownVal = 0;
function _resetIdle() {
if (!sessionStorage.getItem('clavitor_master')) return;
clearTimeout(_idleTimer);
_clearCountdown();
_idleTimer = setTimeout(_startCountdown, 600000); // 10 minutes
}
function _startCountdown() {
_countdownVal = 15;
var banner = document.createElement('div');
banner.id = 'lockCountdownBanner';
banner.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:9999;background:var(--gold,#D4AF37);color:#000;text-align:center;padding:0.5rem;font-weight:600;font-size:0.875rem;';
banner.innerHTML = 'Vault locking in <span id="lockCountdownSec">15</span>s — press any key to stay unlocked';
document.body.appendChild(banner);
_countdownTimer = setInterval(function() {
_countdownVal--;
if (_countdownVal <= 0) {
_clearCountdown();
sessionStorage.removeItem('clavitor_master');
window.location.href = '/app/';
} else {
var el = document.getElementById('lockCountdownSec');
if (el) el.textContent = _countdownVal;
}
}, 1000);
}
function _clearCountdown() {
clearInterval(_countdownTimer);
_countdownTimer = null;
var b = document.getElementById('lockCountdownBanner');
if (b) b.remove();
}
// Initialize topbar: populate nav, start idle timer, check vault lock.
// Called on page load AND after restoreAppLayout() rebuilds the DOM.
function initTopbar() {
var path = location.pathname.replace(/\/$/, '');
var isVault = (path === '/app' || path === '');
var nav = '';
nav += '<a href="/app/"' + (path === '/app' ? ' class="topbar-active"' : '') + '>Vault</a>';
nav += '<a href="/app/import.html"' + (path.indexOf('import') >= 0 ? ' class="topbar-active"' : '') + '>Import</a>';
if (typeof showAudit === 'function') nav += '<a href="#" onclick="showAudit();return false">Audit</a>';
if (typeof showAgents === 'function') nav += '<a href="#" onclick="showAgents();return false">Agents</a>';
nav += '<span style="font-size:0.65rem;opacity:0.35;margin-left:auto;font-family:var(--font-mono)">v2.0.44</span>';
nav += '<button onclick="if(typeof lockVault===\'function\'){lockVault()}else{sessionStorage.removeItem(\'clavitor_master\');location.href=\'/app/\'}" class="topbar-lock">Lock</button>';
var logo = '<a href="/app/" class="topbar-lockup" style="display:inline-flex;gap:10px;align-items:center;text-decoration:none">' +
'<span style="width:32px;height:32px;background:var(--text);flex-shrink:0"></span>' +
'<span style="font-family:var(--font-sans);font-size:18px;font-weight:700;letter-spacing:0.25em;text-transform:uppercase;color:var(--text);line-height:1">CLAVITOR</span>' +
'</a>';
var el = document.getElementById('topbar');
if (el) {
el.innerHTML =
'<header class="topbar">' +
'<div class="topbar-inner" style="padding:0 1rem">' +
logo +
'<div class="topbar-links">' + nav + '</div>' +
'</div>' +
'</header>';
}
// Start idle timer if authenticated
if (getL1Bearer()) {
_resetIdle();
['keydown', 'mousedown', 'touchstart'].forEach(function(evt) {
document.removeEventListener(evt, _resetIdle, true); // prevent duplicates
document.addEventListener(evt, _resetIdle, true);
});
// Vault lock removed in v2 — scoped access replaces it.
}
}
// Run on initial page load
initTopbar();