147 lines
6.0 KiB
JavaScript
147 lines
6.0 KiB
JavaScript
(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 = '<td colspan="6" class="text-center py-5 text-secondary">Brak wyników dla podanego filtra</td>';
|
|
$('#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 = '<div class="table-responsive"><table class="table table-bordered table-dark">';
|
|
html += '<thead><tr><th>Klucz</th><th>Wartość</th></tr></thead><tbody>';
|
|
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 = '<table class="table table-sm table-bordered table-dark mb-0">';
|
|
for (let subKey in parsed) nested += `<tr><td>${subKey}</td><td>${parsed[subKey]}</td></tr>`;
|
|
nested += '</table>';
|
|
value = nested;
|
|
} catch (_) { }
|
|
}
|
|
html += `<tr><td>${key}</td><td>${value}</td></tr>`;
|
|
}
|
|
html += '</tbody></table></div>';
|
|
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 = '<div class="placeholder-glow"><span class="placeholder col-12"></span><span class="placeholder col-10"></span></div>';
|
|
}
|
|
|
|
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 = '<div class="text-danger">Nie udało się pobrać szczegółów.</div>'; })
|
|
.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' });
|
|
}
|
|
})();
|