#!/usr/bin/env python3 """ netIntel Flask Server (multi-tenant + Google login + JWT cookie session + env tokens) Auth: - POST /api/auth/google -> sets HttpOnly cookie "session" - GET /api/me -> returns current user claims - POST /api/auth/logout -> clears cookie Tenant admin (JWT cookie; admin role): - GET/POST /api/admin/environments - POST /api/admin/tokens Agent (X-Env-Token): - POST /receive Tenant-scoped dashboard (JWT cookie): - GET /api/stats - GET /api/computers - GET /api/computer/ - GET /api/export/ - POST /api/chat """ import os import json import time import sqlite3 import traceback import threading import secrets from datetime import datetime, timedelta from functools import wraps from flask import Flask, request, jsonify, Response from dotenv import load_dotenv import jwt from google.oauth2 import id_token from google.auth.transport import requests as grequests # Optional OpenAI try: from openai import OpenAI except Exception: OpenAI = None from flask_cors import CORS load_dotenv() # ---------------------------- # 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__) 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("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() 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, None) conn = db() c = conn.cursor() c.execute(""" SELECT env_name, tenant_id FROM env_tokens WHERE token = ? AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1 """, (tok,)) row = c.fetchone() conn.close() if not row: return (None, None) return (row["env_name"], row["tenant_id"]) # ---------------------------- # Utility # ---------------------------- 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: idinfo = id_token.verify_oauth2_token( credential, grequests.Request(), GOOGLE_CLIENT_ID, ) email = idinfo.get("email") name = idinfo.get("name") or "" picture = idinfo.get("picture") or "" if not email: return jsonify({"error": "No email in token"}), 400 conn = db() c = conn.cursor() # upsert user c.execute("SELECT id FROM users WHERE email=?", (email,)) row = c.fetchone() if row: user_id = row["id"] c.execute("UPDATE users SET name=?, picture=? WHERE id=?", (name, picture, user_id)) else: c.execute( "INSERT INTO users(email, name, picture, created_at) VALUES(?,?,?, datetime('now'))", (email, name, picture), ) user_id = c.lastrowid # membership -> tenant (create tenant if first time) c.execute("SELECT tenant_id, role FROM memberships WHERE user_id=? LIMIT 1", (user_id,)) m = c.fetchone() if not m: tenant_name = email.split("@")[0] c.execute( "INSERT INTO tenants(name, owner_email, created_at) VALUES(?,?, datetime('now'))", (tenant_name, email), ) tenant_id = c.lastrowid role = "admin" c.execute( "INSERT INTO memberships(user_id, tenant_id, role, created_at) VALUES(?,?, 'admin', datetime('now'))", (user_id, tenant_id), ) # default env for this tenant c.execute( "INSERT OR IGNORE INTO environments(tenant_id, name, created_at) VALUES(?, 'default', datetime('now'))", (tenant_id,), ) else: tenant_id = m["tenant_id"] role = m["role"] or "admin" conn.commit() conn.close() session = make_jwt( { "uid": user_id, "email": email, "name": name, "role": role, "tenant_id": tenant_id, }, minutes=60 * 24, ) resp = jsonify({"ok": True, "user": {"email": email, "name": name, "role": role, "tenant_id": tenant_id}}) resp.set_cookie( "session", session, httponly=True, samesite="Lax", secure=COOKIE_SECURE, max_age=60 * 60 * 24, ) return resp except Exception as e: return jsonify({"error": "Invalid Google token", "details": str(e)}), 401 @app.route("/api/me", methods=["GET"]) @require_user() def api_me(): return jsonify({"user": request.user}) @app.route("/api/auth/logout", methods=["POST"]) def auth_logout(): resp = jsonify({"ok": True}) resp.set_cookie("session", "", expires=0) return resp # ---------------------------- # Admin endpoints # ---------------------------- @app.route("/api/admin/environments", methods=["GET"]) @require_tenant_admin() def admin_list_envs(): tenant_id = request.user["tenant_id"] conn = db() c = conn.cursor() c.execute("SELECT name FROM environments WHERE tenant_id=? ORDER BY name", (tenant_id,)) envs = [r["name"] for r in c.fetchall()] conn.close() if not envs: envs = ["default"] return jsonify({"environments": envs}) @app.route("/api/admin/environments", methods=["POST"]) @require_tenant_admin() def admin_create_env(): tenant_id = request.user["tenant_id"] data = request.get_json(force=True, silent=True) or {} name = (data.get("name") or "").strip() if not name: return jsonify({"error": "Missing env name"}), 400 conn = db() c = conn.cursor() try: c.execute( "INSERT INTO environments(tenant_id, name, created_at) VALUES(?, ?, datetime('now'))", (tenant_id, name), ) conn.commit() except Exception as e: conn.close() return jsonify({"error": str(e)}), 400 conn.close() return jsonify({"ok": True, "name": name}) @app.route("/api/admin/tokens", methods=["POST"]) @require_tenant_admin() def admin_generate_token(): tenant_id = request.user["tenant_id"] data = request.get_json(force=True, silent=True) or {} env = (data.get("env") or "").strip() if not env: return jsonify({"error": "Missing env"}), 400 conn = db() c = conn.cursor() c.execute("SELECT 1 FROM environments WHERE tenant_id=? AND name=? LIMIT 1", (tenant_id, env)) if not c.fetchone(): conn.close() return jsonify({"error": "Environment not found"}), 404 token = secrets.token_urlsafe(32) c.execute(""" INSERT INTO env_tokens(tenant_id, env_name, token, created_at, expires_at) VALUES(?, ?, ?, datetime('now'), datetime('now', '+90 days')) """, (tenant_id, env, token)) conn.commit() conn.close() return jsonify({"ok": True, "env": env, "token": token}) # ---------------------------- # 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/", 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/", 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() 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)