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.
This commit is contained in:
Jino Jose committed 2026-07-09 11:11:38 +05:30
1 parent 7540da04a8
commit b3eec34158
57 files changed
+2832 -1354

No files matched your search

+346 -16
View File
@@ -66,7 +66,7 @@
modal.innerHTML = [
'<div id="cezen-upgrade-backdrop"></div>',
'<div id="cezen-upgrade-box">',
' <div id="cezen-upgrade-icon">🔒</div>',
' <div id="cezen-upgrade-icon"><span class="icon-svg icon-2xl" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="10" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span></div>',
' <h3 id="cezen-upgrade-title">Feature Locked</h3>',
' <p id="cezen-upgrade-body"></p>',
' <div id="cezen-upgrade-actions">',
@@ -191,11 +191,11 @@
var badge = document.createElement('span');
badge.className = 'cezen-lock-badge';
badge.setAttribute('title', 'Requires ' + TIER_NAMES[minTier] + ' tier');
badge.textContent = '🔒';
badge.innerHTML = '<span class="icon-svg icon-xs" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="10" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>';
link.appendChild(badge);
// Intercept click — show upgrade modal instead of navigating
var featureName = (link.textContent || file).replace('🔒', '').trim();
var featureName = (link.textContent || file).trim();
link.addEventListener('click', function (e) {
e.preventDefault();
openModal(featureName, minTier);
@@ -214,6 +214,7 @@
var tierSlug = b.tier_slug || 'basic';
ensureApplianceNavLink();
ensureAboutNavLink();
normalizeApplianceLinks();
applyServiceAvailability();
@@ -358,13 +359,13 @@
/* Modal box */
'#cezen-upgrade-box {',
' position: relative;',
' background: #fff;',
' border-radius: 14px;',
' background: var(--surface, #fff);',
' border-radius: var(--radius-lg, 14px);',
' padding: 40px 36px 32px;',
' max-width: 420px;',
' width: 90%;',
' text-align: center;',
' box-shadow: 0 24px 60px rgba(0,0,0,0.22);',
' box-shadow: var(--shadow-lg, 0 24px 60px rgba(0,0,0,0.22));',
' animation: cezen-modal-in 0.2s ease;',
'}',
'@keyframes cezen-modal-in {',
@@ -372,18 +373,18 @@
' to { opacity:1; transform: scale(1) translateY(0); }',
'}',
'#cezen-upgrade-icon { font-size: 2.4rem; margin-bottom: 10px; }',
'#cezen-upgrade-icon { color: var(--brand, #0D9488); margin-bottom: 10px; display: flex; justify-content: center; }',
'#cezen-upgrade-title {',
' font-size: 1.15rem;',
' font-weight: 700;',
' color: #0f172a;',
' color: var(--text-primary, #0f172a);',
' margin: 0 0 10px;',
'}',
'#cezen-upgrade-body {',
' font-size: 0.9rem;',
' color: #475569;',
' color: var(--text-secondary, #475569);',
' line-height: 1.55;',
' margin: 0 0 24px;',
'}',
@@ -392,28 +393,28 @@
'#cezen-upgrade-cta {',
' display: inline-block;',
' background: #0D9488;',
' background: var(--accent, #0D9488);',
' color: #fff !important;',
' padding: 9px 20px;',
' border-radius: 7px;',
' border-radius: var(--radius-md, 7px);',
' font-size: 0.875rem;',
' font-weight: 600;',
' text-decoration: none;',
' transition: background 0.18s;',
'}',
'#cezen-upgrade-cta:hover { background: #0b7a6f; }',
'#cezen-upgrade-cta:hover { background: var(--accent-dark, #0b7a6f); }',
'#cezen-upgrade-close {',
' background: #f1f5f9;',
' background: var(--bg, #f1f5f9);',
' border: none;',
' border-radius: 7px;',
' border-radius: var(--radius-md, 7px);',
' padding: 9px 20px;',
' font-size: 0.875rem;',
' color: #334155;',
' color: var(--text-secondary, #334155);',
' cursor: pointer;',
' transition: background 0.18s;',
'}',
'#cezen-upgrade-close:hover { background: #e2e8f0; }',
'#cezen-upgrade-close:hover { background: var(--border, #e2e8f0); }',
].join('\n');
document.head.appendChild(s);
}
@@ -443,6 +444,38 @@
});
}
// Adds "About" to the end of the SYSTEM nav group on every page (except
// about.html itself, which already has it hardcoded and marked active).
function ensureAboutNavLink() {
if (document.querySelector('a[href="about.html"]')) return;
document.querySelectorAll('.nav-drop-cat').forEach(function (cat) {
if ((cat.textContent || '').trim().toUpperCase() !== 'SYSTEM /') return;
var sib = cat.nextElementSibling;
while (sib && sib.tagName === 'A') { sib = sib.nextElementSibling; }
var link = document.createElement('a');
link.href = 'about.html';
link.textContent = 'About';
cat.parentNode.insertBefore(link, sib || null);
});
}
// ── License status labels (window.licenseStatusLabel) ──────────────────────
// Raw codes from the backend's license evaluation (valid/missing/expired/
// invalid_signature/not_yet_valid/machine_mismatch) aren't meant for display
// — translate them the same way /api/health does, everywhere the portal
// shows a license status to a user.
var LICENSE_STATUS_LABELS = {
valid: 'Valid',
missing: 'No license installed (field-staging mode)',
expired: 'Expired — contact support@cezentech.com',
invalid_signature: 'Invalid signature — re-upload a valid signed license',
not_yet_valid: 'Not yet in its valid date range',
machine_mismatch: 'Bound to different hardware — contact support@cezentech.com',
};
window.licenseStatusLabel = function (status) {
return LICENSE_STATUS_LABELS[status] || (status || 'Unknown');
};
function shadeColor(hex, pct) {
var num = parseInt(hex.replace('#', ''), 16);
var r = Math.min(255, Math.max(0, (num >> 16) + Math.round(2.55 * pct)));
@@ -451,6 +484,303 @@
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
// ── Shared toast notifications (window.showToast) ──────────────────────────
// Single source of truth for transient messages — replaces the assorted
// per-page toast divs and native alert() calls. See style.css for
// #cezen-toast-stack / .cezen-toast styling.
var TOAST_ICONS = {
success: '<svg viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></svg>',
error: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>',
warn: '<svg viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
info: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
};
function ensureToastStack() {
var stack = document.getElementById('cezen-toast-stack');
if (!stack) {
stack = document.createElement('div');
stack.id = 'cezen-toast-stack';
document.body.appendChild(stack);
}
return stack;
}
window.showToast = function (message, type, opts) {
type = TOAST_ICONS[type] ? type : 'info';
opts = opts || {};
var duration = opts.duration !== undefined ? opts.duration : (type === 'error' ? 6000 : 4000);
var stack = ensureToastStack();
var el = document.createElement('div');
el.className = 'cezen-toast ' + type;
el.innerHTML =
'<span class="icon-svg icon-sm" aria-hidden="true">' + TOAST_ICONS[type] + '</span>' +
'<span class="cezen-toast-body"></span>' +
'<button class="cezen-toast-close" aria-label="Dismiss">×</button>';
el.querySelector('.cezen-toast-body').textContent = message;
function remove() {
el.classList.add('leaving');
setTimeout(function () { if (el.parentNode) el.remove(); }, 160);
}
el.querySelector('.cezen-toast-close').onclick = remove;
stack.appendChild(el);
if (duration > 0) setTimeout(remove, duration);
return el;
};
// ── Shared confirmation modal (window.showConfirm) ──────────────────────────
// Replaces native confirm() with a styled, on-brand modal.
// Usage: showConfirm('Delete user?', 'This cannot be undone.', {danger:true})
// .then(function (ok) { if (ok) { ...proceed... } });
function ensureConfirmModal() {
var modal = document.getElementById('cezen-confirm-modal');
if (modal) return modal;
modal = document.createElement('div');
modal.id = 'cezen-confirm-modal';
modal.innerHTML =
'<div id="cezen-confirm-backdrop"></div>' +
'<div id="cezen-confirm-box">' +
' <div id="cezen-confirm-title"></div>' +
' <div id="cezen-confirm-body"></div>' +
' <div id="cezen-confirm-actions">' +
' <button id="cezen-confirm-cancel" class="btn-secondary">Cancel</button>' +
' <button id="cezen-confirm-ok" class="btn-primary">Confirm</button>' +
' </div>' +
'</div>';
document.body.appendChild(modal);
return modal;
}
window.showConfirm = function (title, body, opts) {
opts = opts || {};
return new Promise(function (resolve) {
var modal = ensureConfirmModal();
modal.querySelector('#cezen-confirm-title').textContent = title || 'Are you sure?';
modal.querySelector('#cezen-confirm-body').textContent = body || '';
var okBtn = modal.querySelector('#cezen-confirm-ok');
var cancelBtn = modal.querySelector('#cezen-confirm-cancel');
okBtn.textContent = opts.confirmLabel || 'Confirm';
cancelBtn.textContent = opts.cancelLabel || 'Cancel';
okBtn.className = opts.danger ? 'btn-danger' : 'btn-primary';
var backdrop = modal.querySelector('#cezen-confirm-backdrop');
function close(result) {
modal.classList.remove('cezen-modal-open');
okBtn.onclick = null; cancelBtn.onclick = null; backdrop.onclick = null;
resolve(result);
}
okBtn.onclick = function () { close(true); };
cancelBtn.onclick = function () { close(false); };
backdrop.onclick = function () { close(false); };
modal.classList.add('cezen-modal-open');
});
};
// ── Global command palette (window search / Ctrl+K) ─────────────────────────
// No global or cross-entity search existed anywhere in the portal — only a
// handful of pages had a local filter box. This adds a lightweight, static
// "jump to" index of every page in the product, reachable from any screen
// via Ctrl/Cmd+K or the search icon in the top nav. It's navigation search,
// not a live database query — no new API surface, matches the rest of
// branding.js's pattern of injecting shared UI once per page load.
var CMDK_PAGES = [
{ label: 'Home', href: 'index.html', cat: 'General' },
{ label: 'Quick Start', href: 'quickstart.html', cat: 'General' },
{ label: 'Prompt Library', href: 'prompts.html', cat: 'General' },
{ label: 'Use Cases', href: 'usecases.html', cat: 'General' },
{ label: 'Model Library', href: 'models.html', cat: 'Learn' },
{ label: 'Troubleshooting', href: 'troubleshooting.html', cat: 'Support' },
{ label: 'FAQ', href: 'faq.html', cat: 'Support' },
{ label: 'Glossary', href: 'glossary.html', cat: 'More' },
{ label: "What's New", href: 'whats-new.html', cat: 'More' },
{ label: 'Security & Privacy', href: 'security.html', cat: 'Docs' },
{ label: 'Admin Guide', href: 'admin.html', cat: 'Docs' },
{ label: 'Dashboard', href: 'dashboard.html', cat: 'Monitor' },
{ label: 'Usage Analytics', href: 'analytics.html', cat: 'Monitor' },
{ label: 'Governance & Audit', href: 'audit.html', cat: 'Monitor' },
{ label: 'Health Center', href: 'health.html', cat: 'Monitor', keywords: 'status services gpu database' },
{ label: 'Feedback & Ratings', href: 'feedback.html', cat: 'Monitor' },
{ label: 'Users', href: 'users.html', cat: 'Manage', keywords: 'user management accounts' },
{ label: 'Teams', href: 'teams.html', cat: 'Manage' },
{ label: 'Model Catalog', href: 'models-admin.html', cat: 'Manage', keywords: 'models install pull upload' },
{ label: 'Training', href: 'training.html', cat: 'Manage', keywords: 'fine-tune qlora' },
{ label: 'Knowledge Base', href: 'knowledge.html', cat: 'Manage', keywords: 'rag documents collections kb' },
{ label: 'API Keys', href: 'apikeys.html', cat: 'Tools' },
{ label: 'Benchmarking', href: 'benchmark.html', cat: 'Tools', keywords: 'model benchmark speed tokens' },
{ label: 'Model Compare', href: 'model-compare.html', cat: 'Tools' },
{ label: 'API Playground', href: 'api-playground.html', cat: 'Tools' },
{ label: 'Guardrails', href: 'guardrails.html', cat: 'Tools', keywords: 'content filters pii keyword' },
{ label: 'RAG Quality', href: 'rag-quality.html', cat: 'Tools' },
{ label: 'Model Router', href: 'router.html', cat: 'Tools' },
{ label: 'MCP & Enterprise Connectors', href: 'connectors.html', cat: 'Tools', keywords: 'database folder connector integration' },
{ label: 'Console', href: 'console.html', cat: 'System' },
{ label: 'Settings', href: 'settings.html', cat: 'System' },
{ label: 'Appliance Ops', href: 'appliance.html', cat: 'System' },
{ label: 'Document Intelligence', href: 'documents.html', cat: 'AI Tools' },
{ label: 'AI Workspace', href: 'chat-multi.html', cat: 'AI Tools', keywords: 'multimodal chat' },
{ label: 'Prompt Studio', href: 'prompt-studio.html', cat: 'AI Tools' },
{ label: 'Meeting Assistant', href: 'meeting.html', cat: 'AI Tools' },
{ label: 'Agent Studio', href: 'agents.html', cat: 'AI Tools', keywords: 'agents automation' },
{ label: 'Scheduled Jobs', href: 'schedules.html', cat: 'AI Tools', keywords: 'cron automation jobs' },
{ label: 'Workflow Designer', href: 'workflows.html', cat: 'AI Tools', keywords: 'automation pipeline' },
{ label: 'AI Eval Suite', href: 'evals.html', cat: 'AI Tools', keywords: 'evaluation test suite' },
{ label: 'Chat Rooms', href: 'chatrooms.html', cat: 'AI Tools' },
{ label: 'Notifications', href: 'notifications.html', cat: 'General' },
];
function ensureCmdPalette() {
var modal = document.getElementById('cezen-cmdk');
if (modal) return modal;
modal = document.createElement('div');
modal.id = 'cezen-cmdk';
modal.innerHTML =
'<div id="cezen-cmdk-backdrop"></div>' +
'<div id="cezen-cmdk-box">' +
' <div id="cezen-cmdk-input-wrap">' +
' <span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg></span>' +
' <input id="cezen-cmdk-input" type="text" placeholder="Jump to a page…" autocomplete="off">' +
' <span id="cezen-cmdk-hint">Esc</span>' +
' </div>' +
' <div id="cezen-cmdk-results"></div>' +
'</div>';
document.body.appendChild(modal);
var input = modal.querySelector('#cezen-cmdk-input');
modal.querySelector('#cezen-cmdk-backdrop').onclick = closeCmdPalette;
input.addEventListener('input', function () { renderCmdResults(input.value); });
input.addEventListener('keydown', function (e) {
var results = modal.querySelectorAll('.cezen-cmdk-item');
if (!results.length) return;
var activeIdx = -1;
results.forEach(function (r, i) { if (r.classList.contains('active')) activeIdx = i; });
if (e.key === 'ArrowDown') {
e.preventDefault();
activeIdx = (activeIdx + 1) % results.length;
results.forEach(function (r, i) { r.classList.toggle('active', i === activeIdx); });
results[activeIdx].scrollIntoView({ block: 'nearest' });
} else if (e.key === 'ArrowUp') {
e.preventDefault();
activeIdx = activeIdx <= 0 ? results.length - 1 : activeIdx - 1;
results.forEach(function (r, i) { r.classList.toggle('active', i === activeIdx); });
results[activeIdx].scrollIntoView({ block: 'nearest' });
} else if (e.key === 'Enter') {
e.preventDefault();
var target = modal.querySelector('.cezen-cmdk-item.active') || results[0];
if (target) target.click();
}
});
return modal;
}
function renderCmdResults(query) {
var modal = document.getElementById('cezen-cmdk');
var resultsEl = modal.querySelector('#cezen-cmdk-results');
var q = (query || '').trim().toLowerCase();
var matches = CMDK_PAGES.filter(function (p) {
if (!q) return true;
var hay = (p.label + ' ' + p.cat + ' ' + (p.keywords || '')).toLowerCase();
return hay.indexOf(q) !== -1;
}).slice(0, 8);
if (!matches.length) {
resultsEl.innerHTML = '<div id="cezen-cmdk-empty">No pages match "' + escapeHtml(query) + '".</div>';
return;
}
resultsEl.innerHTML = matches.map(function (p, i) {
return '<a class="cezen-cmdk-item' + (i === 0 ? ' active' : '') + '" href="' + p.href + '">' +
'<span class="cezen-cmdk-item-label">' + escapeHtml(p.label) + '</span>' +
'<span class="cezen-cmdk-item-cat">' + escapeHtml(p.cat) + '</span></a>';
}).join('');
}
function escapeHtml(s) {
return (s || '').replace(/[&<>"']/g, function (c) {
return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
});
}
function openCmdPalette() {
var modal = ensureCmdPalette();
modal.classList.add('cezen-cmdk-open');
var input = modal.querySelector('#cezen-cmdk-input');
input.value = '';
renderCmdResults('');
setTimeout(function () { input.focus(); }, 0);
}
function closeCmdPalette() {
var modal = document.getElementById('cezen-cmdk');
if (modal) modal.classList.remove('cezen-cmdk-open');
}
function injectCmdPaletteTrigger() {
if (document.querySelector('.nav-search-btn')) return;
var topnav = document.querySelector('.topnav');
if (!topnav) return; // page has no shared topnav (e.g. login) — skip
var navMenu = topnav.querySelector(':scope > nav');
if (!navMenu) return;
var btn = document.createElement('button');
btn.className = 'nav-icon-btn nav-search-btn';
btn.type = 'button';
btn.setAttribute('aria-label', 'Search (Ctrl+K)');
btn.title = 'Search (Ctrl+K)';
btn.innerHTML = '<span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg></span>';
btn.onclick = openCmdPalette;
topnav.insertBefore(btn, navMenu.nextSibling);
}
// Two topnav markup variants exist across the portal: some pages wrap the
// logo/tier-badge/notification-bell trio in a .nav-right div (in that DOM
// order), others place the same three elements as flat topnav children in
// the REVERSE order (bell, badge, logo). Relying on CSS `order` to reconcile
// this is fragile because .nav-right is itself a nested flex container, so
// `order` values only resolve within whichever flex context an element
// actually sits in. Instead, physically move all four icon-cluster elements
// into one canonical container in one canonical DOM order, so every page's
// rendered markup is identical after this runs — no reliance on `order` math.
function normalizeNavIconCluster() {
var topnav = document.querySelector('.topnav');
if (!topnav) return;
var search = topnav.querySelector('.nav-search-btn');
var bell = topnav.querySelector('a.nav-icon-btn[href="notifications.html"]');
var badge = topnav.querySelector('[data-brand="tier"]');
var logo = topnav.querySelector('#nav-org-logo');
if (!bell && !badge && !logo) return; // nothing to normalize on this page
var cluster = topnav.querySelector('.nav-right');
if (!cluster) {
cluster = document.createElement('div');
cluster.className = 'nav-right';
topnav.appendChild(cluster);
}
// Canonical left-to-right order: search, bell, tier badge, org logo.
// appendChild on an already-attached node MOVES it, so this deterministically
// reorders in place regardless of the page's original markup pattern.
[search, bell, badge, logo].forEach(function (el) {
if (el) cluster.appendChild(el);
});
}
document.addEventListener('keydown', function (e) {
var isMeta = e.metaKey || e.ctrlKey;
if (isMeta && e.key.toLowerCase() === 'k') {
e.preventDefault();
var modal = document.getElementById('cezen-cmdk');
if (modal && modal.classList.contains('cezen-cmdk-open')) closeCmdPalette();
else openCmdPalette();
return;
}
if (e.key === 'Escape') {
var m = document.getElementById('cezen-cmdk');
if (m && m.classList.contains('cezen-cmdk-open')) closeCmdPalette();
}
});
function setupTopnavIcons() {
injectCmdPaletteTrigger();
normalizeNavIconCluster();
}
document.addEventListener('DOMContentLoaded', setupTopnavIcons);
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setupTopnavIcons();
}
// ── Fetch branding and apply ────────────────────────────────────────────────
fetch('/api/settings/branding', { credentials: 'include' })
.then(function (r) { return r.ok ? r.json() : {}; })