Add two-tier IP exemption system; fix scan button to use /api/exempt
- Add EXEMPT_FILE, readExemptions/saveExemptions/isExempt/addExemption/removeExemption - Filter exempt IPs from scan results (still monitored by fail2ban) - buildBanList() appends exempt entries with jail:'exempt' - API: POST /api/exempt, DELETE /api/exempt/:ip, GET /api/exemptions - Frontend: [EXEMPT] filter tab, exempt jail color, REMOVE/ARREST/THREAT actions - Scan card [WHITELIST] button → [EXEMPT] calling /api/exempt (not ignoreip) - Fix isWhitelisted() to read only jail.local with proper CIDR matching - Fix readIgnoreIP() regex: \s* → [ \t]* to prevent cross-line capture Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
<button data-jail="npm-probe">[NPM]</button>
|
||||
<button data-jail="manual-bans">[PRISON]</button>
|
||||
<button data-jail="whitelist">[WHITELIST]</button>
|
||||
<button data-jail="exempt">[EXEMPT]</button>
|
||||
<button data-jail="scan">[SCAN]</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,6 +120,7 @@ async function api(method, url, body) {
|
||||
function scoreColor(score, jail) {
|
||||
if (jail === 'manual-bans') return '#8B4513';
|
||||
if (jail === 'whitelist') return 'var(--green)';
|
||||
if (jail === 'exempt') return 'var(--dim)';
|
||||
if (score == null) return 'var(--dim)';
|
||||
if (score >= 90) return 'var(--red)';
|
||||
if (score <= 20) return 'var(--green2)';
|
||||
@@ -328,8 +330,8 @@ function makeBanCard(b) {
|
||||
? `<span class="score-badge" style="color:${color};border-color:${color}">${b.score}</span>`
|
||||
: '';
|
||||
|
||||
let meta = `<span>JAIL: ${esc(b.jail)}</span>`;
|
||||
if (b.jail !== 'whitelist') {
|
||||
let meta = `<span>JAIL: ${esc(b.jail.toUpperCase())}</span>`;
|
||||
if (b.jail !== 'whitelist' && b.jail !== 'exempt') {
|
||||
meta += `<span>BANNED: ${b.banTime ? esc(b.banTime.slice(5,16)) : '—'}</span>`;
|
||||
meta += `<span>EXPIRES: ${b.unbanTime ? esc(b.unbanTime.slice(5,16)) : '—'}</span>`;
|
||||
}
|
||||
@@ -338,6 +340,10 @@ function makeBanCard(b) {
|
||||
|
||||
const actions = b.jail === 'whitelist'
|
||||
? `<button class="btn-red" onclick="removeWhitelist('${esc(b.ip)}')">[REMOVE]</button>`
|
||||
: b.jail === 'exempt'
|
||||
? `<button class="btn-red" onclick="removeExemption('${esc(b.ip)}')">[REMOVE]</button>
|
||||
<button class="btn-red" onclick="arrest('${esc(b.ip)}')">[ARREST]</button>
|
||||
<button class="btn-amber" onclick="abuseCheck('${esc(b.ip)}',this)">[THREAT]</button>`
|
||||
: `<button onclick="parole('${esc(b.ip)}','${esc(b.jail)}')">[PAROLE]</button>
|
||||
<button class="btn-red" onclick="arrest('${esc(b.ip)}')">[ARREST]</button>
|
||||
<a class="btn" href="/logs/${esc(b.ip)}" target="_blank">[RECORDS]</a>
|
||||
@@ -364,7 +370,7 @@ function makeScanCard(d) {
|
||||
`<div class="card-actions">
|
||||
<a class="btn" href="/logs/${esc(d.ip)}" target="_blank">[RECORDS]</a>
|
||||
<button class="btn-red" onclick="scanBan('${esc(d.ip)}',this)">[BAN]</button>
|
||||
<button onclick="scanWL('${esc(d.ip)}',this)">[WHITELIST]</button>
|
||||
<button onclick="scanWL('${esc(d.ip)}',this)">[EXEMPT]</button>
|
||||
<button class="btn-amber" onclick="scanAbuse('${esc(d.ip)}',this)">[THREAT]</button>
|
||||
</div>`;
|
||||
return card;
|
||||
@@ -386,6 +392,11 @@ async function removeWhitelist(ip) {
|
||||
catch (e) { alert('Remove failed: ' + e.message); }
|
||||
}
|
||||
|
||||
async function removeExemption(ip) {
|
||||
try { await api('DELETE', `/api/exempt/${encodeURIComponent(ip)}`); await loadBans(); }
|
||||
catch (e) { alert('Remove failed: ' + e.message); }
|
||||
}
|
||||
|
||||
async function abuseCheck(ip, btn) {
|
||||
btn.disabled = true; btn.textContent = '[…]';
|
||||
try {
|
||||
@@ -444,11 +455,11 @@ async function scanBan(ip, btn) {
|
||||
async function scanWL(ip, btn) {
|
||||
btn.disabled = true; btn.textContent = '[…]';
|
||||
try {
|
||||
await api('POST', '/api/whitelist', { ip });
|
||||
await api('POST', '/api/exempt', { ip });
|
||||
btn.closest('.card').style.opacity = '.4';
|
||||
btn.textContent = '[LISTED]';
|
||||
btn.textContent = '[EXEMPTED]';
|
||||
loadBans();
|
||||
} catch (e) { alert('Whitelist failed: ' + e.message); btn.disabled = false; btn.textContent = '[WHITELIST]'; }
|
||||
} catch (e) { alert('Exempt failed: ' + e.message); btn.disabled = false; btn.textContent = '[EXEMPT]'; }
|
||||
}
|
||||
|
||||
async function scanAbuse(ip, btn) {
|
||||
|
||||
Reference in New Issue
Block a user