300 lines
13 KiB
HTML
300 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Audit Log — Nexus One AI</title>
|
||
<link rel="stylesheet" href="style.css?v=4">
|
||
<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">Audit Log</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">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>
|
||
|
||
<div class="page-hero">
|
||
<div class="label">Admin · Audit Log</div>
|
||
<h1>Audit Log</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 · Powered by Cezen · Basic Tier</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"></script>
|
||
</body>
|
||
</html>
|