Compare commits

...

4 Commits

Author SHA1 Message Date
Marco Lanzara
9104c67f97 🚀 Release v1.0.121
- Tipo: patch
- Database schema: database-schema/schema.sql (solo struttura)
- Data: 2026-02-17 08:11:26
2026-02-17 08:11:26 +00:00
marco370
d01eca2cf0 Saved progress at the end of the loop
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2b961dac-b073-4f80-8b6f-8bb8c7b26675
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/6WuDAR4
2026-02-17 08:07:59 +00:00
marco370
b7abd340bc Improve logging for Mikrotik requests and IP blocking operations
Enhance logging in `mikrotik.ts` to include request details, response statuses, and timings. Add verbose logging for successful operations and warnings for errors or slow responses. Update `getExistingBlockedIps` to log total entries and specific list counts per router. Modify `addToAddressList` to log successful additions and specific error conditions. Update `bulkBlockIps` to log detailed operation outcomes, including partial and failed IPs, with a final summary. Add router information to the `BLOCK-ALL` log in `routes.ts`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 3945267e-74c4-4c36-912a-462ddd667392
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/6WuDAR4
2026-02-17 08:07:45 +00:00
marco370
0bd84ed2ed Ensure backend services are running and auto-blocking is functional
Add systemd service for Node.js backend, update scripts, and verify service status and auto-block functionality.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ee67fff9-dcaf-42b7-ac9b-297b17ddfdb3
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/6WuDAR4
2026-02-17 07:53:05 +00:00
5 changed files with 154 additions and 26 deletions

View File

@ -0,0 +1,57 @@
echo "=== TEST PORTA 5000 ===" && curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5000/api/health && echo "=== TEST AUTO-BLOCK MANUALE ===" && sudo -u ids /opt/ids/python_ml/venv/bin/python3 /opt/ids/python_ml/auto_block.py 2>&1 && echo "=== STATO TUTTI I SERVIZI ===" && systemctl status ids-backend ids-ml-backend ids-syslog-parser ids-auto-block.timer --no-pager -l
=== TEST PORTA 5000 ===
HTTP 200
=== TEST AUTO-BLOCK MANUALE ===
[2026-02-17 08:51:22] Starting auto-block cycle...
[2026-02-17 08:51:22] Step 1: Detection ML...
[2026-02-17 08:51:22] Detection completata: 0 anomalie rilevate
[2026-02-17 08:51:22] Step 2: Blocco IP critici sui router...
[2026-02-17 08:51:22] 24 IP bloccati sui router, 0 falliti, 0 gia' bloccati
=== STATO TUTTI I SERVIZI ===
● ids-backend.service - IDS Node.js Backend (Express API + Frontend)
Loaded: loaded (/etc/systemd/system/ids-backend.service; enabled; preset: disabled)
Active: active (running) since Tue 2026-02-17 08:51:09 CET; 57s ago
Process: 31307 ExecStartPre=/bin/bash -c test -f /opt/ids/dist/index.js || (echo "ERRORE: dist/index.js non trovato - eseguire npm run build" && exit 1) (code=exited, status=0/SUCCESS)
Main PID: 31308 (node)
Tasks: 11 (limit: 100409)
Memory: 59.1M (max: 1.0G available: 964.8M)
CPU: 1.669s
CGroup: /system.slice/ids-backend.service
└─31308 node dist/index.js
Feb 17 08:51:09 ids.alfacom.it systemd[1]: Starting IDS Node.js Backend (Express API + Frontend)...
Feb 17 08:51:09 ids.alfacom.it systemd[1]: Started IDS Node.js Backend (Express API + Frontend).
● ids-ml-backend.service - IDS ML Backend (FastAPI)
Loaded: loaded (/etc/systemd/system/ids-ml-backend.service; enabled; preset: disabled)
Active: active (running) since Tue 2026-02-17 08:50:14 CET; 1min 51s ago
Main PID: 31127 (python3)
Tasks: 26 (limit: 100409)
Memory: 256.8M (max: 2.0G available: 1.7G)
CPU: 4.073s
CGroup: /system.slice/ids-ml-backend.service
└─31127 /opt/ids/python_ml/venv/bin/python3 main.py
Feb 17 08:50:14 ids.alfacom.it systemd[1]: Started IDS ML Backend (FastAPI).
● ids-syslog-parser.service - IDS Syslog Parser (Network Logs Processor)
Loaded: loaded (/etc/systemd/system/ids-syslog-parser.service; enabled; preset: disabled)
Active: active (running) since Mon 2026-02-16 12:18:52 CET; 20h ago
Main PID: 1069 (python3)
Tasks: 1 (limit: 100409)
Memory: 9.7M (max: 1.0G available: 1014.2M)
CPU: 1h 59min 34.854s
CGroup: /system.slice/ids-syslog-parser.service
└─1069 /opt/ids/python_ml/venv/bin/python3 syslog_parser.py
Feb 16 12:18:52 ids.alfacom.it systemd[1]: Started IDS Syslog Parser (Network Logs Processor).
● ids-auto-block.timer - IDS Auto-Blocking Timer - Run every 5 minutes
Loaded: loaded (/etc/systemd/system/ids-auto-block.timer; enabled; preset: disabled)
Active: active (running) since Mon 2026-02-16 19:24:04 CET; 13h ago
Until: Mon 2026-02-16 19:24:04 CET; 13h ago
Trigger: n/a
Triggers: ● ids-auto-block.service
Docs: https://github.com/yourusername/ids
Feb 16 19:24:04 ids.alfacom.it systemd[1]: Started IDS Auto-Blocking Timer - Run every 5 minutes.

