const PAGE_SIZE = 15; const REASONS = { 'honeypot': 'Антиспам-поле заполнено', 'login-bruteforce': 'Подбор пароля (10 неудачных входов)', 'share-password-bruteforce': 'Подбор пароля ссылки', 'manual': 'Вручную', }; let allBans = []; let page = 1; function reasonLabel(r) { return REASONS[r] || r || '—'; } async function loadBans() { const res = await fetch(`${API}/api/bans`, { headers: hdr() }); const tbody = document.getElementById('bansBody'); if (!res.ok) { tbody.innerHTML = `Ошибка загрузки (${res.status})`; allBans = []; renderPager(document.getElementById('pager'), 1, 1, () => {}); return; } allBans = await res.json(); const maxPage = Math.max(1, Math.ceil(allBans.length / PAGE_SIZE)); if (page > maxPage) page = maxPage; renderPage(); } function renderPage() { const tbody = document.getElementById('bansBody'); const empty = document.getElementById('bansEmpty'); const slice = allBans.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); empty.style.display = allBans.length ? 'none' : 'block'; tbody.innerHTML = slice.map(b => { const reason = reasonLabel(b.reason); const rcls = ['honeypot', 'login-bruteforce', 'share-password-bruteforce', 'manual'].includes(b.reason) ? `reason-${b.reason}` : 'reason-manual'; return ` ${esc(b.ip)} ${esc(reason)} ${new Date(b.created_at).toLocaleString('ru')} ${new Date(b.banned_until).toLocaleString('ru')} `; }).join(''); renderPager(document.getElementById('pager'), page, Math.max(1, Math.ceil(allBans.length / PAGE_SIZE)), p => { page = p; renderPage(); }); } document.getElementById('bansBody').addEventListener('click', async e => { const ip = e.target.closest('[data-unban]')?.dataset.unban; if (!ip) return; const res = await fetch(`${API}/api/bans/${encodeURIComponent(ip)}`, { method: 'DELETE', headers: hdr() }); if (res.ok) showToast('IP разблокирован: ' + ip); else showToast('Ошибка разблокировки'); loadBans(); }); function openBanModal() { document.getElementById('bIp').value = ''; document.getElementById('bReason').value = ''; document.getElementById('bHours').value = 24; document.getElementById('banModal').classList.add('open'); } function closeBanModal() { document.getElementById('banModal').classList.remove('open'); } async function saveBan() { const ip = document.getElementById('bIp').value.trim(); const reason = document.getElementById('bReason').value.trim(); const hours = parseInt(document.getElementById('bHours').value, 10) || 24; if (!ip) { showToast('Укажите IP-адрес'); return; } const res = await fetch(`${API}/api/bans`, { method: 'POST', headers: hdrJson(), body: JSON.stringify({ ip, reason: reason || 'manual', hours }), }); const data = await res.json().catch(() => ({})); if (res.ok) { showToast(`IP ${ip} заблокирован на ${hours} ч`); closeBanModal(); loadBans(); } else { showToast(data.error || 'Ошибка блокировки'); } } document.getElementById('addBanBtn').addEventListener('click', openBanModal); document.getElementById('bCancel').addEventListener('click', closeBanModal); document.getElementById('bSave').addEventListener('click', saveBan); document.getElementById('banModal').addEventListener('click', e => { if (e.target.id === 'banModal') closeBanModal(); }); document.getElementById('bIp').addEventListener('keydown', e => { if (e.key === 'Enter') saveBan(); }); document.getElementById('bHours').addEventListener('keydown', e => { if (e.key === 'Enter') saveBan(); }); (async () => { if (await requireAdminPage()) { buildSidebar(document.body.dataset.page); loadBans(); } })();