Index: server.py
===================================================================
--- server.py	(revision 2058e5c694bb6097866a5fe6503c519e8f74970f)
+++ server.py	(revision 505f39ae7b51fc1db15123d66bf06aec0dca2f62)
@@ -1,1999 +1,538 @@
 #!/usr/bin/env python3
 """
-POPRAVEN SERVER со Sysmon обработка
+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
 """
+
+import os
+import json
+import time
+import sqlite3
 import traceback
-
-from flask import Flask, request, jsonify, render_template_string
-import json
-import sqlite3
+import threading
+import secrets
 from datetime import datetime, timedelta
-import time
-import os
-import socket
-from collections import defaultdict
-import threading
-import requests
-from openai import OpenAI
-from openai import OpenAI
+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
+
 load_dotenv()
 
-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
-
-
+# ----------------------------
+# 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
+
+# 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
+# ----------------------------
 app = Flask(__name__)
-
-DB_FILE = "lan_logs_sysmon.db"
-LOG_DIR = "received_data_sysmon"
-
-import secrets
-
-ADMIN_KEY = os.environ.get("ADMIN_KEY", "")
-ADMIN_KEY = os.environ.get("ADMIN_KEY", "")
-
-
-def init_admin_tables():
+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():
     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('''CREATE TABLE IF NOT EXISTS environments(
-        id INTEGER PRIMARY KEY AUTOINCREMENT,
-        name TEXT UNIQUE,
-        created_at TEXT
-    )''')
-
-    c.execute('''CREATE TABLE IF NOT EXISTS env_tokens(
-        id INTEGER PRIMARY KEY AUTOINCREMENT,
-        env_name TEXT,
-        token TEXT UNIQUE,
-        created_at TEXT,
-        expires_at TEXT
-    )''')
-
-    # migration ако табелата веќе постои
-    c.execute("PRAGMA table_info(env_tokens)")
-    cols = [r[1] for r in c.fetchall()]
-    if "expires_at" not in cols:
-        c.execute("ALTER TABLE env_tokens ADD COLUMN expires_at TEXT")
-
-    c.execute('''CREATE TABLE IF NOT EXISTS admin_sessions(
-        id INTEGER PRIMARY KEY AUTOINCREMENT,
-        token TEXT UNIQUE,
-        created_at TEXT
-    )''')
-
-    # default env
-    c.execute("INSERT OR IGNORE INTO environments(name, created_at) VALUES('default', datetime('now'))")
+    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)
+        )
+        """)
 
     conn.commit()
     conn.close()
-
-def require_admin_session():
-    tok = request.headers.get("X-Admin-Session", "")
-    if not tok:
-        return False
-    conn = sqlite3.connect(DB_FILE)
-    c = conn.cursor()
-    c.execute("SELECT 1 FROM admin_sessions WHERE token = ?", (tok,))
-    ok = c.fetchone() is not None
-    conn.close()
-    return ok
-
+    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
+            try:
+                claims = read_jwt(tok)
+            except Exception:
+                return jsonify({"error": "Unauthorized"}), 401
+            request.user = claims
+            return fn(*args, **kwargs)
+
+        return wrap
+
+    return deco
+
+
+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
+            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
-
-    conn = sqlite3.connect(DB_FILE)
+        return (None, None)
+
+    conn = db()
     c = conn.cursor()
     c.execute("""
