aipackage/cezen-portal/audit.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

301 lines
13 KiB
HTML
Raw Permalink 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>Governance & Audit — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=12">
<style>
.audit-toolbar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:24px; }
.audit-filter { padding:8px 12px; border:1.5px solid var(--bdr); border-radius:8px; font-size:13px; font-family:inherit; color:var(--ink); background:var(--navy2); outline:none; min-width:140px; }
.audit-filter:focus { border-color:var(--teal); }
.audit-btn { padding:8px 16px; border-radius:8px; font-size:13px; font-weight:600; cursor:pointer; border:1px solid var(--bdr); background:var(--navy2); font-family:inherit; color:var(--med); transition:all .15s; }
.audit-btn:hover { border-color:var(--teal); color:var(--teal); }
.audit-btn.primary { background:var(--teal); color:var(--ink); border:none; }
.audit-btn.primary:hover { background:#0B7A70; }
.audit-table-wrap { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; overflow:hidden; }
table.audit-table { width:100%; border-collapse:collapse; }
.audit-table th { background:rgba(255,255,255,.03); padding:10px 14px; text-align:left; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:var(--lt); border-bottom:1px solid var(--bdr); white-space:nowrap; }
.audit-table td { padding:11px 14px; border-bottom:1px solid var(--bdr); font-size:13px; color:var(--ink); vertical-align:middle; }
.audit-table tr:last-child td { border-bottom:none; }
.audit-table tr:hover td { background:rgba(255,255,255,.03); }
.audit-table td.mono { font-family:monospace; font-size:12px; }
.result-pill { display:inline-block; font-size:10px; font-weight:700; padding:2px 8px; border-radius:8px; text-transform:uppercase; letter-spacing:.3px; }
.result-pill.success { background:rgba(34,197,94,.15); color:#15803D; }
.result-pill.failure { background:#FEE2E2; color:#991B1B; }
.result-pill.info { background:#DBEAFE; color:#93C5FD; }
.action-tag { display:inline-block; font-size:11px; font-weight:600; padding:2px 8px; border-radius:6px; background:rgba(255,255,255,.03); color:var(--med); border:1px solid var(--bdr); }
/* Pagination */
.audit-pager { display:flex; align-items:center; gap:8px; justify-content:center; padding:16px; border-top:1px solid var(--bdr); background:rgba(255,255,255,.03); }
.page-btn { padding:5px 12px; border:1px solid var(--bdr); border-radius:6px; background:var(--navy2); font-size:13px; cursor:pointer; font-family:inherit; color:var(--med); transition:all .15s; }
.page-btn:hover:not(:disabled) { border-color:var(--teal); color:var(--teal); }
.page-btn.active { background:var(--teal); color:var(--ink); border-color:var(--teal); }
.page-btn:disabled { opacity:.4; cursor:not-allowed; }
.page-info { font-size:13px; color:var(--lt); padding:0 6px; }
.audit-empty { padding:32px; text-align:center; color:var(--lt); font-size:14px; }
</style>
</head>
<body data-role="admin">
<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" class="active">Governance & Audit</a>
<a href="health.html">Health Center</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">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>
<div class="page-hero">
<div class="label">Admin · Governance & Audit</div>
<h1>Governance & Audit</h1>
<p>Full history of authentication events, model operations, and administrative actions.</p>
</div>
<div class="content">
<div class="audit-toolbar">
<select class="audit-filter" id="filter-action" onchange="applyFilters()">
<option value="">All Actions</option>
<option value="login">Login</option>
<option value="logout">Logout</option>
<option value="login_failed">Login Failed</option>
<option value="create_user">Create User</option>
<option value="delete_user">Delete User</option>
<option value="reset_password">Reset Password</option>
<option value="change_password">Change Password</option>
<option value="model_pull">Model Pull</option>
<option value="model_delete">Model Delete</option>
<option value="session_revoke">Session Revoke</option>
</select>
<select class="audit-filter" id="filter-result" onchange="applyFilters()">
<option value="">All Results</option>
<option value="success">Success</option>
<option value="failure">Failure</option>
<option value="info">Info</option>
</select>
<input class="audit-filter" id="filter-user" type="text" placeholder="Filter by user…" oninput="applyFilters()">
<button class="audit-btn" onclick="resetFilters()">Clear</button>
<button class="audit-btn" onclick="load()">Refresh</button>
<button class="audit-btn primary" onclick="exportCSV()">↓ Export CSV</button>
</div>
<div class="audit-table-wrap">
<table class="audit-table">
<thead>
<tr>
<th>Time</th>
<th>User</th>
<th>Action</th>
<th>Target</th>
<th>IP</th>
<th>Result</th>
<th>Detail</th>
</tr>
</thead>
<tbody id="audit-tbody">
<tr><td colspan="7"><div class="audit-empty">Loading…</div></td></tr>
</tbody>
</table>
<div class="audit-pager" id="pager" style="display:none">
<button class="page-btn" id="btn-prev" onclick="goPage(currentPage-1)"> Prev</button>
<span class="page-info" id="page-info"></span>
<button class="page-btn" id="btn-next" onclick="goPage(currentPage+1)">Next </button>
</div>
</div>
</div>
<footer>
<p>Nexus One AI &nbsp;·&nbsp; Powered by Cezen &nbsp;·&nbsp; <span data-brand="tier">Basic Tier</span></p>
</footer>
<script>
const _API = '/api';
const PAGE_SIZE = 50;
let allRows = [];
let filtered = [];
let currentPage = 1;
function fmt(iso) {
if (!iso) return '—';
return new Date(iso).toLocaleString([], {dateStyle:'short', timeStyle:'medium'});
}
function resultClass(r) {
if (r === 'success') return 'success';
if (r === 'failure') return 'failure';
return 'info';
}
async function load() {
try {
const res = await fetch(`${_API}/audit?limit=1000`, { credentials:'include' });
const data = await res.json();
allRows = Array.isArray(data) ? data : (Array.isArray(data.events) ? data.events : (Array.isArray(data.audit) ? data.audit : []));
} catch {
allRows = [];
}
applyFilters();
}
function applyFilters() {
const action = document.getElementById('filter-action').value.toLowerCase();
const result = document.getElementById('filter-result').value.toLowerCase();
const user = document.getElementById('filter-user').value.toLowerCase();
filtered = allRows.filter(r => {
if (action && r.action !== action) return false;
if (result && r.result !== result) return false;
if (user && !(r.username || '').toLowerCase().includes(user)) return false;
return true;
});
currentPage = 1;
render();
}
function resetFilters() {
document.getElementById('filter-action').value = '';
document.getElementById('filter-result').value = '';
document.getElementById('filter-user').value = '';
applyFilters();
}
function goPage(p) {
const total = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
currentPage = Math.max(1, Math.min(p, total));
render();
}
function render() {
const total = Math.ceil(filtered.length / PAGE_SIZE) || 1;
const start = (currentPage - 1) * PAGE_SIZE;
const page = filtered.slice(start, start + PAGE_SIZE);
const tbody = document.getElementById('audit-tbody');
if (!page.length) {
tbody.innerHTML = '<tr><td colspan="7"><div class="audit-empty">No events match the current filter.</div></td></tr>';
document.getElementById('pager').style.display = 'none';
return;
}
tbody.innerHTML = page.map(r => `
<tr>
<td class="mono">${fmt(r.created_at)}</td>
<td><strong>${r.username || '—'}</strong></td>
<td><span class="action-tag">${r.action}</span></td>
<td style="color:var(--med)">${r.target || '—'}</td>
<td class="mono">${r.ip_address || '—'}</td>
<td><span class="result-pill ${resultClass(r.result)}">${r.result}</span></td>
<td style="color:var(--lt);font-size:12px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${r.detail || ''}">${r.detail || '—'}</td>
</tr>
`).join('');
// Pager
const pager = document.getElementById('pager');
if (filtered.length > PAGE_SIZE) {
pager.style.display = 'flex';
document.getElementById('page-info').textContent = `Page ${currentPage} of ${total} (${filtered.length} events)`;
document.getElementById('btn-prev').disabled = currentPage <= 1;
document.getElementById('btn-next').disabled = currentPage >= total;
} else {
pager.style.display = filtered.length ? 'flex' : 'none';
document.getElementById('page-info').textContent = `${filtered.length} events`;
document.getElementById('btn-prev').disabled = true;
document.getElementById('btn-next').disabled = true;
}
}
function exportCSV() {
const headers = ['Time','User','Action','Target','IP','Result','Detail'];
const rows = filtered.map(r => [
fmt(r.created_at), r.username||'', r.action, r.target||'', r.ip_address||'', r.result, (r.detail||'').replace(/"/g,"'")
].map(v => `"${v}"`).join(','));
const csv = [headers.join(','), ...rows].join('\n');
const blob = new Blob([csv], {type:'text/csv'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `cezen-audit-${new Date().toISOString().slice(0,10)}.csv`;
a.click();
}
load();
</script>
<script src="auth.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>