Compare commits

..

9 Commits

Author SHA1 Message Date
Marco Lanzara
f6d656ce14 🚀 Release v1.0.122
- Tipo: patch
- Database schema: database-schema/schema.sql (solo struttura)
- Data: 2026-02-17 09:13:40
2026-02-17 09:13:40 +00:00
marco370
17dc79372e 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: 5abe4a2e-1608-4c5e-a264-50329bac4934
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/s2eMVCL
2026-02-17 09:10:10 +00:00
marco370
4118d60d6d Update service monitoring to display detailed status and health
Refactor the services page to dynamically fetch and display the status of various systemd services and timers, improving the observability of the application's backend components.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 48245392-3f34-4eac-aeaf-99e52684ddf2
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/s2eMVCL
2026-02-17 09:09:26 +00:00
marco370
6ce60ed5d3 Improve router connectivity checks and logging efficiency
Refactor logging configuration to reduce noise from slow or failed router connections, consolidating error details and improving the handling of unreachable routers.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2b9a2559-b0f6-49cd-b8db-eb24672eef5e
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:35:42 +00:00
marco370
fe113d5518 Improve error handling and logging for router operations
Implement a circuit breaker pattern for router connections and optimize logging to reduce noise from unresponsive routers.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 85f7e8cc-b58e-4a55-87f1-84eb08509a81
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:19:02 +00:00
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
8 changed files with 888 additions and 425 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

