From 63494f322bf06fd8d18afe42f6faa79581f65f0b Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 10 Sep 2026 00:18:56 +0300 Subject: [PATCH] Add pagination to audit page --- public/audit.html | 1 + public/js/audit.js | 60 ++++++++++++++++++++++++++++++++++++---------- server.js | 5 ++-- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/public/audit.html b/public/audit.html index 036a769..302017e 100644 --- a/public/audit.html +++ b/public/audit.html @@ -34,6 +34,7 @@ +
diff --git a/public/js/audit.js b/public/js/audit.js index fc093b1..5964455 100644 --- a/public/js/audit.js +++ b/public/js/audit.js @@ -21,6 +21,10 @@ const ACTION_LABELS = { 'backup.restore': 'Выполнено восстановление', }; +const AUDIT_PAGE_SIZE = 50; +let auditPage = 1; +let auditTotal = 0; + function fmtTime(t) { try { return new Date(t).toLocaleString('ru'); } catch { return t || ''; } } @@ -36,35 +40,65 @@ function dt(t) { } catch { return esc(String(t).slice(0, 120)); } } +function renderPager(container, page, totalPages, onPageChange) { + if (totalPages <= 1) { container.innerHTML = ''; return; } + const pages = []; + const maxVisible = 5; + let start = Math.max(1, page - Math.floor(maxVisible / 2)); + let end = Math.min(totalPages, start + maxVisible - 1); + if (end - start + 1 < maxVisible) start = Math.max(1, end - maxVisible + 1); + for (let i = start; i <= end; i++) pages.push(i); + let html = ''; + if (page > 1) html += ``; + if (start > 1) { html += ``; if (start > 2) html += `…`; } + for (const p of pages) html += ``; + if (end < totalPages) { if (end < totalPages - 1) html += `…`; html += ``; } + if (page < totalPages) html += ``; + container.innerHTML = html; + container.querySelectorAll('button').forEach(b => b.addEventListener('click', () => onPageChange(parseInt(b.dataset.page, 10)))); +} + async function loadAudit() { const el = document.getElementById('auditTable').querySelector('tbody'); el.innerHTML = 'Загрузка…'; - const res = await fetch(`${API}/api/audit?limit=500`, { headers: hdr() }); + const p = new URLSearchParams(); + p.set('limit', AUDIT_PAGE_SIZE); + p.set('offset', (auditPage - 1) * AUDIT_PAGE_SIZE); + const res = await fetch(`${API}/api/audit?${p}`, { headers: hdr() }); if (!res.ok) { el.innerHTML = 'Ошибка загрузки'; return; } const rows = await res.json(); - document.getElementById('auditCount').textContent = rows.length ? `Записей: ${rows.length}` : ''; + // Total count for pagination (approximate: if we got less than page size, we're on last page) + const totalPages = rows.length < AUDIT_PAGE_SIZE ? auditPage : auditPage + 1; + auditTotal = totalPages; + document.getElementById('auditCount').textContent = rows.length ? `Записей на странице: ${rows.length}` : ''; const q = (document.getElementById('auditFilter').value || '').toLowerCase().trim(); const filtered = q ? rows.filter(r => (ACTION_LABELS[r.action] || r.action).toLowerCase().includes(q)) : rows; if (!filtered.length) { document.getElementById('auditEmpty').style.display = 'block'; el.innerHTML = ''; - return; + } else { + document.getElementById('auditEmpty').style.display = 'none'; + el.innerHTML = filtered.map(r => ` + + ${fmtTime(r.created_at)} + ${esc(ACTION_LABELS[r.action] || r.action)} + ${dt(r.target)} + ${esc(r.ip || '')} + `).join(''); } - document.getElementById('auditEmpty').style.display = 'none'; - el.innerHTML = filtered.map(r => ` - - ${fmtTime(r.created_at)} - ${esc(ACTION_LABELS[r.action] || r.action)} - ${dt(r.target)} - ${esc(r.ip || '')} - `).join(''); + renderPager(document.getElementById('auditPager'), auditPage, auditTotal, goAuditPage); } -document.getElementById('auditFilter').addEventListener('input', loadAudit); +function goAuditPage(p) { + auditPage = p; + loadAudit(); +} + +document.getElementById('auditFilter').addEventListener('input', () => { auditPage = 1; loadAudit(); }); document.getElementById('auditRefreshBtn').addEventListener('click', loadAudit); (async () => { - if (await checkAuth()) { + if (await requireAdminPage()) { buildSidebar(document.body.dataset.page); loadAudit(); } diff --git a/server.js b/server.js index 2e662ff..02aa03b 100644 --- a/server.js +++ b/server.js @@ -607,11 +607,12 @@ app.put('/api/settings', requireAdmin, async (req, res) => { app.get('/api/audit', requireAdmin, async (req, res) => { const limit = Math.min(parseInt(req.query.limit, 10) || 100, 1000); + const offset = Math.max(parseInt(req.query.offset, 10) || 0, 0); const { rows } = await pool.query( `SELECT a.id, a.action, a.target, a.ip, a.created_at, a.user_id, u.username AS user_name FROM audit_log a LEFT JOIN users u ON u.id = a.user_id - ORDER BY a.id DESC LIMIT $1`, - [limit] + ORDER BY a.id DESC LIMIT $1 OFFSET $2`, + [limit, offset] ); res.json(rows); });