source: server.py@ 2058e5c

Last change on this file since 2058e5c was 2058e5c, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Working / before login

  • Property mode set to 100644
File size: 72.9 KB
Line 
1#!/usr/bin/env python3
2"""
3POPRAVEN SERVER со Sysmon обработка
4"""
5import traceback
6
7from flask import Flask, request, jsonify, render_template_string
8import json
9import sqlite3
10from datetime import datetime, timedelta
11import time
12import os
13import socket
14from collections import defaultdict
15import threading
16import requests
17from openai import OpenAI
18from openai import OpenAI
19from dotenv import load_dotenv
20load_dotenv()
21
22OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
23client = OpenAI(
24 api_key=OPENAI_API_KEY,
25 timeout=15 # максимум 15 секунди да чека
26) if OPENAI_API_KEY else None
27
28
29app = Flask(__name__)
30
31DB_FILE = "lan_logs_sysmon.db"
32LOG_DIR = "received_data_sysmon"
33
34import secrets
35
36ADMIN_KEY = os.environ.get("ADMIN_KEY", "")
37ADMIN_KEY = os.environ.get("ADMIN_KEY", "")
38
39
40def init_admin_tables():
41 conn = sqlite3.connect(DB_FILE)
42 c = conn.cursor()
43
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'))")
72
73 conn.commit()
74 conn.close()
75
76def 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
87def get_env_from_token(req):
88 tok = req.headers.get("X-Env-Token", "")
89 if not tok:
90 return None
91
92 conn = sqlite3.connect(DB_FILE)
93 c = conn.cursor()
94 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,))
100 row = c.fetchone()
101 conn.close()
102 return row[0] if row else None
103
104
105def 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'])
195def receive_data():
196 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)
211 c = conn.cursor()
212
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))
226 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}")
288
289 conn.commit()
290 conn.close()
291
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 })
305
306 except Exception as e:
307 print(f"[-] Грешка: {e}")
308 return jsonify({"error": str(e)}), 500
309
310
311def 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('/')
345def dashboard():
346 """Главен dashboard"""
347 conn = sqlite3.connect(DB_FILE)
348 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
416 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, '&quot;')})">
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, '&quot;')})">
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>')
1293def 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')
1406def 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')
1477def 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
1500def 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
1606def 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>')
1666def 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')
1712def 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
1721def 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
1732def 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
1756import time
1757@app.route("/api/chat", methods=["POST"])
1758def 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')
1783def 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')
1834def 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')
1889def 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"])
1954def 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"])
1971def 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
1981 return jsonify({"environments": envs})
1982
1983
1984@app.route("/api/admin/environments", methods=["POST"])
1985def admin_create_env():
1986 if not require_admin_session():
1987 return jsonify({"error": "Unauthorized"}), 401
1988
1989 data = request.get_json(force=True) or {}
1990 name = (data.get("name") or "").strip()
1991 if not name:
1992 return jsonify({"error": "Missing env name"}), 400
1993
1994 conn = sqlite3.connect(DB_FILE)
1995 c = conn.cursor()
1996 try:
1997 c.execute("INSERT INTO environments(name, created_at) VALUES(?, datetime('now'))", (name,))
1998 conn.commit()
1999 except Exception as e:
2000 conn.close()
2001 return jsonify({"error": str(e)}), 400
2002 conn.close()
2003
2004 return jsonify({"ok": True, "name": name})
2005
2006
2007@app.route("/api/admin/tokens", methods=["POST"])
2008def admin_generate_token():
2009 if not require_admin_session():
2010 return jsonify({"error": "Unauthorized"}), 401
2011
2012 data = request.get_json(force=True) or {}
2013 env = (data.get("env") or "").strip()
2014 if not env:
2015 return jsonify({"error": "Missing env"}), 400
2016
2017 conn = sqlite3.connect(DB_FILE)
2018 c = conn.cursor()
2019
2020 c.execute("SELECT 1 FROM environments WHERE name = ?", (env,))
2021 if not c.fetchone():
2022 conn.close()
2023 return jsonify({"error": "Environment not found"}), 404
2024
2025 token = secrets.token_urlsafe(32)
2026 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
2031 conn.commit()
2032 conn.close()
2033
2034 return jsonify({"ok": True, "env": env, "token": token})
2035
2036if __name__ == '__main__':
2037 init_database()
2038 init_admin_tables()
2039
2040
2041 # Стартувај cleanup thread
2042 cleanup_thread = threading.Thread(target=cleanup_old_data, daemon=True)
2043 cleanup_thread.start()
2044
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)
Note: See TracBrowser for help on using the repository browser.