@ -0,0 +1,85 @@
tail -f /var/log/ids/backend.log
9:21:00 AM [express] GET /api/detections 304 in 19ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:21:01 AM [express] GET /api/services/status 200 in 29ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:21:02 AM [express] GET /api/routers 200 in 3ms :: [{"id":"c6904896-59dc-4060-952f-0e55df7dee4a","n…
9:21:12 AM [express] PUT /api/routers/c6904896-59dc-4060-952f-0e55df7dee4a 200 in 8ms :: {"id":"c690…
9:21:12 AM [express] GET /api/routers 304 in 2ms :: [{"id":"c6904896-59dc-4060-952f-0e55df7dee4a","n…
9:22:16 AM [express] POST /api/ml/block-all-critical 200 in 99ms :: {"message":"Nessun IP critico da…
9:24:17 AM [express] POST /api/ml/block-all-critical 200 in 75ms :: {"message":"Nessun IP critico da…
 Using standard PostgreSQL database
9:26:18 AM [express] serving on port 5000
✅ Database connection successful
9:28:19 AM [express] POST /api/ml/block-all-critical 200 in 93ms :: {"message":"Nessun IP critico da…
9:30:19 AM [express] POST /api/ml/block-all-critical 200 in 82ms :: {"message":"Nessun IP critico da…
9:30:25 AM [express] GET /api/training-history 200 in 6ms :: [{"id":"7570df54-8169-4fb2-abdc-e9c1bfa…
9:30:29 AM [express] GET /api/detections 200 in 24ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:30:29 AM [express] GET /api/whitelist 200 in 259ms :: {"items":[{"id":"49b5b9a9-4683-452c-a784-fc5…
9:30:37 AM [express] GET /api/services/status 200 in 22ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:30:39 AM [express] POST /api/services/ids-syslog-parser/start 500 in 22ms :: {"error":"Service con…
9:30:42 AM [express] GET /api/services/status 200 in 21ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:30:47 AM [express] GET /api/services/status 200 in 18ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:30:52 AM [express] GET /api/services/status 200 in 17ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:30:57 AM [express] GET /api/services/status 200 in 17ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:02 AM [express] GET /api/services/status 200 in 17ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:07 AM [express] GET /api/services/status 200 in 23ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:12 AM [express] GET /api/services/status 200 in 18ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:16 AM [express] GET /api/services/status 200 in 18ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:21 AM [express] GET /api/services/status 200 in 17ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:26 AM [express] GET /api/services/status 200 in 17ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:27 AM [express] GET /api/detections 304 in 11ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:31:27 AM [express] GET /api/whitelist 200 in 229ms :: {"items":[{"id":"91108765-7ef6-4e49-87d8-319…
9:31:30 AM [express] GET /api/public-lists 200 in 5ms :: [{"id":"2a835798-02a7-4129-a528-d39a026ad15…
9:31:32 AM [express] GET /api/services/status 200 in 19ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:37 AM [express] GET /api/services/status 200 in 20ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:42 AM [express] GET /api/services/status 200 in 19ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:47 AM [express] GET /api/services/status 200 in 18ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:52 AM [express] GET /api/services/status 200 in 17ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:31:57 AM [express] GET /api/services/status 200 in 21ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:32:02 AM [express] GET /api/services/status 200 in 18ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:32:07 AM [express] GET /api/services/status 200 in 24ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:32:12 AM [express] GET /api/services/status 200 in 24ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:32:17 AM [express] GET /api/services/status 200 in 23ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:32:25 AM [express] GET /api/services/status 200 in 2875ms :: {"services":{"mlBackend":{"name":"ML …
[BLOCK-ALL] Avvio blocco massivo: 11/11 IP con score >= 80 su 2 router
[BULK-BLOCK] Starting: 11 IPs on 2 routers (10.20.30.100, 185.203.24.2)
[BULK-BLOCK] Router 185.203.24.2: 124 IPs already in list (50ms)
9:32:30 AM [express] GET /api/services/status 200 in 31ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:32:35 AM [express] GET /api/services/status 200 in 31ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:32:40 AM [express] GET /api/services/status 200 in 46ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:32:46 AM [express] GET /api/services/status 200 in 75ms :: {"services":{"mlBackend":{"name":"ML Ba…
[MIKROTIK] Failed to get address-list from 10.20.30.100: This operation was aborted
[BULK-BLOCK] Router 10.20.30.100: 0 IPs already in list (20004ms)
[BULK-BLOCK] 0 already blocked, 11 new to block
9:32:51 AM [express] GET /api/services/status 200 in 40ms :: {"services":{"mlBackend":{"name":"ML Ba…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 87.17.182.32: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 185.98.164.31: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 109.115.163.146: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 2.118.225.238: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 74.125.99.38: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 192.178.112.96: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 74.125.111.102: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 212.14.142.214: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8000ms for IP 89.96.215.146: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 172.217.39.230: This operation was aborted
9:32:56 AM [express] GET /api/services/status 200 in 44ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:01 AM [express] GET /api/services/status 200 in 46ms :: {"services":{"mlBackend":{"name":"ML Ba…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 104.19.148.8: This operation was aborted
[BULK-BLOCK] Progress: 11/11
[BULK-BLOCK] Router 10.20.30.100: 0 blocked, 11 failed, 0 skipped
[BULK-BLOCK] Router 185.203.24.2: 11 blocked, 0 failed, 0 skipped
[BULK-BLOCK] Done: 11 blocked, 0 failed, 0 skipped
[BLOCK-ALL] Database aggiornato: 11 IP marcati come bloccati
9:33:03 AM [express] POST /api/ml/block-all-critical 200 in 36137ms :: {"message":"Blocco massivo co…
9:33:07 AM [express] GET /api/services/status 200 in 1125ms :: {"services":{"mlBackend":{"name":"ML …
9:33:12 AM [express] GET /api/services/status 200 in 40ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:17 AM [express] GET /api/services/status 200 in 56ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:22 AM [express] GET /api/services/status 200 in 58ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:27 AM [express] GET /api/services/status 200 in 57ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:32 AM [express] GET /api/services/status 200 in 57ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:37 AM [express] GET /api/services/status 200 in 52ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:42 AM [express] GET /api/services/status 200 in 77ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:48 AM [express] GET /api/services/status 200 in 50ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:53 AM [express] GET /api/services/status 200 in 72ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:33:58 AM [express] GET /api/services/status 200 in 69ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:34:03 AM [express] GET /api/services/status 200 in 64ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:34:08 AM [express] GET /api/services/status 200 in 79ms :: {"services":{"mlBackend":{"name":"ML Ba…

View File

@ -0,0 +1,224 @@
tail -f /var/log/ids/backend.log
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8000ms for IP 188.8.66.34: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 151.101.193.229: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 109.205.211.40: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 151.59.33.110: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 151.73.209.26: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 79.8.248.29: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 217.202.28.18: This operation was aborted
 Using standard PostgreSQL database
9:13:09 AM [express] serving on port 5000
✅ Database connection successful
[BLOCK-ALL] Avvio blocco massivo: 100/100 IP con score >= 80 su 2 router
[BULK-BLOCK] Starting: 100 IPs on 2 routers (10.20.30.100, 185.203.24.2)
[BULK-BLOCK] Router 185.203.24.2: 74 IPs already in list (38ms)
9:14:15 AM [express] GET /api/detections 200 in 17ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:14:15 AM [express] GET /api/services/status 200 in 42ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:14:20 AM [express] GET /api/detections 200 in 19ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:14:20 AM [express] GET /api/services/status 200 in 20ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:14:25 AM [express] GET /api/detections 200 in 7ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:14:25 AM [express] GET /api/whitelist 200 in 222ms :: {"items":[{"id":"4b7a60b4-b47c-431e-8efa-864…
[MIKROTIK] Failed to get address-list from 10.20.30.100: This operation was aborted
[BULK-BLOCK] Router 10.20.30.100: 0 IPs already in list (20004ms)
[BULK-BLOCK] 0 already blocked, 100 new to block
9:14:35 AM [express] GET /api/detections 200 in 8ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:14:39 AM [express] GET /api/training-history 200 in 5ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e2…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 151.73.24.49: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 104.16.248.249: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 77.39.130.185: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 104.16.249.249: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 104.18.36.146: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 193.205.185.20: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 50.93.53.165: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8004ms for IP 91.208.175.82: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8004ms for IP 74.0.42.209: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8005ms for IP 79.6.115.203: This operation was aborted
9:14:50 AM [express] GET /api/detections 200 in 31ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:14:50 AM [express] GET /api/whitelist 200 in 246ms :: {"items":[{"id":"49b5b9a9-4683-452c-a784-fc5…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 142.250.181.168: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 195.32.127.72: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 93.150.41.42: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 212.183.171.12: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 78.134.17.204: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 62.94.77.251: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 151.73.24.107: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 74.125.45.108: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 2.42.47.211: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8004ms for IP 89.97.31.234: This operation was aborted
9:14:52 AM [express] GET /api/dashboard/live 200 in 12ms :: {"totalPackets":75520962,"attackPackets"…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 31.197.212.130: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 44.197.141.31: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 146.75.61.140: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 93.150.200.81: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 101.56.92.15: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 2.33.192.82: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 151.41.104.237: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 77.39.220.111: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 185.96.131.245: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8004ms for IP 142.250.181.164: This operation was aborted
9:15:00 AM [express] GET /api/routers 200 in 16ms :: [{"id":"c6904896-59dc-4060-952f-0e55df7dee4a","…
9:15:00 AM [express] GET /api/detections 200 in 24ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:15:00 AM [express] GET /api/services/status 200 in 28ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:15:05 AM [express] GET /api/detections 200 in 11ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:15:05 AM [express] GET /api/services/status 200 in 18ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:15:06 AM [express] GET /api/stats 200 in 5385ms :: {"routers":{"total":2,"enabled":2},"detections"…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 45.146.216.240: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 62.112.11.234: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 51.159.85.76: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 188.8.66.34: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 151.101.193.229: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 109.205.211.40: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 151.59.33.110: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 151.73.209.26: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 79.8.248.29: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8003ms for IP 217.202.28.18: This operation was aborted
[ANALYTICS QUERY] {
startDate: '2026-01-18T08:15:10.899Z',
endDate: '2026-02-17T08:15:10.899Z',
hourly: true,
hourCondition: 'NOT NULL'
}
[ANALYTICS RESULTS] 41 records found
[ANALYTICS SAMPLE] {
id: 'b3216c05-7f95-4ba9-a5fb-b37461441401',
date: 2026-02-16T00:00:00.000Z,
hour: 23,
totalPackets: 10463767,
totalBytes: 13102201398,
uniqueIps: 25472,
normalPackets: 10463767,
normalBytes: 13102201398,
normalUniqueIps: 25472,
topNormalIps: '[{"ip": "88.39.149.52", "packets": 3744014, "bytes": 5747025096, "country": null}, {"ip": "79.11.175.156", "packets": 1279527, "bytes": 1892212998, "country": null}, {"ip": "95.229.133.69", "packets": 1188856, "bytes": 1758976078, "country": null}, {"ip": "188.12.75.242", "packets": 1168922, "bytes": 1729133469, "country": null}, {"ip": "95.229.85.134", "packets": 1151191, "bytes": 1703382127, "country": null}, {"ip": "74.125.45.108", "packets": 108382, "bytes": 13501983, "country": "United States"}, {"ip": "185.243.5.22", "packets": 87376, "bytes": 38384352, "country": null}, {"ip": "104.16.248.249", "packets": 72117, "bytes": 40864635, "country": "Canada"}, {"ip": "8.8.8.8", "packets": 66005, "bytes": 18862021, "country": "United States"}, {"ip": "95.110.183.67", "packets": 51456, "bytes": 2281666, "country": "Italy"}]',
attackPackets: 0,
attackBytes: 0,
attackUniqueIps: 0,
attacksByCountry: '{}',
attacksByType: '{}',
topAttackers: '[]',
trafficByCountry: '{"Canada": {"normal": 81941, "attacks": 0}, "The Netherlands": {"normal": 49409, "attacks": 0}, "Singapore": {"normal": 61, "attacks": 0}, "Germany": {"normal": 339, "attacks": 0}, "United States":
{"normal": 385230, "attacks": 0}, "Italy": {"normal": 73801, "attacks": 0}, "Netherlands": {"normal": 19928, "attacks": 0}}',
createdAt: 2026-02-17T00:05:00.185Z
}
9:15:10 AM [express] GET /api/analytics/recent 200 in 16ms :: [{"id":"b3216c05-7f95-4ba9-a5fb-b37461…
9:15:12 AM [express] GET /api/training-history 200 in 3ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e2…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7968ms for IP 151.73.139.162: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7969ms for IP 217.28.70.122: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7971ms for IP 79.9.120.141: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7971ms for IP 52.123.129.14: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7970ms for IP 157.240.231.35: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7975ms for IP 46.229.84.162: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7973ms for IP 178.248.182.171: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7974ms for IP 93.43.107.86: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7974ms for IP 178.248.51.104: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 7977ms for IP 93.51.52.251: HTTP 401: {"error":401,"message":"Unauthorized"}
[BULK-BLOCK] Progress: 50/100
9:15:19 AM [express] GET /api/ml/stats 200 in 6466ms :: {"logs":{"total":126216346,"last_hour":0},"d…
9:15:22 AM [express] POST /api/ml/train 200 in 10ms :: {"message":"Training avviato in background","…
9:15:22 AM [express] GET /api/training-history 304 in 3ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e2…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 85.44.118.45: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 2.228.8.122: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8000ms for IP 188.217.110.96: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 151.84.198.239: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 93.54.65.87: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 213.82.166.186: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 23.216.150.137: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 185.110.20.185: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 95.100.171.8: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 79.11.222.236: This operation was aborted
9:15:28 AM [express] GET /api/ml/stats 304 in 6490ms :: {"logs":{"total":126216346,"last_hour":0},"d…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 85.35.59.252: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 79.10.32.9: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 79.13.197.84: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 94.85.21.211: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 213.215.214.82: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 185.82.114.4: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 5.158.71.206: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 94.34.87.184: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 93.67.251.46: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 31.7.147.21: This operation was aborted
9:15:32 AM [express] GET /api/training-history 304 in 15ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 91.187.197.104: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 172.217.38.150: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 217.141.0.110: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 172.217.38.157: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 140.82.121.3: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 95.255.202.79: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 151.58.132.84: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 82.55.154.58: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 62.98.165.6: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 95.110.183.67: This operation was aborted
9:15:42 AM [express] GET /api/training-history 304 in 16ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e…
9:15:45 AM [express] GET /api/ml/stats 304 in 7198ms :: {"logs":{"total":126216346,"last_hour":0},"d…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 93.40.225.146: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 216.128.11.53: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 95.230.242.4: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 2.118.179.69: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 188.8.204.7: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 185.168.176.197: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 93.64.198.214: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 217.202.56.187: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8002ms for IP 5.63.174.25: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 151.64.151.157: This operation was aborted
9:15:52 AM [express] GET /api/training-history 304 in 17ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e…
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 79.2.176.5: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 82.192.139.100: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 93.40.226.33: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 93.146.168.160: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 94.101.59.182: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 185.104.127.31: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 31.197.212.236: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8000ms for IP 79.3.132.252: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 172.217.38.148: This operation was aborted
[BULK-BLOCK] SLOW: Router 10.20.30.100 took 8001ms for IP 172.217.38.151: This operation was aborted
[BULK-BLOCK] Progress: 100/100
[BULK-BLOCK] Router 10.20.30.100: 0 blocked, 100 failed, 0 skipped
[BULK-BLOCK] Router 185.203.24.2: 50 blocked, 0 failed, 50 skipped
[BULK-BLOCK] Done: 100 blocked, 0 failed, 0 skipped
[BLOCK-ALL] Database aggiornato: 100 IP marcati come bloccati
9:15:55 AM [express] POST /api/ml/block-all-critical 200 in 100172ms :: {"message":"Blocco massivo c…
9:16:02 AM [express] GET /api/training-history 304 in 2ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e2…
9:16:02 AM [express] GET /api/ml/stats 200 in 6527ms :: {"logs":{"total":126216346,"last_hour":0},"d…
9:16:12 AM [express] GET /api/training-history 304 in 13ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e…
9:16:19 AM [express] GET /api/ml/stats 304 in 6558ms :: {"logs":{"total":126216346,"last_hour":0},"d…
9:16:19 AM [express] POST /api/ml/block-all-critical 200 in 76ms :: {"message":"Nessun IP critico da…
9:16:22 AM [express] GET /api/training-history 304 in 3ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e2…
9:16:29 AM [express] GET /api/training-history 304 in 3ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e2…
9:16:35 AM [express] GET /api/ml/stats 304 in 6623ms :: {"logs":{"total":126216346,"last_hour":0},"d…
9:16:39 AM [express] GET /api/training-history 304 in 13ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e…
9:16:49 AM [express] GET /api/training-history 304 in 14ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e…
9:16:59 AM [express] GET /api/training-history 304 in 15ms :: [{"id":"4306f5a1-b30b-4241-97d9-6b6a1e…
[ML Stats] Fallback to database - ML Backend error: This operation was aborted
9:17:06 AM [express] GET /api/ml/stats 200 in 20345ms :: {"source":"database_fallback","ml_backend_s…
9:17:08 AM [express] GET /api/detections 200 in 21ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:17:08 AM [express] GET /api/routers 200 in 21ms :: [{"id":"c6904896-59dc-4060-952f-0e55df7dee4a","…
9:17:13 AM [express] GET /api/services/status 200 in 5007ms :: {"services":{"mlBackend":{"name":"ML …
9:17:13 AM [express] GET /api/detections 304 in 8ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:17:14 AM [express] GET /api/stats 200 in 5393ms :: {"routers":{"total":2,"enabled":2},"detections"…
9:17:18 AM [express] GET /api/detections 304 in 7ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:17:23 AM [express] GET /api/services/status 304 in 5006ms :: {"services":{"mlBackend":{"name":"ML …
9:17:23 AM [express] GET /api/detections 304 in 7ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:17:28 AM [express] GET /api/services/status 200 in 85ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:17:28 AM [express] GET /api/detections 304 in 8ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:17:29 AM [express] GET /api/stats 304 in 5314ms :: {"routers":{"total":2,"enabled":2},"detections"…
9:17:33 AM [express] GET /api/services/status 200 in 29ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:17:33 AM [express] GET /api/detections 304 in 7ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:17:38 AM [express] GET /api/services/status 200 in 53ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:17:39 AM [express] GET /api/detections 304 in 7ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:17:43 AM [express] GET /api/services/status 200 in 70ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:17:44 AM [express] GET /api/detections 304 in 8ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:17:44 AM [express] GET /api/stats 304 in 5395ms :: {"routers":{"total":2,"enabled":2},"detections"…
9:17:49 AM [express] GET /api/services/status 200 in 47ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:17:49 AM [express] GET /api/detections 304 in 7ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:17:54 AM [express] GET /api/detections 304 in 12ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:17:54 AM [express] GET /api/services/status 200 in 61ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:17:59 AM [express] GET /api/detections 304 in 11ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:17:59 AM [express] GET /api/services/status 200 in 30ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:18:00 AM [express] GET /api/stats 304 in 5405ms :: {"routers":{"total":2,"enabled":2},"detections"…
9:18:04 AM [express] GET /api/detections 304 in 7ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:18:04 AM [express] GET /api/services/status 200 in 64ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:18:09 AM [express] GET /api/detections 304 in 8ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7cf…
9:18:09 AM [express] GET /api/services/status 200 in 21ms :: {"services":{"mlBackend":{"name":"ML Ba…
9:18:14 AM [express] GET /api/detections 304 in 21ms :: {"detections":[{"id":"fcde52d3-0ae4-4904-a7c…
9:18:15 AM [express] GET /api/stats 304 in 5344ms :: {"routers":{"total":2,"enabled":2},"detections"…

View File

@ -2,25 +2,21 @@ import { useQuery, useMutation } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Activity, Brain, Database, FileText, Terminal, RefreshCw, AlertCircle, Play, Square, RotateCw } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Activity, Brain, Database, FileText, Terminal, RefreshCw, Play, Square, RotateCw, Shield, Trash2, ListChecks, GraduationCap, Server, Clock, Timer } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { queryClient, apiRequest } from "@/lib/queryClient";
interface ServiceStatus {
name: string;
status: "running" | "idle" | "offline" | "error" | "unknown";
status: string;
healthy: boolean;
details: any;
systemdUnit: string;
type: string;
}
interface ServicesStatusResponse {
services: {
mlBackend: ServiceStatus;
database: ServiceStatus;
syslogParser: ServiceStatus;
analyticsAggregator: ServiceStatus;
};
services: Record<string, ServiceStatus>;
}
export default function ServicesPage() {
@ -28,10 +24,9 @@ export default function ServicesPage() {
const { data: servicesStatus, isLoading, refetch } = useQuery<ServicesStatusResponse>({
queryKey: ["/api/services/status"],
refetchInterval: 5000, // Refresh every 5s
refetchInterval: 5000,
});
// Mutation for service control
const serviceControlMutation = useMutation({
mutationFn: async ({ service, action }: { service: string; action: string }) => {
return apiRequest("POST", `/api/services/${service}/${action}`);
@ -39,9 +34,8 @@ export default function ServicesPage() {
onSuccess: (data, variables) => {
toast({
title: "Operazione completata",
description: `Servizio ${variables.service}: ${variables.action} eseguito con successo`,
description: `Servizio ${variables.service}: ${variables.action} eseguito`,
});
// Refresh status after 2 seconds
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["/api/services/status"] });
}, 2000);
@ -59,39 +53,260 @@ export default function ServicesPage() {
serviceControlMutation.mutate({ service, action });
};
const getStatusBadge = (service: ServiceStatus) => {
const getStatusBadge = (service: ServiceStatus, key: string) => {
if (service.healthy) {
return <Badge variant="default" className="bg-green-600" data-testid={`badge-status-healthy`}>Online</Badge>;
return <Badge variant="default" className="bg-green-600" data-testid={`badge-status-${key}-healthy`}>Online</Badge>;
}
if (service.status === 'idle') {
return <Badge variant="secondary" data-testid={`badge-status-idle`}>In Attesa</Badge>;
return <Badge variant="secondary" data-testid={`badge-status-${key}-idle`}>In Attesa</Badge>;
}
if (service.status === 'offline') {
return <Badge variant="destructive" data-testid={`badge-status-offline`}>Offline</Badge>;
return <Badge variant="destructive" data-testid={`badge-status-${key}-offline`}>Offline</Badge>;
}
if (service.status === 'error') {
return <Badge variant="destructive" data-testid={`badge-status-error`}>Errore</Badge>;
return <Badge variant="destructive" data-testid={`badge-status-${key}-error`}>Errore</Badge>;
}
return <Badge variant="outline" data-testid={`badge-status-unknown`}>Sconosciuto</Badge>;
return <Badge variant="outline" data-testid={`badge-status-${key}-unknown`}>Sconosciuto</Badge>;
};
const getStatusIndicator = (service: ServiceStatus) => {
if (service.healthy) {
return <div className="h-3 w-3 rounded-full bg-green-500" />;
return <div className="h-3 w-3 rounded-full bg-green-500 shrink-0" />;
}
if (service.status === 'idle') {
return <div className="h-3 w-3 rounded-full bg-yellow-500" />;
return <div className="h-3 w-3 rounded-full bg-yellow-500 shrink-0" />;
}
return <div className="h-3 w-3 rounded-full bg-red-500" />;
return <div className="h-3 w-3 rounded-full bg-red-500 shrink-0" />;
};
const getServiceIcon = (key: string) => {
const icons: Record<string, any> = {
nodeBackend: Server,
mlBackend: Brain,
database: Database,
syslogParser: FileText,
analyticsAggregator: Activity,
autoBlock: Shield,
cleanup: Trash2,
listFetcher: ListChecks,
mlTraining: GraduationCap,
};
const Icon = icons[key] || Activity;
return <Icon className="h-5 w-5" />;
};
const controllableServices = [
"ids-ml-backend", "ids-syslog-parser", "ids-backend",
"ids-analytics-aggregator", "ids-auto-block", "ids-cleanup",
"ids-list-fetcher", "ids-ml-training"
];
const getLogCommand = (key: string): string | null => {
const logs: Record<string, string> = {
nodeBackend: "tail -f /var/log/ids/backend.log",
mlBackend: "journalctl -u ids-ml-backend -f",
database: "sudo journalctl -u postgresql-16 -f",
syslogParser: "tail -f /var/log/ids/syslog_parser.log",
analyticsAggregator: "journalctl -u ids-analytics-aggregator -f",
autoBlock: "journalctl -u ids-auto-block -f",
cleanup: "journalctl -u ids-cleanup -f",
listFetcher: "journalctl -u ids-list-fetcher -f",
mlTraining: "journalctl -u ids-ml-training -f",
};
return logs[key] || null;
};
const renderDetailRow = (label: string, value: any, variant?: "default" | "destructive" | "secondary" | "outline") => {
if (value === undefined || value === null) return null;
return (
<div className="flex items-center justify-between gap-2 flex-wrap">
<span className="text-sm text-muted-foreground">{label}:</span>
{variant ? (
<Badge variant={variant} className="text-xs">{String(value)}</Badge>
) : (
<span className="text-sm font-mono">{String(value)}</span>
)}
</div>
);
};
const renderServiceDetails = (key: string, service: ServiceStatus) => {
const d = service.details;
if (!d) return null;
switch (key) {
case "nodeBackend":
return (
<>
{renderDetailRow("Porta", d.port)}
{renderDetailRow("Uptime", d.uptime)}
</>
);
case "mlBackend":
return (
<>
{d.modelLoaded !== undefined && renderDetailRow("Modello ML", d.modelLoaded ? "Caricato" : "Non Caricato", d.modelLoaded ? "default" : "secondary")}
{d.error && renderDetailRow("Errore", d.error, "destructive")}
</>
);
case "database":
return (
<>
{d.connected && renderDetailRow("Connessione", "Attiva", "default")}
{d.error && renderDetailRow("Errore", d.error, "destructive")}
</>
);
case "syslogParser":
return (
<>
{d.recentLogs30min !== undefined && renderDetailRow("Log ultimi 30min", d.recentLogs30min.toLocaleString())}
{d.lastLog && renderDetailRow("Ultimo log", typeof d.lastLog === 'string' ? d.lastLog : new Date(d.lastLog).toLocaleString('it-IT'))}
{d.warning && renderDetailRow("Avviso", d.warning, "destructive")}
</>
);
case "analyticsAggregator":
return (
<>
{d.lastRun && renderDetailRow("Ultima esecuzione", new Date(d.lastRun).toLocaleString('it-IT'))}
{d.hoursSinceLastRun && renderDetailRow("Ore dall'ultimo run", d.hoursSinceLastRun + "h", parseFloat(d.hoursSinceLastRun) < 2 ? "default" : "destructive")}
{d.warning && renderDetailRow("Avviso", d.warning, "destructive")}
</>
);
case "autoBlock":
return (
<>
{renderDetailRow("Blocchi ultimi 10min", d.recentBlocks10min)}
{renderDetailRow("Totale bloccati", d.totalBlocked)}
{d.lastBlock && renderDetailRow("Ultimo blocco", typeof d.lastBlock === 'string' && d.lastBlock !== 'Mai' ? new Date(d.lastBlock).toLocaleString('it-IT') : d.lastBlock)}
{renderDetailRow("Intervallo", d.interval)}
</>
);
case "cleanup":
return (
<>
{renderDetailRow("Detection vecchie (>48h)", d.oldDetections48h, d.oldDetections48h > 0 ? "destructive" : "default")}
{renderDetailRow("Detection totali", d.totalDetections)}
{renderDetailRow("Intervallo", d.interval)}
{d.warning && renderDetailRow("Avviso", d.warning, "destructive")}
</>
);
case "listFetcher":
return (
<>
{renderDetailRow("Liste totali", d.totalLists)}
{renderDetailRow("Liste attive", d.enabledLists)}
{d.lastFetched && renderDetailRow("Ultimo fetch", typeof d.lastFetched === 'string' && d.lastFetched !== 'Mai' ? new Date(d.lastFetched).toLocaleString('it-IT') : d.lastFetched)}
{d.hoursSinceLastFetch && renderDetailRow("Ore dall'ultimo fetch", d.hoursSinceLastFetch + "h", parseFloat(d.hoursSinceLastFetch) < 1 ? "default" : "destructive")}
{renderDetailRow("Intervallo", d.interval)}
</>
);
case "mlTraining":
return (
<>
{d.lastTraining && renderDetailRow("Ultimo training", typeof d.lastTraining === 'string' && d.lastTraining !== 'Mai' ? new Date(d.lastTraining).toLocaleString('it-IT') : d.lastTraining)}
{d.daysSinceLastTraining && renderDetailRow("Giorni dall'ultimo", d.daysSinceLastTraining, parseFloat(d.daysSinceLastTraining) < 8 ? "default" : "destructive")}
{d.lastStatus && renderDetailRow("Stato ultimo training", d.lastStatus, d.lastStatus === 'completed' ? "default" : "destructive")}
{d.lastModel && renderDetailRow("Modello", d.lastModel)}
{d.recordsProcessed && renderDetailRow("Record processati", d.recordsProcessed.toLocaleString())}
{renderDetailRow("Intervallo", d.interval)}
</>
);
default:
return d.error ? renderDetailRow("Errore", d.error, "destructive") : null;
}
};
const coreServices = ["nodeBackend", "mlBackend", "database", "syslogParser"];
const timerServices = ["autoBlock", "analyticsAggregator", "cleanup", "listFetcher", "mlTraining"];
const renderServiceCard = (key: string, service: ServiceStatus) => {
const isControllable = controllableServices.includes(service.systemdUnit);
const isTimer = service.type === "timer";
const logCmd = getLogCommand(key);
return (
<Card key={key} data-testid={`card-service-${key}`}>
<CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0 pb-2">
<CardTitle className="flex items-center gap-2 text-base">
{getServiceIcon(key)}
<span className="truncate">{service.name}</span>
</CardTitle>
<div className="flex items-center gap-2 shrink-0">
{isTimer && <Timer className="h-4 w-4 text-muted-foreground" />}
{getStatusIndicator(service)}
</div>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center justify-between gap-2">
<span className="text-sm text-muted-foreground">Stato:</span>
{getStatusBadge(service, key)}
</div>
<div className="flex items-center justify-between gap-2">
<span className="text-sm text-muted-foreground">Systemd:</span>
<Badge variant="outline" className="text-xs font-mono">
{service.systemdUnit}{isTimer ? '.timer' : '.service'}
</Badge>
</div>
{renderServiceDetails(key, service)}
{isControllable && (
<div className="space-y-2 pt-2 border-t">
<p className="text-xs font-medium text-muted-foreground">Controlli:</p>
<div className="flex gap-2 flex-wrap">
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction(service.systemdUnit, "restart")}
disabled={serviceControlMutation.isPending}
data-testid={`button-restart-${key}`}
>
<RotateCw className="h-3 w-3 mr-1" />
Restart
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction(service.systemdUnit, "start")}
disabled={serviceControlMutation.isPending || service.status === 'running'}
data-testid={`button-start-${key}`}
>
<Play className="h-3 w-3 mr-1" />
Start
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction(service.systemdUnit, "stop")}
disabled={serviceControlMutation.isPending || service.status === 'offline'}
data-testid={`button-stop-${key}`}
>
<Square className="h-3 w-3 mr-1" />
Stop
</Button>
</div>
</div>
)}
{logCmd && (
<div className="p-2 bg-muted rounded-md">
<p className="text-xs font-medium text-muted-foreground mb-1">Log:</p>
<code className="text-xs font-mono break-all" data-testid={`code-log-${key}`}>{logCmd}</code>
</div>
)}
</CardContent>
</Card>
);
};
return (
<div className="flex flex-col gap-6 p-6" data-testid="page-services">
<div className="flex items-center justify-between">
<div className="flex items-center justify-between gap-4 flex-wrap">
<div>
<h1 className="text-3xl font-semibold" data-testid="text-services-title">Gestione Servizi</h1>
<p className="text-muted-foreground" data-testid="text-services-subtitle">
Monitoraggio e controllo dei servizi IDS
Monitoraggio e controllo di tutti i servizi IDS
</p>
</div>
<Button onClick={() => refetch()} variant="outline" data-testid="button-refresh">
@ -100,303 +315,40 @@ export default function ServicesPage() {
</Button>
</div>
<Alert data-testid="alert-server-instructions">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Gestione Servizi Systemd</AlertTitle>
<AlertDescription>
I servizi IDS sono gestiti da systemd sul server AlmaLinux.
Usa i pulsanti qui sotto per controllarli oppure esegui i comandi systemctl direttamente sul server.
</AlertDescription>
</Alert>
{/* Services Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* ML Backend Service */}
<Card data-testid="card-ml-backend-service">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Brain className="h-5 w-5" />
ML Backend Python
{servicesStatus && getStatusIndicator(servicesStatus.services.mlBackend)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Stato:</span>
{servicesStatus && getStatusBadge(servicesStatus.services.mlBackend)}
</div>
{servicesStatus?.services.mlBackend.details?.modelLoaded !== undefined && (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Modello ML:</span>
<Badge variant={servicesStatus.services.mlBackend.details.modelLoaded ? "default" : "secondary"}>
{servicesStatus.services.mlBackend.details.modelLoaded ? "Caricato" : "Non Caricato"}
</Badge>
</div>
{isLoading && (
<div className="text-center py-8 text-muted-foreground">Caricamento stato servizi...</div>
)}
{/* Service Controls */}
<div className="mt-4 space-y-2">
<p className="text-xs font-medium mb-2">Controlli Servizio:</p>
<div className="flex gap-2 flex-wrap">
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction("ids-ml-backend", "start")}
disabled={serviceControlMutation.isPending || servicesStatus?.services.mlBackend.status === 'running'}
data-testid="button-start-ml"
>
<Play className="h-3 w-3 mr-1" />
Start
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction("ids-ml-backend", "stop")}
disabled={serviceControlMutation.isPending || servicesStatus?.services.mlBackend.status === 'offline'}
data-testid="button-stop-ml"
>
<Square className="h-3 w-3 mr-1" />
Stop
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction("ids-ml-backend", "restart")}
disabled={serviceControlMutation.isPending}
data-testid="button-restart-ml"
>
<RotateCw className="h-3 w-3 mr-1" />
Restart
</Button>
{servicesStatus && (
<>
<div>
<h2 className="text-lg font-semibold mb-3 flex items-center gap-2">
<Server className="h-5 w-5" />
Servizi Core
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
{coreServices.map((key) => {
const service = (servicesStatus.services as any)[key];
return service ? renderServiceCard(key, service) : null;
})}
</div>
</div>
{/* Manual Commands (fallback) */}
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Comando systemctl (sul server):</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-systemctl-ml">
sudo systemctl {servicesStatus?.services.mlBackend.status === 'offline' ? 'start' : 'restart'} ids-ml-backend
</code>
<div>
<h2 className="text-lg font-semibold mb-3 flex items-center gap-2">
<Clock className="h-5 w-5" />
Timer Systemd (Attivita Periodiche)
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{timerServices.map((key) => {
const service = (servicesStatus.services as any)[key];
return service ? renderServiceCard(key, service) : null;
})}
</div>
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Log:</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-log-ml">
tail -f /var/log/ids/backend.log
</code>
</div>
</CardContent>
</Card>
{/* Database Service */}
<Card data-testid="card-database-service">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Database className="h-5 w-5" />
PostgreSQL Database
{servicesStatus && getStatusIndicator(servicesStatus.services.database)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Stato:</span>
{servicesStatus && getStatusBadge(servicesStatus.services.database)}
</div>
{servicesStatus?.services.database.status === 'running' && (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Connessione:</span>
<Badge variant="default" className="bg-green-600">Connesso</Badge>
</div>
</>
)}
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Verifica status:</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-status-db">
systemctl status postgresql-16
</code>
</div>
{servicesStatus?.services.database.status === 'error' && (
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Riavvia database:</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-restart-db">
sudo systemctl restart postgresql-16
</code>
</div>
)}
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Log:</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-log-db">
sudo journalctl -u postgresql-16 -f
</code>
</div>
</CardContent>
</Card>
{/* Syslog Parser Service */}
<Card data-testid="card-syslog-parser-service">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<FileText className="h-5 w-5" />
Syslog Parser
{servicesStatus && getStatusIndicator(servicesStatus.services.syslogParser)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Stato:</span>
{servicesStatus && getStatusBadge(servicesStatus.services.syslogParser)}
</div>
{servicesStatus?.services.syslogParser.details?.pid && (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">PID Processo:</span>
<Badge variant="outline" className="font-mono">
{servicesStatus.services.syslogParser.details.pid}
</Badge>
</div>
)}
{servicesStatus?.services.syslogParser.details?.systemd_unit && (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Systemd Unit:</span>
<Badge variant="outline" className="font-mono text-xs">
{servicesStatus.services.syslogParser.details.systemd_unit}
</Badge>
</div>
)}
{/* Service Controls */}
<div className="mt-4 space-y-2">
<p className="text-xs font-medium mb-2">Controlli Servizio:</p>
<div className="flex gap-2 flex-wrap">
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction("ids-syslog-parser", "start")}
disabled={serviceControlMutation.isPending || servicesStatus?.services.syslogParser.status === 'running'}
data-testid="button-start-parser"
>
<Play className="h-3 w-3 mr-1" />
Start
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction("ids-syslog-parser", "stop")}
disabled={serviceControlMutation.isPending || servicesStatus?.services.syslogParser.status === 'offline'}
data-testid="button-stop-parser"
>
<Square className="h-3 w-3 mr-1" />
Stop
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleServiceAction("ids-syslog-parser", "restart")}
disabled={serviceControlMutation.isPending}
data-testid="button-restart-parser"
>
<RotateCw className="h-3 w-3 mr-1" />
Restart
</Button>
</div>
</div>
{/* Manual Commands (fallback) */}
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Comando systemctl (sul server):</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-systemctl-parser">
sudo systemctl {servicesStatus?.services.syslogParser.status === 'offline' ? 'start' : 'restart'} ids-syslog-parser
</code>
</div>
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Log:</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-log-parser">
tail -f /var/log/ids/syslog_parser.log
</code>
</div>
</CardContent>
</Card>
{/* Analytics Aggregator Service */}
<Card data-testid="card-analytics-aggregator-service">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Activity className="h-5 w-5" />
Analytics Aggregator
{servicesStatus && getStatusIndicator(servicesStatus.services.analyticsAggregator)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Stato:</span>
{servicesStatus && getStatusBadge(servicesStatus.services.analyticsAggregator)}
</div>
{servicesStatus?.services.analyticsAggregator.details?.lastRun && (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Ultima Aggregazione:</span>
<Badge variant="outline" className="text-xs">
{new Date(servicesStatus.services.analyticsAggregator.details.lastRun).toLocaleString('it-IT')}
</Badge>
</div>
)}
{servicesStatus?.services.analyticsAggregator.details?.hoursSinceLastRun && (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Ore dall'ultimo run:</span>
<Badge variant={parseFloat(servicesStatus.services.analyticsAggregator.details.hoursSinceLastRun) < 2 ? "default" : "destructive"}>
{servicesStatus.services.analyticsAggregator.details.hoursSinceLastRun}h
</Badge>
</div>
)}
{/* CRITICAL ALERT: Aggregator idle for too long */}
{servicesStatus?.services.analyticsAggregator.details?.hoursSinceLastRun &&
parseFloat(servicesStatus.services.analyticsAggregator.details.hoursSinceLastRun) > 2 && (
<Alert variant="destructive" className="mt-2" data-testid="alert-aggregator-idle">
<AlertCircle className="h-4 w-4" />
<AlertTitle className="text-sm font-semibold"> Timer Systemd Non Attivo</AlertTitle>
<AlertDescription className="text-xs mt-1">
<p className="mb-2">L'aggregatore non esegue da {servicesStatus.services.analyticsAggregator.details.hoursSinceLastRun}h! Dashboard e Analytics bloccati.</p>
<p className="font-semibold">Soluzione Immediata (sul server):</p>
<code className="block bg-destructive-foreground/10 p-2 rounded mt-1 font-mono text-xs">
sudo /opt/ids/deployment/setup_analytics_timer.sh
</code>
</AlertDescription>
</Alert>
)}
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Verifica timer:</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-status-aggregator">
systemctl status ids-analytics-aggregator.timer
</code>
</div>
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Avvia aggregazione manualmente:</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-run-aggregator">
cd /opt/ids && ./deployment/run_analytics.sh
</code>
</div>
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-xs font-medium mb-2">Log:</p>
<code className="text-xs bg-background p-2 rounded block font-mono" data-testid="code-log-aggregator">
journalctl -u ids-analytics-aggregator.timer -f
</code>
</div>
</CardContent>
</Card>
</div>
{/* Additional Commands */}
<Card data-testid="card-additional-commands">
<CardHeader>
<CardTitle className="flex items-center gap-2">
@ -406,30 +358,27 @@ export default function ServicesPage() {
</CardHeader>
<CardContent className="space-y-4">
<div>
<p className="text-sm font-medium mb-2">Verifica tutti i processi IDS attivi:</p>
<code className="text-xs bg-muted p-2 rounded block font-mono" data-testid="code-check-processes">
ps aux | grep -E "python.*(main|syslog_parser)" | grep -v grep
<p className="text-sm font-medium mb-2">Stato di tutti i servizi IDS:</p>
<code className="text-xs bg-muted p-2 rounded-md block font-mono" data-testid="code-all-services">
systemctl list-units 'ids-*' --all
</code>
</div>
<div>
<p className="text-sm font-medium mb-2">Stato di tutti i timer IDS:</p>
<code className="text-xs bg-muted p-2 rounded-md block font-mono" data-testid="code-all-timers">
systemctl list-timers 'ids-*' --all
</code>
</div>
<div>
<p className="text-sm font-medium mb-2">Verifica log RSyslog (ricezione log MikroTik):</p>
<code className="text-xs bg-muted p-2 rounded block font-mono" data-testid="code-check-rsyslog">
<code className="text-xs bg-muted p-2 rounded-md block font-mono" data-testid="code-check-rsyslog">
tail -f /var/log/mikrotik/raw.log
</code>
</div>
<div>
<p className="text-sm font-medium mb-2">Esegui training manuale ML:</p>
<code className="text-xs bg-muted p-2 rounded block font-mono" data-testid="code-manual-training">
curl -X POST http://localhost:8000/train -H "Content-Type: application/json" -d '&#123;"max_records": 10000, "hours_back": 24&#125;'
</code>
</div>
<div>
<p className="text-sm font-medium mb-2">Verifica storico training nel database:</p>
<code className="text-xs bg-muted p-2 rounded block font-mono" data-testid="code-check-training">
psql $DATABASE_URL -c "SELECT * FROM training_history ORDER BY trained_at DESC LIMIT 5;"
<p className="text-sm font-medium mb-2">Verifica processi IDS attivi:</p>
<code className="text-xs bg-muted p-2 rounded-md block font-mono" data-testid="code-check-processes">
ps aux | grep -E "python.*(main|syslog_parser)" | grep -v grep
</code>
</div>
</CardContent>

View File

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

View File

@ -1,3 +1,5 @@
const VERBOSE = process.env.MIKROTIK_DEBUG === '1' || process.env.MIKROTIK_DEBUG === 'true';
interface RouterConfig {
id: string;
ipAddress: string;
@ -26,6 +28,7 @@ async function mikrotikRequest(
const protocol = useHttps ? "https" : "http";
const url = `${protocol}://${router.ipAddress}:${router.apiPort}${path}`;
const auth = Buffer.from(`${router.username}:${router.password}`).toString("base64");
const startTime = Date.now();
const origTlsReject = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
if (useHttps) {
@ -60,9 +63,24 @@ async function mikrotikRequest(
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 };
} catch (error: any) {
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) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = origTlsReject;
} else if (useHttps) {
@ -94,19 +112,26 @@ export async function getExistingBlockedIps(
listName: string = "ddos_blocked"
): Promise<Set<string>> {
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);
if (status === 200 && Array.isArray(data)) {
const ips = new Set<string>();
const allLists = new Map<string, number>();
for (const entry of data) {
const count = allLists.get(entry.list) || 0;
allLists.set(entry.list, count + 1);
if (entry.list === listName) {
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;
}
console.warn(`[MIKROTIK] Router ${router.ipAddress}: risposta inattesa status=${status}, data non e' array`);
return new Set();
} 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();
}
}
@ -127,41 +152,53 @@ export async function addToAddressList(
});
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 };
}
if (status === 400 || status === 409) {
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 (VERBOSE) console.log(`[BLOCK] SKIP: ${ipAddress} gia' presente su router ${router.ipAddress} (HTTP ${status})`);
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 {
const verifyResult = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list");
if (verifyResult.status === 200 && Array.isArray(verifyResult.data)) {
for (const entry of verifyResult.data) {
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 };
}
}
}
} 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 {
routerIp: router.ipAddress,
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 {
routerIp: router.ipAddress,
success: false,
error: `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`,
error: errMsg,
};
} 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 {
routerIp: router.ipAddress,
success: false,
error: error.message || "Connection failed",
error: errMsg,
};
}
}
@ -172,8 +209,10 @@ export async function removeFromAddressList(
listName: string = "ddos_blocked"
): Promise<BlockResult> {
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");
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" };
}
@ -182,14 +221,18 @@ export async function removeFromAddressList(
const entryId = entry[".id"];
const delResult = await mikrotikRequest(router, "DELETE", `/rest/ip/firewall/address-list/${entryId}`);
if (delResult.status === 200 || delResult.status === 204) {
console.log(`[UNBLOCK] OK: ${ipAddress} rimosso da router ${router.ipAddress}`);
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}` };
}
}
if (VERBOSE) console.log(`[UNBLOCK] ${ipAddress} non trovato su router ${router.ipAddress} (gia' assente)`);
return { routerIp: router.ipAddress, success: true };
} catch (error: any) {
console.error(`[UNBLOCK] ERRORE: ${ipAddress} su router ${router.ipAddress}: ${error.message}`);
return { routerIp: router.ipAddress, success: false, error: error.message };
}
}
@ -281,6 +324,8 @@ export async function bulkBlockIps(
let blocked = 0;
let failed = 0;
const details: Array<{ ip: string; status: string }> = [];
const partialIps: string[] = [];
const failedIps: string[] = [];
async function processIp(ip: string) {
const routerResults = await Promise.allSettled(
@ -289,7 +334,7 @@ export async function bulkBlockIps(
if (existing.has(ip)) {
const st = routerStatus.get(router.ipAddress);
if (st) st.skip++;
return true;
return { success: true, skipped: true, routerIp: router.ipAddress };
}
const start = Date.now();
const result = await addToAddressList(router, ip, listName, `${commentPrefix} ${ip}`, timeoutDuration);
@ -299,33 +344,52 @@ export async function bulkBlockIps(
if (st) st.ok++;
} else {
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(
(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) {
blocked++;
details.push({ ip, status: "blocked" });
if (!allSuccess) {
partialIps.push(ip);
if (VERBOSE) console.warn(`[BULK-BLOCK] PARZIALE: IP ${ip}: ${perRouterDetail}`);
}
} else {
failed++;
failedIps.push(ip);
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) {
const batch = newIps.slice(i, i + concurrency);
await Promise.allSettled(batch.map((ip) => processIp(ip)));
if ((i + concurrency) % 50 === 0 || i + concurrency >= newIps.length) {
console.log(`[BULK-BLOCK] Progress: ${Math.min(i + concurrency, newIps.length)}/${newIps.length}`);
const progress = Math.min(i + concurrency, 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" });
}
// Report per-router
const totalElapsed = ((Date.now() - bulkStart) / 1000).toFixed(1);
routerStatus.forEach((st, routerIp) => {
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 };
}

View File

@ -672,7 +672,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
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(
enabledRouters as any,
@ -779,39 +780,43 @@ export async function registerRoutes(app: Express): Promise<Server> {
// Services monitoring
app.get("/api/services/status", async (req, res) => {
try {
const mkService = (name: string) => ({ name, status: "unknown" as string, healthy: false, details: null as any, systemdUnit: "" as string, type: "service" as string });
const services = {
mlBackend: { name: "ML Backend Python", status: "unknown", healthy: false, details: null as any },
database: { name: "PostgreSQL Database", status: "unknown", healthy: false, details: null as any },
syslogParser: { name: "Syslog Parser", status: "unknown", healthy: false, details: null as any },
analyticsAggregator: { name: "Analytics Aggregator Timer", status: "unknown", healthy: false, details: null as any },
nodeBackend: { ...mkService("Node.js Backend"), systemdUnit: "ids-backend", type: "service" },
mlBackend: { ...mkService("ML Backend Python"), systemdUnit: "ids-ml-backend", type: "service" },
database: { ...mkService("PostgreSQL Database"), systemdUnit: "postgresql-16", type: "service" },
syslogParser: { ...mkService("Syslog Parser"), systemdUnit: "ids-syslog-parser", type: "service" },
analyticsAggregator: { ...mkService("Analytics Aggregator"), systemdUnit: "ids-analytics-aggregator", type: "timer" },
autoBlock: { ...mkService("Auto Block"), systemdUnit: "ids-auto-block", type: "timer" },
cleanup: { ...mkService("Cleanup Detections"), systemdUnit: "ids-cleanup", type: "timer" },
listFetcher: { ...mkService("Public Lists Fetcher"), systemdUnit: "ids-list-fetcher", type: "timer" },
mlTraining: { ...mkService("ML Training Settimanale"), systemdUnit: "ids-ml-training", type: "timer" },
};
// Node.js Backend - always running if this endpoint responds
services.nodeBackend.status = "running";
services.nodeBackend.healthy = true;
services.nodeBackend.details = { port: 5000, uptime: process.uptime().toFixed(0) + "s" };
// Check ML Backend Python
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const response = await fetch(`${ML_BACKEND_URL}/health`, {
signal: controller.signal,
});
const response = await fetch(`${ML_BACKEND_URL}/health`, { signal: controller.signal });
clearTimeout(timeout);
if (response.ok) {
const data = await response.json();
services.mlBackend.status = "running";
services.mlBackend.healthy = true;
services.mlBackend.details = {
modelLoaded: data.ml_model === "loaded",
timestamp: data.timestamp,
};
services.mlBackend.details = { modelLoaded: data.ml_model === "loaded", timestamp: data.timestamp };
} else {
services.mlBackend.status = "error";
services.mlBackend.details = { error: `HTTP ${response.status}` };
}
} catch (error: any) {
services.mlBackend.status = "offline";
services.mlBackend.details = { error: error.code === 'ECONNREFUSED' ? "Connection refused" : error.message };
services.mlBackend.details = { error: error.code === 'ECONNREFUSED' ? "Connessione rifiutata" : error.message };
}
// Check Database
@ -827,87 +832,156 @@ export async function registerRoutes(app: Express): Promise<Server> {
services.database.details = { error: error.message };
}
// Check Syslog Parser via database (independent of ML Backend)
// Check Syslog Parser via database
try {
const recentLogsResult = await db.execute(
sql`SELECT COUNT(*) as count, MAX(timestamp) as last_log
FROM network_logs
WHERE timestamp > NOW() - INTERVAL '30 minutes'`
sql`SELECT COUNT(*) as count, MAX(timestamp) as last_log FROM network_logs WHERE timestamp > NOW() - INTERVAL '30 minutes'`
);
const logRows = (recentLogsResult as any).rows || recentLogsResult;
const recentLogCount = parseInt(logRows[0]?.count || "0");
const lastLogTime = logRows[0]?.last_log;
if (recentLogCount > 0) {
services.syslogParser.status = "running";
services.syslogParser.healthy = true;
services.syslogParser.details = {
recentLogs30min: recentLogCount,
lastLog: lastLogTime,
};
services.syslogParser.details = { recentLogs30min: recentLogCount, lastLog: lastLogTime };
} else {
const lastLogEverResult = await db.execute(
sql`SELECT MAX(timestamp) as last_log FROM network_logs`
);
const lastLogEverResult = await db.execute(sql`SELECT MAX(timestamp) as last_log FROM network_logs`);
const lastLogEverRows = (lastLogEverResult as any).rows || lastLogEverResult;
const lastLogEver = lastLogEverRows[0]?.last_log;
services.syslogParser.status = "offline";
services.syslogParser.healthy = false;
services.syslogParser.details = {
recentLogs30min: 0,
lastLog: lastLogEver || "Never",
warning: "No logs received in last 30 minutes",
};
services.syslogParser.details = { recentLogs30min: 0, lastLog: lastLogEverRows[0]?.last_log || "Mai", warning: "Nessun log negli ultimi 30 minuti" };
}
} catch (error: any) {
services.syslogParser.status = "error";
services.syslogParser.healthy = false;
services.syslogParser.details = { error: error.message };
}
// Check Analytics Aggregator (via last record timestamp)
try {
const latestAnalytics = await db
.select()
.from(networkAnalytics)
.orderBy(desc(networkAnalytics.date), desc(networkAnalytics.hour))
.limit(1);
const latestAnalytics = await db.select().from(networkAnalytics).orderBy(desc(networkAnalytics.date), desc(networkAnalytics.hour)).limit(1);
if (latestAnalytics.length > 0) {
const lastRun = new Date(latestAnalytics[0].date);
const lastTimestamp = lastRun.toISOString();
const hoursSinceLastRun = (Date.now() - lastRun.getTime()) / (1000 * 60 * 60);
if (hoursSinceLastRun < 2) {
const hoursSince = (Date.now() - lastRun.getTime()) / (1000 * 60 * 60);
if (hoursSince < 2) {
services.analyticsAggregator.status = "running";
services.analyticsAggregator.healthy = true;
services.analyticsAggregator.details = {
lastRun: latestAnalytics[0].date,
lastTimestamp,
hoursSinceLastRun: hoursSinceLastRun.toFixed(1),
};
services.analyticsAggregator.details = { lastRun: latestAnalytics[0].date, hoursSinceLastRun: hoursSince.toFixed(1) };
} else {
services.analyticsAggregator.status = "idle";
services.analyticsAggregator.healthy = false;
services.analyticsAggregator.details = {
lastRun: latestAnalytics[0].date,
lastTimestamp,
hoursSinceLastRun: hoursSinceLastRun.toFixed(1),
warning: "No aggregation in last 2 hours",
};
services.analyticsAggregator.details = { lastRun: latestAnalytics[0].date, hoursSinceLastRun: hoursSince.toFixed(1), warning: "Nessuna aggregazione nelle ultime 2 ore" };
}
} else {
services.analyticsAggregator.status = "idle";
services.analyticsAggregator.healthy = false;
services.analyticsAggregator.details = { error: "No analytics data found" };
services.analyticsAggregator.details = { error: "Nessun dato analytics trovato" };
}
} catch (error: any) {
services.analyticsAggregator.status = "error";
services.analyticsAggregator.healthy = false;
services.analyticsAggregator.details = { error: error.message };
}
// Check Auto Block (via recent blocked detections)
try {
const recentBlockResult = await db.execute(
sql`SELECT COUNT(*) as count, MAX(detected_at) as last_block FROM detections WHERE blocked = true AND detected_at > NOW() - INTERVAL '10 minutes'`
);
const blockRows = (recentBlockResult as any).rows || recentBlockResult;
const recentBlocks = parseInt(blockRows[0]?.count || "0");
const lastBlock = blockRows[0]?.last_block;
const totalBlockedResult = await db.execute(sql`SELECT COUNT(*) as count FROM detections WHERE blocked = true`);
const totalBlockedRows = (totalBlockedResult as any).rows || totalBlockedResult;
const totalBlocked = parseInt(totalBlockedRows[0]?.count || "0");
services.autoBlock.status = recentBlocks > 0 ? "running" : "idle";
services.autoBlock.healthy = true;
services.autoBlock.details = {
recentBlocks10min: recentBlocks,
totalBlocked,
lastBlock: lastBlock || "Mai",
interval: "ogni 5 minuti"
};
} catch (error: any) {
services.autoBlock.status = "error";
services.autoBlock.details = { error: error.message };
}
// Check Cleanup (via absence of old detections)
try {
const oldDetResult = await db.execute(
sql`SELECT COUNT(*) as count FROM detections WHERE detected_at < NOW() - INTERVAL '48 hours'`
);
const oldRows = (oldDetResult as any).rows || oldDetResult;
const oldDetections = parseInt(oldRows[0]?.count || "0");
const totalDetResult = await db.execute(sql`SELECT COUNT(*) as count FROM detections`);
const totalRows = (totalDetResult as any).rows || totalDetResult;
const totalDetections = parseInt(totalRows[0]?.count || "0");
services.cleanup.status = oldDetections === 0 ? "running" : "idle";
services.cleanup.healthy = oldDetections === 0;
services.cleanup.details = {
oldDetections48h: oldDetections,
totalDetections,
interval: "ogni ora",
warning: oldDetections > 0 ? `${oldDetections} detection vecchie non ancora pulite` : undefined
};
} catch (error: any) {
services.cleanup.status = "error";
services.cleanup.details = { error: error.message };
}
// Check List Fetcher (via public lists last_updated)
try {
const listsResult = await db.execute(
sql`SELECT COUNT(*) as total,
COUNT(*) FILTER (WHERE enabled = true) as enabled,
MAX(last_fetch) as last_fetch
FROM public_lists`
);
const listRows = (listsResult as any).rows || listsResult;
const totalLists = parseInt(listRows[0]?.total || "0");
const enabledLists = parseInt(listRows[0]?.enabled || "0");
const lastFetched = listRows[0]?.last_fetch;
if (lastFetched) {
const hoursSince = (Date.now() - new Date(lastFetched).getTime()) / (1000 * 60 * 60);
services.listFetcher.status = hoursSince < 1 ? "running" : "idle";
services.listFetcher.healthy = hoursSince < 1;
services.listFetcher.details = { totalLists, enabledLists, lastFetched, hoursSinceLastFetch: hoursSince.toFixed(1), interval: "ogni 10 minuti" };
} else {
services.listFetcher.status = "idle";
services.listFetcher.details = { totalLists, enabledLists, lastFetched: "Mai", interval: "ogni 10 minuti" };
}
} catch (error: any) {
services.listFetcher.status = "error";
services.listFetcher.details = { error: error.message };
}
// Check ML Training (via training history)
try {
const latestTraining = await db.select().from(trainingHistory).orderBy(desc(trainingHistory.trainedAt)).limit(1);
if (latestTraining.length > 0) {
const lastTrainDate = new Date(latestTraining[0].trainedAt);
const daysSince = (Date.now() - lastTrainDate.getTime()) / (1000 * 60 * 60 * 24);
services.mlTraining.status = daysSince < 8 ? "running" : "idle";
services.mlTraining.healthy = daysSince < 8;
services.mlTraining.details = {
lastTraining: latestTraining[0].trainedAt,
daysSinceLastTraining: daysSince.toFixed(1),
lastStatus: latestTraining[0].status,
lastModel: latestTraining[0].modelVersion,
recordsProcessed: latestTraining[0].recordsProcessed,
interval: "settimanale"
};
} else {
services.mlTraining.status = "idle";
services.mlTraining.details = { lastTraining: "Mai", interval: "settimanale" };
}
} catch (error: any) {
services.mlTraining.status = "error";
services.mlTraining.details = { error: error.message };
}
res.json({ services });
} catch (error: any) {
res.status(500).json({ error: "Failed to check services status" });
@ -915,7 +989,11 @@ export async function registerRoutes(app: Express): Promise<Server> {
});
// Service Control Endpoints (Secured - only allow specific systemd operations)
const ALLOWED_SERVICES = ["ids-ml-backend", "ids-syslog-parser"];
const ALLOWED_SERVICES = [
"ids-ml-backend", "ids-syslog-parser", "ids-backend",
"ids-analytics-aggregator", "ids-auto-block", "ids-cleanup",
"ids-list-fetcher", "ids-ml-training"
];
const ALLOWED_ACTIONS = ["start", "stop", "restart", "status"];
app.post("/api/services/:service/:action", async (req, res) => {

View File

@ -1,7 +1,19 @@
{
"version": "1.0.120",
"lastUpdate": "2026-02-17T07:48:15.846Z",
"version": "1.0.122",
"lastUpdate": "2026-02-17T09:13:40.571Z",
"changelog": [
{
"version": "1.0.122",
"date": "2026-02-17",
"type": "patch",
"description": "Deployment automatico v1.0.122"
},
{
"version": "1.0.121",
"date": "2026-02-17",
"type": "patch",
"description": "Deployment automatico v1.0.121"
},
{
"version": "1.0.120",
"date": "2026-02-17",
@ -289,18 +301,6 @@
"date": "2025-11-25",
"type": "patch",
"description": "Deployment automatico v1.0.73"
},
{
"version": "1.0.72",
"date": "2025-11-25",
"type": "patch",
"description": "Deployment automatico v1.0.72"
},
{
"version": "1.0.71",
"date": "2025-11-25",
"type": "patch",
"description": "Deployment automatico v1.0.71"
}
]
}