aipackage/cezen-portal/settings.html
2026-06-30 15:38:29 +05:30

466 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=4">
<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">Audit Log</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 Manager</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">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">Multimodal Chat</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 Builder</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Automation</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" style="position:relative">🔔</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">🔒</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"></script>
</body>
</html>