Index: server.py
===================================================================
--- server.py	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ server.py	(revision 640ed89213ad0086dc8379de00b8d102057ead1f)
@@ -1,865 +1,1693 @@
 #!/usr/bin/env python3
 """
-netIntel Flask Server (multi-tenant + Google login + JWT cookie session + env tokens)
-
-Auth:
-- POST /api/auth/google  -> sets HttpOnly cookie "session"
-- GET  /api/me           -> returns current user claims
-- POST /api/auth/logout  -> clears cookie
-
-Tenant admin (JWT cookie; admin role):
-- GET/POST /api/admin/environments
-- POST     /api/admin/tokens
-
-Agent (X-Env-Token):
-- POST /receive
-
-Tenant-scoped dashboard (JWT cookie):
-- GET  /api/stats
-- GET  /api/computers
-- GET  /api/computer/<name>
-- GET  /api/export/<name>
-- POST /api/chat
+POPRAVEN SERVER со Sysmon обработка
 """
-
+import traceback
+
+from flask import Flask, request, jsonify, render_template_string
+import json
+import sqlite3
+from datetime import datetime, timedelta
+import time
 import os
-import json
-import time
-import sqlite3
-import traceback
+import socket
+from collections import defaultdict
 import threading
-import secrets
-from datetime import datetime, timedelta
-from functools import wraps
-
-from flask import Flask, request, jsonify, Response
-from dotenv import load_dotenv
-
-import jwt
-from google.oauth2 import id_token
-from google.auth.transport import requests as grequests
-
-# Optional OpenAI
-try:
-    from openai import OpenAI
-except Exception:
-    OpenAI = None
-
-from flask_cors import CORS
-from dotenv import load_dotenv
-from pathlib import Path
-
-BASE_DIR = Path(__file__).resolve().parent
-load_dotenv(BASE_DIR / ".env")
-
-print("GRAFANA_API_TOKEN =", os.environ.get("GRAFANA_API_TOKEN"))
-
-
-# ----------------------------
-# Config
-# ----------------------------
-DB_FILE = os.environ.get("DB_FILE", "lan_logs_sysmon.db")
-LOG_DIR = os.environ.get("LOG_DIR", "received_data_sysmon")
-
-OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
-GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", "")
-JWT_SECRET = os.environ.get("JWT_SECRET", "dev-change-me")
-JWT_ISSUER = os.environ.get("JWT_ISSUER", "netintel")
-COOKIE_SECURE = os.environ.get("COOKIE_SECURE", "0") == "1"  # set 1 only on HTTPS
-GRAFANA_TOKEN = os.environ.get("GRAFANA_TOKEN", "")
-
-# CORS origins (add your LAN origins if needed)
-EXTRA_ORIGINS = [o.strip() for o in os.environ.get("CORS_ORIGINS", "").split(",") if o.strip()]
-DEFAULT_ORIGINS = [
-    "http://localhost:5173",
-    "http://127.0.0.1:5173",
-]
-ALLOWED_ORIGINS = list(dict.fromkeys(DEFAULT_ORIGINS + EXTRA_ORIGINS))
-
-client = OpenAI(api_key=OPENAI_API_KEY, timeout=15) if (OPENAI_API_KEY and OpenAI) else None
-
-# ----------------------------
-# App
-# ----------------------------
+import requests
+from openai import OpenAI
+from openai import OpenAI
+
+OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
+client = OpenAI(
+    api_key=OPENAI_API_KEY,
+    timeout=15  # максимум 15 секунди да чека
+) if OPENAI_API_KEY else None
+
+
+
+
+
 app = Flask(__name__)
-os.makedirs(LOG_DIR, exist_ok=True)
-
-CORS(
-    app,
-    supports_credentials=True,
-    origins=ALLOWED_ORIGINS,
-    allow_headers=[
-        "Content-Type",
-        "X-Admin-Session",
-        "X-Env",
-        "X-Env-Token",
-    ],
-    methods=["GET", "POST", "OPTIONS"],
-)
-
-
-# ----------------------------
-# DB helpers
-# ----------------------------
-def db():
+
+DB_FILE = "lan_logs_sysmon.db"
+LOG_DIR = "received_data_sysmon"
+
+
+def init_database():
+    """Креирај подобрена база со Sysmon табели"""
+    os.makedirs(LOG_DIR, exist_ok=True)
+
     conn = sqlite3.connect(DB_FILE)
-    conn.row_factory = sqlite3.Row
-    return conn
-
-
-def init_db():
-    """Create tables if missing + light migrations."""
-    conn = db()
     c = conn.cursor()
 
