// ========== AUDIT LOG VIEWER ========== (function() { // Inject modal HTML injectModal('audit-modal', `

📜 Audit Log

Loading audit log...
`); const modal = document.getElementById('audit-modal'); const openBtn = document.getElementById('audit-log-btn'); const cancelBtn = document.getElementById('audit-cancel'); const refreshBtn = document.getElementById('audit-refresh-btn'); const clearBtn = document.getElementById('audit-clear-btn'); const filterSelect = document.getElementById('audit-filter'); const container = document.getElementById('audit-log-container'); const loadMoreBtn = document.getElementById('audit-load-more'); let currentOffset = 0; const PAGE_SIZE = 50; async function loadAudit(append) { try { if (!append) { currentOffset = 0; container.innerHTML = '
Loading...
'; } const filter = filterSelect.value; let url = `/api/v1/audit-logs?limit=${PAGE_SIZE}&offset=${currentOffset}`; if (filter) url += `&action=${encodeURIComponent(filter)}`; const res = await fetch(url); const data = await res.json(); const entries = data.success && data.entries ? data.entries : []; if (entries.length === 0 && !append) { container.innerHTML = '
📜No audit log entries yet. Actions will be logged automatically.
'; loadMoreBtn.style.display = 'none'; return; } let html = ''; if (!append) { html = ''; html += ''; } for (const e of entries) { const ok = e.outcome === 'success'; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ''; if (e.details && Object.keys(e.details).length > 0) { html += ``; } } if (!append) { html += '
WhenIPActionResourceResult
${timeAgo(e.timestamp)}${e.ip || '-'}${e.action || '-'}${e.resource || '-'}${ok ? '✓' : '✗'}
'; container.innerHTML = html; } else { // Append rows to existing table const table = container.querySelector('table'); if (table) table.insertAdjacentHTML('beforeend', html); } currentOffset += entries.length; loadMoreBtn.style.display = entries.length >= PAGE_SIZE ? '' : 'none'; // Toggle detail rows on click container.querySelectorAll('.audit-row').forEach(row => { if (row.dataset.wired) return; row.dataset.wired = 'true'; row.addEventListener('click', () => { const detail = row.nextElementSibling; if (detail && detail.classList.contains('audit-detail')) { detail.style.display = detail.style.display === 'none' ? '' : 'none'; } }); }); } catch (e) { container.innerHTML = `
Failed: ${e.message}
`; } } openBtn?.addEventListener('click', () => { modal?.classList.add('show'); loadAudit(false); }); wireModal(modal, cancelBtn); refreshBtn?.addEventListener('click', () => loadAudit(false)); filterSelect?.addEventListener('change', () => loadAudit(false)); loadMoreBtn?.addEventListener('click', () => loadAudit(true)); clearBtn?.addEventListener('click', async () => { if (!confirm('Clear the entire audit log? This cannot be undone.')) return; try { const res = await secureFetch('/api/v1/audit-logs', { method: 'DELETE' }); const data = await res.json(); if (data.success) loadAudit(false); else showNotification('Error: ' + (data.error || 'Clear failed'), 'error'); } catch (e) { showNotification('Error: ' + e.message, 'error'); } }); })();