View File

@ -2,7 +2,7 @@
-- PostgreSQL database dump -- PostgreSQL database dump
-- --
\restrict bpQw5VRf29VbJdb1MkmCTB0bxZyLNpiiYQ99AIDje3SrE77G6EnB1VyJyK44tQd \restrict eyisBUJGZpxqwUy3cADDz6PSlf2SjvYWDM3PJH885ta9lWx9pkI2q38QNzdHOVh
-- Dumped from database version 16.11 (df20cf9) -- Dumped from database version 16.11 (df20cf9)
-- Dumped by pg_dump version 16.10 -- Dumped by pg_dump version 16.10
@ -387,5 +387,5 @@ ALTER TABLE ONLY public.public_blacklist_ips
-- PostgreSQL database dump complete -- PostgreSQL database dump complete
-- --
\unrestrict bpQw5VRf29VbJdb1MkmCTB0bxZyLNpiiYQ99AIDje3SrE77G6EnB1VyJyK44tQd \unrestrict eyisBUJGZpxqwUy3cADDz6PSlf2SjvYWDM3PJH885ta9lWx9pkI2q38QNzdHOVh

View File

@ -1,3 +1,5 @@
const VERBOSE = process.env.MIKROTIK_DEBUG === '1' || process.env.MIKROTIK_DEBUG === 'true';
interface RouterConfig { interface RouterConfig {
id: string; id: string;
ipAddress: string; ipAddress: string;
@ -26,6 +28,7 @@ async function mikrotikRequest(
const protocol = useHttps ? "https" : "http"; const protocol = useHttps ? "https" : "http";
const url = `${protocol}://${router.ipAddress}:${router.apiPort}${path}`; const url = `${protocol}://${router.ipAddress}:${router.apiPort}${path}`;
const auth = Buffer.from(`${router.username}:${router.password}`).toString("base64"); const auth = Buffer.from(`${router.username}:${router.password}`).toString("base64");
const startTime = Date.now();
const origTlsReject = process.env.NODE_TLS_REJECT_UNAUTHORIZED; const origTlsReject = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
if (useHttps) { if (useHttps) {
@ -60,9 +63,24 @@ async function mikrotikRequest(
data = text; data = text;
} }
const elapsed = Date.now() - startTime;
if (VERBOSE) {
const bodyStr = body ? ` body=${JSON.stringify(body)}` : '';
const dataPreview = typeof data === 'string' ? data.substring(0, 200) : JSON.stringify(data).substring(0, 200);
console.log(`[MIKROTIK] ${method} ${url} => HTTP ${response.status} (${elapsed}ms)${bodyStr} response=${dataPreview}`);
} else if (response.status >= 400) {
const dataPreview = typeof data === 'string' ? data.substring(0, 100) : JSON.stringify(data).substring(0, 100);
console.warn(`[MIKROTIK] ${method} ${router.ipAddress}${path} => HTTP ${response.status} (${elapsed}ms) err=${dataPreview}`);
} else if (elapsed > 5000) {
console.warn(`[MIKROTIK] SLOW: ${method} ${router.ipAddress}${path} => HTTP ${response.status} (${elapsed}ms)`);
}
return { status: response.status, data }; return { status: response.status, data };
} catch (error: any) { } catch (error: any) {
clearTimeout(timeout); clearTimeout(timeout);
const elapsed = Date.now() - startTime;
const errMsg = error.name === 'AbortError' ? `TIMEOUT after ${timeoutMs}ms` : error.message;
console.error(`[MIKROTIK] ${method} ${url} => ERRORE: ${errMsg} (${elapsed}ms)`);
if (useHttps && origTlsReject !== undefined) { if (useHttps && origTlsReject !== undefined) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = origTlsReject; process.env.NODE_TLS_REJECT_UNAUTHORIZED = origTlsReject;
} else if (useHttps) { } else if (useHttps) {
@ -94,19 +112,26 @@ export async function getExistingBlockedIps(
listName: string = "ddos_blocked" listName: string = "ddos_blocked"
): Promise<Set<string>> { ): Promise<Set<string>> {
try { try {
if (VERBOSE) console.log(`[MIKROTIK] Fetching address-list da router ${router.ipAddress} (list=${listName}, timeout=20s)...`);
const { status, data } = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list", undefined, 20000); const { status, data } = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list", undefined, 20000);
if (status === 200 && Array.isArray(data)) { if (status === 200 && Array.isArray(data)) {
const ips = new Set<string>(); const ips = new Set<string>();
const allLists = new Map<string, number>();
for (const entry of data) { for (const entry of data) {
const count = allLists.get(entry.list) || 0;
allLists.set(entry.list, count + 1);
if (entry.list === listName) { if (entry.list === listName) {
ips.add(entry.address); ips.add(entry.address);
} }
} }
const listsInfo = Array.from(allLists.entries()).map(([name, count]) => `${name}:${count}`).join(', ');
console.log(`[MIKROTIK] Router ${router.ipAddress}: ${data.length} entries totali (${listsInfo}), ${ips.size} in list "${listName}"`);
return ips; return ips;
} }
console.warn(`[MIKROTIK] Router ${router.ipAddress}: risposta inattesa status=${status}, data non e' array`);
return new Set(); return new Set();
} catch (e: any) { } catch (e: any) {
console.error(`[MIKROTIK] Failed to get address-list from ${router.ipAddress}: ${e.message}`); console.error(`[MIKROTIK] Router ${router.ipAddress}: ERRORE fetch address-list: ${e.message}`);
return new Set(); return new Set();
} }
} }
@ -127,41 +152,53 @@ export async function addToAddressList(
}); });
if (status === 200 || status === 201) { if (status === 200 || status === 201) {
if (VERBOSE) console.log(`[BLOCK] OK: ${ipAddress} aggiunto su router ${router.ipAddress} (HTTP ${status})`);
return { routerIp: router.ipAddress, success: true }; return { routerIp: router.ipAddress, success: true };
} }
if (status === 400 || status === 409) { if (status === 400 || status === 409) {
const text = typeof data === "string" ? data.toLowerCase() : JSON.stringify(data).toLowerCase(); const text = typeof data === "string" ? data.toLowerCase() : JSON.stringify(data).toLowerCase();
if (text.includes("already") || text.includes("exists") || text.includes("duplicate") || text.includes("failure: already")) { if (text.includes("already") || text.includes("exists") || text.includes("duplicate") || text.includes("failure: already")) {
if (VERBOSE) console.log(`[BLOCK] SKIP: ${ipAddress} gia' presente su router ${router.ipAddress} (HTTP ${status})`);
return { routerIp: router.ipAddress, success: true, alreadyExists: true }; return { routerIp: router.ipAddress, success: true, alreadyExists: true };
} }
console.warn(`[BLOCK] VERIFICA: ${ipAddress} su router ${router.ipAddress} HTTP ${status} risposta="${text.substring(0, 150)}", verifico lista...`);
try { try {
const verifyResult = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list"); const verifyResult = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list");
if (verifyResult.status === 200 && Array.isArray(verifyResult.data)) { if (verifyResult.status === 200 && Array.isArray(verifyResult.data)) {
for (const entry of verifyResult.data) { for (const entry of verifyResult.data) {
if (entry.address === ipAddress && entry.list === listName) { if (entry.address === ipAddress && entry.list === listName) {
console.log(`[BLOCK] CONFERMATO: ${ipAddress} trovato nella lista di router ${router.ipAddress} dopo verifica`);
return { routerIp: router.ipAddress, success: true, alreadyExists: true }; return { routerIp: router.ipAddress, success: true, alreadyExists: true };
} }
} }
} }
} catch {} } catch (verifyErr: any) {
console.error(`[BLOCK] ERRORE verifica: ${ipAddress} su router ${router.ipAddress}: ${verifyErr.message}`);
}
const errMsg = `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`;
console.error(`[BLOCK] FALLITO: ${ipAddress} su router ${router.ipAddress}: ${errMsg}`);
return { return {
routerIp: router.ipAddress, routerIp: router.ipAddress,
success: false, success: false,
error: `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`, error: errMsg,
}; };
} }
const errMsg = `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`;
console.error(`[BLOCK] FALLITO: ${ipAddress} su router ${router.ipAddress}: ${errMsg}`);
return { return {
routerIp: router.ipAddress, routerIp: router.ipAddress,
success: false, success: false,
error: `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`, error: errMsg,
}; };
} catch (error: any) { } catch (error: any) {
const errMsg = error.name === 'AbortError' ? `TIMEOUT (8s)` : (error.message || "Connection failed");
console.error(`[BLOCK] ERRORE: ${ipAddress} su router ${router.ipAddress}: ${errMsg}`);
return { return {
routerIp: router.ipAddress, routerIp: router.ipAddress,
success: false, success: false,
error: error.message || "Connection failed", error: errMsg,
}; };
} }
} }
@ -172,8 +209,10 @@ export async function removeFromAddressList(
listName: string = "ddos_blocked" listName: string = "ddos_blocked"
): Promise<BlockResult> { ): Promise<BlockResult> {
try { try {
if (VERBOSE) console.log(`[UNBLOCK] Rimozione ${ipAddress} da router ${router.ipAddress} (list=${listName})...`);
const { status, data } = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list"); const { status, data } = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list");
if (status !== 200 || !Array.isArray(data)) { if (status !== 200 || !Array.isArray(data)) {
console.error(`[UNBLOCK] ERRORE: impossibile leggere address-list da router ${router.ipAddress}: HTTP ${status}`);
return { routerIp: router.ipAddress, success: false, error: "Failed to read address list" }; return { routerIp: router.ipAddress, success: false, error: "Failed to read address list" };
} }
@ -182,14 +221,18 @@ export async function removeFromAddressList(
const entryId = entry[".id"]; const entryId = entry[".id"];
const delResult = await mikrotikRequest(router, "DELETE", `/rest/ip/firewall/address-list/${entryId}`); const delResult = await mikrotikRequest(router, "DELETE", `/rest/ip/firewall/address-list/${entryId}`);
if (delResult.status === 200 || delResult.status === 204) { if (delResult.status === 200 || delResult.status === 204) {
console.log(`[UNBLOCK] OK: ${ipAddress} rimosso da router ${router.ipAddress}`);
return { routerIp: router.ipAddress, success: true }; return { routerIp: router.ipAddress, success: true };
} }
console.error(`[UNBLOCK] FALLITO: eliminazione ${ipAddress} da router ${router.ipAddress}: HTTP ${delResult.status}`);
return { routerIp: router.ipAddress, success: false, error: `Delete failed: ${delResult.status}` }; return { routerIp: router.ipAddress, success: false, error: `Delete failed: ${delResult.status}` };
} }
} }
if (VERBOSE) console.log(`[UNBLOCK] ${ipAddress} non trovato su router ${router.ipAddress} (gia' assente)`);
return { routerIp: router.ipAddress, success: true }; return { routerIp: router.ipAddress, success: true };
} catch (error: any) { } catch (error: any) {
console.error(`[UNBLOCK] ERRORE: ${ipAddress} su router ${router.ipAddress}: ${error.message}`);
return { routerIp: router.ipAddress, success: false, error: error.message }; return { routerIp: router.ipAddress, success: false, error: error.message };
} }
} }
@ -281,6 +324,8 @@ export async function bulkBlockIps(
let blocked = 0; let blocked = 0;
let failed = 0; let failed = 0;
const details: Array<{ ip: string; status: string }> = []; const details: Array<{ ip: string; status: string }> = [];
const partialIps: string[] = [];
const failedIps: string[] = [];
async function processIp(ip: string) { async function processIp(ip: string) {
const routerResults = await Promise.allSettled( const routerResults = await Promise.allSettled(
@ -289,7 +334,7 @@ export async function bulkBlockIps(
if (existing.has(ip)) { if (existing.has(ip)) {
const st = routerStatus.get(router.ipAddress); const st = routerStatus.get(router.ipAddress);
if (st) st.skip++; if (st) st.skip++;
return true; return { success: true, skipped: true, routerIp: router.ipAddress };
} }
const start = Date.now(); const start = Date.now();
const result = await addToAddressList(router, ip, listName, `${commentPrefix} ${ip}`, timeoutDuration); const result = await addToAddressList(router, ip, listName, `${commentPrefix} ${ip}`, timeoutDuration);
@ -299,33 +344,52 @@ export async function bulkBlockIps(
if (st) st.ok++; if (st) st.ok++;
} else { } else {
if (st) st.fail++; if (st) st.fail++;
if (elapsed > 5000) {
console.warn(`[BULK-BLOCK] SLOW: Router ${router.ipAddress} took ${elapsed}ms for IP ${ip}: ${result.error}`);
}
} }
return result.success; return { success: result.success, skipped: false, routerIp: router.ipAddress, elapsed, error: result.error };
}) })
); );
const perRouterDetail = routerResults.map((r) => {
if (r.status === 'fulfilled') {
const v = r.value;
if (v.skipped) return `${v.routerIp}:SKIP`;
if (v.success) return `${v.routerIp}:OK(${v.elapsed}ms)`;
return `${v.routerIp}:FAIL(${v.elapsed}ms,${v.error})`;
}
return 'REJECTED';
}).join(' | ');
const anySuccess = routerResults.some( const anySuccess = routerResults.some(
(r) => r.status === "fulfilled" && r.value === true (r) => r.status === "fulfilled" && r.value.success
);
const allSuccess = routerResults.every(
(r) => r.status === "fulfilled" && r.value.success
); );
if (anySuccess) { if (anySuccess) {
blocked++; blocked++;
details.push({ ip, status: "blocked" }); details.push({ ip, status: "blocked" });
if (!allSuccess) {
partialIps.push(ip);
if (VERBOSE) console.warn(`[BULK-BLOCK] PARZIALE: IP ${ip}: ${perRouterDetail}`);
}
} else { } else {
failed++; failed++;
failedIps.push(ip);
details.push({ ip, status: "failed" }); details.push({ ip, status: "failed" });
if (VERBOSE) console.error(`[BULK-BLOCK] FALLITO: IP ${ip}: ${perRouterDetail}`);
} }
} }
const bulkStart = Date.now();
for (let i = 0; i < newIps.length; i += concurrency) { for (let i = 0; i < newIps.length; i += concurrency) {
const batch = newIps.slice(i, i + concurrency); const batch = newIps.slice(i, i + concurrency);
await Promise.allSettled(batch.map((ip) => processIp(ip))); await Promise.allSettled(batch.map((ip) => processIp(ip)));
if ((i + concurrency) % 50 === 0 || i + concurrency >= newIps.length) { const progress = Math.min(i + concurrency, newIps.length);
console.log(`[BULK-BLOCK] Progress: ${Math.min(i + concurrency, newIps.length)}/${newIps.length}`); if (progress === newIps.length || progress % 50 === 0) {
const elapsed = ((Date.now() - bulkStart) / 1000).toFixed(1);
console.log(`[BULK-BLOCK] Progress: ${progress}/${newIps.length} (${elapsed}s, ${blocked} ok, ${failed} fail)`);
} }
} }
@ -333,11 +397,17 @@ export async function bulkBlockIps(
details.push({ ip, status: "already_blocked" }); details.push({ ip, status: "already_blocked" });
} }
// Report per-router const totalElapsed = ((Date.now() - bulkStart) / 1000).toFixed(1);
routerStatus.forEach((st, routerIp) => { routerStatus.forEach((st, routerIp) => {
console.log(`[BULK-BLOCK] Router ${routerIp}: ${st.ok} blocked, ${st.fail} failed, ${st.skip} skipped`); console.log(`[BULK-BLOCK] Router ${routerIp}: ${st.ok} blocked, ${st.fail} failed, ${st.skip} skipped`);
}); });
console.log(`[BULK-BLOCK] Done: ${blocked} blocked, ${failed} failed, ${skippedIps.length} skipped`); console.log(`[BULK-BLOCK] Completato in ${totalElapsed}s: ${blocked} blocked, ${failed} failed, ${skippedIps.length} already_blocked, ${partialIps.length} parziali`);
if (failedIps.length > 0) {
console.error(`[BULK-BLOCK] IP non bloccati su nessun router (${failedIps.length}): ${failedIps.slice(0, 20).join(', ')}${failedIps.length > 20 ? '...' : ''}`);
}
if (partialIps.length > 0) {
console.warn(`[BULK-BLOCK] IP bloccati solo parzialmente (${partialIps.length}): ${partialIps.slice(0, 20).join(', ')}${partialIps.length > 20 ? '...' : ''}`);
}
return { blocked, failed, skipped: skippedIps.length, details }; return { blocked, failed, skipped: skippedIps.length, details };
} }

