(function () { const $ = (sel, root = document) => root.querySelector(sel); const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel)); // --- SEARCH / FILTER (client-side) --- const searchInput = $('#search-input'); const rows = () => $$('#bans-tbody tr.ban-row'); const noDataRow = () => $('#bans-tbody tr[data-empty]'); function applySearch() { const q = (searchInput?.value || '').trim().toLowerCase(); let visible = 0; rows().forEach(r => { const text = r.textContent.toLowerCase(); const show = !q || text.includes(q); r.classList.toggle('d-none', !show); if (show) visible++; }); if (!visible && !noDataRow()) { const tr = document.createElement('tr'); tr.setAttribute('data-empty', ''); tr.innerHTML = 'Brak wyników dla podanego filtra'; $('#bans-tbody').appendChild(tr); } else if (visible && noDataRow()) { noDataRow().remove(); } } let t; searchInput?.addEventListener('input', () => { clearTimeout(t); t = setTimeout(applySearch, 120); }); // --- BULK SELECT --- const selectAll = $('#select-all'); const counter = $('#selection-counter'); const delBtn = $('#delete-selected'); const bulkForm = $('#bulk-form'); function currentChecks() { return $$('.row-check'); } function selectedCount() { return currentChecks().filter(c => c.checked).length; } function updateState() { const total = currentChecks().length; const sel = selectedCount(); if (counter) counter.textContent = sel + ' zaznaczonych'; if (delBtn) delBtn.disabled = sel === 0; if (selectAll) { selectAll.checked = sel > 0 && sel === total; selectAll.indeterminate = sel > 0 && sel < total; } } selectAll?.addEventListener('change', () => { currentChecks().forEach(c => (c.checked = selectAll.checked)); updateState(); }); document.addEventListener('change', (e) => { if (e.target.classList?.contains('row-check')) updateState(); }); // Kliknięcie w wiersz przełącza zaznaczenie (poza elementami interaktywnymi) document.addEventListener('click', (e) => { const row = e.target.closest('tr.ban-row'); if (!row) return; if (e.target.closest('input,button,a,label,select,textarea')) return; const cb = row.querySelector('.row-check'); if (cb) { cb.checked = !cb.checked; updateState(); } }); updateState(); // Potwierdzenia akcji bulkForm?.addEventListener('submit', (e) => { const submitter = e.submitter; if (!submitter) return; const sel = selectedCount(); const isDeleteAll = submitter.name === 'delete_all'; const msg = isDeleteAll ? 'Usunąć WSZYSTKIE bany?' : `Usunąć ${sel} wybrane bany?`; if (!confirm(msg)) e.preventDefault(); }); // --- ADD BAN: walidacja klientowa --- const addForm = $('#add-ban-form'); addForm?.addEventListener('submit', (e) => { if (!addForm.checkValidity()) { e.preventDefault(); addForm.classList.add('was-validated'); } }); // --- DETAILS MODAL --- function formatBanDetails(data) { let html = '
'; html += ''; for (let key in data) { let value = data[key]; if (key === 'attack_details' || key === 'geo') { try { const parsed = typeof value === 'string' ? JSON.parse(value) : value; let nested = '
KluczWartość
'; for (let subKey in parsed) nested += ``; nested += '
${subKey}${parsed[subKey]}
'; value = nested; } catch (_) { } } html += `${key}${value}`; } html += '
'; return html; } // Otwórz modal po kliknięciu IP document.addEventListener('click', (e) => { const btn = e.target.closest('.ban-ip'); if (!btn) return; const ip = btn.dataset.ip; const modalEl = document.getElementById('banModal'); const bodyEl = document.getElementById('banModalBody'); const titleEl = document.getElementById('banModalLabel'); if (titleEl) titleEl.textContent = 'Szczegóły bana dla ' + ip; if (bodyEl) { bodyEl.innerHTML = '
'; } fetch('/api/banned/' + encodeURIComponent(ip) + '?full_info=1') .then(r => r.ok ? r.json() : Promise.reject(r.status)) .then(data => { bodyEl.innerHTML = formatBanDetails(data); }) .catch(() => { bodyEl.innerHTML = '
Nie udało się pobrać szczegółów.
'; }) .finally(() => { const m = new bootstrap.Modal(modalEl); m.show(); }); }); document.getElementById('delete-all-btn')?.addEventListener('click', async () => { if (!confirm('Usunąć WSZYSTKIE bany?')) return; try { const res = await fetch('/api/banned/all', { method: 'DELETE' }); if (!res.ok) throw new Error('HTTP ' + res.status); window.showToast?.({ text: 'Wszystkie bany usunięte', variant: 'success' }); location.reload(); } catch (e) { window.showToast?.({ text: 'Nie udało się usunąć wszystkich banów', variant: 'danger' }); } }); const url = new URL(location.href); if (url.searchParams.get('created') === '1') { window.showToast?.({ text: 'Ban został dodany.', variant: 'success' }); } })();