-    c.execute("PRAGMA foreign_keys = ON;")
-
-    # Tenants / users / memberships
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS tenants(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            name TEXT NOT NULL,
-            owner_email TEXT NOT NULL,
-            created_at TEXT
-        )
-        """)
-
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS users(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            email TEXT UNIQUE NOT NULL,
-            name TEXT,
-            picture TEXT,
-            created_at TEXT
-        )
-        """)
-
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS memberships(
-            user_id INTEGER NOT NULL,
-            tenant_id INTEGER NOT NULL,
-            role TEXT DEFAULT 'admin',
-            created_at TEXT,
-            PRIMARY KEY(user_id, tenant_id),
-            FOREIGN KEY(user_id) REFERENCES users(id),
-            FOREIGN KEY(tenant_id) REFERENCES tenants(id)
-        )
-        """)
-
-    # Environments (unique per tenant)
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS environments(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            tenant_id INTEGER NOT NULL,
-            name TEXT NOT NULL,
-            created_at TEXT,
-            UNIQUE(tenant_id, name),
-            FOREIGN KEY(tenant_id) REFERENCES tenants(id)
-        )
-        """)
-
-    # Env tokens (agent tokens)
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS env_tokens(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            tenant_id INTEGER NOT NULL,
-            env_name TEXT NOT NULL,
-            token TEXT UNIQUE NOT NULL,
-            created_at TEXT,
-            expires_at TEXT,
-            FOREIGN KEY(tenant_id) REFERENCES tenants(id)
-        )
-        """)
-
-    # Computers
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS computers(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            tenant_id INTEGER NOT NULL,
-            env_name TEXT DEFAULT 'default',
-            name TEXT NOT NULL,
-            user TEXT,
-            ip TEXT,
-            os TEXT,
-            first_seen TEXT,
-            last_seen TEXT,
-            sysmon_available INTEGER DEFAULT 0,
-            UNIQUE(tenant_id, name)
-        )
-        """)
-
-    # History
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS computer_history(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            computer_id INTEGER,
-            cpu_usage REAL,
-            ram_usage REAL,
-            disk_usage REAL,
-            network_sent_mb REAL,
-            network_recv_mb REAL,
-            timestamp TEXT,
-            FOREIGN KEY(computer_id) REFERENCES computers(id)
-        )
-        """)
-
-    # Processes
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS computer_processes(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            computer_id INTEGER,
-            pid INTEGER,
-            name TEXT,
-            cpu_percent REAL,
-            memory_mb REAL,
-            username TEXT,
-            cmdline TEXT,
-            timestamp TEXT,
-            FOREIGN KEY(computer_id) REFERENCES computers(id)
-        )
-        """)
-
-    # Sysmon events
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS sysmon_events(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            computer_id INTEGER,
-            event_id INTEGER,
-            event_type TEXT,
-            message TEXT,
-            timestamp TEXT,
-            details TEXT,
-            FOREIGN KEY(computer_id) REFERENCES computers(id)
-        )
-        """)
-
-    # Network connections
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS network_connections(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            computer_id INTEGER,
-            pid INTEGER,
-            local_address TEXT,
-            remote_address TEXT,
-            status TEXT,
-            process_name TEXT,
-            timestamp TEXT,
-            FOREIGN KEY(computer_id) REFERENCES computers(id)
-        )
-        """)
-
-    # Security alerts
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS security_alerts(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            computer_id INTEGER,
-            alert_type TEXT,
-            severity TEXT,
-            description TEXT,
-            timestamp TEXT,
-            resolved INTEGER DEFAULT 0,
-            FOREIGN KEY(computer_id) REFERENCES computers(id)
-        )
-        """)
-
-    # Environment settings (switch-ови по env)
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS env_settings(
-            tenant_id INTEGER NOT NULL,
-            env_name TEXT NOT NULL,
-            save_process_history INTEGER DEFAULT 0,
-            created_at TEXT,
-            updated_at TEXT,
-            PRIMARY KEY(tenant_id, env_name),
-            FOREIGN KEY(tenant_id) REFERENCES tenants(id)
-        )
-    """)
-
-    # Latest processes (only current snapshot)
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS computer_processes_current(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            computer_id INTEGER NOT NULL,
-            pid INTEGER,
-            name TEXT,
-            cpu_percent REAL,
-            memory_mb REAL,
-            username TEXT,
-            cmdline TEXT,
-            timestamp TEXT,
-            FOREIGN KEY(computer_id) REFERENCES computers(id)
-        )
-    """)
-
-    # History processes (optional, controlled by switch)
-    c.execute("""
-        CREATE TABLE IF NOT EXISTS computer_processes_history(
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            computer_id INTEGER NOT NULL,
-            pid INTEGER,
-            name TEXT,
-            cpu_percent REAL,
-            memory_mb REAL,
-            username TEXT,
-            cmdline TEXT,
-            timestamp TEXT,
-            FOREIGN KEY(computer_id) REFERENCES computers(id)
-        )
-    """)
-
+    # Табела за компјутери
+    c.execute('''CREATE TABLE IF NOT EXISTS computers
+                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
+                  name TEXT UNIQUE,
+                  user TEXT,
+                  ip TEXT,
+                  os TEXT,
+                  first_seen TIMESTAMP,
+                  last_seen TIMESTAMP,
+                  sysmon_available BOOLEAN DEFAULT 0)''')
+
+    # Табела за историја
+    c.execute('''CREATE TABLE IF NOT EXISTS computer_history
+                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
+                  computer_id INTEGER,
+                  cpu_usage REAL,
+                  ram_usage REAL,
+                  disk_usage REAL,
+                  network_sent_mb REAL,
+                  network_recv_mb REAL,
+                  timestamp TIMESTAMP,
+                  FOREIGN KEY(computer_id) REFERENCES computers(id))''')
+
+    # Табела за процеси
+    c.execute('''CREATE TABLE IF NOT EXISTS computer_processes
+                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
+                  computer_id INTEGER,
+                  pid INTEGER,
+                  name TEXT,
+                  cpu_percent REAL,
+                  memory_mb REAL,
+                  username TEXT,
+                  cmdline TEXT,
+                  timestamp TIMESTAMP,
+                  FOREIGN KEY(computer_id) REFERENCES computers(id))''')
+
+    # НОВА: Табела за Sysmon настани
+    c.execute('''CREATE TABLE IF NOT EXISTS sysmon_events
+                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
+                  computer_id INTEGER,
+                  event_id INTEGER,
+                  event_type TEXT,
+                  message TEXT,
+                  timestamp TIMESTAMP,
+                  details TEXT,
+                  FOREIGN KEY(computer_id) REFERENCES computers(id))''')
+
+    # НОВА: Табела за детални мрежни конекции
+    c.execute('''CREATE TABLE IF NOT EXISTS network_connections
+                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
+                  computer_id INTEGER,
+                  pid INTEGER,
+                  local_address TEXT,
+                  remote_address TEXT,
+                  status TEXT,
+                  process_name TEXT,
+                  timestamp TIMESTAMP,
+                  FOREIGN KEY(computer_id) REFERENCES computers(id))''')
+
+    # НОВА: Табела за безбедносни настани
+    c.execute('''CREATE TABLE IF NOT EXISTS security_alerts
+                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
+                  computer_id INTEGER,
+                  alert_type TEXT,
+                  severity TEXT,
+                  description TEXT,
+                  timestamp TIMESTAMP,
+                  resolved BOOLEAN DEFAULT 0,
+                  FOREIGN KEY(computer_id) REFERENCES computers(id))''')
 
     conn.commit()
     conn.close()
-    print(f"✅ DB ready: {DB_FILE}")
-
-
-# ----------------------------
-# JWT helpers
-# ----------------------------
-def make_jwt(payload: dict, minutes=60 * 24):
-    exp = datetime.utcnow() + timedelta(minutes=minutes)
-    data = {**payload, "iss": JWT_ISSUER, "exp": exp}
-    return jwt.encode(data, JWT_SECRET, algorithm="HS256")
-
-
-def read_jwt(token: str):
-    return jwt.decode(token, JWT_SECRET, algorithms=["HS256"], issuer=JWT_ISSUER)
-
-
-def require_user():
-    def deco(fn):
-        @wraps(fn)
-        def wrap(*args, **kwargs):
-            tok = request.cookies.get("session") or ""
-            if not tok:
-                return jsonify({"error": "Unauthorized"}), 401
+
+    print(f"✅ Database created: {DB_FILE}")
+
+
+@app.route('/receive', methods=['POST'])
+def receive_data():
+    try:
+        data = request.json
+
+        if not data:
+            return jsonify({"error": "No data"}), 400
+
+        info = data['info']
+        computer_name = info['computer_name']
+        timestamp = datetime.now()
+        security_data = data.get('security_data', {})
+
+        conn = sqlite3.connect(DB_FILE)
+        c = conn.cursor()
+
+        # Провери дали компјутерот веќе постои
+        c.execute("SELECT id FROM computers WHERE name = ?", (computer_name,))
+        result = c.fetchone()
+
+        if result:
+            # Компјутерот постои - ажурирај го
+            computer_id = result[0]
+            c.execute('''UPDATE computers 
+                        SET user = ?, ip = ?, os = ?, last_seen = ?, sysmon_available = ?
+                        WHERE id = ?''',
+                      (info['user'], info['ip_address'], info['os'],
+                       timestamp.isoformat(), info.get('is_sysmon_available', 0),
+                       computer_id))
+        else:
+            # Нов компјутер - додади го
+            c.execute('''INSERT INTO computers 
+                        (name, user, ip, os, first_seen, last_seen, sysmon_available) 
+                        VALUES (?, ?, ?, ?, ?, ?, ?)''',
+                      (computer_name, info['user'], info['ip_address'],
+                       info['os'], timestamp.isoformat(), timestamp.isoformat(),
+                       info.get('is_sysmon_available', 0)))
+            computer_id = c.lastrowid
+
+        # Додади во историја
+        c.execute('''INSERT INTO computer_history 
+                    (computer_id, cpu_usage, ram_usage, disk_usage,
+                     network_sent_mb, network_recv_mb, timestamp)
+                    VALUES (?, ?, ?, ?, ?, ?, ?)''',
+                  (computer_id, info['cpu_usage'], info['ram_usage'],
+                   info['disk_usage'], info.get('network_sent_mb', 0),
+                   info.get('network_recv_mb', 0), info['timestamp']))
+
+        # Зачувај ги процесите
+        for proc in data.get('processes', []):
+            c.execute('''INSERT INTO computer_processes 
+                        (computer_id, pid, name, cpu_percent, memory_mb, 
+                         username, cmdline, timestamp)
+                        VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
+                      (computer_id, proc.get('pid'), proc.get('name'),
+                       proc.get('cpu_percent', 0), proc.get('memory_mb', 0),
+                       proc.get('user'), proc.get('cmdline'), info['timestamp']))
+
+        # ЗАЧУВАЈ SYSMON НАСТАНИ
+        sysmon_events_count = 0
+        for event in security_data.get('sysmon_events', []):
             try:
-                claims = read_jwt(tok)
-            except Exception:
-                return jsonify({"error": "Unauthorized"}), 401
-            request.user = claims
-            return fn(*args, **kwargs)
-
-        return wrap
-
-    return deco
-def is_process_history_enabled(tenant_id: int, env_name: str) -> bool:
-    conn = db()
-    c = conn.cursor()
-    c.execute("""
-        SELECT save_process_history
-        FROM env_settings
-        WHERE tenant_id=? AND env_name=?
-        LIMIT 1
-    """, (tenant_id, env_name))
-    row = c.fetchone()
-    conn.close()
-
-    # default: OFF ако нема запис
-    return bool(row["save_process_history"]) if row else False
-
-
-def require_tenant_admin():
-    def deco(fn):
-        @wraps(fn)
-        def wrap(*args, **kwargs):
-            tok = request.cookies.get("session") or ""
-            if not tok:
-                return jsonify({"error": "Unauthorized"}), 401
+                c.execute('''INSERT INTO sysmon_events 
+                            (computer_id, event_id, event_type, message, 
+                             timestamp, details)
+                            VALUES (?, ?, ?, ?, ?, ?)''',
+                          (computer_id, event.get('event_id'),
+                           event.get('event_type', 'Unknown'),
+                           event.get('message', ''),
+                           event.get('timestamp', info['timestamp']),
+                           json.dumps(event)))
+                sysmon_events_count += 1
+            except Exception as e:
+                print(f"Error saving Sysmon event: {e}")
+
+        # ЗАЧУВАЈ МРЕЖНИ КОНЕКЦИИ
+        for conn_data in security_data.get('network_connections', []):
             try:
-                claims = read_jwt(tok)
-            except Exception:
-                return jsonify({"error": "Unauthorized"}), 401
-            if claims.get("role") != "admin":
-                return jsonify({"error": "Forbidden"}), 403
-            request.user = claims
-            return fn(*args, **kwargs)
-
-        return wrap
-
-    return deco
-
-
-# ----------------------------
-# Env-token helpers (agents)
-# ----------------------------
-def get_env_from_token(req):
-    tok = req.headers.get("X-Env-Token", "")
-    if not tok:
-        return (None, None)
-
-    conn = db()
-    c = conn.cursor()
-    c.execute("""
-            SELECT env_name, tenant_id
-            FROM env_tokens
-            WHERE token = ?
-              AND (expires_at IS NULL OR expires_at > datetime('now'))
-            LIMIT 1
-        """, (tok,))
-    row = c.fetchone()
-    conn.close()
-
-    if not row:
-        return (None, None)
-    return (row["env_name"], row["tenant_id"])
-
-
-# ----------------------------
-# Utility
-# ----------------------------
+                c.execute('''INSERT INTO network_connections 
+                            (computer_id, pid, local_address, remote_address,
+                             status, process_name, timestamp)
+                            VALUES (?, ?, ?, ?, ?, ?, ?)''',
+                          (computer_id, conn_data.get('pid'),
+                           conn_data.get('local_address'),
+                           conn_data.get('remote_address'),
+                           conn_data.get('status'),
+                           conn_data.get('process_name'),
+                           info['timestamp']))
+            except Exception as e:
+                print(f"Error saving network connection: {e}")
+
+        conn.commit()
+        conn.close()
+
+        # Зачувај во JSON
+        save_json_file(data)
+
+        print(f"[{timestamp.strftime('%H:%M:%S')}] 📥 {computer_name}: "
+              f"CPU {info['cpu_usage']}% | Sysmon: {sysmon_events_count} events")
+
+        return jsonify({
+            "status": "success",
+            "message": f"Data saved for {computer_name}",
+            "computer_id": computer_id,
+            "event_count": sysmon_events_count,
+            "timestamp": timestamp.isoformat()
+        })
+
+    except Exception as e:
+        print(f"[-] Грешка: {e}")
+        return jsonify({"error": str(e)}), 500
+
+
 def save_json_file(data):
-    info = data.get("info") or {}
-    computer_name = info.get("computer_name", "unknown")
+    """Зачувај во JSON"""
+    info = data['info']
+    computer_name = info['computer_name']
     date_str = datetime.now().strftime("%Y%m%d_%H")
 
+    # Креирај folder за компјутерот
     computer_dir = os.path.join(LOG_DIR, computer_name)
     os.makedirs(computer_dir, exist_ok=True)
+
+    # Фајл за секој час (за подобро организирање)
     filepath = os.path.join(computer_dir, f"{date_str}.json")
 
+    # Додади во постоечки фајл или креирај нов
     if os.path.exists(filepath):
-        try:
-            with open(filepath, "r", encoding="utf-8") as f:
-                existing_data = json.load(f)
-        except Exception:
-            existing_data = []
+        with open(filepath, 'r', encoding='utf-8') as f:
+            existing_data = json.load(f)
     else:
         existing_data = []
 
+    # Додади нов запис
     existing_data.append({
-        "timestamp": info.get("timestamp"),
+        "timestamp": info['timestamp'],
         "info": info,
-        "processes_count": len(data.get("processes", [])),
-        "sysmon_events_count": len((data.get("security_data") or {}).get("sysmon_events", [])),
+        "processes_count": len(data.get('processes', [])),
+        "sysmon_events_count": len(data.get('security_data', {}).get('sysmon_events', []))
     })
 
-    with open(filepath, "w", encoding="utf-8") as f:
+    # Зачувај
+    with open(filepath, 'w', encoding='utf-8') as f:
         json.dump(existing_data, f, indent=2, ensure_ascii=False)
 
 
+@app.route('/')
+def dashboard():
+    """Главен dashboard"""
+    conn = sqlite3.connect(DB_FILE)
+    c = conn.cursor()
+
+    # Земaj ги сите компјутери
+    c.execute('''SELECT name, user, ip, os, first_seen, last_seen, sysmon_available 
+                 FROM computers 
+                 ORDER BY last_seen DESC''')
+
+    computers = []
+    for row in c.fetchall():
+        computer_name = row[0]
+
+        # Пресметaj колку записи има во последните 24 часа
+        c.execute('''SELECT COUNT(*) FROM computer_history 
+                     WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
+                     AND timestamp > datetime('now', '-24 hours')''',
+                  (computer_name,))
+        recent_logs = c.fetchone()[0]
+
+        # Земaj ги последните 5 записи за CPU/RAM
+        c.execute('''SELECT cpu_usage, ram_usage, timestamp 
+                     FROM computer_history 
+                     WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
+                     ORDER BY timestamp DESC LIMIT 5''',
+                  (computer_name,))
+        recent_metrics = c.fetchall()
+
+        # Sysmon настани за последните 24 часа
+        c.execute('''SELECT COUNT(*) FROM sysmon_events 
+                     WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
+                     AND timestamp > datetime('now', '-24 hours')''',
+                  (computer_name,))
+        recent_sysmon = c.fetchone()[0]
+
+        # Пресметaj просек
+        avg_cpu = sum(m[0] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0
+        avg_ram = sum(m[1] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0
+
+        # Статус
+        last_seen = datetime.fromisoformat(row[5])
+        time_diff = (datetime.now() - last_seen).seconds
+
+        if time_diff < 60:
+            status = 'online'
+            status_color = 'green'
+        elif time_diff < 300:
+            status = 'idle'
+            status_color = 'orange'
+        else:
+            status = 'offline'
+            status_color = 'gray'
+
+        computers.append({
+            'name': row[0],
+            'user': row[1],
+            'ip': row[2],
+            'os': row[3],
+            'first_seen': row[4],
+            'last_seen': row[5],
+            'sysmon_available': bool(row[6]),
+            'recent_logs': recent_logs,
+            'recent_sysmon': recent_sysmon,
+            'avg_cpu': round(avg_cpu, 1),
+            'avg_ram': round(avg_ram, 1),
+            'status': status,
+            'status_color': status_color
+        })
+
+    conn.close()
+
+    # Генерирај HTML
+    return render_template_string('''
+    <!DOCTYPE html>
+    <html>
+    <head>
+        <title>LAN Log Server - Sysmon Dashboard</title>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
+        <style>
+            :root {
+                --primary-color: #2c3e50;
+                --secondary-color: #3498db;
+                --danger-color: #e74c3c;
+                --warning-color: #f39c12;
+                --success-color: #27ae60;
+            }
+
+            body { 
+                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 
+                margin: 0; 
+                padding: 20px; 
+                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+                min-height: 100vh;
+            }
+
+            .container {
+                max-width: 1400px;
+                margin: 0 auto;
+                background: white;
+                border-radius: 15px;
+                box-shadow: 0 20px 40px rgba(0,0,0,0.1);
+                overflow: hidden;
+            }
+
+            .header {
+                background: linear-gradient(to right, var(--primary-color), #1a2530);
+                color: white;
+                padding: 30px 40px;
+                border-radius: 15px 15px 0 0;
+            }
+
+            .header h1 {
+                margin: 0;
+                font-size: 2.5em;
+                display: flex;
+                align-items: center;
+                gap: 15px;
+            }
+
+            .stats-grid {
+                display: grid;
+                grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+                gap: 20px;
+                padding: 30px;
+            }
+
+            .stat-card {
+                background: white;
+                padding: 25px;
+                border-radius: 10px;
+                box-shadow: 0 5px 15px rgba(0,0,0,0.08);
+                border-left: 5px solid var(--secondary-color);
+                transition: transform 0.3s ease;
+            }
+
+            .stat-card:hover {
+                transform: translateY(-5px);
+            }
+
+            .stat-card.sysmon {
+                border-left-color: var(--danger-color);
+            }
+
+            .stat-card.network {
+                border-left-color: var(--success-color);
+            }
+
+            .stat-value {
+                font-size: 2.8em;
+                font-weight: bold;
+                margin: 10px 0;
+            }
+
+            .stat-label {
+                color: #666;
+                font-size: 0.9em;
+                text-transform: uppercase;
+                letter-spacing: 1px;
+            }
+
+            .computers-grid {
+                display: grid;
+                grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+                gap: 25px;
+                padding: 30px;
+            }
+
+            .computer-card {
+                background: white;
+                border-radius: 12px;
+                padding: 25px;
+                box-shadow: 0 8px 20px rgba(0,0,0,0.1);
+                cursor: pointer;
+                transition: all 0.3s ease;
+                border: 2px solid transparent;
+                position: relative;
+                overflow: hidden;
+            }
+
+            .computer-card:hover {
+                transform: translateY(-8px);
+                box-shadow: 0 15px 30px rgba(0,0,0,0.15);
+                border-color: var(--secondary-color);
+            }
+
+            .computer-card::before {
+                content: '';
+                position: absolute;
+                top: 0;
+                left: 0;
+                right: 0;
+                height: 4px;
+                background: linear-gradient(to right, #667eea, #764ba2);
+            }
+
+            .computer-name {
+                font-size: 1.4em;
+                font-weight: bold;
+                color: var(--primary-color);
+                margin-bottom: 10px;
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+            }
+
+            .status-indicator {
+                display: inline-block;
+                width: 12px;
+                height: 12px;
+                border-radius: 50%;
+                margin-right: 8px;
+            }
+
+            .computer-info {
+                color: #555;
+                margin: 8px 0;
+                font-size: 0.95em;
+                display: flex;
+                align-items: center;
+                gap: 10px;
+            }
+
+            .computer-info i {
+                width: 20px;
+                color: #777;
+            }
+
+            .stats-row {
+                display: flex;
+                justify-content: space-between;
+                margin-top: 20px;
+                padding-top: 20px;
+                border-top: 1px solid #eee;
+            }
+
+            .stat-item {
+                text-align: center;
+            }
+
+            .stat-number {
+                font-size: 1.6em;
+                font-weight: bold;
+                color: var(--primary-color);
+            }
+
+            .stat-text {
+                font-size: 0.8em;
+                color: #777;
+                margin-top: 5px;
+            }
+
+            .sysmon-badge {
+                background: var(--danger-color);
+                color: white;
+                padding: 3px 10px;
+                border-radius: 20px;
+                font-size: 0.8em;
+                display: inline-block;
+                margin-left: 10px;
+            }
+
+            /* Модал */
+            .modal {
+                display: none;
+                position: fixed;
+                top: 0;
+                left: 0;
+                right: 0;
+                bottom: 0;
+                background: rgba(0,0,0,0.7);
+                z-index: 1000;
+                overflow-y: auto;
+            }
+
+            .modal-content {
+                background: white;
+                margin: 50px auto;
+                width: 90%;
+                max-width: 1200px;
+                border-radius: 15px;
+                animation: modalSlideIn 0.3s ease;
+            }
+
+            @keyframes modalSlideIn {
+                from {
+                    transform: translateY(-50px);
+                    opacity: 0;
+                }
+                to {
+                    transform: translateY(0);
+                    opacity: 1;
+                }
+            }
+
+            .modal-header {
+                padding: 25px 30px;
+                background: var(--primary-color);
+                color: white;
+                border-radius: 15px 15px 0 0;
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+            }
+
+            .close-btn {
+                background: none;
+                border: none;
+                color: white;
+                font-size: 1.8em;
+                cursor: pointer;
+                width: 40px;
+                height: 40px;
+                border-radius: 50%;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                transition: background 0.2s;
+            }
+
+            .close-btn:hover {
+                background: rgba(255,255,255,0.1);
+            }
+
+            .tabs {
+                display: flex;
+                border-bottom: 1px solid #ddd;
+                padding: 0 30px;
+                background: #f8f9fa;
+            }
+
+            .tab-btn {
+                padding: 15px 25px;
+                background: none;
+                border: none;
+                cursor: pointer;
+                font-size: 1em;
+                color: #666;
+                position: relative;
+                transition: all 0.3s;
+            }
+
+            .tab-btn:hover {
+                color: var(--primary-color);
+            }
+
+            .tab-btn.active {
+                color: var(--primary-color);
+                font-weight: bold;
+            }
+
+            .tab-btn.active::after {
+                content: '';
+                position: absolute;
+                bottom: -1px;
+                left: 0;
+                right: 0;
+                height: 3px;
+                background: var(--secondary-color);
+            }
+
+            .tab-content {
+                padding: 30px;
+                display: none;
+            }
+
+            .tab-content.active {
+                display: block;
+            }
+
+            .events-table {
+                width: 100%;
+                border-collapse: collapse;
+                margin-top: 20px;
+            }
+
+            .events-table th {
+                background: #f8f9fa;
+                padding: 15px;
+                text-align: left;
+                border-bottom: 2px solid #dee2e6;
+                color: var(--primary-color);
+            }
+
+            .events-table td {
+                padding: 12px 15px;
+                border-bottom: 1px solid #eee;
+            }
+
+            .events-table tr:hover {
+                background: #f8f9fa;
+            }
+
+            .severity-high {
+                background: #ffe6e6;
+                color: var(--danger-color);
+                padding: 3px 10px;
+                border-radius: 3px;
+                font-weight: bold;
+            }
+
+            .severity-medium {
+                background: #fff3cd;
+                color: var(--warning-color);
+                padding: 3px 10px;
+                border-radius: 3px;
+            }
+
+            .event-details {
+                max-width: 500px;
+                word-wrap: break-word;
+                font-family: monospace;
+                font-size: 0.9em;
+            }
+
+            .filter-bar {
+                padding: 20px 30px;
+                background: #f8f9fa;
+                border-bottom: 1px solid #ddd;
+            }
+
+            .search-box {
+                padding: 10px 15px;
+                border: 1px solid #ddd;
+                border-radius: 5px;
+                width: 300px;
+            }
+
+            .btn {
+                padding: 10px 20px;
+                background: var(--secondary-color);
+                color: white;
+                border: none;
+                border-radius: 5px;
+                cursor: pointer;
+                transition: background 0.3s;
+            }
+
+            .btn:hover {
+                background: #2980b9;
+            }
+
+            .chart-container {
+                height: 300px;
+                margin: 20px 0;
+            }
+
+            @media (max-width: 768px) {
+                .computers-grid {
+                    grid-template-columns: 1fr;
+                }
+
+                .modal-content {
+                    width: 95%;
+                    margin: 20px auto;
+                }
+            }
+        </style>
+    </head>
+    <body>
+        <div class="container">
+            <div class="header">
+                <h1>
+                    <span>🛡️ LAN Security Monitor</span>
+                    <small style="font-size: 0.5em; opacity: 0.8;">Sysmon Edition</small>
+                </h1>
+                <p>Server: {{ socket.gethostbyname(socket.gethostname()) }}:5555 | Total Computers: {{ computers|length }}</p>
+            </div>
+
+            <div class="stats-grid">
+                <div class="stat-card">
+                    <div class="stat-label">Total Computers</div>
+                    <div class="stat-value">{{ computers|length }}</div>
+                    <div class="stat-text">Monitored Systems</div>
+                </div>
+
+                <div class="stat-card sysmon">
+                    <div class="stat-label">Sysmon Events (24h)</div>
+                    <div class="stat-value" id="total-sysmon">0</div>
+                    <div class="stat-text">Security Events</div>
+                </div>
+
+                <div class="stat-card network">
+                    <div class="stat-label">Network Connections</div>
+                    <div class="stat-value" id="total-network">0</div>
+                    <div class="stat-text">Active Connections</div>
+                </div>
+            </div>
+
+            <div style="padding: 0 30px;">
+                <h2>Connected Computers</h2>
+                <div class="computers-grid" id="computers-grid">
+                    {% for comp in computers %}
+                    <div class="computer-card" onclick="showComputerDetails('{{ comp.name }}')">
+                        <div class="computer-name">
+                            {{ comp.name }}
+                            <span class="status-indicator" style="background: {{ comp.status_color }}"></span>
+                        </div>
+
+                        <div class="computer-info">
+                            <i>👤</i> {{ comp.user }}
+                        </div>
+
+                        <div class="computer-info">
+                            <i>🌐</i> {{ comp.ip }}
+                        </div>
+
+                        <div class="computer-info">
+                            <i>💻</i> {{ comp.os[:40] }}
+                        </div>
+
+                        <div class="computer-info">
+                            <i>⏰</i> Last seen: {{ comp.last_seen[11:19] }}
+                        </div>
+
+                        {% if comp.sysmon_available %}
+                        <div class="computer-info">
+                            <i>🛡️</i> Sysmon Available
+                            <span class="sysmon-badge">{{ comp.recent_sysmon }} events</span>
+                        </div>
+                        {% endif %}
+
+                        <div class="stats-row">
+                            <div class="stat-item">
+                                <div class="stat-number">{{ comp.avg_cpu }}%</div>
+                                <div class="stat-text">CPU</div>
+                            </div>
+                            <div class="stat-item">
+                                <div class="stat-number">{{ comp.avg_ram }}%</div>
+                                <div class="stat-text">RAM</div>
+                            </div>
+                            <div class="stat-item">
+                                <div class="stat-number">{{ comp.recent_logs }}</div>
+                                <div class="stat-text">Logs (24h)</div>
+                            </div>
+                        </div>
+                    </div>
+                    {% endfor %}
+                </div>
+            </div>
+        </div>
+
+        <!-- Детален модал -->
+        <div class="modal" id="computer-modal">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h2 id="modal-title">Computer Details</h2>
+                    <button class="close-btn" onclick="closeModal()">×</button>
+                </div>
+
+                <div class="filter-bar">
+                    <input type="text" id="event-search" class="search-box" placeholder="Search events..." 
+                           onkeyup="filterEvents()">
+                    <button class="btn" onclick="exportData()">Export Data</button>
+                </div>
+
+                <div class="tabs">
+                    <button class="tab-btn active" onclick="switchTab('overview')">Overview</button>
+                    <button class="tab-btn" onclick="switchTab('sysmon')">Sysmon Events</button>
+                    <button class="tab-btn" onclick="switchTab('processes')">Processes</button>
+                    <button class="tab-btn" onclick="switchTab('network')">Network</button>
+                    <button class="tab-btn" onclick="switchTab('charts')">Charts</button>
+                </div>
+
+                <!-- Overview Tab -->
+                <div class="tab-content active" id="overview-tab">
+                    <h3>System Overview</h3>
+                    <div id="overview-content"></div>
+                </div>
+
+                <!-- Sysmon Events Tab -->
+                <div class="tab-content" id="sysmon-tab">
+                    <h3>Security Events</h3>
+                    <table class="events-table" id="sysmon-table">
+                        <thead>
+                            <tr>
+                                <th>Time</th>
+                                <th>Event ID</th>
+                                <th>Type</th>
+                                <th>Severity</th>
+                                <th>Message</th>
+                                <th>Actions</th>
+                            </tr>
+                        </thead>
+                        <tbody id="sysmon-events">
+                            <!-- Events will be populated here -->
+                        </tbody>
+                    </table>
+                </div>
+
+                <!-- Processes Tab -->
+                <div class="tab-content" id="processes-tab">
+                    <h3>Running Processes</h3>
+                    <table class="events-table">
+                        <thead>
+                            <tr>
+                                <th>PID</th>
+                                <th>Name</th>
+                                <th>User</th>
+                                <th>CPU %</th>
+                                <th>Memory</th>
+                                <th>Command Line</th>
+                            </tr>
+                        </thead>
+                        <tbody id="processes-list">
+                            <!-- Processes will be populated here -->
+                        </tbody>
+                    </table>
+                </div>
+
+                <!-- Network Tab -->
+                <div class="tab-content" id="network-tab">
+                    <h3>Network Connections</h3>
+                    <table class="events-table">
+                        <thead>
+                            <tr>
+                                <th>PID</th>
+                                <th>Process</th>
+                                <th>Local Address</th>
+                                <th>Remote Address</th>
+                                <th>Status</th>
+                                <th>Timestamp</th>
+                            </tr>
+                        </thead>
+                        <tbody id="network-connections">
+                            <!-- Connections will be populated here -->
+                        </tbody>
+                    </table>
+                </div>
+
+                <!-- Charts Tab -->
+                <div class="tab-content" id="charts-tab">
+                    <h3>Performance Analytics</h3>
+                    <div class="chart-container">
+                        <canvas id="performance-chart"></canvas>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- Event Details Modal -->
+        <div class="modal" id="event-modal" style="display: none;">
+            <div class="modal-content" style="max-width: 800px;">
+                <div class="modal-header">
+                    <h2>Event Details</h2>
+                    <button class="close-btn" onclick="closeEventModal()">×</button>
+                </div>
+                <div style="padding: 30px;">
+                    <pre id="event-details-content" style="background: #f5f5f5; padding: 20px; border-radius: 5px; overflow: auto; max-height: 500px;"></pre>
+                </div>
+            </div>
+        </div>
+
+        <script>
+            let currentComputer = null;
+            let allSysmonEvents = [];
+
+            function updateStats() {
+                // Ажурирај ги вкупните статистики
+                fetch('/api/stats')
+                    .then(r => r.json())
+                    .then(data => {
+                        document.getElementById('total-sysmon').textContent = data.total_sysmon_events;
+                        document.getElementById('total-network').textContent = data.total_network_connections;
+                    });
+            }
+
+            function showComputerDetails(computerName) {
+                currentComputer = computerName;
+
+                // Покажи модал
+                document.getElementById('computer-modal').style.display = 'block';
+                document.getElementById('modal-title').textContent = computerName;
+
+                // Вчитувај податоци
+                loadComputerDetails(computerName);
+            }
+
+            function loadComputerDetails(computerName) {
+                // Вчитувај целосни податоци за компјутерот
+                fetch(`/api/computer/${encodeURIComponent(computerName)}`)
+                    .then(response => response.json())
+                    .then(data => {
+                        populateOverview(data);
+                        populateSysmonEvents(data);
+                        populateProcesses(data);
+                        populateNetwork(data);
+                        updateCharts(data);
+                    })
+                    .catch(error => {
+                        console.error('Error loading details:', error);
+                    });
+            }
+
+            function populateOverview(data) {
+                const overview = document.getElementById('overview-content');
+                overview.innerHTML = `
+                    <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
+                        <div>
+                            <h4>System Information</h4>
+                            <p><strong>User:</strong> ${data.computer.user}</p>
+                            <p><strong>IP:</strong> ${data.computer.ip}</p>
+                            <p><strong>OS:</strong> ${data.computer.os}</p>
+                            <p><strong>First Seen:</strong> ${new Date(data.computer.first_seen).toLocaleString()}</p>
+                            <p><strong>Last Seen:</strong> ${new Date(data.computer.last_seen).toLocaleString()}</p>
+                            <p><strong>Total Logs:</strong> ${data.computer.total_logs}</p>
+                        </div>
+                        <div>
+                            <h4>Current Status</h4>
+                            <p><strong>CPU Usage:</strong> ${data.current_metrics?.cpu_usage || 0}%</p>
+                            <p><strong>RAM Usage:</strong> ${data.current_metrics?.ram_usage || 0}%</p>
+                            <p><strong>Disk Usage:</strong> ${data.current_metrics?.disk_usage || 0}%</p>
+                            <p><strong>Network Sent:</strong> ${data.current_metrics?.network_sent_mb || 0} MB</p>
+                            <p><strong>Network Received:</strong> ${data.current_metrics?.network_recv_mb || 0} MB</p>
+                        </div>
+                    </div>
+                `;
+            }
+
+            function populateSysmonEvents(data) {
+                const tableBody = document.getElementById('sysmon-events');
+                tableBody.innerHTML = '';
+                allSysmonEvents = data.sysmon_events || [];
+
+                allSysmonEvents.forEach(event => {
+                    const row = tableBody.insertRow();
+                    const time = new Date(event.timestamp).toLocaleTimeString();
+                    const severity = getSeverityForEvent(event.event_id);
+
+                    row.innerHTML = `
+                        <td>${time}</td>
+                        <td>${event.event_id}</td>
+                        <td>${event.event_type}</td>
+                        <td><span class="severity-${severity}">${severity.toUpperCase()}</span></td>
+                        <td class="event-details">${event.message.substring(0, 100)}...</td>
+                        <td>
+                            <button class="btn" style="padding: 5px 10px; font-size: 0.9em;" 
+                                    onclick="showEventDetails(${JSON.stringify(event).replace(/"/g, '&quot;')})">
+                                View Details
+                            </button>
+                        </td>
+                    `;
+                });
+            }
+
+            function populateProcesses(data) {
+                const tableBody = document.getElementById('processes-list');
+                tableBody.innerHTML = '';
+
+                (data.recent_processes || []).forEach(proc => {
+                    const row = tableBody.insertRow();
+                    row.innerHTML = `
+                        <td>${proc.pid}</td>
+                        <td>${proc.name}</td>
+                        <td>${proc.username}</td>
+                        <td>${proc.cpu_percent || 0}</td>
+                        <td>${proc.memory_mb || 0} MB</td>
+                        <td class="event-details">${proc.cmdline || ''}</td>
+                    `;
+                });
+            }
+
+            function populateNetwork(data) {
+                const tableBody = document.getElementById('network-connections');
+                tableBody.innerHTML = '';
+
+                (data.network_connections || []).forEach(conn => {
+                    const row = tableBody.insertRow();
+                    const time = new Date(conn.timestamp).toLocaleTimeString();
+                    row.innerHTML = `
+                        <td>${conn.pid}</td>
+                        <td>${conn.process_name || 'N/A'}</td>
+                        <td>${conn.local_address || 'N/A'}</td>
+                        <td>${conn.remote_address || 'N/A'}</td>
+                        <td>${conn.status}</td>
+                        <td>${time}</td>
+                    `;
+                });
+            }
+
+            function updateCharts(data) {
+                const ctx = document.getElementById('performance-chart').getContext('2d');
+                const history = data.history || [];
+
+                const labels = history.slice(-20).map(h => 
+                    new Date(h.timestamp).toLocaleTimeString()
+                );
+                const cpuData = history.slice(-20).map(h => h.cpu_usage);
+                const ramData = history.slice(-20).map(h => h.ram_usage);
+
+                if(window.performanceChart) {
+                    window.performanceChart.destroy();
+                }
+
+                window.performanceChart = new Chart(ctx, {
+                    type: 'line',
+                    data: {
+                        labels: labels,
+                        datasets: [
+                            {
+                                label: 'CPU Usage %',
+                                data: cpuData,
+                                borderColor: 'rgb(255, 99, 132)',
+                                backgroundColor: 'rgba(255, 99, 132, 0.2)',
+                                tension: 0.4
+                            },
+                            {
+                                label: 'RAM Usage %',
+                                data: ramData,
+                                borderColor: 'rgb(54, 162, 235)',
+                                backgroundColor: 'rgba(54, 162, 235, 0.2)',
+                                tension: 0.4
+                            }
+                        ]
+                    },
+                    options: {
+                        responsive: true,
+                        maintainAspectRatio: false,
+                        scales: {
+                            y: {
+                                beginAtZero: true,
+                                max: 100
+                            }
+                        }
+                    }
+                });
+            }
+
+            function getSeverityForEvent(eventId) {
+                const highSeverity = [1, 3, 8, 10]; // Process creation, network, remote thread, process access
+                const mediumSeverity = [5, 6, 7, 11, 22]; // Process termination, driver/image load, file create, DNS
+
+                if (highSeverity.includes(parseInt(eventId))) return 'high';
+                if (mediumSeverity.includes(parseInt(eventId))) return 'medium';
+                return 'low';
+            }
+
+            function showEventDetails(event) {
+                document.getElementById('event-details-content').textContent = 
+                    JSON.stringify(event, null, 2);
+                document.getElementById('event-modal').style.display = 'block';
+            }
+
+            function closeEventModal() {
+                document.getElementById('event-modal').style.display = 'none';
+            }
+
+            function filterEvents() {
+                const searchTerm = document.getElementById('event-search').value.toLowerCase();
+                const tableBody = document.getElementById('sysmon-events');
+                tableBody.innerHTML = '';
+
+                allSysmonEvents
+                    .filter(event => 
+                        event.message.toLowerCase().includes(searchTerm) ||
+                        event.event_type.toLowerCase().includes(searchTerm) ||
+                        event.event_id.toString().includes(searchTerm)
+                    )
+                    .forEach(event => {
+                        const row = tableBody.insertRow();
+                        const time = new Date(event.timestamp).toLocaleTimeString();
+                        const severity = getSeverityForEvent(event.event_id);
+
+                        row.innerHTML = `
+                            <td>${time}</td>
+                            <td>${event.event_id}</td>
+                            <td>${event.event_type}</td>
+                            <td><span class="severity-${severity}">${severity.toUpperCase()}</span></td>
+                            <td class="event-details">${event.message.substring(0, 100)}...</td>
+                            <td>
+                                <button class="btn" style="padding: 5px 10px; font-size: 0.9em;" 
+                                        onclick="showEventDetails(${JSON.stringify(event).replace(/"/g, '&quot;')})">
+                                    View Details
+                                </button>
+                            </td>
+                        `;
+                    });
+            }
+
+            function switchTab(tabName) {
+                // Деактивирај сите tab-ови
+                document.querySelectorAll('.tab-btn').forEach(btn => {
+                    btn.classList.remove('active');
+                });
+                document.querySelectorAll('.tab-content').forEach(tab => {
+                    tab.classList.remove('active');
+                });
+
+                // Активирај избраниот tab
+                document.querySelector(`[onclick="switchTab('${tabName}')"]`).classList.add('active');
+                document.getElementById(`${tabName}-tab`).classList.add('active');
+            }
+
+            function closeModal() {
+                document.getElementById('computer-modal').style.display = 'none';
+                currentComputer = null;
+                allSysmonEvents = [];
+            }
+
+            function exportData() {
+                if (!currentComputer) return;
+
+                fetch(`/api/export/${encodeURIComponent(currentComputer)}`)
+                    .then(response => response.blob())
+                    .then(blob => {
+                        const url = window.URL.createObjectURL(blob);
+                        const a = document.createElement('a');
+                        a.href = url;
+                        a.download = `${currentComputer}_export_${new Date().toISOString().slice(0,10)}.json`;
+                        document.body.appendChild(a);
+                        a.click();
+                        document.body.removeChild(a);
+                    });
+            }
+
+            // Auto-refresh
+            setInterval(() => {
+                updateStats();
+
+                // Ажурирај детали ако модал е отворен
+                if (currentComputer && document.getElementById('computer-modal').style.display === 'block') {
+                    loadComputerDetails(currentComputer);
+                }
+            }, 30000);
+
+            // Затвори модал со Escape
+            document.addEventListener('keydown', (e) => {
+                if (e.key === 'Escape') {
+                    closeModal();
+                    closeEventModal();
+                }
+            });
+
+            // Иницијализирај статистики при старт
+            updateStats();
+        </script>
+    </body>
+    </html>
+    ''', computers=computers, socket=socket, datetime=datetime)
+
+
+@app.route('/api/computer/<computer_name>')
+def get_computer_details(computer_name):
+    """Добиј сите детални информации за еден компјутер"""
+    conn = sqlite3.connect(DB_FILE)
+    c = conn.cursor()
+
+    # Основни информации
+    c.execute('''SELECT id, name, user, ip, os, first_seen, last_seen 
+                 FROM computers WHERE name = ?''', (computer_name,))
+    computer_data = c.fetchone()
+
+    if not computer_data:
+        conn.close()
+        return jsonify({"error": "Computer not found"}), 404
+
+    computer_id = computer_data[0]
+
+    # Број на сите записи
+    c.execute('SELECT COUNT(*) FROM computer_history WHERE computer_id = ?', (computer_id,))
+    total_logs = c.fetchone()[0]
+
+    # Сите history записи
+    c.execute('''SELECT cpu_usage, ram_usage, disk_usage, network_sent_mb, network_recv_mb, timestamp 
+                 FROM computer_history 
+                 WHERE computer_id = ? 
+                 ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
+    history = []
+    for row in c.fetchall():
+        history.append({
+            'cpu_usage': row[0],
+            'ram_usage': row[1],
+            'disk_usage': row[2],
+            'network_sent_mb': row[3],
+            'network_recv_mb': row[4],
+            'timestamp': row[5]
+        })
+
+    # Последни метрики
+    current_metrics = history[0] if history else {}
+
+    # Последни процеси
+    c.execute('''SELECT pid, name, username, cpu_percent, memory_mb, cmdline, timestamp 
+                 FROM computer_processes 
+                 WHERE computer_id = ? 
+                 ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
+    recent_processes = []
+    for row in c.fetchall():
+        recent_processes.append({
+            'pid': row[0],
+            'name': row[1],
+            'username': row[2],
+            'cpu_percent': row[3],
+            'memory_mb': row[4],
+            'cmdline': row[5],
+            'timestamp': row[6]
+        })
+
+    # SYSMON НАСТАНИ
+    c.execute('''SELECT event_id, event_type, message, timestamp, details 
+                 FROM sysmon_events 
+                 WHERE computer_id = ? 
+                 ORDER BY timestamp DESC LIMIT 200''', (computer_id,))
+    sysmon_events = []
+    for row in c.fetchall():
+        try:
+            details = json.loads(row[4]) if row[4] else {}
+        except:
+            details = {}
+
+        sysmon_events.append({
+            'event_id': row[0],
+            'event_type': row[1],
+            'message': row[2],
+            'timestamp': row[3],
+            'details': details
+        })
+
+    # Мрежни конекции
+    c.execute('''SELECT pid, process_name, local_address, remote_address, status, timestamp 
+                 FROM network_connections 
+                 WHERE computer_id = ? 
+                 ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
+    network_connections = []
+    for row in c.fetchall():
+        network_connections.append({
+            'pid': row[0],
+            'process_name': row[1],
+            'local_address': row[2],
+            'remote_address': row[3],
+            'status': row[4],
+            'timestamp': row[5]
+        })
+
+    conn.close()
+
+    return jsonify({
+        'computer': {
+            'id': computer_data[0],
+            'name': computer_data[1],
+            'user': computer_data[2],
+            'ip': computer_data[3],
+            'os': computer_data[4],
+            'first_seen': computer_data[5],
+            'last_seen': computer_data[6],
+            'total_logs': total_logs
+        },
+        'history': history,
+        'current_metrics': current_metrics,
+        'recent_processes': recent_processes,
+        'sysmon_events': sysmon_events,
+        'network_connections': network_connections
+    })
+
+@app.route('/api/computers')
+def get_computers():
+    conn = sqlite3.connect(DB_FILE)
+    c = conn.cursor()
+
+    c.execute('''SELECT name, user, ip, os, first_seen, last_seen, sysmon_available 
+                 FROM computers 
+                 ORDER BY last_seen DESC''')
+
+    computers = []
+    for row in c.fetchall():
+        computer_name = row[0]
+
+        # recent logs
+        c.execute('''SELECT COUNT(*) FROM computer_history 
+                     WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
+                     AND timestamp > datetime('now', '-24 hours')''',
+                  (computer_name,))
+        recent_logs = c.fetchone()[0]
+
+        # last 5 metrics
+        c.execute('''SELECT cpu_usage, ram_usage, timestamp 
+                     FROM computer_history 
+                     WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
+                     ORDER BY timestamp DESC LIMIT 5''',
+                  (computer_name,))
+        recent_metrics = c.fetchall()
+
+        c.execute('''SELECT COUNT(*) FROM sysmon_events 
+                     WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
+                     AND timestamp > datetime('now', '-24 hours')''',
+                  (computer_name,))
+        recent_sysmon = c.fetchone()[0]
+
+        avg_cpu = sum(m[0] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0
+        avg_ram = sum(m[1] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0
+
+        last_seen = datetime.fromisoformat(row[5])
+        time_diff = (datetime.now() - last_seen).seconds
+
+        if time_diff < 60:
+            status = 'online'
+            status_color = 'green'
+        elif time_diff < 300:
+            status = 'idle'
+            status_color = 'orange'
+        else:
+            status = 'offline'
+            status_color = 'gray'
+
+        computers.append({
+            'name': row[0],
+            'user': row[1],
+            'ip': row[2],
+            'os': row[3],
+            'first_seen': row[4],
+            'last_seen': row[5],
+            'sysmon_available': bool(row[6]),
+            'recent_logs': recent_logs,
+            'recent_sysmon': recent_sysmon,
+            'avg_cpu': round(avg_cpu, 1),
+            'avg_ram': round(avg_ram, 1),
+            'status': status,
+            'status_color': status_color
+        })
+
+    conn.close()
+    return jsonify(computers)
+
+@app.route('/api/stats')
+def get_stats():
+    """Добиј глобални статистики"""
+    conn = sqlite3.connect(DB_FILE)
+    c = conn.cursor()
+
+    # Вкупно Sysmon настани за последните 24 часа
+    c.execute('''SELECT COUNT(*) FROM sysmon_events 
+                 WHERE timestamp > datetime('now', '-24 hours')''')
+    total_sysmon_events = c.fetchone()[0]
+
+    # Вкупно мрежни конекции за последните 24 часа
+    c.execute('''SELECT COUNT(*) FROM network_connections 
+                 WHERE timestamp > datetime('now', '-24 hours')''')
+    total_network_connections = c.fetchone()[0]
+
+    conn.close()
+
+    return jsonify({
+        'total_sysmon_events': total_sysmon_events,
+        'total_network_connections': total_network_connections,
+        'server_time': datetime.now().isoformat()
+    })
+
+def build_rag_context(question, computer_name=None, limit_per_table=10):
+    try:
+        conn = sqlite3.connect(DB_FILE)
+        c = conn.cursor()
+
+        params = []
+        comp_filter = ""
+        if computer_name:
+            comp_filter = "AND computer_id = (SELECT id FROM computers WHERE name = ?)"
+            params.append(computer_name)
+
+        # Sysmon настани
+        c.execute(
+            f"""
+            SELECT timestamp, event_id, event_type, message
+            FROM sysmon_events
+            WHERE 1=1 {comp_filter}
+            ORDER BY timestamp DESC
+            LIMIT ?
+            """,
+            params + [limit_per_table],
+        )
+        sysmon_rows = c.fetchall()
+
+        # Перформанс историја
+        c.execute(
+            f"""
+            SELECT timestamp, cpu_usage, ram_usage, disk_usage,
+                   network_sent_mb, network_recv_mb
+            FROM computer_history
+            WHERE 1=1 {comp_filter}
+            ORDER BY timestamp DESC
+            LIMIT ?
+            """,
+            params + [limit_per_table],
+        )
+        perf_rows = c.fetchall()
+
+        # Процеси
+        c.execute(
+            f"""
+            SELECT timestamp, pid, name, cpu_percent, memory_mb 
+            FROM computer_processes
+            WHERE 1=1 {comp_filter}
+            ORDER BY timestamp DESC
+            LIMIT ?
+            """,
+            params + [limit_per_table],
+        )
+        proc_rows = c.fetchall()
+
+        conn.close()
+
+        print("[RAG] sysmon:", len(sysmon_rows),
+              "| perf:", len(perf_rows),
+              "| proc:", len(proc_rows))
+
+        lines = []
+
+        for ts, eid, etype, msg in sysmon_rows:
+            lines.append(f"[SYSMON] {ts} | Event {eid} ({etype}): {msg}")
+
+        for ts, cpu, ram, disk, nsent, nrecv in perf_rows:
+            lines.append(
+                f"[PERF] {ts} | CPU {cpu}% | RAM {ram}% | Disk {disk}% | "
+                f"Net sent {nsent}MB / recv {nrecv}MB"
+            )
+
+        for ts, pid, name, cpu, mem in proc_rows:
+            lines.append(f"[PROC] {ts} | PID {pid} | {name} | CPU {cpu}% | RAM {mem}MB")
+
+        context = "\n".join(lines)
+        print("[RAG] sample context:\n", "\n".join(lines[:5]))
+        return context
+
+    except Exception as e:
+        traceback.print_exc()
+        return ""
+
+# def ask_llm_with_context(question, context, language="mk"):
+#     if not client:
+#         return "Немаш поставено OPENAI_API_KEY на серверот."
+#
+#     system_msg = (
+#         "Ти си асистент за безбедност и систем мониторинг. "
+#         "Одговараш базирано ИСКЛУЧИВО на дадените логови што ти се дадени. "
+#         "Ако нема доволно информации, кажи дека нема доволно податоци. "
+#         "Објаснувај кратко и јасно."
+#     )
+#
+#     user_msg = f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}"
+#
+#     try:
+#         completion = client.chat.completions.create(
+#             model="gpt-4.1-mini",
+#             messages=[
+#                 {"role": "system", "content": system_msg},
+#                 {"role": "user", "content": user_msg},
+#             ],
+#             temperature=0.2,
+#         )
+#         return completion.choices[0].message.content
+#     except Exception as e:
+#         # Може попрецизно: openai.APITimeoutError, openai.APIConnectionError итн.
+#         return f"Грешка при повик кон LLM: {e}"
+
+def ask_llm_with_context(question, context, language="mk"):
+    """
+    Вика OpenAI модел и враќа одговор базиран на контекстот од логови.
+    """
+    if not client:
+        return "Немаш поставено OPENAI_API_KEY на серверот."
+
+    system_msg = (
+        "Ти си асистент за безбедност и систем мониторинг. "
+        "Одговараш базирано ИСКЛУЧИВО на дадените логови што ти се дадени. "
+        "Типови редови во логовите:\n"
+        "- [SYSMON] ... Sysmon безбедносни настани.\n"
+        "- [PERF] ... Општи перформанси (CPU, RAM, диск, мрежа).\n"
+        "- [PROC] ... Информации за поединечни процеси: PID, име, CPU%, RAM MB.\n"
+        "Кога корисникот прашува за процеси и нивна потрошувачка, "
+        "СЕКОГАШ користи ги [PROC] редовите. "
+        "Никогаш не кажувај дека 'нема информации за процеси' ако постои барем еден [PROC] ред. "
+        "Ако навистина НЕМА [PROC] редови, тогаш можеш да кажеш дека нема доволно податоци. "
+        "Објаснувај кратко и јасно."
+    )
+
+    user_msg = f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}"
+
+    try:
+        print("[LLM] Calling OpenAI…")
+        completion = client.chat.completions.create(
+            model="gpt-4.1-mini",
+            messages=[
+                {"role": "system", "content": system_msg},
+                {"role": "user", "content": user_msg},
+            ],
+            temperature=0.2,
+        )
+        print("[LLM] Got response")
+        return completion.choices[0].message.content
+
+    except Exception as e:
+        print("[LLM] ERROR:", e)
+        return f"Грешка при повик кон LLM: {e}"
+
+
+
+#
+# def ask_llm_with_context(question, context, language="mk"):
+#     payload = {
+#         "model": "deepseek-r1:1.5b",
+#         "prompt": f"Прашање: {question}\n\nКонтекст:\n{context}",
+#         "stream": False
+#     }
+#
+#     try:
+#         r = requests.post("http://localhost:11434/api/generate", json=payload)
+#         r.raise_for_status()
+#         data = r.json()
+#         return data.get("response", "")
+#     except Exception as e:
+#         return f"Грешка при повик кон Ollama: {e}"
+
+
+@app.route('/api/export/<computer_name>')
+def export_computer_data(computer_name):
+    """Експортирај ги сите податоци за еден компјутер"""
+    data = {}
+
+    conn = sqlite3.connect(DB_FILE)
+    c = conn.cursor()
+
+    # Основни информации за компјутерот
+    c.execute('''SELECT * FROM computers WHERE name = ?''', (computer_name,))
+    columns = [description[0] for description in c.description]
+    computer_data = c.fetchone()
+
+    if computer_data:
+        data['computer'] = dict(zip(columns, computer_data))
+
+        # Историја
+        computer_id = computer_data[0]
+        c.execute('''SELECT * FROM computer_history WHERE computer_id = ? ORDER BY timestamp''', (computer_id,))
+        data['history'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()]
+
+        # Sysmon настани
+        c.execute('''SELECT * FROM sysmon_events WHERE computer_id = ? ORDER BY timestamp''', (computer_id,))
+        data['sysmon_events'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()]
+
+        # Процеси
+        c.execute('''SELECT * FROM computer_processes WHERE computer_id = ? ORDER BY timestamp''', (computer_id,))
+        data['processes'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()]
+
+        # Мрежни конекции
+        c.execute('''SELECT * FROM network_connections WHERE computer_id = ? ORDER BY timestamp''', (computer_id,))
+        data['network_connections'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()]
+
+    conn.close()
+
+    # Креирај JSON одговор
+    from flask import Response
+    response = Response(
+        json.dumps(data, indent=2, default=str),
+        mimetype='application/json',
+        headers={'Content-Disposition': f'attachment; filename={computer_name}_export.json'}
+    )
+
+    return response
+
+
+@app.route('/ping')
+def ping():
+    return jsonify({
+        'status': 'alive',
+        'server_time': datetime.now().isoformat(),
+        'version': '2.0_sysmon',
+        'database': DB_FILE
+    })
+
+
+def get_local_ip():
+    try:
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        s.connect(("8.8.8.8", 80))
+        local_ip = s.getsockname()[0]
+        s.close()
+        return local_ip
+    except:
+        return "127.0.0.1"
+
+
 def cleanup_old_data():
-    """Deletes old rows (30 days) every hour."""
+    """Чисти стари податоци од базата (стари од 30 дена)"""
     while True:
-        time.sleep(3600)
+        time.sleep(3600)  # Чисти на секој час
         try:
-            conn = db()
+            conn = sqlite3.connect(DB_FILE)
             c = conn.cursor()
-            cutoff = (datetime.now() - timedelta(days=30)).isoformat()
-
-            c.execute("DELETE FROM computer_history WHERE timestamp < ?", (cutoff,))
-            c.execute("DELETE FROM sysmon_events WHERE timestamp < ?", (cutoff,))
-            c.execute("DELETE FROM network_connections WHERE timestamp < ?", (cutoff,))
-            c.execute("DELETE FROM computer_processes_history WHERE timestamp < ?", (cutoff,))
+
+            cutoff_date = (datetime.now() - timedelta(days=30)).isoformat()
+
+            # Избриши стари записи
+            c.execute("DELETE FROM computer_history WHERE timestamp < ?", (cutoff_date,))
+            c.execute("DELETE FROM computer_processes WHERE timestamp < ?", (cutoff_date,))
+            c.execute("DELETE FROM sysmon_events WHERE timestamp < ?", (cutoff_date,))
+            c.execute("DELETE FROM network_connections WHERE timestamp < ?", (cutoff_date,))
 
             conn.commit()
             conn.close()
-            print(f"[Cleanup] Deleted entries older than {cutoff}")
+
+            print(f"[Cleanup] Избришани стари записи пред {cutoff_date}")
+
         except Exception as e:
-            print("[Cleanup] Error:", e)
-
-
-# ----------------------------
-# Auth endpoints
-# ----------------------------
-@app.route("/api/auth/google", methods=["POST"])
-def auth_google():
-    data = request.get_json(force=True, silent=True) or {}
-    credential = (data.get("credential") or "").strip()
-    if not credential:
-        return jsonify({"error": "Missing credential"}), 400
-    if not GOOGLE_CLIENT_ID:
-        return jsonify({"error": "Server missing GOOGLE_CLIENT_ID"}), 500
-
+            print(f"[Cleanup] Грешка: {e}")
+
+import time
+@app.route("/api/chat", methods=["POST"])
+def api_chat():
     try:
-        idinfo = id_token.verify_oauth2_token(
-            credential,
-            grequests.Request(),
-            GOOGLE_CLIENT_ID,
-        )
-        email = idinfo.get("email")
-        name = idinfo.get("name") or ""
-        picture = idinfo.get("picture") or ""
-
-        if not email:
-            return jsonify({"error": "No email in token"}), 400
-
-        conn = db()
-        c = conn.cursor()
-
-        # upsert user
-        c.execute("SELECT id FROM users WHERE email=?", (email,))
-        row = c.fetchone()
-        if row:
-            user_id = row["id"]
-            c.execute("UPDATE users SET name=?, picture=? WHERE id=?", (name, picture, user_id))
-        else:
-            c.execute(
-                "INSERT INTO users(email, name, picture, created_at) VALUES(?,?,?, datetime('now'))",
-                (email, name, picture),
-            )
-            user_id = c.lastrowid
-
-        # membership -> tenant (create tenant if first time)
-        c.execute("SELECT tenant_id, role FROM memberships WHERE user_id=? LIMIT 1", (user_id,))
-        m = c.fetchone()
-
-        if not m:
-            tenant_name = email.split("@")[0]
-            c.execute(
-                "INSERT INTO tenants(name, owner_email, created_at) VALUES(?,?, datetime('now'))",
-                (tenant_name, email),
-            )
-            tenant_id = c.lastrowid
-            role = "admin"
-
-            c.execute(
-                "INSERT INTO memberships(user_id, tenant_id, role, created_at) VALUES(?,?, 'admin', datetime('now'))",
-                (user_id, tenant_id),
-            )
-
-            # default env for this tenant
-            c.execute(
-                "INSERT OR IGNORE INTO environments(tenant_id, name, created_at) VALUES(?, 'default', datetime('now'))",
-                (tenant_id,),
-            )
-        else:
-            tenant_id = m["tenant_id"]
-            role = m["role"] or "admin"
-
-        conn.commit()
-        conn.close()
-
-        session = make_jwt(
-            {
-                "uid": user_id,
-                "email": email,
-                "name": name,
-                "role": role,
-                "tenant_id": tenant_id,
-            },
-            minutes=60 * 24,
-        )
-
-        resp = jsonify({"ok": True, "user": {"email": email, "name": name, "role": role, "tenant_id": tenant_id}})
-        resp.set_cookie(
-            "session",
-            session,
-            httponly=True,
-            samesite="Lax",
-            secure=COOKIE_SECURE,
-            max_age=60 * 60 * 24,
-        )
-        return resp
-
-    except Exception as e:
-        return jsonify({"error": "Invalid Google token", "details": str(e)}), 401
-
-
-@app.route("/api/me", methods=["GET"])
-@require_user()
-def api_me():
-    return jsonify({"user": request.user})
-
-
-@app.route("/api/auth/logout", methods=["POST"])
-def auth_logout():
-    resp = jsonify({"ok": True})
-    resp.set_cookie("session", "", expires=0)
-    return resp
-
-
-# ----------------------------
-# Admin endpoints
-# ----------------------------
-@app.route("/api/admin/environments", methods=["GET"])
-@require_tenant_admin()
-def admin_list_envs():
-    tenant_id = request.user["tenant_id"]
-    conn = db()
-    c = conn.cursor()
-    c.execute("SELECT name FROM environments WHERE tenant_id=? ORDER BY name", (tenant_id,))
-    envs = [r["name"] for r in c.fetchall()]
-    conn.close()
-    if not envs:
-        envs = ["default"]
-    return jsonify({"environments": envs})
-
-
-@app.route("/api/admin/environments", methods=["POST"])
-@require_tenant_admin()
-def admin_create_env():
-    tenant_id = request.user["tenant_id"]
-    data = request.get_json(force=True, silent=True) or {}
-    name = (data.get("name") or "").strip()
-    if not name:
-        return jsonify({"error": "Missing env name"}), 400
-
-    conn = db()
-    c = conn.cursor()
-    try:
-        c.execute(
-            "INSERT INTO environments(tenant_id, name, created_at) VALUES(?, ?, datetime('now'))",
-            (tenant_id, name),
-        )
-        conn.commit()
-    except Exception as e:
-        conn.close()
-        return jsonify({"error": str(e)}), 400
-    conn.close()
-    return jsonify({"ok": True, "name": name})
-
-
-@app.route("/api/admin/tokens", methods=["POST"])
-@require_tenant_admin()
-def admin_generate_token():
-    tenant_id = request.user["tenant_id"]
-    data = request.get_json(force=True, silent=True) or {}
-    env = (data.get("env") or "").strip()
-    if not env:
-        return jsonify({"error": "Missing env"}), 400
-
-    conn = db()
-    c = conn.cursor()
-    c.execute("SELECT 1 FROM environments WHERE tenant_id=? AND name=? LIMIT 1", (tenant_id, env))
-    if not c.fetchone():
-        conn.close()
-        return jsonify({"error": "Environment not found"}), 404
-
-    token = secrets.token_urlsafe(32)
-    c.execute("""
-            INSERT INTO env_tokens(tenant_id, env_name, token, created_at, expires_at)
-            VALUES(?, ?, ?, datetime('now'), datetime('now', '+90 days'))
-        """, (tenant_id, env, token))
-    conn.commit()
-    conn.close()
-
-    return jsonify({"ok": True, "env": env, "token": token})
-
-@app.route("/api/admin/env-settings/<env_name>", methods=["GET"])
-@require_tenant_admin()
-def admin_get_env_settings(env_name):
-    tenant_id = request.user["tenant_id"]
-
-    conn = db()
-    c = conn.cursor()
-    c.execute("""
-        SELECT save_process_history
-        FROM env_settings
-        WHERE tenant_id=? AND env_name=?
-        LIMIT 1
-    """, (tenant_id, env_name))
-    row = c.fetchone()
-    conn.close()
-
-    return jsonify({
-        "env": env_name,
-        "save_process_history": bool(row["save_process_history"]) if row else False
-    })
-
-@app.route("/api/admin/env-settings/<env_name>", methods=["POST"])
-@require_tenant_admin()
-def admin_set_env_settings(env_name):
-    tenant_id = request.user["tenant_id"]
-    data = request.get_json(force=True, silent=True) or {}
-    enabled = 1 if bool(data.get("save_process_history")) else 0
-
-    conn = db()
-    c = conn.cursor()
-    c.execute("""
-        INSERT INTO env_settings(tenant_id, env_name, save_process_history, created_at, updated_at)
-        VALUES(?, ?, ?, datetime('now'), datetime('now'))
-        ON CONFLICT(tenant_id, env_name) DO UPDATE SET
-            save_process_history=excluded.save_process_history,
-            updated_at=datetime('now')
-    """, (tenant_id, env_name, enabled))
-    conn.commit()
-    conn.close()
-
-    return jsonify({"ok": True, "env": env_name, "save_process_history": bool(enabled)})
-
-# ----------------------------
-# Agent endpoint
-# ----------------------------
-@app.route("/receive", methods=["POST"])
-def receive_data():
-    try:
-        data = request.get_json(force=True, silent=True) or {}
-        if not data:
-            return jsonify({"error": "No data"}), 400
-
-        env_name, tenant_id = get_env_from_token(request)
-        if not env_name or not tenant_id:
-            return jsonify({"error": "Missing or invalid X-Env-Token"}), 401
-
-        info = data.get("info") or {}
-        computer_name = info.get("computer_name")
-        if not computer_name:
-            return jsonify({"error": "Missing info.computer_name"}), 400
-
-        now_iso = datetime.now().isoformat()
-        security_data = data.get("security_data") or {}
-
-        conn = db()
-        c = conn.cursor()
-
-        # Find by (tenant_id + name)
-        c.execute(
-            "SELECT id FROM computers WHERE tenant_id=? AND name=? LIMIT 1",
-            (tenant_id, computer_name),
-        )
-        row = c.fetchone()
-
-        if row:
-            computer_id = row["id"]
-            c.execute("""
-                UPDATE computers
-                SET user=?, ip=?, os=?, last_seen=?, sysmon_available=?, env_name=?
-                WHERE id=? AND tenant_id=?
-            """, (
-                info.get("user"),
-                info.get("ip_address"),
-                info.get("os"),
-                now_iso,
-                int(info.get("is_sysmon_available", 0) or 0),
-                env_name,
-                computer_id,
-                tenant_id,
-            ))
-        else:
-            c.execute("""
-                INSERT INTO computers(
-                    tenant_id, env_name, name, user, ip, os,
-                    first_seen, last_seen, sysmon_available
-                )
-                VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)
-            """, (
-                tenant_id,
-                env_name,
-                computer_name,
-                info.get("user"),
-                info.get("ip_address"),
-                info.get("os"),
-                now_iso,
-                now_iso,
-                int(info.get("is_sysmon_available", 0) or 0),
-            ))
-            computer_id = c.lastrowid
-
-        # history row
-        c.execute("""
-            INSERT INTO computer_history(
-                computer_id, cpu_usage, ram_usage, disk_usage,
-                network_sent_mb, network_recv_mb, timestamp
-            )
-            VALUES(?, ?, ?, ?, ?, ?, ?)
-        """, (
-            computer_id,
-            float(info.get("cpu_usage") or 0),
-            float(info.get("ram_usage") or 0),
-            float(info.get("disk_usage") or 0),
-            float(info.get("network_sent_mb") or 0),
-            float(info.get("network_recv_mb") or 0),
-            info.get("timestamp") or now_iso,
-        ))
-
-        # ----------------------------
-        # processes: CURRENT (always)
-        # ----------------------------
-        # Clear old current snapshot for this computer
-        c.execute("DELETE FROM computer_processes_current WHERE computer_id=?", (computer_id,))
-
-        # Insert new snapshot
-        for proc in (data.get("processes") or []):
-            c.execute("""
-                INSERT INTO computer_processes_current(
-                    computer_id, pid, name, cpu_percent, memory_mb, username, cmdline, timestamp
-                )
-                VALUES(?, ?, ?, ?, ?, ?, ?, ?)
-            """, (
-                computer_id,
-                proc.get("pid"),
-                proc.get("name"),
-                float(proc.get("cpu_percent") or 0),
-                float(proc.get("memory_mb") or 0),
-                proc.get("user") or proc.get("username"),
-                proc.get("cmdline"),
-                info.get("timestamp") or now_iso,
-            ))
-
-        # ----------------------------
-        # processes: HISTORY (optional)
-        # ----------------------------
-        if is_process_history_enabled(tenant_id, env_name):
-            for proc in (data.get("processes") or []):
-                c.execute("""
-                    INSERT INTO computer_processes_history(
-                        computer_id, pid, name, cpu_percent, memory_mb, username, cmdline, timestamp
-                    )
-                    VALUES(?, ?, ?, ?, ?, ?, ?, ?)
-                """, (
-                    computer_id,
-                    proc.get("pid"),
-                    proc.get("name"),
-                    float(proc.get("cpu_percent") or 0),
-                    float(proc.get("memory_mb") or 0),
-                    proc.get("user") or proc.get("username"),
-                    proc.get("cmdline"),
-                    info.get("timestamp") or now_iso,
-                ))
-
-        # sysmon events
-        sysmon_count = 0
-        for ev in (security_data.get("sysmon_events") or []):
-            c.execute("""
-                INSERT INTO sysmon_events(
-                    computer_id, event_id, event_type, message, timestamp, details
-                )
-                VALUES(?, ?, ?, ?, ?, ?)
-            """, (
-                computer_id,
-                ev.get("event_id"),
-                ev.get("event_type", "Unknown"),
-                ev.get("message", ""),
-                ev.get("timestamp") or (info.get("timestamp") or now_iso),
-                json.dumps(ev, ensure_ascii=False),
-            ))
-            sysmon_count += 1
-
-        # network connections
-        for nc in (security_data.get("network_connections") or []):
-            c.execute("""
-                INSERT INTO network_connections(
-                    computer_id, pid, local_address, remote_address, status, process_name, timestamp
-                )
-                VALUES(?, ?, ?, ?, ?, ?, ?)
-            """, (
-                computer_id,
-                nc.get("pid"),
-                nc.get("local_address"),
-                nc.get("remote_address"),
-                nc.get("status"),
-                nc.get("process_name"),
-                info.get("timestamp") or now_iso,
-            ))
-
-        conn.commit()
-        conn.close()
-
-        # optional JSON archive
-        try:
-            save_json_file(data)
-        except Exception:
-            pass
-
-        return jsonify({
-            "ok": True,
-            "computer_id": computer_id,
-            "env": env_name,
-            "tenant_id": tenant_id,
-            "sysmon_events_saved": sysmon_count,
-            "server_time": now_iso,
-            "process_history_enabled": bool(is_process_history_enabled(tenant_id, env_name)),
-        })
-
+        t0 = time.time()
+        data = request.get_json(force=True) or {}
+        question = data.get("question", "").strip()
+        computer_name = data.get("computer_name") or None
+
+        if not question:
+            return jsonify({"error": "No question provided"}), 400
+
+        context = build_rag_context(question, computer_name=computer_name)
+        t1 = time.time()
+
+        answer = ask_llm_with_context(question, context)
+        t2 = time.time()
+
+        print(f"[CHAT] build_rag_context: {t1 - t0:.2f}s, LLM: {t2 - t1:.2f}s")
+
+        return jsonify({"answer": answer})
     except Exception as e:
         traceback.print_exc()
@@ -867,899 +1695,196 @@
 
 
-# ----------------------------
-# Dashboard API (tenant scoped)
-# ----------------------------
-@app.route("/api/computers", methods=["GET"])
-@require_user()
-def api_computers():
-    tenant_id = request.user["tenant_id"]
-    env = request.headers.get("X-Env", "default")
-
-    conn = db()
+@app.route('/api/computer/<computer_name>/sysmon')
+def get_sysmon_processes(computer_name):
+    """Добиј Sysmon процеси за компјутер"""
+    conn = sqlite3.connect(DB_FILE)
     c = conn.cursor()
-    c.execute("""
-            SELECT id, name, user, ip, os, first_seen, last_seen, sysmon_available
-            FROM computers
-            WHERE tenant_id=? AND env_name=?
-            ORDER BY last_seen DESC
-        """, (tenant_id, env))
-    rows = c.fetchall()
-
-    computers = []
-    for r in rows:
-        cid = r["id"]
-
-        c.execute("""
-                SELECT COUNT(*) AS n
-                FROM computer_history
-                WHERE computer_id=? AND timestamp > datetime('now','-24 hours')
-            """, (cid,))
-        recent_logs = int(c.fetchone()["n"] or 0)
-
-        c.execute("""
-                SELECT COUNT(*) AS n
-                FROM sysmon_events
-                WHERE computer_id=? AND timestamp > datetime('now','-24 hours')
-            """, (cid,))
-        recent_sysmon = int(c.fetchone()["n"] or 0)
-
-        c.execute("""
-                SELECT cpu_usage, ram_usage, timestamp
-                FROM computer_history
-                WHERE computer_id=?
-                ORDER BY timestamp DESC
-                LIMIT 5
-            """, (cid,))
-        metrics = c.fetchall()
-        avg_cpu = round(sum(m["cpu_usage"] for m in metrics) / len(metrics), 1) if metrics else 0.0
-        avg_ram = round(sum(m["ram_usage"] for m in metrics) / len(metrics), 1) if metrics else 0.0
-
-        status = "offline"
-        status_color = "gray"
-        try:
-            last_seen = r["last_seen"]
-            if last_seen:
-                dt = datetime.fromisoformat(
-                    last_seen.replace("Z", "+00:00")) if "T" in last_seen else datetime.fromisoformat(last_seen)
-                diff = (datetime.now() - dt).total_seconds()
-                if diff < 60:
-                    status, status_color = "online", "green"
-                elif diff < 300:
-                    status, status_color = "idle", "orange"
-        except Exception:
-            pass
-
-        computers.append({
-            "name": r["name"],
-            "user": r["user"],
-            "ip": r["ip"],
-            "os": r["os"],
-            "first_seen": r["first_seen"],
-            "last_seen": r["last_seen"],
-            "sysmon_available": bool(r["sysmon_available"]),
-            "recent_logs": recent_logs,
-            "recent_sysmon": recent_sysmon,
-            "avg_cpu": avg_cpu,
-            "avg_ram": avg_ram,
-            "status": status,
-            "status_color": status_color,
-        })
-
-    conn.close()
-    return jsonify(computers)
-
-
-@app.route("/api/stats", methods=["GET"])
-@require_user()
-def api_stats():
-    tenant_id = request.user["tenant_id"]
-
-    conn = db()
-    c = conn.cursor()
-
-    c.execute("""
-            SELECT COUNT(*) AS n
-            FROM sysmon_events s
-            JOIN computers c2 ON c2.id = s.computer_id
-            WHERE s.timestamp > datetime('now','-24 hours')
-              AND c2.tenant_id = ?
-        """, (tenant_id,))
-    total_sysmon = int(c.fetchone()["n"] or 0)
-
-    c.execute("""
-            SELECT COUNT(*) AS n
-            FROM network_connections n
-            JOIN computers c2 ON c2.id = n.computer_id
-            WHERE n.timestamp > datetime('now','-24 hours')
-              AND c2.tenant_id = ?
-        """, (tenant_id,))
-    total_net = int(c.fetchone()["n"] or 0)
-
-    conn.close()
-    return jsonify({
-        "total_sysmon_events": total_sysmon,
-        "total_network_connections": total_net,
-        "server_time": datetime.now().isoformat(),
-    })
-
-
-@app.route("/api/computer/<computer_name>", methods=["GET"])
-@require_user()
-def api_computer_details(computer_name):
-    tenant_id = request.user["tenant_id"]
-
-    conn = db()
-    c = conn.cursor()
-    c.execute("""
-            SELECT id, name, user, ip, os, first_seen, last_seen, env_name
-            FROM computers
-            WHERE tenant_id=? AND name=?
-            LIMIT 1
-        """, (tenant_id, computer_name))
-    comp = c.fetchone()
-    if not comp:
+
+    # Прво најди го computer_id
+    c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,))
+    result = c.fetchone()
+
+    if not result:
         conn.close()
         return jsonify({"error": "Computer not found"}), 404
 
-    computer_id = comp["id"]
-
-    c.execute("SELECT COUNT(*) AS n FROM computer_history WHERE computer_id=?", (computer_id,))
-    total_logs = int(c.fetchone()["n"] or 0)
-
-    c.execute("""
-            SELECT cpu_usage, ram_usage, disk_usage, network_sent_mb, network_recv_mb, timestamp
-            FROM computer_history
-            WHERE computer_id=?
-            ORDER BY timestamp DESC
-            LIMIT 100
-        """, (computer_id,))
-    history = [dict(r) for r in c.fetchall()]
-    current_metrics = history[0] if history else {}
-
-    c.execute("""
-        SELECT pid, name, username, cpu_percent, memory_mb, cmdline, timestamp
-        FROM computer_processes_current
-        WHERE computer_id=?
-        ORDER BY timestamp DESC
-        LIMIT 200
-    """, (computer_id,))
-    processes = [dict(r) for r in c.fetchall()]
-
-    c.execute("""
-            SELECT event_id, event_type, message, timestamp, details
-            FROM sysmon_events
-            WHERE computer_id=?
-            ORDER BY timestamp DESC
-            LIMIT 200
-        """, (computer_id,))
-    sysmon = []
-    for r in c.fetchall():
-        d = {}
+    computer_id = result[0]
+
+    # Земaj ги Sysmon процесите (последни 100)
+    c.execute('''SELECT 
+                 event_id, 
+                 event_type, 
+                 message, 
+                 timestamp, 
+                 details
+                 FROM sysmon_events 
+                 WHERE computer_id = ? 
+                 ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
+
+    processes = []
+    for row in c.fetchall():
         try:
-            d = json.loads(r["details"]) if r["details"] else {}
-        except Exception:
-            d = {}
-        sysmon.append({
-            "event_id": r["event_id"],
-            "event_type": r["event_type"],
-            "message": r["message"],
-            "timestamp": r["timestamp"],
-            "details": d,
+            details = json.loads(row[4]) if row[4] else {}
+        except:
+            details = {}
+
+        processes.append({
+            'process_id': row[0],  # Event ID како PID
+            'process_name': row[1],  # Event type како име
+            'timestamp': row[3],
+            'message': row[2],
+            'details': details
         })
 
-    c.execute("""
-            SELECT pid, process_name, local_address, remote_address, status, timestamp
-            FROM network_connections
-            WHERE computer_id=?
-            ORDER BY timestamp DESC
-            LIMIT 100
-        """, (computer_id,))
-    net = [dict(r) for r in c.fetchall()]
-
     conn.close()
 
     return jsonify({
-        "computer": {
-            "id": comp["id"],
-            "name": comp["name"],
-            "user": comp["user"],
-            "ip": comp["ip"],
-            "os": comp["os"],
-            "first_seen": comp["first_seen"],
-            "last_seen": comp["last_seen"],
-            "env_name": comp["env_name"],
-            "total_logs": total_logs,
-        },
-        "history": history,
-        "current_metrics": current_metrics,
-        "recent_processes": processes,
-        "sysmon_events": sysmon,
-        "network_connections": net,
+        'computer_name': computer_name,
+        'processes': processes,
+        'count': len(processes)
     })
 
 
-@app.route("/api/export/<computer_name>", methods=["GET"])
-@require_user()
-def api_export(computer_name):
-    tenant_id = request.user["tenant_id"]
-
-    conn = db()
+@app.route('/api/computer/<computer_name>/network')
+def get_network_connections(computer_name):
+    """Добиј мрежни конекции за компјутер"""
+    conn = sqlite3.connect(DB_FILE)
     c = conn.cursor()
-    c.execute("""
-            SELECT *
-            FROM computers
-            WHERE tenant_id=? AND name=?
-            LIMIT 1
-        """, (tenant_id, computer_name))
-    comp = c.fetchone()
-    if not comp:
+
+    # Прво најди го computer_id
+    c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,))
+    result = c.fetchone()
+
+    if not result:
         conn.close()
         return jsonify({"error": "Computer not found"}), 404
 
-    computer_id = comp["id"]
-    out = {"computer": dict(comp)}
-
-    c.execute("SELECT * FROM computer_history WHERE computer_id=? ORDER BY timestamp", (computer_id,))
-    out["history"] = [dict(r) for r in c.fetchall()]
-
-    c.execute("SELECT * FROM sysmon_events WHERE computer_id=? ORDER BY timestamp", (computer_id,))
-    out["sysmon_events"] = [dict(r) for r in c.fetchall()]
-
-    c.execute("SELECT * FROM computer_processes_current WHERE computer_id=? ORDER BY timestamp", (computer_id,))
-    out["processes_current"] = [dict(r) for r in c.fetchall()]
-
-    c.execute("SELECT * FROM computer_processes_history WHERE computer_id=? ORDER BY timestamp", (computer_id,))
-    out["processes_history"] = [dict(r) for r in c.fetchall()]
-
-    c.execute("SELECT * FROM network_connections WHERE computer_id=? ORDER BY timestamp", (computer_id,))
-    out["network_connections"] = [dict(r) for r in c.fetchall()]
+    computer_id = result[0]
+
+    # Земaj ги мрежните конекции (последни 100)
+    c.execute('''SELECT 
+                 pid, 
+                 process_name, 
+                 local_address, 
+                 remote_address,
+                 status, 
+                 timestamp
+                 FROM network_connections 
+                 WHERE computer_id = ? 
+                 ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
+
+    connections = []
+    for row in c.fetchall():
+        # Парсирај IP и порта
+        local = row[2] or "N/A"
+        remote = row[3] or "N/A"
+
+        connections.append({
+            'pid': row[0],
+            'process_name': row[1] or 'Unknown',
+            'local_address': local,
+            'remote_address': remote,
+            'local_port': local.split(':')[-1] if ':' in local else '',
+            'remote_port': remote.split(':')[-1] if ':' in remote else '',
+            'protocol': 'TCP' if ':443' in local or ':80' in local else 'Unknown',
+            'state': row[4] or 'ESTABLISHED',
+            'timestamp': row[5]
+        })
 
     conn.close()
 
-    return Response(
-        json.dumps(out, indent=2, default=str, ensure_ascii=False),
-        mimetype="application/json",
-        headers={"Content-Disposition": f"attachment; filename={computer_name}_export.json"},
-    )
-
-
-# ----------------------------
-# Simple RAG chat
-# ----------------------------
-# def build_rag_context(tenant_id: int, question: str, computer_name=None, limit_per_table=10):
-#     conn = db()
-#     c = conn.cursor()
-#
-#     params = [tenant_id]
-#     comp_clause = ""
-#     if computer_name:
-#         comp_clause = "AND c2.name = ?"
-#         params.append(computer_name)
-#
-#     # sysmon
-#     c.execute(f"""
-#             SELECT s.timestamp, s.event_id, s.event_type, s.message
-#             FROM sysmon_events s
-#             JOIN computers c2 ON c2.id = s.computer_id
-#             WHERE c2.tenant_id = ? {comp_clause}
-#             ORDER BY s.timestamp DESC
-#             LIMIT ?
-#         """, (*params, limit_per_table))
-#     sysmon_rows = c.fetchall()
-#
-#     # perf
-#     c.execute(f"""
-#             SELECT h.timestamp, h.cpu_usage, h.ram_usage, h.disk_usage, h.network_sent_mb, h.network_recv_mb
-#             FROM computer_history h
-#             JOIN computers c2 ON c2.id = h.computer_id
-#             WHERE c2.tenant_id = ? {comp_clause}
-#             ORDER BY h.timestamp DESC
-#             LIMIT ?
-#         """, (*params, limit_per_table))
-#     perf_rows = c.fetchall()
-#
-#     # proc
-#     c.execute(f"""
-#             SELECT p.timestamp, p.pid, p.name, p.cpu_percent, p.memory_mb
-#             FROM computer_processes_current p
-#             JOIN computers c2 ON c2.id = p.computer_id
-#             WHERE c2.tenant_id = ? {comp_clause}
-#             ORDER BY p.timestamp DESC
-#             LIMIT ?
-#         """, (*params, limit_per_table))
-#     proc_rows = c.fetchall()
-#
-#     conn.close()
-#
-#     lines = []
-#     for r in sysmon_rows:
-#         lines.append(f"[SYSMON] {r['timestamp']} | Event {r['event_id']} ({r['event_type']}): {r['message']}")
-#     for r in perf_rows:
-#         lines.append(
-#             f"[PERF] {r['timestamp']} | CPU {r['cpu_usage']}% | RAM {r['ram_usage']}% | Disk {r['disk_usage']}% | "
-#             f"Net sent {r['network_sent_mb']}MB / recv {r['network_recv_mb']}MB"
-#         )
-#     for r in proc_rows:
-#         lines.append(
-#             f"[PROC] {r['timestamp']} | PID {r['pid']} | {r['name']} | CPU {r['cpu_percent']}% | RAM {r['memory_mb']}MB"
-#         )
-#
-#     return "\n".join(lines)
-def ask_llm_with_context(question, context):
-    if not client:
-        return "Немаш поставено OPENAI_API_KEY на серверот."
-
-    prompt = f"""Ти си асистент за безбедност и систем мониторинг.
-Одговарај базирано ИСКЛУЧИВО на дадените логови.
-Ако нема доволно податоци, кажи: 'Нема доволно информации во логовите.'.
-
-Прашање: {question}
-
-Логови:
-{context or 'Нема логови.'}
-"""
-
-    resp = client.responses.create(
-        model="gpt-4.1-mini",
-        input=[{"role": "user", "content": [{"type": "input_text", "text": prompt}]}],
-    )
-
-    text = getattr(resp, "output_text", "") or ""
-    return text.strip() or "Нема доволно информации во логовите."
-
-def require_grafana():
-    def deco(fn):
-        @wraps(fn)
-        def wrap(*args, **kwargs):
-            tok = request.headers.get("X-Grafana-Token", "")
-            if not GRAFANA_TOKEN or tok != GRAFANA_TOKEN:
-                return jsonify({"error": "Unauthorized"}), 401
-            return fn(*args, **kwargs)
-        return wrap
-    return deco
-@app.route("/api/public/stats", methods=["GET"])
-@require_grafana()
-def grafana_stats():
-    # ако сакаш само една околина:
-    env = request.args.get("env", "default")
-
-    conn = db()
+    return jsonify({
+        'computer_name': computer_name,
+        'connections': connections,
+        'count': len(connections)
+    })
+
+
+@app.route('/api/computer/<computer_name>/detailed-sysmon')
+def get_detailed_sysmon(computer_name):
+    """Добиј детални Sysmon процеси со повеќе информации"""
+    conn = sqlite3.connect(DB_FILE)
     c = conn.cursor()
 
