- Timestamp:
- 01/21/26 13:35:44 (6 months ago)
- Branches:
- master
- Children:
- d42aac3
- Parents:
- 2058e5c
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
server.py
r2058e5c r505f39a 1 1 #!/usr/bin/env python3 2 2 """ 3 POPRAVEN SERVER со Sysmon обработка 3 netIntel Flask Server (multi-tenant + Google login + JWT cookie session + env tokens) 4 5 Auth: 6 - POST /api/auth/google -> sets HttpOnly cookie "session" 7 - GET /api/me -> returns current user claims 8 - POST /api/auth/logout -> clears cookie 9 10 Tenant admin (JWT cookie; admin role): 11 - GET/POST /api/admin/environments 12 - POST /api/admin/tokens 13 14 Agent (X-Env-Token): 15 - POST /receive 16 17 Tenant-scoped dashboard (JWT cookie): 18 - GET /api/stats 19 - GET /api/computers 20 - GET /api/computer/<name> 21 - GET /api/export/<name> 22 - POST /api/chat 4 23 """ 24 25 import os 26 import json 27 import time 28 import sqlite3 5 29 import traceback 6 7 from flask import Flask, request, jsonify, render_template_string 8 import json 9 import sqlite3 30 import threading 31 import secrets 10 32 from datetime import datetime, timedelta 11 import time 12 import os 13 import socket 14 from collections import defaultdict 15 import threading 16 import requests 17 from openai import OpenAI 18 from openai import OpenAI 33 from functools import wraps 34 35 from flask import Flask, request, jsonify, Response 19 36 from dotenv import load_dotenv 37 38 import jwt 39 from google.oauth2 import id_token 40 from google.auth.transport import requests as grequests 41 42 # Optional OpenAI 43 try: 44 from openai import OpenAI 45 except Exception: 46 OpenAI = None 47 48 from flask_cors import CORS 49 20 50 load_dotenv() 21 51 22 OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") 23 client = OpenAI( 24 api_key=OPENAI_API_KEY, 25 timeout=15 # максимум 15 секунди да чека 26 ) if OPENAI_API_KEY else None 27 28 52 # ---------------------------- 53 # Config 54 # ---------------------------- 55 DB_FILE = os.environ.get("DB_FILE", "lan_logs_sysmon.db") 56 LOG_DIR = os.environ.get("LOG_DIR", "received_data_sysmon") 57 58 OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "") 59 GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", "") 60 JWT_SECRET = os.environ.get("JWT_SECRET", "dev-change-me") 61 JWT_ISSUER = os.environ.get("JWT_ISSUER", "netintel") 62 COOKIE_SECURE = os.environ.get("COOKIE_SECURE", "0") == "1" # set 1 only on HTTPS 63 64 # CORS origins (add your LAN origins if needed) 65 EXTRA_ORIGINS = [o.strip() for o in os.environ.get("CORS_ORIGINS", "").split(",") if o.strip()] 66 DEFAULT_ORIGINS = [ 67 "http://localhost:5173", 68 "http://127.0.0.1:5173", 69 ] 70 ALLOWED_ORIGINS = list(dict.fromkeys(DEFAULT_ORIGINS + EXTRA_ORIGINS)) 71 72 client = OpenAI(api_key=OPENAI_API_KEY, timeout=15) if (OPENAI_API_KEY and OpenAI) else None 73 74 # ---------------------------- 75 # App 76 # ---------------------------- 29 77 app = Flask(__name__) 30 31 DB_FILE = "lan_logs_sysmon.db" 32 LOG_DIR = "received_data_sysmon" 33 34 import secrets 35 36 ADMIN_KEY = os.environ.get("ADMIN_KEY", "") 37 ADMIN_KEY = os.environ.get("ADMIN_KEY", "") 38 39 40 def init_admin_tables(): 78 os.makedirs(LOG_DIR, exist_ok=True) 79 80 CORS( 81 app, 82 supports_credentials=True, 83 origins=ALLOWED_ORIGINS, 84 allow_headers=[ 85 "Content-Type", 86 "X-Admin-Session", 87 "X-Env", 88 "X-Env-Token", 89 ], 90 methods=["GET", "POST", "OPTIONS"], 91 ) 92 93 94 # ---------------------------- 95 # DB helpers 96 # ---------------------------- 97 def db(): 41 98 conn = sqlite3.connect(DB_FILE) 99 conn.row_factory = sqlite3.Row 100 return conn 101 102 103 def init_db(): 104 """Create tables if missing + light migrations.""" 105 conn = db() 42 106 c = conn.cursor() 43 107 44 c.execute('''CREATE TABLE IF NOT EXISTS environments( 45 id INTEGER PRIMARY KEY AUTOINCREMENT, 46 name TEXT UNIQUE, 47 created_at TEXT 48 )''') 49 50 c.execute('''CREATE TABLE IF NOT EXISTS env_tokens( 51 id INTEGER PRIMARY KEY AUTOINCREMENT, 52 env_name TEXT, 53 token TEXT UNIQUE, 54 created_at TEXT, 55 expires_at TEXT 56 )''') 57 58 # migration ако табелата веќе постои 59 c.execute("PRAGMA table_info(env_tokens)") 60 cols = [r[1] for r in c.fetchall()] 61 if "expires_at" not in cols: 62 c.execute("ALTER TABLE env_tokens ADD COLUMN expires_at TEXT") 63 64 c.execute('''CREATE TABLE IF NOT EXISTS admin_sessions( 65 id INTEGER PRIMARY KEY AUTOINCREMENT, 66 token TEXT UNIQUE, 67 created_at TEXT 68 )''') 69 70 # default env 71 c.execute("INSERT OR IGNORE INTO environments(name, created_at) VALUES('default', datetime('now'))") 108 c.execute("PRAGMA foreign_keys = ON;") 109 110 # Tenants / users / memberships 111 c.execute(""" 112 CREATE TABLE IF NOT EXISTS tenants( 113 id INTEGER PRIMARY KEY AUTOINCREMENT, 114 name TEXT NOT NULL, 115 owner_email TEXT NOT NULL, 116 created_at TEXT 117 ) 118 """) 119 120 c.execute(""" 121 CREATE TABLE IF NOT EXISTS users( 122 id INTEGER PRIMARY KEY AUTOINCREMENT, 123 email TEXT UNIQUE NOT NULL, 124 name TEXT, 125 picture TEXT, 126 created_at TEXT 127 ) 128 """) 129 130 c.execute(""" 131 CREATE TABLE IF NOT EXISTS memberships( 132 user_id INTEGER NOT NULL, 133 tenant_id INTEGER NOT NULL, 134 role TEXT DEFAULT 'admin', 135 created_at TEXT, 136 PRIMARY KEY(user_id, tenant_id), 137 FOREIGN KEY(user_id) REFERENCES users(id), 138 FOREIGN KEY(tenant_id) REFERENCES tenants(id) 139 ) 140 """) 141 142 # Environments (unique per tenant) 143 c.execute(""" 144 CREATE TABLE IF NOT EXISTS environments( 145 id INTEGER PRIMARY KEY AUTOINCREMENT, 146 tenant_id INTEGER NOT NULL, 147 name TEXT NOT NULL, 148 created_at TEXT, 149 UNIQUE(tenant_id, name), 150 FOREIGN KEY(tenant_id) REFERENCES tenants(id) 151 ) 152 """) 153 154 # Env tokens (agent tokens) 155 c.execute(""" 156 CREATE TABLE IF NOT EXISTS env_tokens( 157 id INTEGER PRIMARY KEY AUTOINCREMENT, 158 tenant_id INTEGER NOT NULL, 159 env_name TEXT NOT NULL, 160 token TEXT UNIQUE NOT NULL, 161 created_at TEXT, 162 expires_at TEXT, 163 FOREIGN KEY(tenant_id) REFERENCES tenants(id) 164 ) 165 """) 166 167 # Computers 168 c.execute(""" 169 CREATE TABLE IF NOT EXISTS computers( 170 id INTEGER PRIMARY KEY AUTOINCREMENT, 171 tenant_id INTEGER NOT NULL, 172 env_name TEXT DEFAULT 'default', 173 name TEXT NOT NULL, 174 user TEXT, 175 ip TEXT, 176 os TEXT, 177 first_seen TEXT, 178 last_seen TEXT, 179 sysmon_available INTEGER DEFAULT 0, 180 UNIQUE(tenant_id, name) 181 ) 182 """) 183 184 # History 185 c.execute(""" 186 CREATE TABLE IF NOT EXISTS computer_history( 187 id INTEGER PRIMARY KEY AUTOINCREMENT, 188 computer_id INTEGER, 189 cpu_usage REAL, 190 ram_usage REAL, 191 disk_usage REAL, 192 network_sent_mb REAL, 193 network_recv_mb REAL, 194 timestamp TEXT, 195 FOREIGN KEY(computer_id) REFERENCES computers(id) 196 ) 197 """) 198 199 # Processes 200 c.execute(""" 201 CREATE TABLE IF NOT EXISTS computer_processes( 202 id INTEGER PRIMARY KEY AUTOINCREMENT, 203 computer_id INTEGER, 204 pid INTEGER, 205 name TEXT, 206 cpu_percent REAL, 207 memory_mb REAL, 208 username TEXT, 209 cmdline TEXT, 210 timestamp TEXT, 211 FOREIGN KEY(computer_id) REFERENCES computers(id) 212 ) 213 """) 214 215 # Sysmon events 216 c.execute(""" 217 CREATE TABLE IF NOT EXISTS sysmon_events( 218 id INTEGER PRIMARY KEY AUTOINCREMENT, 219 computer_id INTEGER, 220 event_id INTEGER, 221 event_type TEXT, 222 message TEXT, 223 timestamp TEXT, 224 details TEXT, 225 FOREIGN KEY(computer_id) REFERENCES computers(id) 226 ) 227 """) 228 229 # Network connections 230 c.execute(""" 231 CREATE TABLE IF NOT EXISTS network_connections( 232 id INTEGER PRIMARY KEY AUTOINCREMENT, 233 computer_id INTEGER, 234 pid INTEGER, 235 local_address TEXT, 236 remote_address TEXT, 237 status TEXT, 238 process_name TEXT, 239 timestamp TEXT, 240 FOREIGN KEY(computer_id) REFERENCES computers(id) 241 ) 242 """) 243 244 # Security alerts 245 c.execute(""" 246 CREATE TABLE IF NOT EXISTS security_alerts( 247 id INTEGER PRIMARY KEY AUTOINCREMENT, 248 computer_id INTEGER, 249 alert_type TEXT, 250 severity TEXT, 251 description TEXT, 252 timestamp TEXT, 253 resolved INTEGER DEFAULT 0, 254 FOREIGN KEY(computer_id) REFERENCES computers(id) 255 ) 256 """) 72 257 73 258 conn.commit() 74 259 conn.close() 75 76 def require_admin_session(): 77 tok = request.headers.get("X-Admin-Session", "") 78 if not tok: 79 return False 80 conn = sqlite3.connect(DB_FILE) 81 c = conn.cursor() 82 c.execute("SELECT 1 FROM admin_sessions WHERE token = ?", (tok,)) 83 ok = c.fetchone() is not None 84 conn.close() 85 return ok 86 260 print(f"✅ DB ready: {DB_FILE}") 261 262 263 # ---------------------------- 264 # JWT helpers 265 # ---------------------------- 266 def make_jwt(payload: dict, minutes=60 * 24): 267 exp = datetime.utcnow() + timedelta(minutes=minutes) 268 data = {**payload, "iss": JWT_ISSUER, "exp": exp} 269 return jwt.encode(data, JWT_SECRET, algorithm="HS256") 270 271 272 def read_jwt(token: str): 273 return jwt.decode(token, JWT_SECRET, algorithms=["HS256"], issuer=JWT_ISSUER) 274 275 276 def require_user(): 277 def deco(fn): 278 @wraps(fn) 279 def wrap(*args, **kwargs): 280 tok = request.cookies.get("session") or "" 281 if not tok: 282 return jsonify({"error": "Unauthorized"}), 401 283 try: 284 claims = read_jwt(tok) 285 except Exception: 286 return jsonify({"error": "Unauthorized"}), 401 287 request.user = claims 288 return fn(*args, **kwargs) 289 290 return wrap 291 292 return deco 293 294 295 def require_tenant_admin(): 296 def deco(fn): 297 @wraps(fn) 298 def wrap(*args, **kwargs): 299 tok = request.cookies.get("session") or "" 300 if not tok: 301 return jsonify({"error": "Unauthorized"}), 401 302 try: 303 claims = read_jwt(tok) 304 except Exception: 305 return jsonify({"error": "Unauthorized"}), 401 306 if claims.get("role") != "admin": 307 return jsonify({"error": "Forbidden"}), 403 308 request.user = claims 309 return fn(*args, **kwargs) 310 311 return wrap 312 313 return deco 314 315 316 # ---------------------------- 317 # Env-token helpers (agents) 318 # ---------------------------- 87 319 def get_env_from_token(req): 88 320 tok = req.headers.get("X-Env-Token", "") 89 321 if not tok: 90 return None91 92 conn = sqlite3.connect(DB_FILE)322 return (None, None) 323 324 conn = db() 93 325 c = conn.cursor() 94 326 c.execute(""" 95 SELECT env_name 96 FROM env_tokens 97 WHERE token = ? 98 AND (expires_at IS NULL OR expires_at > datetime('now')) 99 """, (tok,)) 327 SELECT env_name, tenant_id 328 FROM env_tokens 329 WHERE token = ? 330 AND (expires_at IS NULL OR expires_at > datetime('now')) 331 LIMIT 1 332 """, (tok,)) 100 333 row = c.fetchone() 101 334 conn.close() 102 return row[0] if row else None 103 104 105 def init_database(): 106 """Креирај подобрена база со Sysmon табели""" 107 os.makedirs(LOG_DIR, exist_ok=True) 108 109 conn = sqlite3.connect(DB_FILE) 110 c = conn.cursor() 111 112 # Табела за компјутери 113 c.execute('''CREATE TABLE IF NOT EXISTS computers 114 (id INTEGER PRIMARY KEY AUTOINCREMENT, 115 name TEXT UNIQUE, 116 user TEXT, 117 ip TEXT, 118 os TEXT, 119 first_seen TIMESTAMP, 120 last_seen TIMESTAMP, 121 sysmon_available BOOLEAN DEFAULT 0)''') 122 # --- MIGRATION: add env_name column if missing --- 123 c.execute("PRAGMA table_info(computers)") 124 cols = [r[1] for r in c.fetchall()] 125 if "env_name" not in cols: 126 c.execute("ALTER TABLE computers ADD COLUMN env_name TEXT DEFAULT 'default'") 127 # --- END MIGRATION --- 128 129 # Табела за историја 130 c.execute('''CREATE TABLE IF NOT EXISTS computer_history 131 (id INTEGER PRIMARY KEY AUTOINCREMENT, 132 computer_id INTEGER, 133 cpu_usage REAL, 134 ram_usage REAL, 135 disk_usage REAL, 136 network_sent_mb REAL, 137 network_recv_mb REAL, 138 timestamp TIMESTAMP, 139 FOREIGN KEY(computer_id) REFERENCES computers(id))''') 140 141 # Табела за процеси 142 c.execute('''CREATE TABLE IF NOT EXISTS computer_processes 143 (id INTEGER PRIMARY KEY AUTOINCREMENT, 144 computer_id INTEGER, 145 pid INTEGER, 146 name TEXT, 147 cpu_percent REAL, 148 memory_mb REAL, 149 username TEXT, 150 cmdline TEXT, 151 timestamp TIMESTAMP, 152 FOREIGN KEY(computer_id) REFERENCES computers(id))''') 153 154 # НОВА: Табела за Sysmon настани 155 c.execute('''CREATE TABLE IF NOT EXISTS sysmon_events 156 (id INTEGER PRIMARY KEY AUTOINCREMENT, 157 computer_id INTEGER, 158 event_id INTEGER, 159 event_type TEXT, 160 message TEXT, 161 timestamp TIMESTAMP, 162 details TEXT, 163 FOREIGN KEY(computer_id) REFERENCES computers(id))''') 164 165 # НОВА: Табела за детални мрежни конекции 166 c.execute('''CREATE TABLE IF NOT EXISTS network_connections 167 (id INTEGER PRIMARY KEY AUTOINCREMENT, 168 computer_id INTEGER, 169 pid INTEGER, 170 local_address TEXT, 171 remote_address TEXT, 172 status TEXT, 173 process_name TEXT, 174 timestamp TIMESTAMP, 175 FOREIGN KEY(computer_id) REFERENCES computers(id))''') 176 177 # НОВА: Табела за безбедносни настани 178 c.execute('''CREATE TABLE IF NOT EXISTS security_alerts 179 (id INTEGER PRIMARY KEY AUTOINCREMENT, 180 computer_id INTEGER, 181 alert_type TEXT, 182 severity TEXT, 183 description TEXT, 184 timestamp TIMESTAMP, 185 resolved BOOLEAN DEFAULT 0, 186 FOREIGN KEY(computer_id) REFERENCES computers(id))''') 187 188 conn.commit() 189 conn.close() 190 191 print(f"✅ Database created: {DB_FILE}") 192 193 194 @app.route('/receive', methods=['POST']) 195 def receive_data(): 335 336 if not row: 337 return (None, None) 338 return (row["env_name"], row["tenant_id"]) 339 340 341 # ---------------------------- 342 # Utility 343 # ---------------------------- 344 def save_json_file(data): 345 info = data.get("info") or {} 346 computer_name = info.get("computer_name", "unknown") 347 date_str = datetime.now().strftime("%Y%m%d_%H") 348 349 computer_dir = os.path.join(LOG_DIR, computer_name) 350 os.makedirs(computer_dir, exist_ok=True) 351 filepath = os.path.join(computer_dir, f"{date_str}.json") 352 353 if os.path.exists(filepath): 354 try: 355 with open(filepath, "r", encoding="utf-8") as f: 356 existing_data = json.load(f) 357 except Exception: 358 existing_data = [] 359 else: 360 existing_data = [] 361 362 existing_data.append({ 363 "timestamp": info.get("timestamp"), 364 "info": info, 365 "processes_count": len(data.get("processes", [])), 366 "sysmon_events_count": len((data.get("security_data") or {}).get("sysmon_events", [])), 367 }) 368 369 with open(filepath, "w", encoding="utf-8") as f: 370 json.dump(existing_data, f, indent=2, ensure_ascii=False) 371 372 373 def cleanup_old_data(): 374 """Deletes old rows (30 days) every hour.""" 375 while True: 376 time.sleep(3600) 377 try: 378 conn = db() 379 c = conn.cursor() 380 cutoff = (datetime.now() - timedelta(days=30)).isoformat() 381 382 c.execute("DELETE FROM computer_history WHERE timestamp < ?", (cutoff,)) 383 c.execute("DELETE FROM computer_processes WHERE timestamp < ?", (cutoff,)) 384 c.execute("DELETE FROM sysmon_events WHERE timestamp < ?", (cutoff,)) 385 c.execute("DELETE FROM network_connections WHERE timestamp < ?", (cutoff,)) 386 387 conn.commit() 388 conn.close() 389 print(f"[Cleanup] Deleted entries older than {cutoff}") 390 except Exception as e: 391 print("[Cleanup] Error:", e) 392 393 394 # ---------------------------- 395 # Auth endpoints 396 # ---------------------------- 397 @app.route("/api/auth/google", methods=["POST"]) 398 def auth_google(): 399 data = request.get_json(force=True, silent=True) or {} 400 credential = (data.get("credential") or "").strip() 401 if not credential: 402 return jsonify({"error": "Missing credential"}), 400 403 if not GOOGLE_CLIENT_ID: 404 return jsonify({"error": "Server missing GOOGLE_CLIENT_ID"}), 500 405 196 406 try: 197 data = request.json 198 199 if not data: 200 return jsonify({"error": "No data"}), 400 201 env_name = get_env_from_token(request) 202 if not env_name: 203 return jsonify({"error": "Missing or invalid X-Env-Token"}), 401 204 205 info = data['info'] 206 computer_name = info['computer_name'] 207 timestamp = datetime.now() 208 security_data = data.get('security_data', {}) 209 210 conn = sqlite3.connect(DB_FILE) 407 idinfo = id_token.verify_oauth2_token( 408 credential, 409 grequests.Request(), 410 GOOGLE_CLIENT_ID, 411 ) 412 email = idinfo.get("email") 413 name = idinfo.get("name") or "" 414 picture = idinfo.get("picture") or "" 415 416 if not email: 417 return jsonify({"error": "No email in token"}), 400 418 419 conn = db() 211 420 c = conn.cursor() 212 421 213 # Провери дали компјутерот веќе постои 214 c.execute("SELECT id FROM computers WHERE name = ?", (computer_name,)) 215 result = c.fetchone() 216 217 if result: 218 # Компјутерот постои - ажурирај го 219 computer_id = result[0] 220 c.execute('''UPDATE computers 221 SET user = ?, ip = ?, os = ?, last_seen = ?, sysmon_available = ?, env_name = ? 222 WHERE id = ?''', 223 (info['user'], info['ip_address'], info['os'], 224 timestamp.isoformat(), info.get('is_sysmon_available', 0), 225 env_name, computer_id)) 422 # upsert user 423 c.execute("SELECT id FROM users WHERE email=?", (email,)) 424 row = c.fetchone() 425 if row: 426 user_id = row["id"] 427 c.execute("UPDATE users SET name=?, picture=? WHERE id=?", (name, picture, user_id)) 226 428 else: 227 # Нов компјутер - додади го 228 c.execute('''INSERT INTO computers 229 (name, user, ip, os, first_seen, last_seen, sysmon_available, env_name) 230 VALUES (?, ?, ?, ?, ?, ?, ?, ?)''', 231 (computer_name, info['user'], info['ip_address'], 232 info['os'], timestamp.isoformat(), timestamp.isoformat(), 233 info.get('is_sysmon_available', 0), env_name)) 234 235 computer_id = c.lastrowid 236 237 # Додади во историја 238 c.execute('''INSERT INTO computer_history 239 (computer_id, cpu_usage, ram_usage, disk_usage, 240 network_sent_mb, network_recv_mb, timestamp) 241 VALUES (?, ?, ?, ?, ?, ?, ?)''', 242 (computer_id, info['cpu_usage'], info['ram_usage'], 243 info['disk_usage'], info.get('network_sent_mb', 0), 244 info.get('network_recv_mb', 0), info['timestamp'])) 245 246 # Зачувај ги процесите 247 for proc in data.get('processes', []): 248 c.execute('''INSERT INTO computer_processes 249 (computer_id, pid, name, cpu_percent, memory_mb, 250 username, cmdline, timestamp) 251 VALUES (?, ?, ?, ?, ?, ?, ?, ?)''', 252 (computer_id, proc.get('pid'), proc.get('name'), 253 proc.get('cpu_percent', 0), proc.get('memory_mb', 0), 254 proc.get('user'), proc.get('cmdline'), info['timestamp'])) 255 256 # ЗАЧУВАЈ SYSMON НАСТАНИ 257 sysmon_events_count = 0 258 for event in security_data.get('sysmon_events', []): 259 try: 260 c.execute('''INSERT INTO sysmon_events 261 (computer_id, event_id, event_type, message, 262 timestamp, details) 263 VALUES (?, ?, ?, ?, ?, ?)''', 264 (computer_id, event.get('event_id'), 265 event.get('event_type', 'Unknown'), 266 event.get('message', ''), 267 event.get('timestamp', info['timestamp']), 268 json.dumps(event))) 269 sysmon_events_count += 1 270 except Exception as e: 271 print(f"Error saving Sysmon event: {e}") 272 273 # ЗАЧУВАЈ МРЕЖНИ КОНЕКЦИИ 274 for conn_data in security_data.get('network_connections', []): 275 try: 276 c.execute('''INSERT INTO network_connections 277 (computer_id, pid, local_address, remote_address, 278 status, process_name, timestamp) 279 VALUES (?, ?, ?, ?, ?, ?, ?)''', 280 (computer_id, conn_data.get('pid'), 281 conn_data.get('local_address'), 282 conn_data.get('remote_address'), 283 conn_data.get('status'), 284 conn_data.get('process_name'), 285 info['timestamp'])) 286 except Exception as e: 287 print(f"Error saving network connection: {e}") 429 c.execute( 430 "INSERT INTO users(email, name, picture, created_at) VALUES(?,?,?, datetime('now'))", 431 (email, name, picture), 432 ) 433 user_id = c.lastrowid 434 435 # membership -> tenant (create tenant if first time) 436 c.execute("SELECT tenant_id, role FROM memberships WHERE user_id=? LIMIT 1", (user_id,)) 437 m = c.fetchone() 438 439 if not m: 440 tenant_name = email.split("@")[0] 441 c.execute( 442 "INSERT INTO tenants(name, owner_email, created_at) VALUES(?,?, datetime('now'))", 443 (tenant_name, email), 444 ) 445 tenant_id = c.lastrowid 446 role = "admin" 447 448 c.execute( 449 "INSERT INTO memberships(user_id, tenant_id, role, created_at) VALUES(?,?, 'admin', datetime('now'))", 450 (user_id, tenant_id), 451 ) 452 453 # default env for this tenant 454 c.execute( 455 "INSERT OR IGNORE INTO environments(tenant_id, name, created_at) VALUES(?, 'default', datetime('now'))", 456 (tenant_id,), 457 ) 458 else: 459 tenant_id = m["tenant_id"] 460 role = m["role"] or "admin" 288 461 289 462 conn.commit() 290 463 conn.close() 291 464 292 # Зачувај во JSON 293 save_json_file(data) 294 295 print(f"[{timestamp.strftime('%H:%M:%S')}] 📥 {computer_name}: " 296 f"CPU {info['cpu_usage']}% | Sysmon: {sysmon_events_count} events") 297 298 return jsonify({ 299 "status": "success", 300 "message": f"Data saved for {computer_name}", 301 "computer_id": computer_id, 302 "event_count": sysmon_events_count, 303 "timestamp": timestamp.isoformat() 304 }) 465 session = make_jwt( 466 { 467 "uid": user_id, 468 "email": email, 469 "name": name, 470 "role": role, 471 "tenant_id": tenant_id, 472 }, 473 minutes=60 * 24, 474 ) 475 476 resp = jsonify({"ok": True, "user": {"email": email, "name": name, "role": role, "tenant_id": tenant_id}}) 477 resp.set_cookie( 478 "session", 479 session, 480 httponly=True, 481 samesite="Lax", 482 secure=COOKIE_SECURE, 483 max_age=60 * 60 * 24, 484 ) 485 return resp 305 486 306 487 except Exception as e: 307 print(f"[-] Грешка: {e}") 308 return jsonify({"error": str(e)}), 500 309 310 311 def save_json_file(data): 312 """Зачувај во JSON""" 313 info = data['info'] 314 computer_name = info['computer_name'] 315 date_str = datetime.now().strftime("%Y%m%d_%H") 316 317 # Креирај folder за компјутерот 318 computer_dir = os.path.join(LOG_DIR, computer_name) 319 os.makedirs(computer_dir, exist_ok=True) 320 321 # Фајл за секој час (за подобро организирање) 322 filepath = os.path.join(computer_dir, f"{date_str}.json") 323 324 # Додади во постоечки фајл или креирај нов 325 if os.path.exists(filepath): 326 with open(filepath, 'r', encoding='utf-8') as f: 327 existing_data = json.load(f) 328 else: 329 existing_data = [] 330 331 # Додади нов запис 332 existing_data.append({ 333 "timestamp": info['timestamp'], 334 "info": info, 335 "processes_count": len(data.get('processes', [])), 336 "sysmon_events_count": len(data.get('security_data', {}).get('sysmon_events', [])) 337 }) 338 339 # Зачувај 340 with open(filepath, 'w', encoding='utf-8') as f: 341 json.dump(existing_data, f, indent=2, ensure_ascii=False) 342 343 344 @app.route('/') 345 def dashboard(): 346 """Главен dashboard""" 347 conn = sqlite3.connect(DB_FILE) 488 return jsonify({"error": "Invalid Google token", "details": str(e)}), 401 489 490 491 @app.route("/api/me", methods=["GET"]) 492 @require_user() 493 def api_me(): 494 return jsonify({"user": request.user}) 495 496 497 @app.route("/api/auth/logout", methods=["POST"]) 498 def auth_logout(): 499 resp = jsonify({"ok": True}) 500 resp.set_cookie("session", "", expires=0) 501 return resp 502 503 504 # ---------------------------- 505 # Admin endpoints 506 # ---------------------------- 507 @app.route("/api/admin/environments", methods=["GET"]) 508 @require_tenant_admin() 509 def admin_list_envs(): 510 tenant_id = request.user["tenant_id"] 511 conn = db() 348 512 c = conn.cursor() 349 350 # Земaj ги сите компјутери 351 c.execute('''SELECT name, user, ip, os, first_seen, last_seen, sysmon_available, env_name 352 FROM computers 353 ORDER BY last_seen DESC''') 354 355 computers = [] 356 for row in c.fetchall(): 357 computer_name = row[0] 358 359 # Пресметaj колку записи има во последните 24 часа 360 c.execute('''SELECT COUNT(*) FROM computer_history 361 WHERE computer_id = (SELECT id FROM computers WHERE name = ?) 362 AND timestamp > datetime('now', '-24 hours')''', 363 (computer_name,)) 364 recent_logs = c.fetchone()[0] 365 366 # Земaj ги последните 5 записи за CPU/RAM 367 c.execute('''SELECT cpu_usage, ram_usage, timestamp 368 FROM computer_history 369 WHERE computer_id = (SELECT id FROM computers WHERE name = ?) 370 ORDER BY timestamp DESC LIMIT 5''', 371 (computer_name,)) 372 recent_metrics = c.fetchall() 373 374 # Sysmon настани за последните 24 часа 375 c.execute('''SELECT COUNT(*) FROM sysmon_events 376 WHERE computer_id = (SELECT id FROM computers WHERE name = ?) 377 AND timestamp > datetime('now', '-24 hours')''', 378 (computer_name,)) 379 recent_sysmon = c.fetchone()[0] 380 381 # Пресметaj просек 382 avg_cpu = sum(m[0] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0 383 avg_ram = sum(m[1] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0 384 385 # Статус 386 last_seen = datetime.fromisoformat(row[5]) 387 time_diff = (datetime.now() - last_seen).seconds 388 389 if time_diff < 60: 390 status = 'online' 391 status_color = 'green' 392 elif time_diff < 300: 393 status = 'idle' 394 status_color = 'orange' 395 else: 396 status = 'offline' 397 status_color = 'gray' 398 399 computers.append({ 400 'name': row[0], 401 'user': row[1], 402 'ip': row[2], 403 'os': row[3], 404 'first_seen': row[4], 405 'last_seen': row[5], 406 'sysmon_available': bool(row[6]), 407 'recent_logs': recent_logs, 408 'recent_sysmon': recent_sysmon, 409 'avg_cpu': round(avg_cpu, 1), 410 'avg_ram': round(avg_ram, 1), 411 'status': status, 412 'status_color': status_color, 413 'env_name': row[7], 414 }) 415 513 c.execute("SELECT name FROM environments WHERE tenant_id=? ORDER BY name", (tenant_id,)) 514 envs = [r["name"] for r in c.fetchall()] 416 515 conn.close() 417 418 # Генерирај HTML 419 return render_template_string(''' 420 <!DOCTYPE html> 421 <html> 422 <head> 423 <title>LAN Log Server - Sysmon Dashboard</title> 424 <meta charset="utf-8"> 425 <meta name="viewport" content="width=device-width, initial-scale=1"> 426 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> 427 <style> 428 :root { 429 --primary-color: #2c3e50; 430 --secondary-color: #3498db; 431 --danger-color: #e74c3c; 432 --warning-color: #f39c12; 433 --success-color: #27ae60; 434 } 435 436 body { 437 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 438 margin: 0; 439 padding: 20px; 440 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 441 min-height: 100vh; 442 } 443 444 .container { 445 max-width: 1400px; 446 margin: 0 auto; 447 background: white; 448 border-radius: 15px; 449 box-shadow: 0 20px 40px rgba(0,0,0,0.1); 450 overflow: hidden; 451 } 452 453 .header { 454 background: linear-gradient(to right, var(--primary-color), #1a2530); 455 color: white; 456 padding: 30px 40px; 457 border-radius: 15px 15px 0 0; 458 } 459 460 .header h1 { 461 margin: 0; 462 font-size: 2.5em; 463 display: flex; 464 align-items: center; 465 gap: 15px; 466 } 467 468 .stats-grid { 469 display: grid; 470 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 471 gap: 20px; 472 padding: 30px; 473 } 474 475 .stat-card { 476 background: white; 477 padding: 25px; 478 border-radius: 10px; 479 box-shadow: 0 5px 15px rgba(0,0,0,0.08); 480 border-left: 5px solid var(--secondary-color); 481 transition: transform 0.3s ease; 482 } 483 484 .stat-card:hover { 485 transform: translateY(-5px); 486 } 487 488 .stat-card.sysmon { 489 border-left-color: var(--danger-color); 490 } 491 492 .stat-card.network { 493 border-left-color: var(--success-color); 494 } 495 496 .stat-value { 497 font-size: 2.8em; 498 font-weight: bold; 499 margin: 10px 0; 500 } 501 502 .stat-label { 503 color: #666; 504 font-size: 0.9em; 505 text-transform: uppercase; 506 letter-spacing: 1px; 507 } 508 509 .computers-grid { 510 display: grid; 511 grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); 512 gap: 25px; 513 padding: 30px; 514 } 515 516 .computer-card { 517 background: white; 518 border-radius: 12px; 519 padding: 25px; 520 box-shadow: 0 8px 20px rgba(0,0,0,0.1); 521 cursor: pointer; 522 transition: all 0.3s ease; 523 border: 2px solid transparent; 524 position: relative; 525 overflow: hidden; 526 } 527 528 .computer-card:hover { 529 transform: translateY(-8px); 530 box-shadow: 0 15px 30px rgba(0,0,0,0.15); 531 border-color: var(--secondary-color); 532 } 533 534 .computer-card::before { 535 content: ''; 536 position: absolute; 537 top: 0; 538 left: 0; 539 right: 0; 540 height: 4px; 541 background: linear-gradient(to right, #667eea, #764ba2); 542 } 543 544 .computer-name { 545 font-size: 1.4em; 546 font-weight: bold; 547 color: var(--primary-color); 548 margin-bottom: 10px; 549 display: flex; 550 justify-content: space-between; 551 align-items: center; 552 } 553 554 .status-indicator { 555 display: inline-block; 556 width: 12px; 557 height: 12px; 558 border-radius: 50%; 559 margin-right: 8px; 560 } 561 562 .computer-info { 563 color: #555; 564 margin: 8px 0; 565 font-size: 0.95em; 566 display: flex; 567 align-items: center; 568 gap: 10px; 569 } 570 571 .computer-info i { 572 width: 20px; 573 color: #777; 574 } 575 576 .stats-row { 577 display: flex; 578 justify-content: space-between; 579 margin-top: 20px; 580 padding-top: 20px; 581 border-top: 1px solid #eee; 582 } 583 584 .stat-item { 585 text-align: center; 586 } 587 588 .stat-number { 589 font-size: 1.6em; 590 font-weight: bold; 591 color: var(--primary-color); 592 } 593 594 .stat-text { 595 font-size: 0.8em; 596 color: #777; 597 margin-top: 5px; 598 } 599 600 .sysmon-badge { 601 background: var(--danger-color); 602 color: white; 603 padding: 3px 10px; 604 border-radius: 20px; 605 font-size: 0.8em; 606 display: inline-block; 607 margin-left: 10px; 608 } 609 610 /* Модал */ 611 .modal { 612 display: none; 613 position: fixed; 614 top: 0; 615 left: 0; 616 right: 0; 617 bottom: 0; 618 background: rgba(0,0,0,0.7); 619 z-index: 1000; 620 overflow-y: auto; 621 } 622 623 .modal-content { 624 background: white; 625 margin: 50px auto; 626 width: 90%; 627 max-width: 1200px; 628 border-radius: 15px; 629 animation: modalSlideIn 0.3s ease; 630 } 631 632 @keyframes modalSlideIn { 633 from { 634 transform: translateY(-50px); 635 opacity: 0; 636 } 637 to { 638 transform: translateY(0); 639 opacity: 1; 640 } 641 } 642 643 .modal-header { 644 padding: 25px 30px; 645 background: var(--primary-color); 646 color: white; 647 border-radius: 15px 15px 0 0; 648 display: flex; 649 justify-content: space-between; 650 align-items: center; 651 } 652 653 .close-btn { 654 background: none; 655 border: none; 656 color: white; 657 font-size: 1.8em; 658 cursor: pointer; 659 width: 40px; 660 height: 40px; 661 border-radius: 50%; 662 display: flex; 663 align-items: center; 664 justify-content: center; 665 transition: background 0.2s; 666 } 667 668 .close-btn:hover { 669 background: rgba(255,255,255,0.1); 670 } 671 672 .tabs { 673 display: flex; 674 border-bottom: 1px solid #ddd; 675 padding: 0 30px; 676 background: #f8f9fa; 677 } 678 679 .tab-btn { 680 padding: 15px 25px; 681 background: none; 682 border: none; 683 cursor: pointer; 684 font-size: 1em; 685 color: #666; 686 position: relative; 687 transition: all 0.3s; 688 } 689 690 .tab-btn:hover { 691 color: var(--primary-color); 692 } 693 694 .tab-btn.active { 695 color: var(--primary-color); 696 font-weight: bold; 697 } 698 699 .tab-btn.active::after { 700 content: ''; 701 position: absolute; 702 bottom: -1px; 703 left: 0; 704 right: 0; 705 height: 3px; 706 background: var(--secondary-color); 707 } 708 709 .tab-content { 710 padding: 30px; 711 display: none; 712 } 713 714 .tab-content.active { 715 display: block; 716 } 717 718 .events-table { 719 width: 100%; 720 border-collapse: collapse; 721 margin-top: 20px; 722 } 723 724 .events-table th { 725 background: #f8f9fa; 726 padding: 15px; 727 text-align: left; 728 border-bottom: 2px solid #dee2e6; 729 color: var(--primary-color); 730 } 731 732 .events-table td { 733 padding: 12px 15px; 734 border-bottom: 1px solid #eee; 735 } 736 737 .events-table tr:hover { 738 background: #f8f9fa; 739 } 740 741 .severity-high { 742 background: #ffe6e6; 743 color: var(--danger-color); 744 padding: 3px 10px; 745 border-radius: 3px; 746 font-weight: bold; 747 } 748 749 .severity-medium { 750 background: #fff3cd; 751 color: var(--warning-color); 752 padding: 3px 10px; 753 border-radius: 3px; 754 } 755 756 .event-details { 757 max-width: 500px; 758 word-wrap: break-word; 759 font-family: monospace; 760 font-size: 0.9em; 761 } 762 763 .filter-bar { 764 padding: 20px 30px; 765 background: #f8f9fa; 766 border-bottom: 1px solid #ddd; 767 } 768 769 .search-box { 770 padding: 10px 15px; 771 border: 1px solid #ddd; 772 border-radius: 5px; 773 width: 300px; 774 } 775 776 .btn { 777 padding: 10px 20px; 778 background: var(--secondary-color); 779 color: white; 780 border: none; 781 border-radius: 5px; 782 cursor: pointer; 783 transition: background 0.3s; 784 } 785 786 .btn:hover { 787 background: #2980b9; 788 } 789 790 .chart-container { 791 height: 300px; 792 margin: 20px 0; 793 } 794 795 @media (max-width: 768px) { 796 .computers-grid { 797 grid-template-columns: 1fr; 798 } 799 800 .modal-content { 801 width: 95%; 802 margin: 20px auto; 803 } 804 } 805 </style> 806 </head> 807 <body> 808 <div class="container"> 809 <div class="header"> 810 <h1> 811 <span>🛡️ LAN Security Monitor</span> 812 <small style="font-size: 0.5em; opacity: 0.8;">Sysmon Edition</small> 813 </h1> 814 <p>Server: {{ socket.gethostbyname(socket.gethostname()) }}:5555 | Total Computers: {{ computers|length }}</p> 815 </div> 816 817 <div class="stats-grid"> 818 <div class="stat-card"> 819 <div class="stat-label">Total Computers</div> 820 <div class="stat-value">{{ computers|length }}</div> 821 <div class="stat-text">Monitored Systems</div> 822 </div> 823 824 <div class="stat-card sysmon"> 825 <div class="stat-label">Sysmon Events (24h)</div> 826 <div class="stat-value" id="total-sysmon">0</div> 827 <div class="stat-text">Security Events</div> 828 </div> 829 830 <div class="stat-card network"> 831 <div class="stat-label">Network Connections</div> 832 <div class="stat-value" id="total-network">0</div> 833 <div class="stat-text">Active Connections</div> 834 </div> 835 </div> 836 837 <div style="padding: 0 30px;"> 838 <h2>Connected Computers</h2> 839 <div class="computers-grid" id="computers-grid"> 840 {% for comp in computers %} 841 <div class="computer-card" onclick="showComputerDetails('{{ comp.name }}')"> 842 <div class="computer-name"> 843 {{ comp.name }} 844 <span class="status-indicator" style="background: {{ comp.status_color }}"></span> 845 </div> 846 847 <div class="computer-info"> 848 <i>👤</i> {{ comp.user }} 849 </div> 850 851 <div class="computer-info"> 852 <i>🌐</i> {{ comp.ip }} 853 </div> 854 <div class="computer-info"> 855 <i>🏷️</i> Env: {{ comp.env_name }}</div> 856 857 858 <div class="computer-info"> 859 <i>💻</i> {{ comp.os[:40] }} 860 </div> 861 862 <div class="computer-info"> 863 <i>⏰</i> Last seen: {{ comp.last_seen[11:19] }} 864 </div> 865 866 {% if comp.sysmon_available %} 867 <div class="computer-info"> 868 <i>🛡️</i> Sysmon Available 869 <span class="sysmon-badge">{{ comp.recent_sysmon }} events</span> 870 </div> 871 {% endif %} 872 873 <div class="stats-row"> 874 <div class="stat-item"> 875 <div class="stat-number">{{ comp.avg_cpu }}%</div> 876 <div class="stat-text">CPU</div> 877 </div> 878 <div class="stat-item"> 879 <div class="stat-number">{{ comp.avg_ram }}%</div> 880 <div class="stat-text">RAM</div> 881 </div> 882 <div class="stat-item"> 883 <div class="stat-number">{{ comp.recent_logs }}</div> 884 <div class="stat-text">Logs (24h)</div> 885 </div> 886 </div> 887 </div> 888 {% endfor %} 889 </div> 890 </div> 891 </div> 892 893 <!-- Детален модал --> 894 <div class="modal" id="computer-modal"> 895 <div class="modal-content"> 896 <div class="modal-header"> 897 <h2 id="modal-title">Computer Details</h2> 898 <button class="close-btn" onclick="closeModal()">×</button> 899 </div> 900 901 <div class="filter-bar"> 902 <input type="text" id="event-search" class="search-box" placeholder="Search events..." 903 onkeyup="filterEvents()"> 904 <button class="btn" onclick="exportData()">Export Data</button> 905 </div> 906 907 <div class="tabs"> 908 <button class="tab-btn active" onclick="switchTab('overview')">Overview</button> 909 <button class="tab-btn" onclick="switchTab('sysmon')">Sysmon Events</button> 910 <button class="tab-btn" onclick="switchTab('processes')">Processes</button> 911 <button class="tab-btn" onclick="switchTab('network')">Network</button> 912 <button class="tab-btn" onclick="switchTab('charts')">Charts</button> 913 </div> 914 915 <!-- Overview Tab --> 916 <div class="tab-content active" id="overview-tab"> 917 <h3>System Overview</h3> 918 <div id="overview-content"></div> 919 </div> 920 921 <!-- Sysmon Events Tab --> 922 <div class="tab-content" id="sysmon-tab"> 923 <h3>Security Events</h3> 924 <table class="events-table" id="sysmon-table"> 925 <thead> 926 <tr> 927 <th>Time</th> 928 <th>Event ID</th> 929 <th>Type</th> 930 <th>Severity</th> 931 <th>Message</th> 932 <th>Actions</th> 933 </tr> 934 </thead> 935 <tbody id="sysmon-events"> 936 <!-- Events will be populated here --> 937 </tbody> 938 </table> 939 </div> 940 941 <!-- Processes Tab --> 942 <div class="tab-content" id="processes-tab"> 943 <h3>Running Processes</h3> 944 <table class="events-table"> 945 <thead> 946 <tr> 947 <th>PID</th> 948 <th>Name</th> 949 <th>User</th> 950 <th>CPU %</th> 951 <th>Memory</th> 952 <th>Command Line</th> 953 </tr> 954 </thead> 955 <tbody id="processes-list"> 956 <!-- Processes will be populated here --> 957 </tbody> 958 </table> 959 </div> 960 961 <!-- Network Tab --> 962 <div class="tab-content" id="network-tab"> 963 <h3>Network Connections</h3> 964 <table class="events-table"> 965 <thead> 966 <tr> 967 <th>PID</th> 968 <th>Process</th> 969 <th>Local Address</th> 970 <th>Remote Address</th> 971 <th>Status</th> 972 <th>Timestamp</th> 973 </tr> 974 </thead> 975 <tbody id="network-connections"> 976 <!-- Connections will be populated here --> 977 </tbody> 978 </table> 979 </div> 980 981 <!-- Charts Tab --> 982 <div class="tab-content" id="charts-tab"> 983 <h3>Performance Analytics</h3> 984 <div class="chart-container"> 985 <canvas id="performance-chart"></canvas> 986 </div> 987 </div> 988 </div> 989 </div> 990 991 <!-- Event Details Modal --> 992 <div class="modal" id="event-modal" style="display: none;"> 993 <div class="modal-content" style="max-width: 800px;"> 994 <div class="modal-header"> 995 <h2>Event Details</h2> 996 <button class="close-btn" onclick="closeEventModal()">×</button> 997 </div> 998 <div style="padding: 30px;"> 999 <pre id="event-details-content" style="background: #f5f5f5; padding: 20px; border-radius: 5px; overflow: auto; max-height: 500px;"></pre> 1000 </div> 1001 </div> 1002 </div> 1003 1004 <script> 1005 let currentComputer = null; 1006 let allSysmonEvents = []; 1007 1008 function updateStats() { 1009 // Ажурирај ги вкупните статистики 1010 fetch('/api/stats') 1011 .then(r => r.json()) 1012 .then(data => { 1013 document.getElementById('total-sysmon').textContent = data.total_sysmon_events; 1014 document.getElementById('total-network').textContent = data.total_network_connections; 1015 }); 1016 } 1017 1018 function showComputerDetails(computerName) { 1019 currentComputer = computerName; 1020 1021 // Покажи модал 1022 document.getElementById('computer-modal').style.display = 'block'; 1023 document.getElementById('modal-title').textContent = computerName; 1024 1025 // Вчитувај податоци 1026 loadComputerDetails(computerName); 1027 } 1028 1029 function loadComputerDetails(computerName) { 1030 // Вчитувај целосни податоци за компјутерот 1031 fetch(`/api/computer/${encodeURIComponent(computerName)}`) 1032 .then(response => response.json()) 1033 .then(data => { 1034 populateOverview(data); 1035 populateSysmonEvents(data); 1036 populateProcesses(data); 1037 populateNetwork(data); 1038 updateCharts(data); 1039 }) 1040 .catch(error => { 1041 console.error('Error loading details:', error); 1042 }); 1043 } 1044 1045 function populateOverview(data) { 1046 const overview = document.getElementById('overview-content'); 1047 overview.innerHTML = ` 1048 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;"> 1049 <div> 1050 <h4>System Information</h4> 1051 <p><strong>User:</strong> ${data.computer.user}</p> 1052 <p><strong>IP:</strong> ${data.computer.ip}</p> 1053 <p><strong>OS:</strong> ${data.computer.os}</p> 1054 <p><strong>First Seen:</strong> ${new Date(data.computer.first_seen).toLocaleString()}</p> 1055 <p><strong>Last Seen:</strong> ${new Date(data.computer.last_seen).toLocaleString()}</p> 1056 <p><strong>Total Logs:</strong> ${data.computer.total_logs}</p> 1057 </div> 1058 <div> 1059 <h4>Current Status</h4> 1060 <p><strong>CPU Usage:</strong> ${data.current_metrics?.cpu_usage || 0}%</p> 1061 <p><strong>RAM Usage:</strong> ${data.current_metrics?.ram_usage || 0}%</p> 1062 <p><strong>Disk Usage:</strong> ${data.current_metrics?.disk_usage || 0}%</p> 1063 <p><strong>Network Sent:</strong> ${data.current_metrics?.network_sent_mb || 0} MB</p> 1064 <p><strong>Network Received:</strong> ${data.current_metrics?.network_recv_mb || 0} MB</p> 1065 </div> 1066 </div> 1067 `; 1068 } 1069 1070 function populateSysmonEvents(data) { 1071 const tableBody = document.getElementById('sysmon-events'); 1072 tableBody.innerHTML = ''; 1073 allSysmonEvents = data.sysmon_events || []; 1074 1075 allSysmonEvents.forEach(event => { 1076 const row = tableBody.insertRow(); 1077 const time = new Date(event.timestamp).toLocaleTimeString(); 1078 const severity = getSeverityForEvent(event.event_id); 1079 1080 row.innerHTML = ` 1081 <td>${time}</td> 1082 <td>${event.event_id}</td> 1083 <td>${event.event_type}</td> 1084 <td><span class="severity-${severity}">${severity.toUpperCase()}</span></td> 1085 <td class="event-details">${event.message.substring(0, 100)}...</td> 1086 <td> 1087 <button class="btn" style="padding: 5px 10px; font-size: 0.9em;" 1088 onclick="showEventDetails(${JSON.stringify(event).replace(/"/g, '"')})"> 1089 View Details 1090 </button> 1091 </td> 1092 `; 1093 }); 1094 } 1095 1096 function populateProcesses(data) { 1097 const tableBody = document.getElementById('processes-list'); 1098 tableBody.innerHTML = ''; 1099 1100 (data.recent_processes || []).forEach(proc => { 1101 const row = tableBody.insertRow(); 1102 row.innerHTML = ` 1103 <td>${proc.pid}</td> 1104 <td>${proc.name}</td> 1105 <td>${proc.username}</td> 1106 <td>${proc.cpu_percent || 0}</td> 1107 <td>${proc.memory_mb || 0} MB</td> 1108 <td class="event-details">${proc.cmdline || ''}</td> 1109 `; 1110 }); 1111 } 1112 1113 function populateNetwork(data) { 1114 const tableBody = document.getElementById('network-connections'); 1115 tableBody.innerHTML = ''; 1116 1117 (data.network_connections || []).forEach(conn => { 1118 const row = tableBody.insertRow(); 1119 const time = new Date(conn.timestamp).toLocaleTimeString(); 1120 row.innerHTML = ` 1121 <td>${conn.pid}</td> 1122 <td>${conn.process_name || 'N/A'}</td> 1123 <td>${conn.local_address || 'N/A'}</td> 1124 <td>${conn.remote_address || 'N/A'}</td> 1125 <td>${conn.status}</td> 1126 <td>${time}</td> 1127 `; 1128 }); 1129 } 1130 1131 function updateCharts(data) { 1132 const ctx = document.getElementById('performance-chart').getContext('2d'); 1133 const history = data.history || []; 1134 1135 const labels = history.slice(-20).map(h => 1136 new Date(h.timestamp).toLocaleTimeString() 1137 ); 1138 const cpuData = history.slice(-20).map(h => h.cpu_usage); 1139 const ramData = history.slice(-20).map(h => h.ram_usage); 1140 1141 if(window.performanceChart) { 1142 window.performanceChart.destroy(); 1143 } 1144 1145 window.performanceChart = new Chart(ctx, { 1146 type: 'line', 1147 data: { 1148 labels: labels, 1149 datasets: [ 1150 { 1151 label: 'CPU Usage %', 1152 data: cpuData, 1153 borderColor: 'rgb(255, 99, 132)', 1154 backgroundColor: 'rgba(255, 99, 132, 0.2)', 1155 tension: 0.4 1156 }, 1157 { 1158 label: 'RAM Usage %', 1159 data: ramData, 1160 borderColor: 'rgb(54, 162, 235)', 1161 backgroundColor: 'rgba(54, 162, 235, 0.2)', 1162 tension: 0.4 1163 } 1164 ] 1165 }, 1166 options: { 1167 responsive: true, 1168 maintainAspectRatio: false, 1169 scales: { 1170 y: { 1171 beginAtZero: true, 1172 max: 100 1173 } 1174 } 1175 } 1176 }); 1177 } 1178 1179 function getSeverityForEvent(eventId) { 1180 const highSeverity = [1, 3, 8, 10]; // Process creation, network, remote thread, process access 1181 const mediumSeverity = [5, 6, 7, 11, 22]; // Process termination, driver/image load, file create, DNS 1182 1183 if (highSeverity.includes(parseInt(eventId))) return 'high'; 1184 if (mediumSeverity.includes(parseInt(eventId))) return 'medium'; 1185 return 'low'; 1186 } 1187 1188 function showEventDetails(event) { 1189 document.getElementById('event-details-content').textContent = 1190 JSON.stringify(event, null, 2); 1191 document.getElementById('event-modal').style.display = 'block'; 1192 } 1193 1194 function closeEventModal() { 1195 document.getElementById('event-modal').style.display = 'none'; 1196 } 1197 1198 function filterEvents() { 1199 const searchTerm = document.getElementById('event-search').value.toLowerCase(); 1200 const tableBody = document.getElementById('sysmon-events'); 1201 tableBody.innerHTML = ''; 1202 1203 allSysmonEvents 1204 .filter(event => 1205 event.message.toLowerCase().includes(searchTerm) || 1206 event.event_type.toLowerCase().includes(searchTerm) || 1207 event.event_id.toString().includes(searchTerm) 1208 ) 1209 .forEach(event => { 1210 const row = tableBody.insertRow(); 1211 const time = new Date(event.timestamp).toLocaleTimeString(); 1212 const severity = getSeverityForEvent(event.event_id); 1213 1214 row.innerHTML = ` 1215 <td>${time}</td> 1216 <td>${event.event_id}</td> 1217 <td>${event.event_type}</td> 1218 <td><span class="severity-${severity}">${severity.toUpperCase()}</span></td> 1219 <td class="event-details">${event.message.substring(0, 100)}...</td> 1220 <td> 1221 <button class="btn" style="padding: 5px 10px; font-size: 0.9em;" 1222 onclick="showEventDetails(${JSON.stringify(event).replace(/"/g, '"')})"> 1223 View Details 1224 </button> 1225 </td> 1226 `; 1227 }); 1228 } 1229 1230 function switchTab(tabName) { 1231 // Деактивирај сите tab-ови 1232 document.querySelectorAll('.tab-btn').forEach(btn => { 1233 btn.classList.remove('active'); 1234 }); 1235 document.querySelectorAll('.tab-content').forEach(tab => { 1236 tab.classList.remove('active'); 1237 }); 1238 1239 // Активирај избраниот tab 1240 document.querySelector(`[onclick="switchTab('${tabName}')"]`).classList.add('active'); 1241 document.getElementById(`${tabName}-tab`).classList.add('active'); 1242 } 1243 1244 function closeModal() { 1245 document.getElementById('computer-modal').style.display = 'none'; 1246 currentComputer = null; 1247 allSysmonEvents = []; 1248 } 1249 1250 function exportData() { 1251 if (!currentComputer) return; 1252 1253 fetch(`/api/export/${encodeURIComponent(currentComputer)}`) 1254 .then(response => response.blob()) 1255 .then(blob => { 1256 const url = window.URL.createObjectURL(blob); 1257 const a = document.createElement('a'); 1258 a.href = url; 1259 a.download = `${currentComputer}_export_${new Date().toISOString().slice(0,10)}.json`; 1260 document.body.appendChild(a); 1261 a.click(); 1262 document.body.removeChild(a); 1263 }); 1264 } 1265 1266 // Auto-refresh 1267 setInterval(() => { 1268 updateStats(); 1269 1270 // Ажурирај детали ако модал е отворен 1271 if (currentComputer && document.getElementById('computer-modal').style.display === 'block') { 1272 loadComputerDetails(currentComputer); 1273 } 1274 }, 30000); 1275 1276 // Затвори модал со Escape 1277 document.addEventListener('keydown', (e) => { 1278 if (e.key === 'Escape') { 1279 closeModal(); 1280 closeEventModal(); 1281 } 1282 }); 1283 1284 // Иницијализирај статистики при старт 1285 updateStats(); 1286 </script> 1287 </body> 1288 </html> 1289 ''', computers=computers, socket=socket, datetime=datetime) 1290 1291 1292 @app.route('/api/computer/<computer_name>') 1293 def get_computer_details(computer_name): 1294 """Добиј сите детални информации за еден компјутер""" 1295 conn = sqlite3.connect(DB_FILE) 1296 c = conn.cursor() 1297 1298 # Основни информации 1299 c.execute('''SELECT id, name, user, ip, os, first_seen, last_seen 1300 FROM computers WHERE name = ?''', (computer_name,)) 1301 computer_data = c.fetchone() 1302 1303 if not computer_data: 1304 conn.close() 1305 return jsonify({"error": "Computer not found"}), 404 1306 1307 computer_id = computer_data[0] 1308 1309 # Број на сите записи 1310 c.execute('SELECT COUNT(*) FROM computer_history WHERE computer_id = ?', (computer_id,)) 1311 total_logs = c.fetchone()[0] 1312 1313 # Сите history записи 1314 c.execute('''SELECT cpu_usage, ram_usage, disk_usage, network_sent_mb, network_recv_mb, timestamp 1315 FROM computer_history 1316 WHERE computer_id = ? 1317 ORDER BY timestamp DESC LIMIT 100''', (computer_id,)) 1318 history = [] 1319 for row in c.fetchall(): 1320 history.append({ 1321 'cpu_usage': row[0], 1322 'ram_usage': row[1], 1323 'disk_usage': row[2], 1324 'network_sent_mb': row[3], 1325 'network_recv_mb': row[4], 1326 'timestamp': row[5] 1327 }) 1328 1329 # Последни метрики 1330 current_metrics = history[0] if history else {} 1331 1332 # Последни процеси 1333 c.execute('''SELECT pid, name, username, cpu_percent, memory_mb, cmdline, timestamp 1334 FROM computer_processes 1335 WHERE computer_id = ? 1336 ORDER BY timestamp DESC LIMIT 100''', (computer_id,)) 1337 recent_processes = [] 1338 for row in c.fetchall(): 1339 recent_processes.append({ 1340 'pid': row[0], 1341 'name': row[1], 1342 'username': row[2], 1343 'cpu_percent': row[3], 1344 'memory_mb': row[4], 1345 'cmdline': row[5], 1346 'timestamp': row[6] 1347 }) 1348 1349 # SYSMON НАСТАНИ 1350 c.execute('''SELECT event_id, event_type, message, timestamp, details 1351 FROM sysmon_events 1352 WHERE computer_id = ? 1353 ORDER BY timestamp DESC LIMIT 200''', (computer_id,)) 1354 sysmon_events = [] 1355 for row in c.fetchall(): 1356 try: 1357 details = json.loads(row[4]) if row[4] else {} 1358 except: 1359 details = {} 1360 1361 sysmon_events.append({ 1362 'event_id': row[0], 1363 'event_type': row[1], 1364 'message': row[2], 1365 'timestamp': row[3], 1366 'details': details 1367 }) 1368 1369 # Мрежни конекции 1370 c.execute('''SELECT pid, process_name, local_address, remote_address, status, timestamp 1371 FROM network_connections 1372 WHERE computer_id = ? 1373 ORDER BY timestamp DESC LIMIT 100''', (computer_id,)) 1374 network_connections = [] 1375 for row in c.fetchall(): 1376 network_connections.append({ 1377 'pid': row[0], 1378 'process_name': row[1], 1379 'local_address': row[2], 1380 'remote_address': row[3], 1381 'status': row[4], 1382 'timestamp': row[5] 1383 }) 1384 1385 conn.close() 1386 1387 return jsonify({ 1388 'computer': { 1389 'id': computer_data[0], 1390 'name': computer_data[1], 1391 'user': computer_data[2], 1392 'ip': computer_data[3], 1393 'os': computer_data[4], 1394 'first_seen': computer_data[5], 1395 'last_seen': computer_data[6], 1396 'total_logs': total_logs 1397 }, 1398 'history': history, 1399 'current_metrics': current_metrics, 1400 'recent_processes': recent_processes, 1401 'sysmon_events': sysmon_events, 1402 'network_connections': network_connections 1403 }) 1404 1405 @app.route('/api/computers') 1406 def get_computers(): 1407 conn = sqlite3.connect(DB_FILE) 1408 c = conn.cursor() 1409 env = request.headers.get("X-Env", "default") 1410 1411 c.execute('''SELECT name, user, ip, os, first_seen, last_seen, sysmon_available 1412 FROM computers 1413 WHERE env_name = ? 1414 ORDER BY last_seen DESC''', (env,)) 1415 1416 computers = [] 1417 for row in c.fetchall(): 1418 computer_name = row[0] 1419 1420 # recent logs 1421 c.execute('''SELECT COUNT(*) FROM computer_history 1422 WHERE computer_id = (SELECT id FROM computers WHERE name = ?) 1423 AND timestamp > datetime('now', '-24 hours')''', 1424 (computer_name,)) 1425 recent_logs = c.fetchone()[0] 1426 1427 # last 5 metrics 1428 c.execute('''SELECT cpu_usage, ram_usage, timestamp 1429 FROM computer_history 1430 WHERE computer_id = (SELECT id FROM computers WHERE name = ?) 1431 ORDER BY timestamp DESC LIMIT 5''', 1432 (computer_name,)) 1433 recent_metrics = c.fetchall() 1434 1435 c.execute('''SELECT COUNT(*) FROM sysmon_events 1436 WHERE computer_id = (SELECT id FROM computers WHERE name = ?) 1437 AND timestamp > datetime('now', '-24 hours')''', 1438 (computer_name,)) 1439 recent_sysmon = c.fetchone()[0] 1440 1441 avg_cpu = sum(m[0] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0 1442 avg_ram = sum(m[1] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0 1443 1444 last_seen = datetime.fromisoformat(row[5]) 1445 time_diff = (datetime.now() - last_seen).seconds 1446 1447 if time_diff < 60: 1448 status = 'online' 1449 status_color = 'green' 1450 elif time_diff < 300: 1451 status = 'idle' 1452 status_color = 'orange' 1453 else: 1454 status = 'offline' 1455 status_color = 'gray' 1456 1457 computers.append({ 1458 'name': row[0], 1459 'user': row[1], 1460 'ip': row[2], 1461 'os': row[3], 1462 'first_seen': row[4], 1463 'last_seen': row[5], 1464 'sysmon_available': bool(row[6]), 1465 'recent_logs': recent_logs, 1466 'recent_sysmon': recent_sysmon, 1467 'avg_cpu': round(avg_cpu, 1), 1468 'avg_ram': round(avg_ram, 1), 1469 'status': status, 1470 'status_color': status_color 1471 }) 1472 1473 conn.close() 1474 return jsonify(computers) 1475 1476 @app.route('/api/stats') 1477 def get_stats(): 1478 """Добиј глобални статистики""" 1479 conn = sqlite3.connect(DB_FILE) 1480 c = conn.cursor() 1481 1482 # Вкупно Sysmon настани за последните 24 часа 1483 c.execute('''SELECT COUNT(*) FROM sysmon_events 1484 WHERE timestamp > datetime('now', '-24 hours')''') 1485 total_sysmon_events = c.fetchone()[0] 1486 1487 # Вкупно мрежни конекции за последните 24 часа 1488 c.execute('''SELECT COUNT(*) FROM network_connections 1489 WHERE timestamp > datetime('now', '-24 hours')''') 1490 total_network_connections = c.fetchone()[0] 1491 1492 conn.close() 1493 1494 return jsonify({ 1495 'total_sysmon_events': total_sysmon_events, 1496 'total_network_connections': total_network_connections, 1497 'server_time': datetime.now().isoformat() 1498 }) 1499 1500 def build_rag_context(question, computer_name=None, limit_per_table=10): 1501 try: 1502 conn = sqlite3.connect(DB_FILE) 1503 c = conn.cursor() 1504 1505 params = [] 1506 comp_filter = "" 1507 if computer_name: 1508 comp_filter = "AND computer_id = (SELECT id FROM computers WHERE name = ?)" 1509 params.append(computer_name) 1510 1511 # Sysmon настани 1512 c.execute( 1513 f""" 1514 SELECT timestamp, event_id, event_type, message 1515 FROM sysmon_events 1516 WHERE 1=1 {comp_filter} 1517 ORDER BY timestamp DESC 1518 LIMIT ? 1519 """, 1520 params + [limit_per_table], 1521 ) 1522 sysmon_rows = c.fetchall() 1523 1524 # Перформанс историја 1525 c.execute( 1526 f""" 1527 SELECT timestamp, cpu_usage, ram_usage, disk_usage, 1528 network_sent_mb, network_recv_mb 1529 FROM computer_history 1530 WHERE 1=1 {comp_filter} 1531 ORDER BY timestamp DESC 1532 LIMIT ? 1533 """, 1534 params + [limit_per_table], 1535 ) 1536 perf_rows = c.fetchall() 1537 1538 # Процеси 1539 c.execute( 1540 f""" 1541 SELECT timestamp, pid, name, cpu_percent, memory_mb 1542 FROM computer_processes 1543 WHERE 1=1 {comp_filter} 1544 ORDER BY timestamp DESC 1545 LIMIT ? 1546 """, 1547 params + [limit_per_table], 1548 ) 1549 proc_rows = c.fetchall() 1550 1551 conn.close() 1552 1553 print("[RAG] sysmon:", len(sysmon_rows), 1554 "| perf:", len(perf_rows), 1555 "| proc:", len(proc_rows)) 1556 1557 lines = [] 1558 1559 for ts, eid, etype, msg in sysmon_rows: 1560 lines.append(f"[SYSMON] {ts} | Event {eid} ({etype}): {msg}") 1561 1562 for ts, cpu, ram, disk, nsent, nrecv in perf_rows: 1563 lines.append( 1564 f"[PERF] {ts} | CPU {cpu}% | RAM {ram}% | Disk {disk}% | " 1565 f"Net sent {nsent}MB / recv {nrecv}MB" 1566 ) 1567 1568 for ts, pid, name, cpu, mem in proc_rows: 1569 lines.append(f"[PROC] {ts} | PID {pid} | {name} | CPU {cpu}% | RAM {mem}MB") 1570 1571 context = "\n".join(lines) 1572 print("[RAG] sample context:\n", "\n".join(lines[:5])) 1573 return context 1574 1575 except Exception as e: 1576 traceback.print_exc() 1577 return "" 1578 1579 # def ask_llm_with_context(question, context, language="mk"): 1580 # if not client: 1581 # return "Немаш поставено OPENAI_API_KEY на серверот." 1582 # 1583 # system_msg = ( 1584 # "Ти си асистент за безбедност и систем мониторинг. " 1585 # "Одговараш базирано ИСКЛУЧИВО на дадените логови што ти се дадени. " 1586 # "Ако нема доволно информации, кажи дека нема доволно податоци. " 1587 # "Објаснувај кратко и јасно." 1588 # ) 1589 # 1590 # user_msg = f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}" 1591 # 1592 # try: 1593 # completion = client.chat.completions.create( 1594 # model="gpt-4.1-mini", 1595 # messages=[ 1596 # {"role": "system", "content": system_msg}, 1597 # {"role": "user", "content": user_msg}, 1598 # ], 1599 # temperature=0.2, 1600 # ) 1601 # return completion.choices[0].message.content 1602 # except Exception as e: 1603 # # Може попрецизно: openai.APITimeoutError, openai.APIConnectionError итн. 1604 # return f"Грешка при повик кон LLM: {e}" 1605 1606 def ask_llm_with_context(question, context, language="mk"): 1607 """ 1608 Вика OpenAI модел и враќа одговор базиран на контекстот од логови. 1609 """ 1610 if not client: 1611 return "Немаш поставено OPENAI_API_KEY на серверот." 1612 1613 system_msg = ( 1614 "Ти си асистент за безбедност и систем мониторинг. " 1615 "Одговараш базирано ИСКЛУЧИВО на дадените логови што ти се дадени. " 1616 "Типови редови во логовите:\n" 1617 "- [SYSMON] ... Sysmon безбедносни настани.\n" 1618 "- [PERF] ... Општи перформанси (CPU, RAM, диск, мрежа).\n" 1619 "- [PROC] ... Информации за поединечни процеси: PID, име, CPU%, RAM MB.\n" 1620 "Кога корисникот прашува за процеси и нивна потрошувачка, " 1621 "СЕКОГАШ користи ги [PROC] редовите. " 1622 "Никогаш не кажувај дека 'нема информации за процеси' ако постои барем еден [PROC] ред. " 1623 "Ако навистина НЕМА [PROC] редови, тогаш можеш да кажеш дека нема доволно податоци. " 1624 "Објаснувај кратко и јасно." 1625 ) 1626 1627 user_msg = f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}" 1628 1629 try: 1630 print("[LLM] Calling OpenAI…") 1631 completion = client.chat.completions.create( 1632 model="gpt-4.1-mini", 1633 messages=[ 1634 {"role": "system", "content": system_msg}, 1635 {"role": "user", "content": user_msg}, 1636 ], 1637 temperature=0.2, 1638 ) 1639 print("[LLM] Got response") 1640 return completion.choices[0].message.content 1641 1642 except Exception as e: 1643 print("[LLM] ERROR:", e) 1644 return f"Грешка при повик кон LLM: {e}" 1645 1646 1647 1648 # 1649 # def ask_llm_with_context(question, context, language="mk"): 1650 # payload = { 1651 # "model": "deepseek-r1:1.5b", 1652 # "prompt": f"Прашање: {question}\n\nКонтекст:\n{context}", 1653 # "stream": False 1654 # } 1655 # 1656 # try: 1657 # r = requests.post("http://localhost:11434/api/generate", json=payload) 1658 # r.raise_for_status() 1659 # data = r.json() 1660 # return data.get("response", "") 1661 # except Exception as e: 1662 # return f"Грешка при повик кон Ollama: {e}" 1663 1664 1665 @app.route('/api/export/<computer_name>') 1666 def export_computer_data(computer_name): 1667 """Експортирај ги сите податоци за еден компјутер""" 1668 data = {} 1669 1670 conn = sqlite3.connect(DB_FILE) 1671 c = conn.cursor() 1672 1673 # Основни информации за компјутерот 1674 c.execute('''SELECT * FROM computers WHERE name = ?''', (computer_name,)) 1675 columns = [description[0] for description in c.description] 1676 computer_data = c.fetchone() 1677 1678 if computer_data: 1679 data['computer'] = dict(zip(columns, computer_data)) 1680 1681 # Историја 1682 computer_id = computer_data[0] 1683 c.execute('''SELECT * FROM computer_history WHERE computer_id = ? ORDER BY timestamp''', (computer_id,)) 1684 data['history'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()] 1685 1686 # Sysmon настани 1687 c.execute('''SELECT * FROM sysmon_events WHERE computer_id = ? ORDER BY timestamp''', (computer_id,)) 1688 data['sysmon_events'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()] 1689 1690 # Процеси 1691 c.execute('''SELECT * FROM computer_processes WHERE computer_id = ? ORDER BY timestamp''', (computer_id,)) 1692 data['processes'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()] 1693 1694 # Мрежни конекции 1695 c.execute('''SELECT * FROM network_connections WHERE computer_id = ? ORDER BY timestamp''', (computer_id,)) 1696 data['network_connections'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()] 1697 1698 conn.close() 1699 1700 # Креирај JSON одговор 1701 from flask import Response 1702 response = Response( 1703 json.dumps(data, indent=2, default=str), 1704 mimetype='application/json', 1705 headers={'Content-Disposition': f'attachment; filename={computer_name}_export.json'} 1706 ) 1707 1708 return response 1709 1710 1711 @app.route('/ping') 1712 def ping(): 1713 return jsonify({ 1714 'status': 'alive', 1715 'server_time': datetime.now().isoformat(), 1716 'version': '2.0_sysmon', 1717 'database': DB_FILE 1718 }) 1719 1720 1721 def get_local_ip(): 1722 try: 1723 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 1724 s.connect(("8.8.8.8", 80)) 1725 local_ip = s.getsockname()[0] 1726 s.close() 1727 return local_ip 1728 except: 1729 return "127.0.0.1" 1730 1731 1732 def cleanup_old_data(): 1733 """Чисти стари податоци од базата (стари од 30 дена)""" 1734 while True: 1735 time.sleep(3600) # Чисти на секој час 1736 try: 1737 conn = sqlite3.connect(DB_FILE) 1738 c = conn.cursor() 1739 1740 cutoff_date = (datetime.now() - timedelta(days=30)).isoformat() 1741 1742 # Избриши стари записи 1743 c.execute("DELETE FROM computer_history WHERE timestamp < ?", (cutoff_date,)) 1744 c.execute("DELETE FROM computer_processes WHERE timestamp < ?", (cutoff_date,)) 1745 c.execute("DELETE FROM sysmon_events WHERE timestamp < ?", (cutoff_date,)) 1746 c.execute("DELETE FROM network_connections WHERE timestamp < ?", (cutoff_date,)) 1747 1748 conn.commit() 1749 conn.close() 1750 1751 print(f"[Cleanup] Избришани стари записи пред {cutoff_date}") 1752 1753 except Exception as e: 1754 print(f"[Cleanup] Грешка: {e}") 1755 1756 import time 1757 @app.route("/api/chat", methods=["POST"]) 1758 def api_chat(): 1759 try: 1760 t0 = time.time() 1761 data = request.get_json(force=True) or {} 1762 question = data.get("question", "").strip() 1763 computer_name = data.get("computer_name") or None 1764 1765 if not question: 1766 return jsonify({"error": "No question provided"}), 400 1767 1768 context = build_rag_context(question, computer_name=computer_name) 1769 t1 = time.time() 1770 1771 answer = ask_llm_with_context(question, context) 1772 t2 = time.time() 1773 1774 print(f"[CHAT] build_rag_context: {t1 - t0:.2f}s, LLM: {t2 - t1:.2f}s") 1775 1776 return jsonify({"answer": answer}) 1777 except Exception as e: 1778 traceback.print_exc() 1779 return jsonify({"error": str(e)}), 500 1780 1781 1782 @app.route('/api/computer/<computer_name>/sysmon') 1783 def get_sysmon_processes(computer_name): 1784 """Добиј Sysmon процеси за компјутер""" 1785 conn = sqlite3.connect(DB_FILE) 1786 c = conn.cursor() 1787 1788 # Прво најди го computer_id 1789 c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,)) 1790 result = c.fetchone() 1791 1792 if not result: 1793 conn.close() 1794 return jsonify({"error": "Computer not found"}), 404 1795 1796 computer_id = result[0] 1797 1798 # Земaj ги Sysmon процесите (последни 100) 1799 c.execute('''SELECT 1800 event_id, 1801 event_type, 1802 message, 1803 timestamp, 1804 details 1805 FROM sysmon_events 1806 WHERE computer_id = ? 1807 ORDER BY timestamp DESC LIMIT 100''', (computer_id,)) 1808 1809 processes = [] 1810 for row in c.fetchall(): 1811 try: 1812 details = json.loads(row[4]) if row[4] else {} 1813 except: 1814 details = {} 1815 1816 processes.append({ 1817 'process_id': row[0], # Event ID како PID 1818 'process_name': row[1], # Event type како име 1819 'timestamp': row[3], 1820 'message': row[2], 1821 'details': details 1822 }) 1823 1824 conn.close() 1825 1826 return jsonify({ 1827 'computer_name': computer_name, 1828 'processes': processes, 1829 'count': len(processes) 1830 }) 1831 1832 1833 @app.route('/api/computer/<computer_name>/network') 1834 def get_network_connections(computer_name): 1835 """Добиј мрежни конекции за компјутер""" 1836 conn = sqlite3.connect(DB_FILE) 1837 c = conn.cursor() 1838 1839 # Прво најди го computer_id 1840 c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,)) 1841 result = c.fetchone() 1842 1843 if not result: 1844 conn.close() 1845 return jsonify({"error": "Computer not found"}), 404 1846 1847 computer_id = result[0] 1848 1849 # Земaj ги мрежните конекции (последни 100) 1850 c.execute('''SELECT 1851 pid, 1852 process_name, 1853 local_address, 1854 remote_address, 1855 status, 1856 timestamp 1857 FROM network_connections 1858 WHERE computer_id = ? 1859 ORDER BY timestamp DESC LIMIT 100''', (computer_id,)) 1860 1861 connections = [] 1862 for row in c.fetchall(): 1863 # Парсирај IP и порта 1864 local = row[2] or "N/A" 1865 remote = row[3] or "N/A" 1866 1867 connections.append({ 1868 'pid': row[0], 1869 'process_name': row[1] or 'Unknown', 1870 'local_address': local, 1871 'remote_address': remote, 1872 'local_port': local.split(':')[-1] if ':' in local else '', 1873 'remote_port': remote.split(':')[-1] if ':' in remote else '', 1874 'protocol': 'TCP' if ':443' in local or ':80' in local else 'Unknown', 1875 'state': row[4] or 'ESTABLISHED', 1876 'timestamp': row[5] 1877 }) 1878 1879 conn.close() 1880 1881 return jsonify({ 1882 'computer_name': computer_name, 1883 'connections': connections, 1884 'count': len(connections) 1885 }) 1886 1887 1888 @app.route('/api/computer/<computer_name>/detailed-sysmon') 1889 def get_detailed_sysmon(computer_name): 1890 """Добиј детални Sysmon процеси со повеќе информации""" 1891 conn = sqlite3.connect(DB_FILE) 1892 c = conn.cursor() 1893 1894 c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,)) 1895 result = c.fetchone() 1896 1897 if not result: 1898 conn.close() 1899 return jsonify({"error": "Computer not found"}), 404 1900 1901 computer_id = result[0] 1902 1903 c.execute('''SELECT event_id, event_type, message, timestamp, details 1904 FROM sysmon_events 1905 WHERE computer_id = ? 1906 ORDER BY timestamp DESC LIMIT 50''', (computer_id,)) 1907 1908 detailed_processes = [] 1909 1910 for row in c.fetchall(): 1911 event_id = row[0] 1912 event_type = row[1] 1913 message = row[2] 1914 timestamp = row[3] 1915 details_str = row[4] 1916 1917 # Пробај да го парсираш details JSON 1918 try: 1919 details = json.loads(details_str) if details_str else {} 1920 except: 1921 details = {} 1922 1923 # Екстрактирај информации од details 1924 process_info = { 1925 'event_id': event_id, 1926 'event_type': event_type, 1927 'timestamp': timestamp, 1928 'message': message, 1929 'process_name': details.get('ProcessName', 'Unknown'), 1930 'process_id': details.get('ProcessId', event_id), 1931 'command_line': details.get('CommandLine', ''), 1932 'parent_process': details.get('ParentProcessName', ''), 1933 'parent_pid': details.get('ParentProcessId', ''), 1934 'user': details.get('User', ''), 1935 'details': details 1936 } 1937 1938 # Додади дополнителни полиња за различни типови на настани 1939 if event_id in [3, 22]: # Network connection or DNS 1940 process_info['source_ip'] = details.get('SourceIp', '') 1941 process_info['dest_ip'] = details.get('DestinationIp', '') 1942 process_info['dest_port'] = details.get('DestinationPort', '') 1943 1944 detailed_processes.append(process_info) 1945 1946 conn.close() 1947 1948 return jsonify({ 1949 'computer_name': computer_name, 1950 'processes': detailed_processes, 1951 'count': len(detailed_processes) 1952 }) 1953 @app.route("/api/admin/login", methods=["POST"]) 1954 def admin_login(): 1955 data = request.get_json(force=True) or {} 1956 key = (data.get("admin_key") or "").strip() 1957 if not ADMIN_KEY or key != ADMIN_KEY: 1958 return jsonify({"error": "Unauthorized"}), 401 1959 1960 session_token = secrets.token_urlsafe(32) 1961 conn = sqlite3.connect(DB_FILE) 1962 c = conn.cursor() 1963 c.execute("INSERT INTO admin_sessions(token, created_at) VALUES(?, datetime('now'))", (session_token,)) 1964 conn.commit() 1965 conn.close() 1966 1967 return jsonify({"ok": True, "session": session_token}) 1968 1969 1970 @app.route("/api/admin/environments", methods=["GET"]) 1971 def admin_list_envs(): 1972 if not require_admin_session(): 1973 return jsonify({"error": "Unauthorized"}), 401 1974 1975 conn = sqlite3.connect(DB_FILE) 1976 c = conn.cursor() 1977 c.execute("SELECT name FROM environments ORDER BY name") 1978 envs = [r[0] for r in c.fetchall()] 1979 conn.close() 1980 516 if not envs: 517 envs = ["default"] 1981 518 return jsonify({"environments": envs}) 1982 519 1983 520 1984 521 @app.route("/api/admin/environments", methods=["POST"]) 522 @require_tenant_admin() 1985 523 def admin_create_env(): 1986 if not require_admin_session(): 1987 return jsonify({"error": "Unauthorized"}), 401 1988 1989 data = request.get_json(force=True) or {} 524 tenant_id = request.user["tenant_id"] 525 data = request.get_json(force=True, silent=True) or {} 1990 526 name = (data.get("name") or "").strip() 1991 527 if not name: 1992 528 return jsonify({"error": "Missing env name"}), 400 1993 529 1994 conn = sqlite3.connect(DB_FILE)530 conn = db() 1995 531 c = conn.cursor() 1996 532 try: 1997 c.execute("INSERT INTO environments(name, created_at) VALUES(?, datetime('now'))", (name,)) 533 c.execute( 534 "INSERT INTO environments(tenant_id, name, created_at) VALUES(?, ?, datetime('now'))", 535 (tenant_id, name), 536 ) 1998 537 conn.commit() 1999 538 except Exception as e: … … 2001 540 return jsonify({"error": str(e)}), 400 2002 541 conn.close() 2003 2004 542 return jsonify({"ok": True, "name": name}) 2005 543 2006 544 2007 545 @app.route("/api/admin/tokens", methods=["POST"]) 546 @require_tenant_admin() 2008 547 def admin_generate_token(): 2009 if not require_admin_session(): 2010 return jsonify({"error": "Unauthorized"}), 401 2011 2012 data = request.get_json(force=True) or {} 548 tenant_id = request.user["tenant_id"] 549 data = request.get_json(force=True, silent=True) or {} 2013 550 env = (data.get("env") or "").strip() 2014 551 if not env: 2015 552 return jsonify({"error": "Missing env"}), 400 2016 553 2017 conn = sqlite3.connect(DB_FILE)554 conn = db() 2018 555 c = conn.cursor() 2019 2020 c.execute("SELECT 1 FROM environments WHERE name = ?", (env,)) 556 c.execute("SELECT 1 FROM environments WHERE tenant_id=? AND name=? LIMIT 1", (tenant_id, env)) 2021 557 if not c.fetchone(): 2022 558 conn.close() … … 2025 561 token = secrets.token_urlsafe(32) 2026 562 c.execute(""" 2027 INSERT INTO env_tokens(env_name, token, created_at, expires_at) 2028 VALUES(?, ?, datetime('now'), datetime('now', '+15 minutes')) 2029 """, (env, token)) 2030 563 INSERT INTO env_tokens(tenant_id, env_name, token, created_at, expires_at) 564 VALUES(?, ?, ?, datetime('now'), datetime('now', '+90 days')) 565 """, (tenant_id, env, token)) 2031 566 conn.commit() 2032 567 conn.close() … … 2034 569 return jsonify({"ok": True, "env": env, "token": token}) 2035 570 2036 if __name__ == '__main__': 2037 init_database() 2038 init_admin_tables() 2039 2040 2041 # Стартувај cleanup thread 571 572 # ---------------------------- 573 # Agent endpoint 574 # ---------------------------- 575 @app.route("/receive", methods=["POST"]) 576 def receive_data(): 577 try: 578 data = request.get_json(force=True, silent=True) or {} 579 if not data: 580 return jsonify({"error": "No data"}), 400 581 582 env_name, tenant_id = get_env_from_token(request) 583 if not env_name or not tenant_id: 584 return jsonify({"error": "Missing or invalid X-Env-Token"}), 401 585 586 info = data.get("info") or {} 587 computer_name = info.get("computer_name") 588 if not computer_name: 589 return jsonify({"error": "Missing info.computer_name"}), 400 590 591 now_iso = datetime.now().isoformat() 592 security_data = data.get("security_data") or {} 593 594 conn = db() 595 c = conn.cursor() 596 597 # Find by (tenant_id + name) 598 c.execute("SELECT id FROM computers WHERE tenant_id=? AND name=? LIMIT 1", (tenant_id, computer_name)) 599 row = c.fetchone() 600 601 if row: 602 computer_id = row["id"] 603 c.execute(""" 604 UPDATE computers 605 SET user=?, ip=?, os=?, last_seen=?, sysmon_available=?, env_name=? 606 WHERE id=? AND tenant_id=? 607 """, ( 608 info.get("user"), 609 info.get("ip_address"), 610 info.get("os"), 611 now_iso, 612 int(info.get("is_sysmon_available", 0) or 0), 613 env_name, 614 computer_id, 615 tenant_id, 616 )) 617 else: 618 c.execute(""" 619 INSERT INTO computers(tenant_id, env_name, name, user, ip, os, first_seen, last_seen, sysmon_available) 620 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) 621 """, ( 622 tenant_id, 623 env_name, 624 computer_name, 625 info.get("user"), 626 info.get("ip_address"), 627 info.get("os"), 628 now_iso, 629 now_iso, 630 int(info.get("is_sysmon_available", 0) or 0), 631 )) 632 computer_id = c.lastrowid 633 634 # history row 635 c.execute(""" 636 INSERT INTO computer_history(computer_id, cpu_usage, ram_usage, disk_usage, 637 network_sent_mb, network_recv_mb, timestamp) 638 VALUES(?, ?, ?, ?, ?, ?, ?) 639 """, ( 640 computer_id, 641 float(info.get("cpu_usage") or 0), 642 float(info.get("ram_usage") or 0), 643 float(info.get("disk_usage") or 0), 644 float(info.get("network_sent_mb") or 0), 645 float(info.get("network_recv_mb") or 0), 646 info.get("timestamp") or now_iso, 647 )) 648 649 # processes 650 for proc in (data.get("processes") or []): 651 c.execute(""" 652 INSERT INTO computer_processes(computer_id, pid, name, cpu_percent, memory_mb, username, cmdline, timestamp) 653 VALUES(?, ?, ?, ?, ?, ?, ?, ?) 654 """, ( 655 computer_id, 656 proc.get("pid"), 657 proc.get("name"), 658 float(proc.get("cpu_percent") or 0), 659 float(proc.get("memory_mb") or 0), 660 proc.get("user") or proc.get("username"), 661 proc.get("cmdline"), 662 info.get("timestamp") or now_iso, 663 )) 664 665 # sysmon events 666 sysmon_count = 0 667 for ev in (security_data.get("sysmon_events") or []): 668 c.execute(""" 669 INSERT INTO sysmon_events(computer_id, event_id, event_type, message, timestamp, details) 670 VALUES(?, ?, ?, ?, ?, ?) 671 """, ( 672 computer_id, 673 ev.get("event_id"), 674 ev.get("event_type", "Unknown"), 675 ev.get("message", ""), 676 ev.get("timestamp") or (info.get("timestamp") or now_iso), 677 json.dumps(ev, ensure_ascii=False), 678 )) 679 sysmon_count += 1 680 681 # network connections 682 for nc in (security_data.get("network_connections") or []): 683 c.execute(""" 684 INSERT INTO network_connections(computer_id, pid, local_address, remote_address, status, process_name, timestamp) 685 VALUES(?, ?, ?, ?, ?, ?, ?) 686 """, ( 687 computer_id, 688 nc.get("pid"), 689 nc.get("local_address"), 690 nc.get("remote_address"), 691 nc.get("status"), 692 nc.get("process_name"), 693 info.get("timestamp") or now_iso, 694 )) 695 696 conn.commit() 697 conn.close() 698 699 # optional JSON archive 700 try: 701 save_json_file(data) 702 except Exception: 703 pass 704 705 return jsonify({ 706 "ok": True, 707 "computer_id": computer_id, 708 "env": env_name, 709 "tenant_id": tenant_id, 710 "sysmon_events_saved": sysmon_count, 711 "server_time": now_iso, 712 }) 713 714 except Exception as e: 715 traceback.print_exc() 716 return jsonify({"error": str(e)}), 500 717 718 719 # ---------------------------- 720 # Dashboard API (tenant scoped) 721 # ---------------------------- 722 @app.route("/api/computers", methods=["GET"]) 723 @require_user() 724 def api_computers(): 725 tenant_id = request.user["tenant_id"] 726 env = request.headers.get("X-Env", "default") 727 728 conn = db() 729 c = conn.cursor() 730 c.execute(""" 731 SELECT id, name, user, ip, os, first_seen, last_seen, sysmon_available 732 FROM computers 733 WHERE tenant_id=? AND env_name=? 734 ORDER BY last_seen DESC 735 """, (tenant_id, env)) 736 rows = c.fetchall() 737 738 computers = [] 739 for r in rows: 740 cid = r["id"] 741 742 c.execute(""" 743 SELECT COUNT(*) AS n 744 FROM computer_history 745 WHERE computer_id=? AND timestamp > datetime('now','-24 hours') 746 """, (cid,)) 747 recent_logs = int(c.fetchone()["n"] or 0) 748 749 c.execute(""" 750 SELECT COUNT(*) AS n 751 FROM sysmon_events 752 WHERE computer_id=? AND timestamp > datetime('now','-24 hours') 753 """, (cid,)) 754 recent_sysmon = int(c.fetchone()["n"] or 0) 755 756 c.execute(""" 757 SELECT cpu_usage, ram_usage, timestamp 758 FROM computer_history 759 WHERE computer_id=? 760 ORDER BY timestamp DESC 761 LIMIT 5 762 """, (cid,)) 763 metrics = c.fetchall() 764 avg_cpu = round(sum(m["cpu_usage"] for m in metrics) / len(metrics), 1) if metrics else 0.0 765 avg_ram = round(sum(m["ram_usage"] for m in metrics) / len(metrics), 1) if metrics else 0.0 766 767 status = "offline" 768 status_color = "gray" 769 try: 770 last_seen = r["last_seen"] 771 if last_seen: 772 dt = datetime.fromisoformat( 773 last_seen.replace("Z", "+00:00")) if "T" in last_seen else datetime.fromisoformat(last_seen) 774 diff = (datetime.now() - dt).total_seconds() 775 if diff < 60: 776 status, status_color = "online", "green" 777 elif diff < 300: 778 status, status_color = "idle", "orange" 779 except Exception: 780 pass 781 782 computers.append({ 783 "name": r["name"], 784 "user": r["user"], 785 "ip": r["ip"], 786 "os": r["os"], 787 "first_seen": r["first_seen"], 788 "last_seen": r["last_seen"], 789 "sysmon_available": bool(r["sysmon_available"]), 790 "recent_logs": recent_logs, 791 "recent_sysmon": recent_sysmon, 792 "avg_cpu": avg_cpu, 793 "avg_ram": avg_ram, 794 "status": status, 795 "status_color": status_color, 796 }) 797 798 conn.close() 799 return jsonify(computers) 800 801 802 @app.route("/api/stats", methods=["GET"]) 803 @require_user() 804 def api_stats(): 805 tenant_id = request.user["tenant_id"] 806 807 conn = db() 808 c = conn.cursor() 809 810 c.execute(""" 811 SELECT COUNT(*) AS n 812 FROM sysmon_events s 813 JOIN computers c2 ON c2.id = s.computer_id 814 WHERE s.timestamp > datetime('now','-24 hours') 815 AND c2.tenant_id = ? 816 """, (tenant_id,)) 817 total_sysmon = int(c.fetchone()["n"] or 0) 818 819 c.execute(""" 820 SELECT COUNT(*) AS n 821 FROM network_connections n 822 JOIN computers c2 ON c2.id = n.computer_id 823 WHERE n.timestamp > datetime('now','-24 hours') 824 AND c2.tenant_id = ? 825 """, (tenant_id,)) 826 total_net = int(c.fetchone()["n"] or 0) 827 828 conn.close() 829 return jsonify({ 830 "total_sysmon_events": total_sysmon, 831 "total_network_connections": total_net, 832 "server_time": datetime.now().isoformat(), 833 }) 834 835 836 @app.route("/api/computer/<computer_name>", methods=["GET"]) 837 @require_user() 838 def api_computer_details(computer_name): 839 tenant_id = request.user["tenant_id"] 840 841 conn = db() 842 c = conn.cursor() 843 c.execute(""" 844 SELECT id, name, user, ip, os, first_seen, last_seen, env_name 845 FROM computers 846 WHERE tenant_id=? AND name=? 847 LIMIT 1 848 """, (tenant_id, computer_name)) 849 comp = c.fetchone() 850 if not comp: 851 conn.close() 852 return jsonify({"error": "Computer not found"}), 404 853 854 computer_id = comp["id"] 855 856 c.execute("SELECT COUNT(*) AS n FROM computer_history WHERE computer_id=?", (computer_id,)) 857 total_logs = int(c.fetchone()["n"] or 0) 858 859 c.execute(""" 860 SELECT cpu_usage, ram_usage, disk_usage, network_sent_mb, network_recv_mb, timestamp 861 FROM computer_history 862 WHERE computer_id=? 863 ORDER BY timestamp DESC 864 LIMIT 100 865 """, (computer_id,)) 866 history = [dict(r) for r in c.fetchall()] 867 current_metrics = history[0] if history else {} 868 869 c.execute(""" 870 SELECT pid, name, username, cpu_percent, memory_mb, cmdline, timestamp 871 FROM computer_processes 872 WHERE computer_id=? 873 ORDER BY timestamp DESC 874 LIMIT 100 875 """, (computer_id,)) 876 processes = [dict(r) for r in c.fetchall()] 877 878 c.execute(""" 879 SELECT event_id, event_type, message, timestamp, details 880 FROM sysmon_events 881 WHERE computer_id=? 882 ORDER BY timestamp DESC 883 LIMIT 200 884 """, (computer_id,)) 885 sysmon = [] 886 for r in c.fetchall(): 887 d = {} 888 try: 889 d = json.loads(r["details"]) if r["details"] else {} 890 except Exception: 891 d = {} 892 sysmon.append({ 893 "event_id": r["event_id"], 894 "event_type": r["event_type"], 895 "message": r["message"], 896 "timestamp": r["timestamp"], 897 "details": d, 898 }) 899 900 c.execute(""" 901 SELECT pid, process_name, local_address, remote_address, status, timestamp 902 FROM network_connections 903 WHERE computer_id=? 904 ORDER BY timestamp DESC 905 LIMIT 100 906 """, (computer_id,)) 907 net = [dict(r) for r in c.fetchall()] 908 909 conn.close() 910 911 return jsonify({ 912 "computer": { 913 "id": comp["id"], 914 "name": comp["name"], 915 "user": comp["user"], 916 "ip": comp["ip"], 917 "os": comp["os"], 918 "first_seen": comp["first_seen"], 919 "last_seen": comp["last_seen"], 920 "env_name": comp["env_name"], 921 "total_logs": total_logs, 922 }, 923 "history": history, 924 "current_metrics": current_metrics, 925 "recent_processes": processes, 926 "sysmon_events": sysmon, 927 "network_connections": net, 928 }) 929 930 931 @app.route("/api/export/<computer_name>", methods=["GET"]) 932 @require_user() 933 def api_export(computer_name): 934 tenant_id = request.user["tenant_id"] 935 936 conn = db() 937 c = conn.cursor() 938 c.execute(""" 939 SELECT * 940 FROM computers 941 WHERE tenant_id=? AND name=? 942 LIMIT 1 943 """, (tenant_id, computer_name)) 944 comp = c.fetchone() 945 if not comp: 946 conn.close() 947 return jsonify({"error": "Computer not found"}), 404 948 949 computer_id = comp["id"] 950 out = {"computer": dict(comp)} 951 952 c.execute("SELECT * FROM computer_history WHERE computer_id=? ORDER BY timestamp", (computer_id,)) 953 out["history"] = [dict(r) for r in c.fetchall()] 954 955 c.execute("SELECT * FROM sysmon_events WHERE computer_id=? ORDER BY timestamp", (computer_id,)) 956 out["sysmon_events"] = [dict(r) for r in c.fetchall()] 957 958 c.execute("SELECT * FROM computer_processes WHERE computer_id=? ORDER BY timestamp", (computer_id,)) 959 out["processes"] = [dict(r) for r in c.fetchall()] 960 961 c.execute("SELECT * FROM network_connections WHERE computer_id=? ORDER BY timestamp", (computer_id,)) 962 out["network_connections"] = [dict(r) for r in c.fetchall()] 963 964 conn.close() 965 966 return Response( 967 json.dumps(out, indent=2, default=str, ensure_ascii=False), 968 mimetype="application/json", 969 headers={"Content-Disposition": f"attachment; filename={computer_name}_export.json"}, 970 ) 971 972 973 # ---------------------------- 974 # Simple RAG chat 975 # ---------------------------- 976 def build_rag_context(tenant_id: int, question: str, computer_name=None, limit_per_table=10): 977 conn = db() 978 c = conn.cursor() 979 980 params = [tenant_id] 981 comp_clause = "" 982 if computer_name: 983 comp_clause = "AND c2.name = ?" 984 params.append(computer_name) 985 986 # sysmon 987 c.execute(f""" 988 SELECT s.timestamp, s.event_id, s.event_type, s.message 989 FROM sysmon_events s 990 JOIN computers c2 ON c2.id = s.computer_id 991 WHERE c2.tenant_id = ? {comp_clause} 992 ORDER BY s.timestamp DESC 993 LIMIT ? 994 """, (*params, limit_per_table)) 995 sysmon_rows = c.fetchall() 996 997 # perf 998 c.execute(f""" 999 SELECT h.timestamp, h.cpu_usage, h.ram_usage, h.disk_usage, h.network_sent_mb, h.network_recv_mb 1000 FROM computer_history h 1001 JOIN computers c2 ON c2.id = h.computer_id 1002 WHERE c2.tenant_id = ? {comp_clause} 1003 ORDER BY h.timestamp DESC 1004 LIMIT ? 1005 """, (*params, limit_per_table)) 1006 perf_rows = c.fetchall() 1007 1008 # proc 1009 c.execute(f""" 1010 SELECT p.timestamp, p.pid, p.name, p.cpu_percent, p.memory_mb 1011 FROM computer_processes p 1012 JOIN computers c2 ON c2.id = p.computer_id 1013 WHERE c2.tenant_id = ? {comp_clause} 1014 ORDER BY p.timestamp DESC 1015 LIMIT ? 1016 """, (*params, limit_per_table)) 1017 proc_rows = c.fetchall() 1018 1019 conn.close() 1020 1021 lines = [] 1022 for r in sysmon_rows: 1023 lines.append(f"[SYSMON] {r['timestamp']} | Event {r['event_id']} ({r['event_type']}): {r['message']}") 1024 for r in perf_rows: 1025 lines.append( 1026 f"[PERF] {r['timestamp']} | CPU {r['cpu_usage']}% | RAM {r['ram_usage']}% | Disk {r['disk_usage']}% | " 1027 f"Net sent {r['network_sent_mb']}MB / recv {r['network_recv_mb']}MB" 1028 ) 1029 for r in proc_rows: 1030 lines.append( 1031 f"[PROC] {r['timestamp']} | PID {r['pid']} | {r['name']} | CPU {r['cpu_percent']}% | RAM {r['memory_mb']}MB" 1032 ) 1033 1034 return "\n".join(lines) 1035 1036 1037 def ask_llm_with_context(question, context): 1038 if not client: 1039 return "Немаш поставено OPENAI_API_KEY на серверот." 1040 1041 system_msg = ( 1042 "Ти си асистент за безбедност и систем мониторинг. " 1043 "Одговараш базирано ИСКЛУЧИВО на дадените логови. " 1044 "Ако нема доволно податоци, кажи дека нема доволно информации. " 1045 "Кога корисникот прашува за процеси и нивна потрошувачка, користи ги [PROC] редовите." 1046 ) 1047 1048 completion = client.chat.completions.create( 1049 model="gpt-4.1-mini", 1050 messages=[ 1051 {"role": "system", "content": system_msg}, 1052 {"role": "user", "content": f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}"}, 1053 ], 1054 temperature=0.2, 1055 ) 1056 return completion.choices[0].message.content 1057 1058 1059 @app.route("/api/chat", methods=["POST"]) 1060 @require_user() 1061 def api_chat(): 1062 try: 1063 tenant_id = request.user["tenant_id"] 1064 data = request.get_json(force=True, silent=True) or {} 1065 question = (data.get("question") or "").strip() 1066 computer_name = data.get("computer_name") or None 1067 1068 if not question: 1069 return jsonify({"error": "No question provided"}), 400 1070 1071 ctx = build_rag_context(tenant_id, question, computer_name=computer_name, limit_per_table=10) 1072 ans = ask_llm_with_context(question, ctx) 1073 return jsonify({"answer": ans or ""}), 200 1074 1075 except Exception as e: 1076 traceback.print_exc() 1077 return jsonify({"error": str(e)}), 500 1078 1079 1080 # ---------------------------- 1081 # Misc 1082 # ---------------------------- 1083 @app.route("/ping") 1084 def ping(): 1085 return jsonify({ 1086 "status": "alive", 1087 "server_time": datetime.now().isoformat(), 1088 "database": DB_FILE, 1089 "cors_origins": ALLOWED_ORIGINS, 1090 }) 1091 1092 1093 @app.route("/") 1094 def root(): 1095 return jsonify({ 1096 "ok": True, 1097 "service": "netIntel server", 1098 "hint": "frontend should call /api/* endpoints", 1099 }) 1100 1101 1102 if __name__ == "__main__": 1103 init_db() 1104 2042 1105 cleanup_thread = threading.Thread(target=cleanup_old_data, daemon=True) 2043 1106 cleanup_thread.start() 2044 1107 2045 local_ip = get_local_ip() 2046 2047 print("\n" + "=" * 70) 2048 print("🚀 ENHANCED LAN LOG SERVER со Sysmon") 2049 print("=" * 70) 2050 print(f"🌐 Server IP: {local_ip}:5555") 2051 print(f"📊 Dashboard: http://{local_ip}:5555/") 2052 print(f"📊 Alternative: http://localhost:5555/") 2053 print(f"🏥 Health check: http://{local_ip}:5555/ping") 2054 print("\n🛡️ Sysmon monitoring е активирано") 2055 print("✅ Кликни на секој компјутер за детални информации") 2056 print("✅ Сите Sysmon настани се прикажани во табла") 2057 print("✅ Кликни на секој настан за детали") 2058 print("=" * 70 + "\n") 2059 2060 app.run(host='0.0.0.0', port=5555, debug=False, threaded=True) 1108 print("🚀 netIntel server running on 0.0.0.0:5555") 1109 # debug=False recommended; if you need debug, set True temporarily 1110 app.run(host="0.0.0.0", port=5555, debug=False, threaded=True)
Note:
See TracChangeset
for help on using the changeset viewer.
