<?php ?> <script nonce="<?= $cspNonce ?> "> (function(){ var panel = document.getElementById('panel-wd-enforce'); if(!panel) return; panel.innerHTML = '<div class="wd-toolbar">' + '<button id="wd-enf-refresh" class="btn btn-sm">Refresh</button>' + '<button id="wd-enf-add" class="btn btn-sm btn-danger">+ Block IP</button>' + '</div>' + '<div class="wd-stats-row" id="wd-enf-stats"></div>' + '<table class="wd-table"><thead><tr><th>IP</th><th>Fingerprint</th><th>Reason</th><th>Severity</th><th>Proxy</th><th>Filter</th><th>Expires</th><th>Actions</th></tr></thead><tbody id="wd-enf-tbody"></tbody></table>' + '<div id="wd-enf-modal" class="wd-modal" style="display:none">' + '<div class="wd-modal-content">' + '<h3>Block IP</h3>' + '<input id="wd-enf-ip" class="form-input" placeholder="IP Address" />' + '<input id="wd-enf-reason" class="form-input" placeholder="Reason" />' + '<select id="wd-enf-severity" class="form-select"><option value="block">Block</option><option value="blackhole">Blackhole (all nodes)</option><option value="warn">Warn only</option></select>' + '<select id="wd-enf-expiry" class="form-select"><option value="">Permanent</option><option value="1h">1 hour</option><option value="24h">24 hours</option><option value="7d">7 days</option><option value="30d">30 days</option></select>' + '<div class="wd-modal-actions"><button id="wd-enf-submit" class="btn btn-danger">Block</button><button id="wd-enf-cancel" class="btn">Cancel</button></div>' + '</div>' + '</div>'; var tbody = document.getElementById('wd-enf-tbody'); var modal = document.getElementById('wd-enf-modal'); document.getElementById('wd-enf-add').addEventListener('click', function(){ modal.style.display='flex'; }); document.getElementById('wd-enf-cancel').addEventListener('click', function(){ modal.style.display='none'; }); document.getElementById('wd-enf-submit').addEventListener('click', function(){ var ip = document.getElementById('wd-enf-ip').value.trim(); var reason = document.getElementById('wd-enf-reason').value.trim(); var sev = document.getElementById('wd-enf-severity').value; var exp = document.getElementById('wd-enf-expiry').value; if(!ip){ alert('IP required'); return; } var fd = new FormData(); fd.append('_csrf', window._csrf||''); fd.append('action', 'watchdog_blackhole'); fd.append('ip', ip); fd.append('reason', reason || 'Manual block'); fd.append('severity', sev); fd.append('expiry', exp); fetch('system/verify.php', {method:'POST', body:fd, credentials:'same-origin'}) .then(function(r){return r.json()}) .then(function(d){ modal.style.display='none'; alert(d.ok ? 'Blocked on '+(d.nodes?JSON.stringify(d.nodes):'origin') : (d.error||'Failed')); loadBlocklist(); }).catch(function(){alert('Network error')}); }); function loadBlocklist(){ fetch('api/system/pulse.php?scope=watchdog_blocklist&_t='+Date.now(), {credentials:'same-origin'}) .then(function(r){return r.json()}) .then(function(d){ if(d.stats){ document.getElementById('wd-enf-stats').innerHTML = '<div class="wd-stat-card">'+d.stats.total+' Total</div>' + '<div class="wd-stat-card wd-critical">'+d.stats.blackhole+' Blackholed</div>' + '<div class="wd-stat-card wd-high">'+d.stats.blocked+' Blocked</div>' + '<div class="wd-stat-card wd-medium">'+d.stats.warned+' Warned</div>'; } tbody.innerHTML = ''; if(!d.entries) return; d.entries.forEach(function(e){ var tr = document.createElement('tr'); tr.innerHTML = '<td>'+e.ip+'</td>' + '<td>'+(e.fp||'').substring(0,12)+'</td>' + '<td>'+e.reason+'</td>' + '<td><span class="wd-badge wd-'+e.severity+'">'+e.severity+'</span></td>' + '<td>'+(e.proxy?'✓':'✗')+'</td>' + '<td>'+(e.filter?'✓':'✗')+'</td>' + '<td>'+(e.expires||'Permanent')+'</td>' + '<td><button class="btn btn-xs wd-unblock" data-ip="'+e.ip+'">Unblock</button></td>'; tbody.appendChild(tr); }); tbody.querySelectorAll('.wd-unblock').forEach(function(b){ b.addEventListener('click', function(){ if(!confirm('Unblock '+b.dataset.ip+'?')) return; var fd = new FormData(); fd.append('_csrf', window._csrf||''); fd.append('action', 'watchdog_unblock'); fd.append('ip', b.dataset.ip); fetch('system/verify.php', {method:'POST', body:fd, credentials:'same-origin'}) .then(function(r){return r.json()}).then(function(d){ if(d.ok) loadBlocklist(); }); }); }); }).catch(function(){}); } document.getElementById('wd-enf-refresh').addEventListener('click', loadBlocklist); loadBlocklist(); })(); </script>