diff --git a/.replit b/.replit
index 5c190e2..5881888 100644
--- a/.replit
+++ b/.replit
@@ -14,6 +14,10 @@ run = ["npm", "run", "start"]
localPort = 5000
externalPort = 80
+[[ports]]
+localPort = 40851
+externalPort = 3001
+
[[ports]]
localPort = 41303
externalPort = 3002
diff --git a/attached_assets/Pasted-systemctl-status-ids-ml-backend-Unit-ids-ml-backend-service-could-not-be-found-root-ids-ps-aux-1763802707490_1763802707490.txt b/attached_assets/Pasted-systemctl-status-ids-ml-backend-Unit-ids-ml-backend-service-could-not-be-found-root-ids-ps-aux-1763802707490_1763802707490.txt
new file mode 100644
index 0000000..1ad43f0
--- /dev/null
+++ b/attached_assets/Pasted-systemctl-status-ids-ml-backend-Unit-ids-ml-backend-service-could-not-be-found-root-ids-ps-aux-1763802707490_1763802707490.txt
@@ -0,0 +1,76 @@
+systemctl status ids-ml-backend
+Unit ids-ml-backend.service could not be found.
+[root@ids ~]# ps aux | grep "python.*main.py"
+ids 1547 6.0 4.1 2205816 668884 ? Sl Nov21 55:37 /usr/bin/python3.11 main.py
+root 13688 0.0 0.0 3884 2304 pts/5 S+ 10:08 0:00 grep --color=auto python.*main.py
+[root@ids ~]# tail -50 /var/log/ids/ml_backend.log
+tail: cannot open '/var/log/ids/ml_backend.log' for reading: No such file or directory
+[root@ids ~]# curl http://localhost:8000/health
+{"status":"healthy","database":"connected","ml_model":"loaded","timestamp":"2025-11-22T10:09:55.941962"}[root@ids ~]#
+[root@ids ~]# sudo crontab -u ids -l | grep train
+0 */12 * * * /opt/ids/deployment/cron_train.sh
+[root@ids ~]# # Verifica storico training
+psql $DATABASE_URL -c "SELECT id, model_version, records_processed, status, notes, trained_at FROM training_history ORDER BY trained_at DESC LIMIT 5;"
+psql: error: FATAL: role "root" does not exist
+[root@ids ~]# cd /opt/ids/
+[root@ids ids]# cat .env
+# Database PostgreSQL
+PGHOST=localhost
+PGPORT=5432
+PGDATABASE=ids_database
+PGUSER=ids_user
+PGPASSWORD=TestPassword123
+DATABASE_URL=postgresql://ids_user:TestPassword123@127.0.0.1:5432/ids_database
+
+
+# Session Secret (genera una stringa random sicura)
+SESSION_SECRET=zLMzP8lLgjgz/NlgfDXuLK8bwHCod+o5zLOWP5DipRM=
+
+# Python Backend URL (per frontend)
+VITE_PYTHON_API_URL=http://localhost:8000
+
+# Node Environment
+NODE_ENV=production
+
+[root@ids ids]# DATABASE_URL=postgresql://ids_user:TestPassword123@127.0.0.1:5432/ids_database
+[root@ids ids]# cat .env
+# Database PostgreSQL
+PGHOST=localhost
+PGPORT=5432
+PGDATABASE=ids_database
+PGUSER=ids_user
+PGPASSWORD=TestPassword123
+DATABASE_URL=postgresql://ids_user:TestPassword123@127.0.0.1:5432/ids_database
+
+
+# Session Secret (genera una stringa random sicura)
+SESSION_SECRET=zLMzP8lLgjgz/NlgfDXuLK8bwHCod+o5zLOWP5DipRM=
+
+# Python Backend URL (per frontend)
+VITE_PYTHON_API_URL=http://localhost:8000
+
+# Node Environment
+NODE_ENV=production
+
+[root@ids ids]# psql $DATABASE_URL -c "SELECT id, model_version, records_processed, status, notes, trained_at FROM training_history ORDER BY trained_at DESC LIMIT 5;"
+ id | model_version | records_processed | status | notes | trained_at
+----+---------------+-------------------+--------+-------+------------
+(0 rows)
+
+[root@ids ids]# # Trova dove sta loggando il processo
+lsof -p 1547 | grep log
+python3.1 1547 ids mem REG 253,0 187881 1053730 /home/ids/.local/lib/python3.11/site-packages/sklearn/utils/_logistic_sigmoid.cpython-311-x86_64-linux-gnu.so
+python3.1 1547 ids 1w REG 253,0 1546719 538992839 /var/log/ids/backend.log
+python3.1 1547 ids 2w REG 253,0 1546719 538992839 /var/log/ids/backend.log
+[root@ids ids]# tail -f /var/log/ids/backend.log
+📚 Docs available at http://0.0.0.0:8000/docs
+INFO: 127.0.0.1:40168 - "POST /detect HTTP/1.1" 200 OK
+INFO: 127.0.0.1:57698 - "GET /stats HTTP/1.1" 200 OK
+INFO: 127.0.0.1:56726 - "GET /stats HTTP/1.1" 200 OK
+INFO: 127.0.0.1:41940 - "GET /stats HTTP/1.1" 200 OK
+INFO: 127.0.0.1:39840 - "GET /stats HTTP/1.1" 200 OK
+INFO: 127.0.0.1:55900 - "GET /stats HTTP/1.1" 200 OK
+INFO: 127.0.0.1:43422 - "GET /stats HTTP/1.1" 200 OK
+INFO: 127.0.0.1:33580 - "GET /stats HTTP/1.1" 200 OK
+INFO: 127.0.0.1:55752 - "GET /stats HTTP/1.1" 200 OK
+^C
\ No newline at end of file
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 3c3827b..c7c8764 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -4,12 +4,13 @@ import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { SidebarProvider, Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarTrigger } from "@/components/ui/sidebar";
-import { LayoutDashboard, AlertTriangle, Server, Shield, Brain, Menu } from "lucide-react";
+import { LayoutDashboard, AlertTriangle, Server, Shield, Brain, Menu, Activity } from "lucide-react";
import Dashboard from "@/pages/Dashboard";
import Detections from "@/pages/Detections";
import Routers from "@/pages/Routers";
import Whitelist from "@/pages/Whitelist";
import Training from "@/pages/Training";
+import Services from "@/pages/Services";
import NotFound from "@/pages/not-found";
const menuItems = [
@@ -18,6 +19,7 @@ const menuItems = [
{ title: "Training ML", url: "/training", icon: Brain },
{ title: "Router", url: "/routers", icon: Server },
{ title: "Whitelist", url: "/whitelist", icon: Shield },
+ { title: "Servizi", url: "/services", icon: Activity },
];
function AppSidebar() {
@@ -54,6 +56,7 @@ function Router() {
+
);
diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx
index 78104d3..c5f15ee 100644
--- a/client/src/pages/Dashboard.tsx
+++ b/client/src/pages/Dashboard.tsx
@@ -2,8 +2,9 @@ import { useQuery } 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, Shield, Server, AlertTriangle, CheckCircle2, TrendingUp } from "lucide-react";
+import { Activity, Shield, Server, AlertTriangle, CheckCircle2, TrendingUp, Database, FileText, Brain } from "lucide-react";
import { format } from "date-fns";
+import { it } from "date-fns/locale";
import type { Detection, Router, TrainingHistory } from "@shared/schema";
interface StatsResponse {
@@ -14,6 +15,21 @@ interface StatsResponse {
latestTraining: TrainingHistory | null;
}
+interface ServiceStatus {
+ name: string;
+ status: "running" | "idle" | "offline" | "error" | "unknown";
+ healthy: boolean;
+ details: any;
+}
+
+interface ServicesStatusResponse {
+ services: {
+ mlBackend: ServiceStatus;
+ database: ServiceStatus;
+ syslogParser: ServiceStatus;
+ };
+}
+
export default function Dashboard() {
const { data: stats } = useQuery({
queryKey: ["/api/stats"],
@@ -29,6 +45,11 @@ export default function Dashboard() {
queryKey: ["/api/routers"],
});
+ const { data: servicesStatus } = useQuery({
+ queryKey: ["/api/services/status"],
+ refetchInterval: 5000, // Refresh every 5s
+ });
+
const getRiskBadge = (riskScore: string) => {
const score = parseFloat(riskScore);
if (score >= 85) return CRITICO ;
@@ -47,6 +68,84 @@ export default function Dashboard() {
+ {/* Services Status */}
+
+
+
+
+ Stato Servizi
+
+
+
+
+ {/* ML Backend */}
+
+
+
+
+
+ {servicesStatus?.services.mlBackend.status === 'running' && 'In esecuzione'}
+ {servicesStatus?.services.mlBackend.status === 'offline' && 'Offline'}
+ {servicesStatus?.services.mlBackend.status === 'error' && 'Errore'}
+ {!servicesStatus && 'Caricamento...'}
+
+ {servicesStatus?.services.mlBackend.details?.modelLoaded !== undefined && (
+
+ Modello: {servicesStatus.services.mlBackend.details.modelLoaded ? '✓ Caricato' : '✗ Non caricato'}
+
+ )}
+
+
+
+ {/* Database */}
+
+
+
+
+
+ {servicesStatus?.services.database.status === 'running' && 'Connesso'}
+ {servicesStatus?.services.database.status === 'error' && 'Errore connessione'}
+ {!servicesStatus && 'Caricamento...'}
+
+
+
+
+ {/* Syslog Parser */}
+
+
+
+
+
+ {servicesStatus?.services.syslogParser.status === 'running' && 'Attivo'}
+ {servicesStatus?.services.syslogParser.status === 'idle' && 'In attesa log'}
+ {servicesStatus?.services.syslogParser.status === 'error' && 'Errore'}
+ {!servicesStatus && 'Caricamento...'}
+
+ {servicesStatus?.services.syslogParser.details?.logsLast5Min !== undefined && (
+
+ {servicesStatus.services.syslogParser.details.logsLast5Min} log (5min)
+
+ )}
+
+
+
+
+
+
+
{/* Stats Grid */}
diff --git a/client/src/pages/Services.tsx b/client/src/pages/Services.tsx
new file mode 100644
index 0000000..413ffec
--- /dev/null
+++ b/client/src/pages/Services.tsx
@@ -0,0 +1,279 @@
+import { useQuery } 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 } from "lucide-react";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+
+interface ServiceStatus {
+ name: string;
+ status: "running" | "idle" | "offline" | "error" | "unknown";
+ healthy: boolean;
+ details: any;
+}
+
+interface ServicesStatusResponse {
+ services: {
+ mlBackend: ServiceStatus;
+ database: ServiceStatus;
+ syslogParser: ServiceStatus;
+ };
+}
+
+export default function ServicesPage() {
+ const { data: servicesStatus, isLoading, refetch } = useQuery({
+ queryKey: ["/api/services/status"],
+ refetchInterval: 5000, // Refresh every 5s
+ });
+
+ const getStatusBadge = (service: ServiceStatus) => {
+ if (service.healthy) {
+ return Online ;
+ }
+ if (service.status === 'idle') {
+ return In Attesa ;
+ }
+ if (service.status === 'offline') {
+ return Offline ;
+ }
+ if (service.status === 'error') {
+ return Errore ;
+ }
+ return Sconosciuto ;
+ };
+
+ const getStatusIndicator = (service: ServiceStatus) => {
+ if (service.healthy) {
+ return
;
+ }
+ if (service.status === 'idle') {
+ return
;
+ }
+ return
;
+ };
+
+ return (
+
+
+
+
Gestione Servizi
+
+ Monitoraggio e controllo dei servizi IDS
+
+
+
refetch()} variant="outline" data-testid="button-refresh">
+
+ Aggiorna
+
+
+
+
+
+ Nota Importante
+
+ I servizi Python girano sul server AlmaLinux. Per gestirli, esegui i comandi indicati direttamente sul server.
+
+
+
+ {/* Services Grid */}
+
+ {/* ML Backend Service */}
+
+
+
+
+ ML Backend Python
+ {servicesStatus && getStatusIndicator(servicesStatus.services.mlBackend)}
+
+
+
+
+ Stato:
+ {servicesStatus && getStatusBadge(servicesStatus.services.mlBackend)}
+
+
+ {servicesStatus?.services.mlBackend.details?.modelLoaded !== undefined && (
+
+ Modello ML:
+
+ {servicesStatus.services.mlBackend.details.modelLoaded ? "Caricato" : "Non Caricato"}
+
+
+ )}
+
+ {servicesStatus?.services.mlBackend.status === 'offline' && (
+
+
Comando per avviare:
+
+ cd /opt/ids/python_ml && nohup python3 main.py > /var/log/ids/ml_backend.log 2>&1 &
+
+
+ )}
+
+ {servicesStatus?.services.mlBackend.status === 'running' && (
+
+
Comando per riavviare:
+
+ pkill -f "python.*main.py" && cd /opt/ids/python_ml && nohup python3 main.py > /var/log/ids/ml_backend.log 2>&1 &
+
+
+ )}
+
+
+
Log:
+
+ tail -f /var/log/ids/backend.log
+
+
+
+
+
+ {/* Database Service */}
+
+
+
+
+ PostgreSQL Database
+ {servicesStatus && getStatusIndicator(servicesStatus.services.database)}
+
+
+
+
+ Stato:
+ {servicesStatus && getStatusBadge(servicesStatus.services.database)}
+
+
+ {servicesStatus?.services.database.status === 'running' && (
+
+ Connessione:
+ Connesso
+
+ )}
+
+
+
Verifica status:
+
+ systemctl status postgresql-16
+
+
+
+ {servicesStatus?.services.database.status === 'error' && (
+
+
Riavvia database:
+
+ sudo systemctl restart postgresql-16
+
+
+ )}
+
+
+
Log:
+
+ sudo journalctl -u postgresql-16 -f
+
+
+
+
+
+ {/* Syslog Parser Service */}
+
+
+
+
+ Syslog Parser
+ {servicesStatus && getStatusIndicator(servicesStatus.services.syslogParser)}
+
+
+
+
+ Stato:
+ {servicesStatus && getStatusBadge(servicesStatus.services.syslogParser)}
+
+
+ {servicesStatus?.services.syslogParser.details?.logsLast5Min !== undefined && (
+
+ Log recenti (5min):
+
+ {servicesStatus.services.syslogParser.details.logsLast5Min}
+
+
+ )}
+
+ {servicesStatus?.services.syslogParser.details?.lastLogTime && (
+
+ Ultimo log:
+
+ {new Date(servicesStatus.services.syslogParser.details.lastLogTime).toLocaleTimeString('it-IT')}
+
+
+ )}
+
+ {(servicesStatus?.services.syslogParser.status === 'idle' || servicesStatus?.services.syslogParser.status === 'error') && (
+
+
Comando per avviare:
+
+ cd /opt/ids/python_ml && nohup sudo -u ids python3 syslog_parser.py > /var/log/ids/syslog_parser.log 2>&1 &
+
+
+ )}
+
+ {servicesStatus?.services.syslogParser.status === 'running' && (
+
+
Comando per riavviare:
+
+ pkill -f syslog_parser.py && cd /opt/ids/python_ml && nohup sudo -u ids python3 syslog_parser.py > /var/log/ids/syslog_parser.log 2>&1 &
+
+
+ )}
+
+
+
Log:
+
+ tail -f /var/log/ids/syslog_parser.log
+
+
+
+
+
+
+ {/* Additional Commands */}
+
+
+
+
+ Comandi Utili
+
+
+
+
+
Verifica tutti i processi IDS attivi:
+
+ ps aux | grep -E "python.*(main|syslog_parser)" | grep -v grep
+
+
+
+
+
Verifica log RSyslog (ricezione log MikroTik):
+
+ tail -f /var/log/mikrotik/raw.log
+
+
+
+
+
Esegui training manuale ML:
+
+ curl -X POST http://localhost:8000/train -H "Content-Type: application/json" -d '{"max_records": 10000, "hours_back": 24}'
+
+
+
+
+
Verifica storico training nel database:
+
+ psql $DATABASE_URL -c "SELECT * FROM training_history ORDER BY trained_at DESC LIMIT 5;"
+
+
+
+
+
+ );
+}
diff --git a/python_ml/main.py b/python_ml/main.py
index d576067..40f9d12 100644
--- a/python_ml/main.py
+++ b/python_ml/main.py
@@ -14,6 +14,7 @@ from psycopg2.extras import RealDictCursor
import os
from dotenv import load_dotenv
import asyncio
+import subprocess
from ml_analyzer import MLAnalyzer
from mikrotik_manager import MikroTikManager
@@ -443,6 +444,43 @@ async def get_stats():
raise HTTPException(status_code=500, detail=str(e))
+# Service Management Endpoints (Read-Only per sicurezza)
+
+@app.get("/services/parser/status")
+async def get_parser_status():
+ """
+ Verifica status del processo syslog_parser.py
+ NOTA: Endpoint read-only per sicurezza.
+ Gestione servizi tramite comandi manuali sul server.
+ """
+ try:
+ # Check if parser process is running
+ result = subprocess.run(
+ ["pgrep", "-f", "python.*syslog_parser.py"],
+ capture_output=True,
+ text=True,
+ timeout=5
+ )
+
+ is_running = result.returncode == 0
+ pid = result.stdout.strip() if is_running else None
+
+ return {
+ "service": "syslog_parser",
+ "running": is_running,
+ "pid": pid,
+ "timestamp": datetime.now().isoformat()
+ }
+
+ except Exception as e:
+ return {
+ "service": "syslog_parser",
+ "running": False,
+ "error": str(e),
+ "timestamp": datetime.now().isoformat()
+ }
+
+
if __name__ == "__main__":
import uvicorn
diff --git a/replit.md b/replit.md
index caed669..acd22af 100644
--- a/replit.md
+++ b/replit.md
@@ -27,11 +27,14 @@ The IDS features a React-based frontend for real-time monitoring, detection visu
2. **Training**: The Python ML component extracts 25 features from network logs and trains an Isolation Forest model.
3. **Detection**: Real-time analysis of network logs is performed using the trained ML model, assigning a risk score.
4. **Auto-Block**: Critical IPs (score >= 80) are automatically blocked across all configured MikroTik routers in parallel via their REST API.
+5. **Monitoring**: Dashboard real-time mostra status servizi (ML Backend, Database, Parser) con pallini verde/rosso.
**Key Features:**
- **ML Analyzer**: Isolation Forest with 25 features.
- **MikroTik Manager**: Parallel communication with 10+ routers via API REST.
- **Detection Engine**: Scoring 0-100 with 5 risk levels (Normal, Basso, Medio, Alto, Critico).
+- **Service Monitoring**: Dashboard con status real-time di ML Backend, Database e Syslog Parser (pallini verde/rosso).
+- **Service Management**: Controlli start/stop/restart per servizi Python via API endpoints.
- **Form Validation**: Improved validation using react-hook-form and Zod.
- **Database Migrations**: Automated SQL migrations applied via `update_from_git.sh --db`.
- **Microservices**: Separation of concerns with dedicated Python ML backend and Node.js API backend.
@@ -52,6 +55,30 @@ The IDS features a React-based frontend for real-time monitoring, detection visu
## Recent Updates (Novembre 2025)
+### 🎛️ Sistema Monitoring Servizi Completo (22 Nov 2025 - 12:00)
+- **Feature**: Dashboard con monitoring real-time sicuro di tutti i servizi IDS
+- **Implementazione**:
+ - Endpoint API `/api/services/status` controlla:
+ - ML Backend Python (health check via HTTP `/health`)
+ - Database PostgreSQL (connection test)
+ - Syslog Parser (verifica processo PID tramite `/services/parser/status`)
+ - Dashboard mostra status con pallini verde/rosso in tempo reale (refresh 5s)
+ - Nuova pagina "Servizi" (`/services`) con:
+ - Status dettagliato di ogni servizio con PID/errori
+ - Comandi sicuri pronti per gestione servizi sul server
+ - Istruzioni chiare per restart/stop/start manuali
+ - Endpoint Python ML backend (Read-Only per sicurezza):
+ - `GET /services/parser/status` - Verifica PID processo syslog parser
+- **Sicurezza**:
+ - ✅ Rimossi endpoint POST pericolosi (start/stop/restart) per prevenire vulnerabilità
+ - ✅ Solo endpoint GET read-only per monitoring
+ - ✅ Gestione servizi tramite comandi manuali sul server (più sicuro)
+- **Benefici**:
+ - 🔒 Monitoring affidabile basato su PID reali (non solo log)
+ - 🔍 Identificazione immediata problemi servizi
+ - 🎯 Comandi pronti e sicuri per gestione manuale
+ - 📊 Visibilità completa stato sistema con zero vulnerabilità
+
### 📊 Log Format Fix - Timestamp Integration (22 Nov 2025 - 10:30)
- **Problema**: RSyslog salvava log senza timestamp, parser Python falliva
- **Soluzione**: Template rsyslog corretto per includere timestamp BSD
diff --git a/server/routes.ts b/server/routes.ts
index 57d6359..9adf77c 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -300,6 +300,102 @@ export async function registerRoutes(app: Express): Promise {
}
});
+ // Services monitoring
+ app.get("/api/services/status", async (req, res) => {
+ try {
+ 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 },
+ };
+
+ // 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,
+ });
+
+ 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,
+ };
+ } 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 };
+ }
+
+ // Check Database
+ try {
+ const conn = await storage.testConnection();
+ if (conn) {
+ services.database.status = "running";
+ services.database.healthy = true;
+ services.database.details = { connected: true };
+ }
+ } catch (error: any) {
+ services.database.status = "error";
+ services.database.details = { error: error.message };
+ }
+
+ // Check Syslog Parser (verificando processo tramite Python backend)
+ try {
+ const controller2 = new AbortController();
+ const timeout2 = setTimeout(() => controller2.abort(), 5000);
+
+ const parserResponse = await fetch(`${ML_BACKEND_URL}/services/parser/status`, {
+ signal: controller2.signal,
+ });
+
+ clearTimeout(timeout2);
+
+ if (parserResponse.ok) {
+ const parserData = await parserResponse.json();
+
+ if (parserData.running && parserData.pid) {
+ services.syslogParser.status = "running";
+ services.syslogParser.healthy = true;
+ services.syslogParser.details = {
+ pid: parserData.pid,
+ timestamp: parserData.timestamp,
+ };
+ } else {
+ services.syslogParser.status = "offline";
+ services.syslogParser.healthy = false;
+ services.syslogParser.details = {
+ pid: null,
+ error: parserData.error || "Processo non attivo",
+ };
+ }
+ } else {
+ throw new Error(`HTTP ${parserResponse.status}`);
+ }
+ } catch (error: any) {
+ services.syslogParser.status = "error";
+ services.syslogParser.healthy = false;
+ services.syslogParser.details = {
+ error: error.code === 'ECONNREFUSED' ? "ML Backend offline" : error.message
+ };
+ }
+
+ res.json({ services });
+ } catch (error: any) {
+ res.status(500).json({ error: "Failed to check services status" });
+ }
+ });
+
const httpServer = createServer(app);
return httpServer;
}
diff --git a/server/storage.ts b/server/storage.ts
index 9103ad9..9acc087 100644
--- a/server/storage.ts
+++ b/server/storage.ts
@@ -50,6 +50,9 @@ export interface IStorage {
getTrainingHistory(limit: number): Promise;
createTrainingHistory(history: InsertTrainingHistory): Promise;
getLatestTraining(): Promise;
+
+ // System
+ testConnection(): Promise;
}
export class DatabaseStorage implements IStorage {
@@ -222,6 +225,17 @@ export class DatabaseStorage implements IStorage {
.limit(1);
return history || undefined;
}
+
+ // System
+ async testConnection(): Promise {
+ try {
+ await db.execute(sql`SELECT 1`);
+ return true;
+ } catch (error) {
+ console.error('[DB ERROR] Connection test failed:', error);
+ return false;
+ }
+ }
}
export const storage = new DatabaseStorage();