diff --git a/agent/model.py b/agent/model.py index 8ae1d8f..88f4c7d 100644 --- a/agent/model.py +++ b/agent/model.py @@ -153,7 +153,7 @@ class EnsembleDetector: 5 % heavier services (higher RAM, some connections) """ rng = np.random.default_rng(42) - n = 2000 + n = 10_000 cpu_idle = rng.exponential(0.3, int(n * 0.75)) cpu_light = rng.uniform(0.5, 15, int(n * 0.20)) diff --git a/backend/main.py b/backend/main.py index a5f3f91..21faf35 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,7 +7,7 @@ and pushes real-time anomaly alerts to connected dashboard clients. import logging import os from contextlib import asynccontextmanager -from datetime import date, datetime, timezone +from datetime import date, datetime, timedelta, timezone from typing import Any import aiosqlite @@ -48,7 +48,9 @@ async def _init_db() -> None: pid INTEGER, process_name TEXT, reason TEXT, - severity TEXT + severity TEXT, + last_seen TEXT, + count INTEGER DEFAULT 1 ); CREATE TABLE IF NOT EXISTS file_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -66,6 +68,14 @@ async def _init_db() -> None: """) await db.commit() + # Migration for existing databases + for col, definition in [("last_seen", "TEXT"), ("count", "INTEGER DEFAULT 1")]: + try: + await db.execute(f"ALTER TABLE alerts ADD COLUMN {col} {definition}") + await db.commit() + except Exception: + pass + # ── WebSocket manager ───────────────────────────────────────────────────────── @@ -310,23 +320,42 @@ async def post_metrics(batch: MetricsBatch) -> dict[str, int]: # Все аномалии: ML + пороговые anomalies = [p for p in batch.processes if p.is_anomaly] + cutoff = (datetime.now(timezone.utc) - timedelta(seconds=30)).isoformat() for proc in anomalies: sev = _severity(proc) reason = _reason(proc) - await db.execute( - "INSERT INTO alerts (timestamp, pid, process_name, reason, severity) VALUES (?,?,?,?,?)", - (ts, proc.pid, proc.name, reason, sev), - ) - await _ws.broadcast( - { - "type": "alert", - "pid": proc.pid, - "process_name": proc.name, - "reason": reason, - "severity": sev, - "timestamp": ts, - } - ) + # Dedup: update existing alert if same process was anomalous recently + async with db.execute( + """SELECT id FROM alerts + WHERE pid=? AND process_name=? + AND (last_seen >= ? OR (last_seen IS NULL AND timestamp >= ?)) + ORDER BY id DESC LIMIT 1""", + (proc.pid, proc.name, cutoff, cutoff), + ) as cur: + existing = await cur.fetchone() + + if existing: + await db.execute( + "UPDATE alerts SET last_seen=?, count=count+1 WHERE id=?", + (ts, existing[0]), + ) + else: + await db.execute( + """INSERT INTO alerts + (timestamp, pid, process_name, reason, severity, last_seen, count) + VALUES (?,?,?,?,?,?,1)""", + (ts, proc.pid, proc.name, reason, sev, ts), + ) + await _ws.broadcast( + { + "type": "alert", + "pid": proc.pid, + "process_name": proc.name, + "reason": reason, + "severity": sev, + "timestamp": ts, + } + ) await db.commit() diff --git a/frontend/src/components/Alerts.tsx b/frontend/src/components/Alerts.tsx index d5abeca..e113bf0 100644 --- a/frontend/src/components/Alerts.tsx +++ b/frontend/src/components/Alerts.tsx @@ -26,6 +26,16 @@ function severityLabel(s: Alert['severity']): string { return 'низкий' } +function fmtDuration(first: string, last: string | null): string | null { + if (!last) return null + const secs = Math.round((new Date(last).getTime() - new Date(first).getTime()) / 1000) + if (secs < 5) return null + if (secs < 60) return `${secs}с` + const m = Math.floor(secs / 60) + const s = secs % 60 + return s > 0 ? `${m}м ${s}с` : `${m}м` +} + interface Props { wsConnected: boolean } @@ -70,7 +80,8 @@ export function Alerts({ wsConnected }: Props) {