-    # вкупно sysmon во 24h (за сите tenants, или ако сакаш само 1 tenant додади филтер)
-    c.execute("""
-        SELECT COUNT(*) AS n
-        FROM sysmon_events s
-        JOIN computers c2 ON c2.id = s.computer_id
-        WHERE s.timestamp > datetime('now','-24 hours')
-          AND c2.env_name = ?
-    """, (env,))
-    total_sysmon = int(c.fetchone()["n"] or 0)
-
-    c.execute("""
-        SELECT COUNT(*) AS n
-        FROM network_connections n
-        JOIN computers c2 ON c2.id = n.computer_id
-        WHERE n.timestamp > datetime('now','-24 hours')
-          AND c2.env_name = ?
-    """, (env,))
-    total_net = int(c.fetchone()["n"] or 0)
+    c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,))
+    result = c.fetchone()
+
+    if not result:
+        conn.close()
+        return jsonify({"error": "Computer not found"}), 404
+
+    computer_id = result[0]
+
+    c.execute('''SELECT event_id, event_type, message, timestamp, details
+                 FROM sysmon_events 
+                 WHERE computer_id = ? 
+                 ORDER BY timestamp DESC LIMIT 50''', (computer_id,))
+
+    detailed_processes = []
+
+    for row in c.fetchall():
+        event_id = row[0]
+        event_type = row[1]
+        message = row[2]
+        timestamp = row[3]
+        details_str = row[4]
+
+        # Пробај да го парсираш details JSON
+        try:
+            details = json.loads(details_str) if details_str else {}
+        except:
+            details = {}
+
+        # Екстрактирај информации од details
+        process_info = {
+            'event_id': event_id,
+            'event_type': event_type,
+            'timestamp': timestamp,
+            'message': message,
+            'process_name': details.get('ProcessName', 'Unknown'),
+            'process_id': details.get('ProcessId', event_id),
+            'command_line': details.get('CommandLine', ''),
+            'parent_process': details.get('ParentProcessName', ''),
+            'parent_pid': details.get('ParentProcessId', ''),
+            'user': details.get('User', ''),
+            'details': details
+        }
+
+        # Додади дополнителни полиња за различни типови на настани
+        if event_id in [3, 22]:  # Network connection or DNS
+            process_info['source_ip'] = details.get('SourceIp', '')
+            process_info['dest_ip'] = details.get('DestinationIp', '')
+            process_info['dest_port'] = details.get('DestinationPort', '')
+
+        detailed_processes.append(process_info)
 
     conn.close()