-        SELECT env_name
-        FROM env_tokens
-        WHERE token = ?
-          AND (expires_at IS NULL OR expires_at > datetime('now'))
-    """, (tok,))
+            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()
-    return row[0] if row else None
-
-
-def init_database():
-    """Креирај подобрена база со Sysmon табели"""
-    os.makedirs(LOG_DIR, exist_ok=True)
-
-    conn = sqlite3.connect(DB_FILE)
-    c = conn.cursor()
-
-    # Табела за компјутери
-    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)''')
-    # --- MIGRATION: add env_name column if missing ---
-    c.execute("PRAGMA table_info(computers)")
-    cols = [r[1] for r in c.fetchall()]
-    if "env_name" not in cols:
-        c.execute("ALTER TABLE computers ADD COLUMN env_name TEXT DEFAULT 'default'")
-    # --- END MIGRATION ---
-
-    # Табела за историја
-    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"✅ Database created: {DB_FILE}")
-
-
-@app.route('/receive', methods=['POST'])
-def receive_data():
+
+    if not row:
+        return (None, None)
+    return (row["env_name"], row["tenant_id"])
+
+
+# ----------------------------
+# Utility
+# ----------------------------
+def save_json_file(data):
+    info = data.get("info") or {}
+    computer_name = info.get("computer_name", "unknown")
+    date_str = datetime.now().strftime("%Y%m%d_%H")
+
+    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 = []
+    else:
+        existing_data = []
+
+    existing_data.append({
+        "timestamp": info.get("timestamp"),
+        "info": info,
+        "processes_count": len(data.get("processes", [])),
+        "sysmon_events_count": len((data.get("security_data") or {}).get("sysmon_events", [])),
+    })
+
+    with open(filepath, "w", encoding="utf-8") as f:
+        json.dump(existing_data, f, indent=2, ensure_ascii=False)
+
+
+def cleanup_old_data():
+    """Deletes old rows (30 days) every hour."""
+    while True:
+        time.sleep(3600)
+        try:
+            conn = db()
+            c = conn.cursor()
+            cutoff = (datetime.now() - timedelta(days=30)).isoformat()
+
+            c.execute("DELETE FROM computer_history WHERE timestamp < ?", (cutoff,))
+            c.execute("DELETE FROM computer_processes WHERE timestamp < ?", (cutoff,))
+            c.execute("DELETE FROM sysmon_events WHERE timestamp < ?", (cutoff,))
+            c.execute("DELETE FROM network_connections WHERE timestamp < ?", (cutoff,))
+
+            conn.commit()
+            conn.close()
+            print(f"[Cleanup] Deleted entries older than {cutoff}")
+        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
+
     try:
-        data = request.json
-
-        if not data:
-            return jsonify({"error": "No data"}), 400
-        env_name = get_env_from_token(request)
-        if not env_name:
-            return jsonify({"error": "Missing or invalid X-Env-Token"}), 401
-
-        info = data['info']
-        computer_name = info['computer_name']
-        timestamp = datetime.now()
-        security_data = data.get('security_data', {})
-
-        conn = sqlite3.connect(DB_FILE)
+        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()
 
-        # Провери дали компјутерот веќе постои
-        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 = ?, env_name = ?
-                        WHERE id = ?''',
-                      (info['user'], info['ip_address'], info['os'],
-                       timestamp.isoformat(), info.get('is_sysmon_available', 0),
-                       env_name, computer_id))
+        # 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 computers 
-                        (name, user, ip, os, first_seen, last_seen, sysmon_available, env_name) 
-                        VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
-                      (computer_name, info['user'], info['ip_address'],
-                       info['os'], timestamp.isoformat(), timestamp.isoformat(),
-                       info.get('is_sysmon_available', 0), env_name))
-
-            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:
-                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:
-                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}")
+            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()
 
-        # Зачувај во 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()
-        })
+        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:
-        print(f"[-] Грешка: {e}")
-        return jsonify({"error": str(e)}), 500
-
-
-def save_json_file(data):
-    """Зачувај во 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):
-        with open(filepath, 'r', encoding='utf-8') as f:
-            existing_data = json.load(f)
-    else:
-        existing_data = []
-
-    # Додади нов запис
-    existing_data.append({
-        "timestamp": info['timestamp'],
-        "info": info,
-        "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:
-        json.dump(existing_data, f, indent=2, ensure_ascii=False)
-
-
-@app.route('/')
-def dashboard():
-    """Главен dashboard"""
-    conn = sqlite3.connect(DB_FILE)
+        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()
-
-    # Земaj ги сите компјутери
-    c.execute('''SELECT name, user, ip, os, first_seen, last_seen, sysmon_available, env_name
-                 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,
-            'env_name': row[7],
-        })
-
+    c.execute("SELECT name FROM environments WHERE tenant_id=? ORDER BY name", (tenant_id,))
+    envs = [r["name"] for r in c.fetchall()]
     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> Env: {{ comp.env_name }}</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()
-    env = request.headers.get("X-Env", "default")
-
-    c.execute('''SELECT name, user, ip, os, first_seen, last_seen, sysmon_available 
-                 FROM computers 
-                 WHERE env_name = ?
-                 ORDER BY last_seen DESC''', (env,))
-
-    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():
-    """Чисти стари податоци од базата (стари од 30 дена)"""
-    while True:
-        time.sleep(3600)  # Чисти на секој час
-        try:
-            conn = sqlite3.connect(DB_FILE)
-            c = conn.cursor()
-
-            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] Избришани стари записи пред {cutoff_date}")
-
-        except Exception as e:
-            print(f"[Cleanup] Грешка: {e}")
-
-import time
-@app.route("/api/chat", methods=["POST"])
-def api_chat():
-    try:
-        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()
-        return jsonify({"error": str(e)}), 500
-
-
-@app.route('/api/computer/<computer_name>/sysmon')
-def get_sysmon_processes(computer_name):
-    """Добиј Sysmon процеси за компјутер"""
-    conn = sqlite3.connect(DB_FILE)
-    c = conn.cursor()
-
-    # Прво најди го 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 = 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:
-            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
-        })
-
-    conn.close()
-
-    return jsonify({
-        'computer_name': computer_name,
-        'processes': processes,
-        'count': len(processes)
-    })
-
-
-@app.route('/api/computer/<computer_name>/network')
-def get_network_connections(computer_name):
-    """Добиј мрежни конекции за компјутер"""
-    conn = sqlite3.connect(DB_FILE)
-    c = conn.cursor()
-
-    # Прво најди го 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 = 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 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()
-
-    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({
-        'computer_name': computer_name,
-        'processes': detailed_processes,
-        'count': len(detailed_processes)
-    })
-@app.route("/api/admin/login", methods=["POST"])
-def admin_login():
-    data = request.get_json(force=True) or {}
-    key = (data.get("admin_key") or "").strip()
-    if not ADMIN_KEY or key != ADMIN_KEY:
-        return jsonify({"error": "Unauthorized"}), 401
-
-    session_token = secrets.token_urlsafe(32)
-    conn = sqlite3.connect(DB_FILE)
-    c = conn.cursor()
-    c.execute("INSERT INTO admin_sessions(token, created_at) VALUES(?, datetime('now'))", (session_token,))
-    conn.commit()
-    conn.close()
-
-    return jsonify({"ok": True, "session": session_token})
-
-
-@app.route("/api/admin/environments", methods=["GET"])
-def admin_list_envs():
-    if not require_admin_session():
-        return jsonify({"error": "Unauthorized"}), 401
-
-    conn = sqlite3.connect(DB_FILE)
-    c = conn.cursor()
-    c.execute("SELECT name FROM environments ORDER BY name")
-    envs = [r[0] 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():
-    if not require_admin_session():
-        return jsonify({"error": "Unauthorized"}), 401
-
-    data = request.get_json(force=True) or {}
+    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 = sqlite3.connect(DB_FILE)
+    conn = db()
     c = conn.cursor()
     try:
-        c.execute("INSERT INTO environments(name, created_at) VALUES(?, datetime('now'))", (name,))
+        c.execute(
+            "INSERT INTO environments(tenant_id, name, created_at) VALUES(?, ?, datetime('now'))",
+            (tenant_id, name),
+        )
         conn.commit()
     except Exception as e:
@@ -2001,22 +540,19 @@
         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():
-    if not require_admin_session():
-        return jsonify({"error": "Unauthorized"}), 401
-
-    data = request.get_json(force=True) or {}
+    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 = sqlite3.connect(DB_FILE)
+    conn = db()
     c = conn.cursor()
-
-    c.execute("SELECT 1 FROM environments WHERE name = ?", (env,))
+    c.execute("SELECT 1 FROM environments WHERE tenant_id=? AND name=? LIMIT 1", (tenant_id, env))
     if not c.fetchone():
         conn.close()
@@ -2025,8 +561,7 @@
     token = secrets.token_urlsafe(32)
     c.execute("""
-        INSERT INTO env_tokens(env_name, token, created_at, expires_at)
-        VALUES(?, ?, datetime('now'), datetime('now', '+15 minutes'))
-    """, (env, token))
-
+            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()
@@ -2034,27 +569,542 @@
     return jsonify({"ok": True, "env": env, "token": token})
 
-if __name__ == '__main__':
-    init_database()
-    init_admin_tables()
-
-
-    # Стартувај cleanup thread
+
+# ----------------------------
+# 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
+        for proc in (data.get("processes") or []):
+            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"),
+                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,
+        })
+
+    except Exception as e:
+        traceback.print_exc()
+        return jsonify({"error": str(e)}), 500
+
+
+# ----------------------------
+# 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()
+    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:
+        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
+            WHERE computer_id=?
+            ORDER BY timestamp DESC
+            LIMIT 100
+        """, (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 = {}
+        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,
+        })
+
+    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,
+    })
+
+
+@app.route("/api/export/<computer_name>", methods=["GET"])
+@require_user()
+def api_export(computer_name):
+    tenant_id = request.user["tenant_id"]
+
+    conn = db()
+    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:
+        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 WHERE computer_id=? ORDER BY timestamp", (computer_id,))
+    out["processes"] = [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()]
+
+    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 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 на серверот."
+
+    system_msg = (
+        "Ти си асистент за безбедност и систем мониторинг. "
+        "Одговараш базирано ИСКЛУЧИВО на дадените логови. "
+        "Ако нема доволно податоци, кажи дека нема доволно информации. "
+        "Кога корисникот прашува за процеси и нивна потрошувачка, користи ги [PROC] редовите."
+    )
+
+    completion = client.chat.completions.create(
+        model="gpt-4.1-mini",
+        messages=[
+            {"role": "system", "content": system_msg},
+            {"role": "user", "content": f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}"},
+        ],
+        temperature=0.2,
+    )
+    return completion.choices[0].message.content
+
+
+@app.route("/api/chat", methods=["POST"])
+@require_user()
+def api_chat():
+    try:
+        tenant_id = request.user["tenant_id"]
+        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
+
+        ctx = build_rag_context(tenant_id, question, computer_name=computer_name, limit_per_table=10)
+        ans = ask_llm_with_context(question, ctx)
+        return jsonify({"answer": ans or ""}), 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()
+
     cleanup_thread = threading.Thread(target=cleanup_old_data, daemon=True)
     cleanup_thread.start()
 
-    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)
+    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)
