aipackage/cezen-portal/settings.html
Jino Jose b3eec34158 Sync cezen-portal/ from live working copy (V1.0 GA polish + search-icon ordering fix)
Brings the ISO-packaged portal copy current with the live cezen-portal/
working directory, which had drifted across the entire V1.0 GA polish
initiative (Phases 1-10) plus this session's topnav icon-order bugfix.

- 52 existing files updated, 2 new pages added (about.html, health.html)
- cgit/cezen-portal/ is now byte-identical to cezen-portal/
- Backend files (rag_ingest.py, cezen_license.py) already in sync;
  main.py intentionally left as-is (cgit is ahead there with
  workstation-category commercial-fit logic not yet backported to the
  live working copy)

Next: rebuild the ISO (bash autoinstall/build-iso.sh) to bake this in.
2026-07-09 11:11:38 +05:30

467 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=12">
<style>
.settings-grid { display:grid; grid-template-columns:1fr 1fr; gap:20px; margin-bottom:24px; }
.settings-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:28px; }
.settings-card h2 { font-size:17px; font-weight:700; color:var(--ink); margin-bottom:6px; }
.settings-card .desc { font-size:13px; color:var(--lt); margin-bottom:22px; }
/* Logo upload */
.logo-area { display:flex; gap:20px; align-items:flex-start; margin-bottom:22px; }
.logo-preview { width:100px; height:80px; border:2px dashed var(--bdr); border-radius:10px; display:flex; align-items:center; justify-content:center; background:rgba(255,255,255,.03); overflow:hidden; flex-shrink:0; }
.logo-preview img { max-width:90%; max-height:90%; object-fit:contain; display:none; }
.logo-placeholder { font-size:11px; color:var(--lt); text-align:center; line-height:1.5; }
.logo-controls { display:flex; flex-direction:column; gap:8px; justify-content:center; }
.upload-btn { display:inline-flex; align-items:center; gap:7px; background:var(--navy2); border:1.5px solid var(--bdr); color:var(--ink); padding:8px 16px; border-radius:8px; cursor:pointer; font-size:13px; font-weight:600; font-family:inherit; transition:all .15s; }
.upload-btn:hover { border-color:var(--teal); color:var(--teal); }
#logo-file-input { display:none; }
.remove-logo-btn { font-size:12px; color:#dc2626; cursor:pointer; background:none; border:none; padding:0; font-family:inherit; text-align:left; }
.logo-hint { font-size:11px; color:var(--lt); line-height:1.6; }
/* Form fields */
.form-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:16px; }
.form-group { display:flex; flex-direction:column; gap:5px; }
.form-group label { font-size:12px; font-weight:600; color:var(--med); text-transform:uppercase; letter-spacing:.04em; }
.form-group input { padding:9px 12px; border:1.5px solid var(--bdr); border-radius:8px; font-size:14px; color:var(--ink); font-family:inherit; outline:none; transition:.15s; background:var(--navy2); }
.form-group input:focus { border-color:var(--teal); }
.form-group input[type=color] { padding:3px 5px; height:40px; cursor:pointer; }
.accent-preview { display:flex; align-items:center; gap:8px; margin-top:6px; font-size:13px; color:var(--med); }
.accent-swatch { width:24px; height:24px; border-radius:5px; border:1px solid var(--bdr); }
/* Buttons */
.form-actions { display:flex; gap:10px; margin-top:20px; flex-wrap:wrap; }
.btn { padding:9px 20px; border-radius:8px; font-size:14px; font-weight:600; cursor:pointer; border:none; font-family:inherit; transition:all .15s; }
.btn-primary { background:var(--teal); color:var(--ink); }
.btn-primary:hover { background:#0B7A70; }
.btn-ghost { background:var(--navy2); color:var(--med); border:1.5px solid var(--bdr); }
.btn-ghost:hover { border-color:var(--teal); color:var(--teal); }
.btn-danger { background:var(--navy2); color:#dc2626; border:1.5px solid #fecaca; }
.btn-danger:hover { background:rgba(185,28,28,.08); }
/* Sessions card */
.sessions-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:28px; margin-bottom:20px; }
.sessions-card h2 { font-size:17px; font-weight:700; color:var(--ink); margin-bottom:6px; }
.sessions-card .desc { font-size:13px; color:var(--lt); margin-bottom:18px; }
.sessions-list { display:flex; flex-wrap:wrap; gap:10px; }
.session-chip { display:flex; align-items:center; gap:8px; background:rgba(255,255,255,.03); border:1px solid var(--bdr); border-radius:20px; padding:7px 16px; font-size:13px; }
.online-dot { width:8px; height:8px; border-radius:50%; background:#22c55e; box-shadow:0 0 0 2px #dcfce7; flex-shrink:0; }
.session-role { font-size:11px; color:var(--lt); margin-left:4px; }
/* Sysinfo */
.sysinfo-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:28px; }
.sysinfo-card h2 { font-size:17px; font-weight:700; color:var(--ink); margin-bottom:18px; }
.sysinfo-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:12px; }
.sysinfo-item { background:rgba(255,255,255,.03); border:1px solid var(--bdr); border-radius:10px; padding:14px; }
.sysinfo-label { font-size:11px; color:var(--lt); font-weight:600; text-transform:uppercase; letter-spacing:.04em; margin-bottom:4px; }
.sysinfo-value { font-size:15px; font-weight:700; color:var(--ink); }
/* Toast */
#toast { position:fixed; bottom:24px; right:24px; padding:12px 20px; border-radius:10px; font-size:14px; font-weight:600; display:none; z-index:999; }
#toast.success { background:rgba(34,197,94,.15); color:#15803D; border:1px solid rgba(134,239,172,.3); }
#toast.error { background:rgba(185,28,28,.08); color:#B91C1C; border:1px solid rgba(239,68,68,.25); }
@media(max-width:640px){
.settings-grid,
.form-row { grid-template-columns:1fr; }
.settings-card,
.sessions-card,
.sysinfo-card { padding:22px; }
.logo-area { flex-direction:column; }
}
</style>
</head>
<body data-role="admin">
<!-- ── Top nav ──────────────────────────────────────────────────────────────── -->
<header class="topnav">
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
<nav>
<a href="index.html">Home</a>
<a href="quickstart.html">Quick Start</a>
<a href="prompts.html">Prompt Library</a>
<a href="usecases.html">Use Cases</a>
<span class="nav-sep"></span>
<div class="nav-dropdown">
<button class="nav-drop-btn">Help ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">LEARN /</span>
<a href="quickstart.html">Quick Start</a>
<a href="models.html">Models</a>
<span class="nav-drop-cat">SUPPORT /</span>
<a href="troubleshooting.html">Troubleshoot</a>
<a href="faq.html">FAQ</a>
<span class="nav-drop-cat">MORE /</span>
<a href="glossary.html">Glossary</a>
<a href="whats-new.html">What's New</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn active">Admin ▾</button>
<div class="nav-drop-menu nav-drop-menu-wide">
<span class="nav-drop-cat">DOCS /</span>
<a href="security.html">Security & Privacy</a>
<a href="admin.html">Admin Guide</a>
<span class="nav-drop-cat">MONITOR /</span>
<a href="dashboard.html">Dashboard</a>
<a href="analytics.html">Usage Analytics</a>
<a href="audit.html">Governance & Audit</a>
<a href="health.html">Health Center</a>
<a href="feedback.html">Feedback &amp; Ratings</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
<a href="teams.html">Teams</a>
<a href="models-admin.html">Model Catalog</a>
<a href="training.html">Training</a>
<a href="knowledge.html">Knowledge Base</a>
<span class="nav-drop-cat">TOOLS /</span>
<a href="apikeys.html">API Keys</a>
<a href="benchmark.html">Benchmarking</a>
<a href="model-compare.html">Model Compare</a>
<a href="api-playground.html">API Playground</a>
<a href="guardrails.html">Guardrails</a>
<a href="rag-quality.html">RAG Quality</a>
<a href="router.html">Model Router</a>
<a href="connectors.html">MCP & Enterprise Connectors</a>
<span class="nav-drop-cat">SYSTEM /</span>
<a href="appliance.html">Appliance Ops</a>
<a href="console.html">Console</a>
<a href="settings.html" class="active">Settings</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn">AI Tools ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">INTELLIGENCE /</span>
<a href="documents.html">Document Intelligence</a>
<a href="chat-multi.html">AI Workspace</a>
<a href="prompt-studio.html">Prompt Studio</a>
<a href="meeting.html">Meeting Assistant</a>
<span class="nav-drop-cat">AUTOMATION /</span>
<a href="agents.html">Agent Studio</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Designer</a>
<span class="nav-drop-cat">QUALITY /</span>
<a href="evals.html">AI Eval Suite</a>
<a href="chatrooms.html">Chat Rooms</a>
</div>
</div>
</nav>
<a href="notifications.html" class="nav-icon-btn" style="position:relative" aria-label="Notifications"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M15 17h5l-1.4-1.4A2 2 0 0 1 18 14.2V11a6 6 0 1 0-12 0v3.2a2 2 0 0 1-.6 1.4L4 17h5"/><path d="M10 17a2 2 0 0 0 4 0"/></svg></span></a>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
<!-- ── Hero ─────────────────────────────────────────────────────────────────── -->
<div class="page-hero">
<div class="label">Admin · Settings</div>
<h1>Settings</h1>
<p>White-label branding, active sessions, and system information.</p>
</div>
<!-- ── Content ──────────────────────────────────────────────────────────────── -->
<div class="content">
<!-- Branding card (full width) -->
<div class="settings-card" style="margin-bottom:20px">
<h2>White-Label Branding</h2>
<p class="desc">Customise the portal identity for your PSU client. Changes take effect immediately across all active sessions.</p>
<!-- Logo -->
<div class="logo-area">
<div class="logo-preview" id="logo-preview-box">
<div class="logo-placeholder" id="logo-placeholder">No logo<br>uploaded</div>
<img id="logo-preview-img" src="" alt="Logo">
</div>
<div class="logo-controls">
<label class="upload-btn" for="logo-file-input">Upload Logo</label>
<input type="file" id="logo-file-input" accept="image/png,image/jpeg,image/svg+xml,image/webp">
<div class="logo-hint">PNG, JPG, SVG, WebP · Max 2 MB<br>Recommended: 200 × 60 px</div>
<button class="remove-logo-btn" onclick="removeLogo()">Remove logo</button>
</div>
</div>
<!-- Fields -->
<div class="form-row">
<div class="form-group">
<label>Organisation Name</label>
<input type="text" id="org_name" placeholder="e.g. BEL, DRDO, ISRO">
</div>
<div class="form-group">
<label>Stack Name (shown in nav badge)</label>
<input type="text" id="stack_name" placeholder="e.g. BEL AI Suite">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Accent Color</label>
<input type="color" id="accent_color" value="#0D9488">
<div class="accent-preview">
<div class="accent-swatch" id="accent-swatch" style="background:#0D9488"></div>
<span id="accent-hex">#0D9488</span>
</div>
</div>
<div class="form-group" id="tier-field-group">
<label>Tier Label (shown in nav badge)</label>
<input type="text" id="tier_label" placeholder="e.g. Pro Tier, BEL Edition">
<!-- Shown instead when tier is locked by Cezen -->
<div id="tier-locked-display" style="display:none;margin-top:8px;">
<div style="display:flex;align-items:center;gap:8px;background:#F0FDFA;border:1.5px solid #99F6E4;border-radius:8px;padding:10px 14px;">
<span style="font-size:18px">Lock</span>
<div>
<div id="tier-locked-value" style="font-weight:700;font-size:14px;color:var(--ink)"></div>
<div style="font-size:12px;color:#0F766E;margin-top:2px">Set by Cezen — contact Cezen to change your tier</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Footer Text</label>
<input type="text" id="footer_text" placeholder="Powered by Cezen">
</div>
<div class="form-group">
<label>Support Email</label>
<input type="email" id="support_email" placeholder="support@cezentech.com">
</div>
</div>
<div class="form-actions">
<button class="btn btn-primary" onclick="saveBranding()">Save Branding</button>
<button class="btn btn-ghost" onclick="previewBranding()">Preview</button>
<button class="btn btn-danger" onclick="resetBranding()">Reset to Defaults</button>
</div>
</div>
<!-- Active sessions -->
<div class="sessions-card">
<h2>Active Sessions</h2>
<p class="desc">Users who have logged in within the last 8 hours. Refreshed every 30 s.</p>
<div class="sessions-list" id="sessions-list">
<span style="color:var(--lt);font-size:13px">Loading…</span>
</div>
</div>
<!-- System info -->
<div class="sysinfo-card">
<h2>System Information</h2>
<div class="sysinfo-grid" id="sysinfo-grid">
<span style="color:var(--lt);font-size:13px">Loading…</span>
</div>
</div>
</div><!-- /.content -->
<footer>
<p>Nexus One AI &nbsp;·&nbsp; Powered by Cezen &nbsp;·&nbsp; <span data-brand="tier">Basic Tier</span></p>
</footer>
<div id="toast"></div>
<script>
function jsonOrNull(res) {
if (!res || !res.ok) return null;
var type = res.headers.get('content-type') || '';
if (!type.includes('application/json')) return null;
return res.json().catch(function(){ return null; });
}
function toast(msg, type) {
var t = document.getElementById('toast');
t.textContent = msg;
t.className = type || 'success';
t.style.display = 'block';
clearTimeout(t._timer);
t._timer = setTimeout(function(){ t.style.display='none'; }, 3500);
}
// ── Load branding ──────────────────────────────────────────────────────────
function loadBranding() {
fetch('/api/settings/branding', {credentials:'include'})
.then(jsonOrNull)
.then(function(b) {
b = b || {};
document.getElementById('org_name').value = b.org_name || '';
document.getElementById('stack_name').value = b.stack_name || '';
document.getElementById('footer_text').value = b.footer_text || '';
document.getElementById('support_email').value = b.support_email || '';
var ac = b.accent_color || '#0D9488';
document.getElementById('accent_color').value = ac;
document.getElementById('accent-swatch').style.background = ac;
document.getElementById('accent-hex').textContent = ac;
if (b.logo_url) showLogoPreview(b.logo_url);
// Tier locking: if server says tier_locked=true, show readonly display
if (b.tier_locked === 'true') {
document.getElementById('tier_label').style.display = 'none';
var locked = document.getElementById('tier-locked-display');
locked.style.display = '';
document.getElementById('tier-locked-value').textContent = b.tier_label || '';
} else {
document.getElementById('tier_label').value = b.tier_label || '';
document.getElementById('tier_label').style.display = '';
document.getElementById('tier-locked-display').style.display = 'none';
}
})
.catch(function(){ /* keep default empty fields in static/demo mode */ });
}
document.getElementById('accent_color').addEventListener('input', function() {
document.getElementById('accent-swatch').style.background = this.value;
document.getElementById('accent-hex').textContent = this.value;
});
// ── Logo ───────────────────────────────────────────────────────────────────
document.getElementById('logo-file-input').addEventListener('change', function() {
var file = this.files[0];
if (!file) return;
if (file.size > 2*1024*1024) { toast('Logo must be under 2 MB', 'error'); return; }
var fd = new FormData();
fd.append('file', file);
fetch('/api/settings/logo', {method:'POST', credentials:'include', body:fd})
.then(r => r.json())
.then(function(d) {
if (d.logo_url) { showLogoPreview(d.logo_url); toast('Logo uploaded'); }
else toast(d.detail || 'Upload failed', 'error');
})
.catch(function(){ toast('Upload error', 'error'); });
});
function showLogoPreview(url) {
document.getElementById('logo-placeholder').style.display = 'none';
var img = document.getElementById('logo-preview-img');
img.src = url; img.style.display = 'block';
}
function removeLogo() {
if (!confirm('Remove the current logo?')) return;
fetch('/api/settings/branding', {
method:'PUT', credentials:'include',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({logo_url:''})
}).then(function() {
document.getElementById('logo-placeholder').style.display = '';
var img = document.getElementById('logo-preview-img');
img.style.display='none'; img.src='';
toast('Logo removed');
});
}
// ── Save ───────────────────────────────────────────────────────────────────
function saveBranding() {
var tierInput = document.getElementById('tier_label');
var tierLocked = tierInput.style.display === 'none'; // hidden = locked
var payload = {
org_name: document.getElementById('org_name').value.trim(),
stack_name: document.getElementById('stack_name').value.trim(),
footer_text: document.getElementById('footer_text').value.trim(),
support_email: document.getElementById('support_email').value.trim(),
accent_color: document.getElementById('accent_color').value,
};
if (!tierLocked) {
payload.tier_label = tierInput.value.trim();
}
fetch('/api/settings/branding', {
method:'PUT', credentials:'include',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload)
})
.then(r => r.json())
.then(function(d) {
if (d.ok) { toast('Branding saved — reload any page to see changes'); previewBranding(); }
else toast(d.detail || 'Save failed', 'error');
})
.catch(function(){ toast('Network error', 'error'); });
}
function previewBranding() {
// live-apply accent color to teal variable
var accent = document.getElementById('accent_color').value;
document.documentElement.style.setProperty('--teal', accent);
// Use locked tier value if tier field is hidden
var tierInput = document.getElementById('tier_label');
var tierLocked = tierInput.style.display === 'none';
var tierText = tierLocked
? document.getElementById('tier-locked-value').textContent
: (tierInput.value || 'Basic Tier');
document.querySelector('.badge').textContent = tierText;
toast('Preview applied to this page');
}
function resetBranding() {
if (!confirm('Reset all branding to Nexus One AI defaults?')) return;
fetch('/api/settings/branding', {
method:'PUT', credentials:'include',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({
org_name:'Nexus One AI', stack_name:'Nexus One AI', logo_url:'',
accent_color:'#0D9488', footer_text:'Powered by Cezen',
support_email:'support@cezentech.com', tier_label:'Basic Tier'
})
}).then(function(){ loadBranding(); toast('Reset to defaults'); });
}
// ── Active sessions ────────────────────────────────────────────────────────
function loadSessions() {
fetch('/api/users/sessions', {credentials:'include'})
.then(r => r.ok ? r.json() : null)
.then(function(data) {
var el = document.getElementById('sessions-list');
if (!data || !data.sessions || !data.sessions.length) {
el.innerHTML = '<span style="color:var(--lt);font-size:13px">No active sessions found.</span>';
return;
}
el.innerHTML = data.sessions.map(function(s) {
return '<div class="session-chip"><div class="online-dot"></div>'
+ '<strong>' + esc(s.username) + '</strong>'
+ '<span class="session-role">' + esc(s.role) + '</span></div>';
}).join('');
})
.catch(function(){
document.getElementById('sessions-list').innerHTML =
'<span style="color:var(--lt);font-size:13px">Session data unavailable.</span>';
});
}
// ── System info ────────────────────────────────────────────────────────────
function loadSysInfo() {
fetch('/api/system/info', {credentials:'include'})
.then(jsonOrNull)
.then(function(d) {
if (!d) throw new Error('system info unavailable');
var pairs = [
['Hostname', d.hostname||'—'], ['OS', d.os||'—'],
['CPU Cores', d.cpu_cores||'—'],
['RAM Total', d.ram_total_gb ? d.ram_total_gb+' GB' : '—'],
['Disk Total', d.disk_total_gb ? d.disk_total_gb+' GB' : '—'],
['GPU', d.gpu_name||'—'], ['Uptime', d.uptime||'—'],
['Backend', 'v'+(d.version||'1.0')],
];
document.getElementById('sysinfo-grid').innerHTML = pairs.map(function(p){
return '<div class="sysinfo-item"><div class="sysinfo-label">'+p[0]+'</div>'
+'<div class="sysinfo-value">'+esc(p[1])+'</div></div>';
}).join('');
})
.catch(function(){
document.getElementById('sysinfo-grid').innerHTML =
'<span style="color:var(--lt)">Could not load system info.</span>';
});
}
function esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
loadBranding();
loadSessions();
loadSysInfo();
setInterval(loadSessions, 30000);
</script>
<script src="auth.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>