+
     return jsonify({
-        "total_sysmon_events": total_sysmon,
-        "total_network_connections": total_net,
-        "server_time": datetime.now().isoformat(),
-        "env": env,
+        'computer_name': computer_name,
+        'processes': detailed_processes,
+        'count': len(detailed_processes)
     })
-
-def require_grafana_token(fn):
-    @wraps(fn)
-    def wrapper(*args, **kwargs):
-        expected = os.environ.get("GRAFANA_API_TOKEN") or ""
-
-        # 1) пробај Authorization: Bearer <token>
-        auth = request.headers.get("Authorization", "")
-        token = ""
-        if auth.startswith("Bearer "):
-            token = auth.replace("Bearer ", "").strip()
-
-        # 2) ако нема header, пробај query param ?token=
-        if not token:
-            token = (request.args.get("token") or "").strip()
-
-        if not expected or token != expected:
-            return jsonify({"error": "Invalid Grafana token"}), 401
-
-        return fn(*args, **kwargs)
-    return wrapper
-
-@app.route("/api/public/ips", methods=["GET"])
-@require_grafana_token
-def api_public_ips():
-    tenant_id = request.args.get("tenant_id", type=int)
-    env = request.args.get("env", "default")
-    hours = int(request.args.get("hours", "24"))
-    limit = int(request.args.get("limit", "200"))
-
-    if not tenant_id:
-        return jsonify({"error": "Missing tenant_id"}), 400
-
-    conn = db()
-    c = conn.cursor()
-
-    c.execute("""
-        SELECT DISTINCT
-            substr(nc.remote_address, 1,
-                   CASE
-                       WHEN instr(nc.remote_address, ':') > 0
-                       THEN instr(nc.remote_address, ':') - 1
-                       ELSE length(nc.remote_address)
-                   END
-            ) AS ip
-        FROM network_connections nc
-        JOIN computers c2 ON c2.id = nc.computer_id
-        WHERE c2.tenant_id = ?
-          AND c2.env_name = ?
-          AND nc.timestamp > datetime('now', ?)
-          AND nc.remote_address IS NOT NULL
-          AND nc.remote_address != ''
-        ORDER BY ip
-        LIMIT ?
-    """, (tenant_id, env, f"-{hours} hours", limit))
-
-    ips = [r["ip"] for r in c.fetchall()]
-    conn.close()
-
-    return jsonify({"ips": ips})
-
-
-
-@app.route("/api/public/sankey", methods=["GET"])
-@require_grafana_token
-def api_public_sankey():
-    tenant_id = int(request.args.get("tenant_id", "1"))
-    env = request.args.get("env", "office1")
-    hours = int(request.args.get("hours", "24"))
-    limit = int(request.args.get("limit", "20"))
-
-    computers = (request.args.get("computers") or "all").strip()
-    ips = (request.args.get("ips") or "all").strip()
-
-    # normalize grafana All values
-    if computers in ("", "__all"):
-        computers = "all"
-    if ips in ("", "__all"):
-        ips = "all"
-
-    comp_list = [c.strip() for c in computers.split(",") if c.strip()] if computers != "all" else []
-    ip_list = [i.strip() for i in ips.split(",") if i.strip()] if ips != "all" else []
-
-    comp_clause = ""
-    ip_clause = ""
-    params = [tenant_id, env, f"-{hours} hours"]
-
-    # ✅ target без port за IPv4: "1.2.3.4:443" -> "1.2.3.4"
-    # ⚠️ за IPv6 ќе го остави како што е (за да не го расипеме со split по ':')
-    target_expr = """
-    CASE
-      WHEN nc.remote_address LIKE '%:%' AND nc.remote_address NOT LIKE '%:%:%'
-      THEN substr(nc.remote_address, 1, instr(nc.remote_address, ':') - 1)
-      ELSE nc.remote_address
-    END
-    """
-
-    if comp_list:
-        comp_clause = "AND comp.name IN (" + ",".join(["?"] * len(comp_list)) + ")"
-        params.extend(comp_list)
-
-    # ✅ Филтер по IP без порт (истиот expr)
-    if ip_list:
-        ip_clause = f"AND ({target_expr}) IN (" + ",".join(["?"] * len(ip_list)) + ")"
-        params.extend(ip_list)
-
-    # LIMIT на крај
-    params.append(limit)
-
-    conn = db()
-    c = conn.cursor()
-
-    c.execute(f"""
-        SELECT
-            comp.name AS source,
-            ({target_expr}) AS target,
-            COUNT(*) AS value
-        FROM network_connections nc
-        JOIN computers comp ON comp.id = nc.computer_id
-        WHERE comp.tenant_id = ?
-          AND comp.env_name = ?
-          AND nc.timestamp > datetime('now', ?)
-          AND nc.remote_address IS NOT NULL
-          AND nc.remote_address != ''
-
-          -- ✅ тргни loopback spam (овие прават да изгледа како да нема линии)
-          AND nc.remote_address NOT LIKE '127.0.0.1%'
-          AND nc.remote_address NOT LIKE '::1%'
-
-          {comp_clause}
-          {ip_clause}
-        GROUP BY comp.name, target
-        ORDER BY value DESC
-        LIMIT ?
-    """, params)
-
-    rows = [dict(r) for r in c.fetchall()]
-    conn.close()
-    return jsonify(rows)
-
-from urllib.parse import urlparse
-
-def _ip_only(addr: str):
-    # addr може да е "1.2.3.4:443" или "hostname:port"
-    if not addr:
-        return None
-    # ако има повеќе ':' (ipv6), ќе го оставиме целото; за ipv4 split е ок
-    if addr.count(":") == 1:
-        return addr.split(":")[0]
-    return addr
-
-@app.route("/api/graph/net", methods=["GET"])
-@require_user()
-def api_graph_net():
-    tenant_id = request.user["tenant_id"]
-    env = request.args.get("env") or request.headers.get("X-Env", "default")
-    computer = request.args.get("computer")  # optional
-    since_hours = int(request.args.get("since_hours") or 24)
-
-    conn = db()
-    c = conn.cursor()
-
-    params = [tenant_id, env, since_hours]
-    comp_clause = ""
-    if computer:
-        comp_clause = "AND c2.name = ?"
-        params.append(computer)
-
-    # Вади top remote IP по број на конекции
-    c.execute(f"""
-        SELECT
-          c2.name AS computer_name,
-          n.remote_address AS remote_address,
-          COUNT(*) AS cnt
-        FROM network_connections n
-        JOIN computers c2 ON c2.id = n.computer_id
-        WHERE c2.tenant_id = ?
-          AND c2.env_name = ?
-          AND n.timestamp > datetime('now', '-' || ? || ' hours')
-          {comp_clause}
-          AND n.remote_address IS NOT NULL
-          AND n.remote_address <> ''
-        GROUP BY c2.name, n.remote_address
-        ORDER BY cnt DESC
-        LIMIT 200
-    """, params)
-
-    rows = c.fetchall()
-    conn.close()
-
-    # Build NodeGraph-like structure
-    nodes = {}
-    edges = []
-
-    def add_node(node_id, title, ntype):
-        if node_id not in nodes:
-            nodes[node_id] = {"id": node_id, "title": title, "subTitle": ntype}
-
-    for r in rows:
-        comp_name = r["computer_name"]
-        remote = _ip_only(r["remote_address"]) or r["remote_address"]
-        cnt = int(r["cnt"] or 0)
-
-        comp_id = f"comp:{comp_name}"
-        ip_id = f"ip:{remote}"
-
-        add_node(comp_id, comp_name, "computer")
-        add_node(ip_id, remote, "remote_ip")
-
-        edges.append({
-            "id": f"{comp_id}->{ip_id}",
-            "source": comp_id,
-            "target": ip_id,
-            "mainStat": str(cnt),
-        })
-
-    return jsonify({
-        "nodes": list(nodes.values()),
-        "edges": edges,
-        "meta": {"env": env, "computer": computer, "since_hours": since_hours}
-    })
-
-@app.route("/api/public/netgraph", methods=["GET"])
-@require_grafana_token
-def api_public_netgraph():
-    tenant_id = int(request.args.get("tenant_id", "1"))
-    env = request.args.get("env", "default")
-    hours = int(request.args.get("hours", "6"))
-    limit_edges = int(request.args.get("limit", "200"))
-    computer = request.args.get("computer")  # <-- NEW (optional)
-
-    conn = db()
-    c = conn.cursor()
-
-    comp_clause = ""
-    params = [tenant_id, env, f"-{hours} hours"]
-
-    if computer and computer != "all":
-        comp_clause = "AND comp.name = ?"
-        params.append(computer)
-
-    params.append(limit_edges)
-
-    c.execute(f"""
-        SELECT
-            comp.name AS src,
-            nc.remote_address AS remote,
-            COUNT(*) AS cnt
-        FROM network_connections nc
-        JOIN computers comp ON comp.id = nc.computer_id
-        WHERE comp.tenant_id = ?
-          AND comp.env_name = ?
-          AND nc.timestamp > datetime('now', ?)
-          {comp_clause}
-          AND nc.remote_address IS NOT NULL
-          AND nc.remote_address != ''
-        GROUP BY comp.name, nc.remote_address
-        ORDER BY cnt DESC
-        LIMIT ?
-    """, params)
-
-    rows = c.fetchall()
-    conn.close()
-
-    def ip_only(addr: str) -> str:
-        if not addr:
-            return ""
-        if addr.startswith("[") and "]" in addr:
-            return addr[1:addr.index("]")]
-        if ":" in addr:
-            return addr.split(":")[0]
-        return addr
-
-    nodes = {}
-    edges = []
-
-    for r in rows:
-        src = r["src"]
-        remote_ip = ip_only(r["remote"])
-        cnt = int(r["cnt"] or 0)
-        if not remote_ip:
-            continue
-
-        nodes[src] = {"id": src, "title": src, "subTitle": env, "mainStat": "PC"}
-        ip_id = f"IP:{remote_ip}"
-        if ip_id not in nodes:
-            nodes[ip_id] = {"id": ip_id, "title": remote_ip, "subTitle": "remote", "mainStat": "IP"}
-
-        edges.append({
-            "id": f"{src}->{ip_id}",
-            "source": src,
-            "target": ip_id,
-            "mainStat": cnt
-        })
-
-    return jsonify({"nodes": list(nodes.values()), "edges": edges})
-@app.route("/api/public/computers", methods=["GET"])
-@require_grafana_token
-def api_public_computers():
-    tenant_id = int(request.args.get("tenant_id", "1"))
-    env = request.args.get("env", "default")
-
-    conn = db()
-    c = conn.cursor()
-    c.execute("""
-        SELECT name
-        FROM computers
-        WHERE tenant_id = ? AND env_name = ?
-        ORDER BY name
-    """, (tenant_id, env))
-    names = [r["name"] for r in c.fetchall()]
-    conn.close()
-
-    return jsonify({"computers": names})
-
-@app.route("/api/public/stats", methods=["GET"])
-@require_grafana_token
-def public_stats():
-    # ⚠️ ако сакаш tenant по tenant, ќе мора да додадеш tenant_id како query param,
-    # или да имаш 1 tenant во проект за сега.
-    tenant_id = request.args.get("tenant_id", type=int)
-    if not tenant_id:
-        return jsonify({"error": "Missing tenant_id"}), 400
-
-    conn = db()
-    c = conn.cursor()
-
-    c.execute("""
-        SELECT COUNT(*) AS n
-        FROM sysmon_events s
-        JOIN computers c2 ON c2.id = s.computer_id
-        WHERE s.timestamp > datetime('now','-24 hours')
-          AND c2.tenant_id = ?
-    """, (tenant_id,))
-    total_sysmon = int(c.fetchone()["n"] or 0)
-
-    c.execute("""
-        SELECT COUNT(*) AS n
-        FROM network_connections n
-        JOIN computers c2 ON c2.id = n.computer_id
-        WHERE n.timestamp > datetime('now','-24 hours')
-          AND c2.tenant_id = ?
-    """, (tenant_id,))
-    total_net = int(c.fetchone()["n"] or 0)
-
-    conn.close()
-
-    return jsonify({
-        "total_sysmon_events": total_sysmon,
-        "total_network_connections": total_net,
-        "server_time": datetime.now().isoformat(),
-    })
-
-
-def ask_llm_sql_generator(question: str, tenant_id: int, env: str, computer_name: str | None):
-    """
-    Returns dict: { "queries": [...], "notes": "...", "scope": {...} }
-    Only SELECT/WITH queries, no modifications.
-    """
-    if not client:
-        return {
-            "queries": [],
-            "notes": "Немаш поставено OPENAI_API_KEY на серверот.",
-            "scope": {"tenant_id": tenant_id, "env": env, "computer_name": computer_name},
-        }
-
-    schema = """
-SQLite schema (важни табели):
-- computers(id, tenant_id, env_name, name, user, ip, os, first_seen, last_seen, sysmon_available)
-- computer_history(computer_id, cpu_usage, ram_usage, disk_usage, network_sent_mb, network_recv_mb, timestamp)
-- computer_processes_current(computer_id, pid, name, cpu_percent, memory_mb, username, cmdline, timestamp)
-- computer_processes_history(computer_id, pid, name, cpu_percent, memory_mb, username, cmdline, timestamp)
-- sysmon_events(computer_id, event_id, event_type, message, timestamp, details)
-- network_connections(computer_id, pid, local_address, remote_address, status, process_name, timestamp)
-
-Правила:
-- Врати САМО валиден JSON.
-- JSON формат: {"queries":[...], "notes":"..."}.
-- queries мора да бидат само SELECT или WITH (никако INSERT/UPDATE/DELETE/ALTER/DROP).
-- максимум 3 queries.
-- користи placeholders: :tenant_id, :env, :computer_name (ако е дадено).
-- ако нема доволно инфо -> queries празно и notes објаснува.
-""".strip()
-
-    user_scope = {
-        "tenant_id": tenant_id,
-        "env": env,
-        "computer_name": computer_name,
-    }
-
-    user_msg = f"""
-Прашање од корисник: {question}
-
-Scope (вметни во WHERE):
-- tenant_id = :tenant_id
-- env = :env
-- computer_name = :computer_name (ако не е null)
-
-Врати 1-3 SELECT/WITH queries за да се добијат потребните податоци.
-""".strip()
-
-    completion = client.chat.completions.create(
-        model="gpt-4.1-mini",
-        messages=[
-            {"role": "system", "content": schema},
-            {"role": "user", "content": user_msg},
-        ],
-        temperature=0.1,
-    )
-
-    raw = completion.choices[0].message.content or ""
-
-    # Safe parse JSON
-    try:
-        out = json.loads(raw)
-        if not isinstance(out, dict):
-            raise ValueError("Not an object")
-        queries = out.get("queries") or []
-        notes = out.get("notes") or ""
-
-        # hard safety filter: keep only SELECT/WITH
-        safe_queries = []
-        for q in queries:
-            if not isinstance(q, str):
-                continue
-            qs = q.strip().lower()
-            if qs.startswith("select") or qs.startswith("with"):
-                # block obvious multi-statement
-                if ";" in q.strip().rstrip(";"):
-                    continue
-                safe_queries.append(q.strip())
-
-        return {"queries": safe_queries[:3], "notes": notes, "scope": user_scope}
-
-    except Exception:
-        # fallback ако AI не врати JSON
-        return {
-            "queries": [],
-            "notes": "AI не врати валиден JSON. Обиди се повторно (или намали прашање).",
-            "scope": user_scope,
-            "raw": raw[:1200],
-        }
-
-
-@app.route("/api/chat", methods=["POST"])
-@require_user()
-def api_chat():
-    try:
-        tenant_id = request.user["tenant_id"]
-        env = request.headers.get("X-Env", "default")  # важно!
-        data = request.get_json(force=True, silent=True) or {}
-
-        question = (data.get("question") or "").strip()
-        computer_name = data.get("computer_name") or None
-
-        if not question:
-            return jsonify({"error": "No question provided"}), 400
-
-        out = ask_llm_sql_generator(
-            question=question,
-            tenant_id=tenant_id,
-            env=env,
-            computer_name=computer_name,
-        )
-
-        # Тука не извршуваме SQL, само враќаме query за копирање.
-        return jsonify({
-            "ok": True,
-            "queries": out.get("queries", []),
-            "notes": out.get("notes", ""),
-            "scope": out.get("scope", {}),
-            # "raw": out.get("raw")  # ако сакаш debug
-        }), 200
-
-    except Exception as e:
-        traceback.print_exc()
-        return jsonify({"error": str(e)}), 500
-
-
-
-# ----------------------------
-# Misc
-# ----------------------------
-@app.route("/ping")
-def ping():
-    return jsonify({
-        "status": "alive",
-        "server_time": datetime.now().isoformat(),
-        "database": DB_FILE,
-        "cors_origins": ALLOWED_ORIGINS,
-    })
-
-
-@app.route("/")
-def root():
-    return jsonify({
-        "ok": True,
-        "service": "netIntel server",
-        "hint": "frontend should call /api/* endpoints",
-    })
-
-
-if __name__ == "__main__":
-    init_db()
-
+if __name__ == '__main__':
+    init_database()
+
+    # Стартувај cleanup thread
     cleanup_thread = threading.Thread(target=cleanup_old_data, daemon=True)
     cleanup_thread.start()
 
-    print(" netIntel server running on 0.0.0.0:5555")
-    # debug=False recommended; if you need debug, set True temporarily
-    app.run(host="0.0.0.0", port=5555, debug=False, threaded=True)
+    local_ip = get_local_ip()
+
+    print("\n" + "=" * 70)
+    print("🚀 ENHANCED LAN LOG SERVER со Sysmon")
+    print("=" * 70)
+    print(f"🌐 Server IP: {local_ip}:5555")
+    print(f"📊 Dashboard: http://{local_ip}:5555/")
+    print(f"📊 Alternative: http://localhost:5555/")
+    print(f"🏥 Health check: http://{local_ip}:5555/ping")
+    print("\n🛡️  Sysmon monitoring е активирано")
+    print("✅ Кликни на секој компјутер за детални информации")
+    print("✅ Сите Sysmon настани се прикажани во табла")
+    print("✅ Кликни на секој настан за детали")
+    print("=" * 70 + "\n")
+
+    app.run(host='0.0.0.0', port=5555, debug=False, threaded=True)