View File

@ -672,7 +672,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
const ipList = rows.map((r: any) => r.source_ip); const ipList = rows.map((r: any) => r.source_ip);
console.log(`[BLOCK-ALL] Avvio blocco massivo: ${ipList.length}/${totalUnblocked} IP con score >= ${min_score} su ${enabledRouters.length} router`); const routerInfo = enabledRouters.map((r: any) => `${r.name || r.ipAddress}(${r.ipAddress}:${r.apiPort})`).join(', ');
console.log(`[BLOCK-ALL] Avvio blocco massivo: ${ipList.length}/${totalUnblocked} IP con score >= ${min_score} su ${enabledRouters.length} router: ${routerInfo}`);
const result = await bulkBlockIps( const result = await bulkBlockIps(
enabledRouters as any, enabledRouters as any,

View File

@ -1,7 +1,13 @@
{ {
"version": "1.0.120", "version": "1.0.121",
"lastUpdate": "2026-02-17T07:48:15.846Z", "lastUpdate": "2026-02-17T08:11:26.352Z",
"changelog": [ "changelog": [
{
"version": "1.0.121",
"date": "2026-02-17",
"type": "patch",
"description": "Deployment automatico v1.0.121"
},
{ {
"version": "1.0.120", "version": "1.0.120",
"date": "2026-02-17", "date": "2026-02-17",
@ -295,12 +301,6 @@
"date": "2025-11-25", "date": "2025-11-25",
"type": "patch", "type": "patch",
"description": "Deployment automatico v1.0.72" "description": "Deployment automatico v1.0.72"
},
{
"version": "1.0.71",
"date": "2025-11-25",
"type": "patch",
"description": "Deployment automatico v1.0.71"
} }
] ]
} }