#!/usr/bin/env python3
"""
POPRAVEN SERVER со Sysmon обработка
"""
import traceback
from flask import Flask, request, jsonify, render_template_string
import json
import sqlite3
from datetime import datetime, timedelta
import time
import os
import socket
from collections import defaultdict
import threading
import requests
from openai import OpenAI
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
client = OpenAI(
api_key=OPENAI_API_KEY,
timeout=15 # максимум 15 секунди да чека
) if OPENAI_API_KEY else None
app = Flask(__name__)
DB_FILE = "lan_logs_sysmon.db"
LOG_DIR = "received_data_sysmon"
import secrets
ADMIN_KEY = os.environ.get("ADMIN_KEY", "")
ADMIN_KEY = os.environ.get("ADMIN_KEY", "")
def init_admin_tables():
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS environments(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,
created_at TEXT
)''')
c.execute('''CREATE TABLE IF NOT EXISTS env_tokens(
id INTEGER PRIMARY KEY AUTOINCREMENT,
env_name TEXT,
token TEXT UNIQUE,
created_at TEXT,
expires_at TEXT
)''')
# migration ако табелата веќе постои
c.execute("PRAGMA table_info(env_tokens)")
cols = [r[1] for r in c.fetchall()]
if "expires_at" not in cols:
c.execute("ALTER TABLE env_tokens ADD COLUMN expires_at TEXT")
c.execute('''CREATE TABLE IF NOT EXISTS admin_sessions(
id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT UNIQUE,
created_at TEXT
)''')
# default env
c.execute("INSERT OR IGNORE INTO environments(name, created_at) VALUES('default', datetime('now'))")
conn.commit()
conn.close()
def require_admin_session():
tok = request.headers.get("X-Admin-Session", "")
if not tok:
return False
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute("SELECT 1 FROM admin_sessions WHERE token = ?", (tok,))
ok = c.fetchone() is not None
conn.close()
return ok
def get_env_from_token(req):
tok = req.headers.get("X-Env-Token", "")
if not tok:
return None
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute("""
SELECT env_name
FROM env_tokens
WHERE token = ?
AND (expires_at IS NULL OR expires_at > datetime('now'))
""", (tok,))
row = c.fetchone()
conn.close()
return row[0] if row else None
def init_database():
"""Креирај подобрена база со Sysmon табели"""
os.makedirs(LOG_DIR, exist_ok=True)
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Табела за компјутери
c.execute('''CREATE TABLE IF NOT EXISTS computers
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,
user TEXT,
ip TEXT,
os TEXT,
first_seen TIMESTAMP,
last_seen TIMESTAMP,
sysmon_available BOOLEAN DEFAULT 0)''')
# --- MIGRATION: add env_name column if missing ---
c.execute("PRAGMA table_info(computers)")
cols = [r[1] for r in c.fetchall()]
if "env_name" not in cols:
c.execute("ALTER TABLE computers ADD COLUMN env_name TEXT DEFAULT 'default'")
# --- END MIGRATION ---
# Табела за историја
c.execute('''CREATE TABLE IF NOT EXISTS computer_history
(id INTEGER PRIMARY KEY AUTOINCREMENT,
computer_id INTEGER,
cpu_usage REAL,
ram_usage REAL,
disk_usage REAL,
network_sent_mb REAL,
network_recv_mb REAL,
timestamp TIMESTAMP,
FOREIGN KEY(computer_id) REFERENCES computers(id))''')
# Табела за процеси
c.execute('''CREATE TABLE IF NOT EXISTS computer_processes
(id INTEGER PRIMARY KEY AUTOINCREMENT,
computer_id INTEGER,
pid INTEGER,
name TEXT,
cpu_percent REAL,
memory_mb REAL,
username TEXT,
cmdline TEXT,
timestamp TIMESTAMP,
FOREIGN KEY(computer_id) REFERENCES computers(id))''')
# НОВА: Табела за Sysmon настани
c.execute('''CREATE TABLE IF NOT EXISTS sysmon_events
(id INTEGER PRIMARY KEY AUTOINCREMENT,
computer_id INTEGER,
event_id INTEGER,
event_type TEXT,
message TEXT,
timestamp TIMESTAMP,
details TEXT,
FOREIGN KEY(computer_id) REFERENCES computers(id))''')
# НОВА: Табела за детални мрежни конекции
c.execute('''CREATE TABLE IF NOT EXISTS network_connections
(id INTEGER PRIMARY KEY AUTOINCREMENT,
computer_id INTEGER,
pid INTEGER,
local_address TEXT,
remote_address TEXT,
status TEXT,
process_name TEXT,
timestamp TIMESTAMP,
FOREIGN KEY(computer_id) REFERENCES computers(id))''')
# НОВА: Табела за безбедносни настани
c.execute('''CREATE TABLE IF NOT EXISTS security_alerts
(id INTEGER PRIMARY KEY AUTOINCREMENT,
computer_id INTEGER,
alert_type TEXT,
severity TEXT,
description TEXT,
timestamp TIMESTAMP,
resolved BOOLEAN DEFAULT 0,
FOREIGN KEY(computer_id) REFERENCES computers(id))''')
conn.commit()
conn.close()
print(f"✅ Database created: {DB_FILE}")
@app.route('/receive', methods=['POST'])
def receive_data():
try:
data = request.json
if not data:
return jsonify({"error": "No data"}), 400
env_name = get_env_from_token(request)
if not env_name:
return jsonify({"error": "Missing or invalid X-Env-Token"}), 401
info = data['info']
computer_name = info['computer_name']
timestamp = datetime.now()
security_data = data.get('security_data', {})
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Провери дали компјутерот веќе постои
c.execute("SELECT id FROM computers WHERE name = ?", (computer_name,))
result = c.fetchone()
if result:
# Компјутерот постои - ажурирај го
computer_id = result[0]
c.execute('''UPDATE computers
SET user = ?, ip = ?, os = ?, last_seen = ?, sysmon_available = ?, env_name = ?
WHERE id = ?''',
(info['user'], info['ip_address'], info['os'],
timestamp.isoformat(), info.get('is_sysmon_available', 0),
env_name, computer_id))
else:
# Нов компјутер - додади го
c.execute('''INSERT INTO computers
(name, user, ip, os, first_seen, last_seen, sysmon_available, env_name)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
(computer_name, info['user'], info['ip_address'],
info['os'], timestamp.isoformat(), timestamp.isoformat(),
info.get('is_sysmon_available', 0), env_name))
computer_id = c.lastrowid
# Додади во историја
c.execute('''INSERT INTO computer_history
(computer_id, cpu_usage, ram_usage, disk_usage,
network_sent_mb, network_recv_mb, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?)''',
(computer_id, info['cpu_usage'], info['ram_usage'],
info['disk_usage'], info.get('network_sent_mb', 0),
info.get('network_recv_mb', 0), info['timestamp']))
# Зачувај ги процесите
for proc in data.get('processes', []):
c.execute('''INSERT INTO computer_processes
(computer_id, pid, name, cpu_percent, memory_mb,
username, cmdline, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
(computer_id, proc.get('pid'), proc.get('name'),
proc.get('cpu_percent', 0), proc.get('memory_mb', 0),
proc.get('user'), proc.get('cmdline'), info['timestamp']))
# ЗАЧУВАЈ SYSMON НАСТАНИ
sysmon_events_count = 0
for event in security_data.get('sysmon_events', []):
try:
c.execute('''INSERT INTO sysmon_events
(computer_id, event_id, event_type, message,
timestamp, details)
VALUES (?, ?, ?, ?, ?, ?)''',
(computer_id, event.get('event_id'),
event.get('event_type', 'Unknown'),
event.get('message', ''),
event.get('timestamp', info['timestamp']),
json.dumps(event)))
sysmon_events_count += 1
except Exception as e:
print(f"Error saving Sysmon event: {e}")
# ЗАЧУВАЈ МРЕЖНИ КОНЕКЦИИ
for conn_data in security_data.get('network_connections', []):
try:
c.execute('''INSERT INTO network_connections
(computer_id, pid, local_address, remote_address,
status, process_name, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?)''',
(computer_id, conn_data.get('pid'),
conn_data.get('local_address'),
conn_data.get('remote_address'),
conn_data.get('status'),
conn_data.get('process_name'),
info['timestamp']))
except Exception as e:
print(f"Error saving network connection: {e}")
conn.commit()
conn.close()
# Зачувај во JSON
save_json_file(data)
print(f"[{timestamp.strftime('%H:%M:%S')}] 📥 {computer_name}: "
f"CPU {info['cpu_usage']}% | Sysmon: {sysmon_events_count} events")
return jsonify({
"status": "success",
"message": f"Data saved for {computer_name}",
"computer_id": computer_id,
"event_count": sysmon_events_count,
"timestamp": timestamp.isoformat()
})
except Exception as e:
print(f"[-] Грешка: {e}")
return jsonify({"error": str(e)}), 500
def save_json_file(data):
"""Зачувај во JSON"""
info = data['info']
computer_name = info['computer_name']
date_str = datetime.now().strftime("%Y%m%d_%H")
# Креирај folder за компјутерот
computer_dir = os.path.join(LOG_DIR, computer_name)
os.makedirs(computer_dir, exist_ok=True)
# Фајл за секој час (за подобро организирање)
filepath = os.path.join(computer_dir, f"{date_str}.json")
# Додади во постоечки фајл или креирај нов
if os.path.exists(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
existing_data = json.load(f)
else:
existing_data = []
# Додади нов запис
existing_data.append({
"timestamp": info['timestamp'],
"info": info,
"processes_count": len(data.get('processes', [])),
"sysmon_events_count": len(data.get('security_data', {}).get('sysmon_events', []))
})
# Зачувај
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(existing_data, f, indent=2, ensure_ascii=False)
@app.route('/')
def dashboard():
"""Главен dashboard"""
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Земaj ги сите компјутери
c.execute('''SELECT name, user, ip, os, first_seen, last_seen, sysmon_available, env_name
FROM computers
ORDER BY last_seen DESC''')
computers = []
for row in c.fetchall():
computer_name = row[0]
# Пресметaj колку записи има во последните 24 часа
c.execute('''SELECT COUNT(*) FROM computer_history
WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
AND timestamp > datetime('now', '-24 hours')''',
(computer_name,))
recent_logs = c.fetchone()[0]
# Земaj ги последните 5 записи за CPU/RAM
c.execute('''SELECT cpu_usage, ram_usage, timestamp
FROM computer_history
WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
ORDER BY timestamp DESC LIMIT 5''',
(computer_name,))
recent_metrics = c.fetchall()
# Sysmon настани за последните 24 часа
c.execute('''SELECT COUNT(*) FROM sysmon_events
WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
AND timestamp > datetime('now', '-24 hours')''',
(computer_name,))
recent_sysmon = c.fetchone()[0]
# Пресметaj просек
avg_cpu = sum(m[0] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0
avg_ram = sum(m[1] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0
# Статус
last_seen = datetime.fromisoformat(row[5])
time_diff = (datetime.now() - last_seen).seconds
if time_diff < 60:
status = 'online'
status_color = 'green'
elif time_diff < 300:
status = 'idle'
status_color = 'orange'
else:
status = 'offline'
status_color = 'gray'
computers.append({
'name': row[0],
'user': row[1],
'ip': row[2],
'os': row[3],
'first_seen': row[4],
'last_seen': row[5],
'sysmon_available': bool(row[6]),
'recent_logs': recent_logs,
'recent_sysmon': recent_sysmon,
'avg_cpu': round(avg_cpu, 1),
'avg_ram': round(avg_ram, 1),
'status': status,
'status_color': status_color,
'env_name': row[7],
})
conn.close()
# Генерирај HTML
return render_template_string('''
LAN Log Server - Sysmon Dashboard
Total Computers
{{ computers|length }}
Monitored Systems
Sysmon Events (24h)
0
Security Events
Network Connections
0
Active Connections
Connected Computers
{% for comp in computers %}
{{ comp.name }}
👤 {{ comp.user }}
🌐 {{ comp.ip }}
🏷️ Env: {{ comp.env_name }}
💻 {{ comp.os[:40] }}
⏰ Last seen: {{ comp.last_seen[11:19] }}
{% if comp.sysmon_available %}
🛡️ Sysmon Available
{{ comp.recent_sysmon }} events
{% endif %}
{{ comp.recent_logs }}
Logs (24h)
{% endfor %}
''', computers=computers, socket=socket, datetime=datetime)
@app.route('/api/computer/')
def get_computer_details(computer_name):
"""Добиј сите детални информации за еден компјутер"""
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Основни информации
c.execute('''SELECT id, name, user, ip, os, first_seen, last_seen
FROM computers WHERE name = ?''', (computer_name,))
computer_data = c.fetchone()
if not computer_data:
conn.close()
return jsonify({"error": "Computer not found"}), 404
computer_id = computer_data[0]
# Број на сите записи
c.execute('SELECT COUNT(*) FROM computer_history WHERE computer_id = ?', (computer_id,))
total_logs = c.fetchone()[0]
# Сите history записи
c.execute('''SELECT cpu_usage, ram_usage, disk_usage, network_sent_mb, network_recv_mb, timestamp
FROM computer_history
WHERE computer_id = ?
ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
history = []
for row in c.fetchall():
history.append({
'cpu_usage': row[0],
'ram_usage': row[1],
'disk_usage': row[2],
'network_sent_mb': row[3],
'network_recv_mb': row[4],
'timestamp': row[5]
})
# Последни метрики
current_metrics = history[0] if history else {}
# Последни процеси
c.execute('''SELECT pid, name, username, cpu_percent, memory_mb, cmdline, timestamp
FROM computer_processes
WHERE computer_id = ?
ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
recent_processes = []
for row in c.fetchall():
recent_processes.append({
'pid': row[0],
'name': row[1],
'username': row[2],
'cpu_percent': row[3],
'memory_mb': row[4],
'cmdline': row[5],
'timestamp': row[6]
})
# SYSMON НАСТАНИ
c.execute('''SELECT event_id, event_type, message, timestamp, details
FROM sysmon_events
WHERE computer_id = ?
ORDER BY timestamp DESC LIMIT 200''', (computer_id,))
sysmon_events = []
for row in c.fetchall():
try:
details = json.loads(row[4]) if row[4] else {}
except:
details = {}
sysmon_events.append({
'event_id': row[0],
'event_type': row[1],
'message': row[2],
'timestamp': row[3],
'details': details
})
# Мрежни конекции
c.execute('''SELECT pid, process_name, local_address, remote_address, status, timestamp
FROM network_connections
WHERE computer_id = ?
ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
network_connections = []
for row in c.fetchall():
network_connections.append({
'pid': row[0],
'process_name': row[1],
'local_address': row[2],
'remote_address': row[3],
'status': row[4],
'timestamp': row[5]
})
conn.close()
return jsonify({
'computer': {
'id': computer_data[0],
'name': computer_data[1],
'user': computer_data[2],
'ip': computer_data[3],
'os': computer_data[4],
'first_seen': computer_data[5],
'last_seen': computer_data[6],
'total_logs': total_logs
},
'history': history,
'current_metrics': current_metrics,
'recent_processes': recent_processes,
'sysmon_events': sysmon_events,
'network_connections': network_connections
})
@app.route('/api/computers')
def get_computers():
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
env = request.headers.get("X-Env", "default")
c.execute('''SELECT name, user, ip, os, first_seen, last_seen, sysmon_available
FROM computers
WHERE env_name = ?
ORDER BY last_seen DESC''', (env,))
computers = []
for row in c.fetchall():
computer_name = row[0]
# recent logs
c.execute('''SELECT COUNT(*) FROM computer_history
WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
AND timestamp > datetime('now', '-24 hours')''',
(computer_name,))
recent_logs = c.fetchone()[0]
# last 5 metrics
c.execute('''SELECT cpu_usage, ram_usage, timestamp
FROM computer_history
WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
ORDER BY timestamp DESC LIMIT 5''',
(computer_name,))
recent_metrics = c.fetchall()
c.execute('''SELECT COUNT(*) FROM sysmon_events
WHERE computer_id = (SELECT id FROM computers WHERE name = ?)
AND timestamp > datetime('now', '-24 hours')''',
(computer_name,))
recent_sysmon = c.fetchone()[0]
avg_cpu = sum(m[0] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0
avg_ram = sum(m[1] for m in recent_metrics) / len(recent_metrics) if recent_metrics else 0
last_seen = datetime.fromisoformat(row[5])
time_diff = (datetime.now() - last_seen).seconds
if time_diff < 60:
status = 'online'
status_color = 'green'
elif time_diff < 300:
status = 'idle'
status_color = 'orange'
else:
status = 'offline'
status_color = 'gray'
computers.append({
'name': row[0],
'user': row[1],
'ip': row[2],
'os': row[3],
'first_seen': row[4],
'last_seen': row[5],
'sysmon_available': bool(row[6]),
'recent_logs': recent_logs,
'recent_sysmon': recent_sysmon,
'avg_cpu': round(avg_cpu, 1),
'avg_ram': round(avg_ram, 1),
'status': status,
'status_color': status_color
})
conn.close()
return jsonify(computers)
@app.route('/api/stats')
def get_stats():
"""Добиј глобални статистики"""
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Вкупно Sysmon настани за последните 24 часа
c.execute('''SELECT COUNT(*) FROM sysmon_events
WHERE timestamp > datetime('now', '-24 hours')''')
total_sysmon_events = c.fetchone()[0]
# Вкупно мрежни конекции за последните 24 часа
c.execute('''SELECT COUNT(*) FROM network_connections
WHERE timestamp > datetime('now', '-24 hours')''')
total_network_connections = c.fetchone()[0]
conn.close()
return jsonify({
'total_sysmon_events': total_sysmon_events,
'total_network_connections': total_network_connections,
'server_time': datetime.now().isoformat()
})
def build_rag_context(question, computer_name=None, limit_per_table=10):
try:
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
params = []
comp_filter = ""
if computer_name:
comp_filter = "AND computer_id = (SELECT id FROM computers WHERE name = ?)"
params.append(computer_name)
# Sysmon настани
c.execute(
f"""
SELECT timestamp, event_id, event_type, message
FROM sysmon_events
WHERE 1=1 {comp_filter}
ORDER BY timestamp DESC
LIMIT ?
""",
params + [limit_per_table],
)
sysmon_rows = c.fetchall()
# Перформанс историја
c.execute(
f"""
SELECT timestamp, cpu_usage, ram_usage, disk_usage,
network_sent_mb, network_recv_mb
FROM computer_history
WHERE 1=1 {comp_filter}
ORDER BY timestamp DESC
LIMIT ?
""",
params + [limit_per_table],
)
perf_rows = c.fetchall()
# Процеси
c.execute(
f"""
SELECT timestamp, pid, name, cpu_percent, memory_mb
FROM computer_processes
WHERE 1=1 {comp_filter}
ORDER BY timestamp DESC
LIMIT ?
""",
params + [limit_per_table],
)
proc_rows = c.fetchall()
conn.close()
print("[RAG] sysmon:", len(sysmon_rows),
"| perf:", len(perf_rows),
"| proc:", len(proc_rows))
lines = []
for ts, eid, etype, msg in sysmon_rows:
lines.append(f"[SYSMON] {ts} | Event {eid} ({etype}): {msg}")
for ts, cpu, ram, disk, nsent, nrecv in perf_rows:
lines.append(
f"[PERF] {ts} | CPU {cpu}% | RAM {ram}% | Disk {disk}% | "
f"Net sent {nsent}MB / recv {nrecv}MB"
)
for ts, pid, name, cpu, mem in proc_rows:
lines.append(f"[PROC] {ts} | PID {pid} | {name} | CPU {cpu}% | RAM {mem}MB")
context = "\n".join(lines)
print("[RAG] sample context:\n", "\n".join(lines[:5]))
return context
except Exception as e:
traceback.print_exc()
return ""
# def ask_llm_with_context(question, context, language="mk"):
# if not client:
# return "Немаш поставено OPENAI_API_KEY на серверот."
#
# system_msg = (
# "Ти си асистент за безбедност и систем мониторинг. "
# "Одговараш базирано ИСКЛУЧИВО на дадените логови што ти се дадени. "
# "Ако нема доволно информации, кажи дека нема доволно податоци. "
# "Објаснувај кратко и јасно."
# )
#
# user_msg = f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}"
#
# try:
# completion = client.chat.completions.create(
# model="gpt-4.1-mini",
# messages=[
# {"role": "system", "content": system_msg},
# {"role": "user", "content": user_msg},
# ],
# temperature=0.2,
# )
# return completion.choices[0].message.content
# except Exception as e:
# # Може попрецизно: openai.APITimeoutError, openai.APIConnectionError итн.
# return f"Грешка при повик кон LLM: {e}"
def ask_llm_with_context(question, context, language="mk"):
"""
Вика OpenAI модел и враќа одговор базиран на контекстот од логови.
"""
if not client:
return "Немаш поставено OPENAI_API_KEY на серверот."
system_msg = (
"Ти си асистент за безбедност и систем мониторинг. "
"Одговараш базирано ИСКЛУЧИВО на дадените логови што ти се дадени. "
"Типови редови во логовите:\n"
"- [SYSMON] ... Sysmon безбедносни настани.\n"
"- [PERF] ... Општи перформанси (CPU, RAM, диск, мрежа).\n"
"- [PROC] ... Информации за поединечни процеси: PID, име, CPU%, RAM MB.\n"
"Кога корисникот прашува за процеси и нивна потрошувачка, "
"СЕКОГАШ користи ги [PROC] редовите. "
"Никогаш не кажувај дека 'нема информации за процеси' ако постои барем еден [PROC] ред. "
"Ако навистина НЕМА [PROC] редови, тогаш можеш да кажеш дека нема доволно податоци. "
"Објаснувај кратко и јасно."
)
user_msg = f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}"
try:
print("[LLM] Calling OpenAI…")
completion = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=0.2,
)
print("[LLM] Got response")
return completion.choices[0].message.content
except Exception as e:
print("[LLM] ERROR:", e)
return f"Грешка при повик кон LLM: {e}"
#
# def ask_llm_with_context(question, context, language="mk"):
# payload = {
# "model": "deepseek-r1:1.5b",
# "prompt": f"Прашање: {question}\n\nКонтекст:\n{context}",
# "stream": False
# }
#
# try:
# r = requests.post("http://localhost:11434/api/generate", json=payload)
# r.raise_for_status()
# data = r.json()
# return data.get("response", "")
# except Exception as e:
# return f"Грешка при повик кон Ollama: {e}"
@app.route('/api/export/')
def export_computer_data(computer_name):
"""Експортирај ги сите податоци за еден компјутер"""
data = {}
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Основни информации за компјутерот
c.execute('''SELECT * FROM computers WHERE name = ?''', (computer_name,))
columns = [description[0] for description in c.description]
computer_data = c.fetchone()
if computer_data:
data['computer'] = dict(zip(columns, computer_data))
# Историја
computer_id = computer_data[0]
c.execute('''SELECT * FROM computer_history WHERE computer_id = ? ORDER BY timestamp''', (computer_id,))
data['history'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()]
# Sysmon настани
c.execute('''SELECT * FROM sysmon_events WHERE computer_id = ? ORDER BY timestamp''', (computer_id,))
data['sysmon_events'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()]
# Процеси
c.execute('''SELECT * FROM computer_processes WHERE computer_id = ? ORDER BY timestamp''', (computer_id,))
data['processes'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()]
# Мрежни конекции
c.execute('''SELECT * FROM network_connections WHERE computer_id = ? ORDER BY timestamp''', (computer_id,))
data['network_connections'] = [dict(zip([d[0] for d in c.description], row)) for row in c.fetchall()]
conn.close()
# Креирај JSON одговор
from flask import Response
response = Response(
json.dumps(data, indent=2, default=str),
mimetype='application/json',
headers={'Content-Disposition': f'attachment; filename={computer_name}_export.json'}
)
return response
@app.route('/ping')
def ping():
return jsonify({
'status': 'alive',
'server_time': datetime.now().isoformat(),
'version': '2.0_sysmon',
'database': DB_FILE
})
def get_local_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
local_ip = s.getsockname()[0]
s.close()
return local_ip
except:
return "127.0.0.1"
def cleanup_old_data():
"""Чисти стари податоци од базата (стари од 30 дена)"""
while True:
time.sleep(3600) # Чисти на секој час
try:
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
cutoff_date = (datetime.now() - timedelta(days=30)).isoformat()
# Избриши стари записи
c.execute("DELETE FROM computer_history WHERE timestamp < ?", (cutoff_date,))
c.execute("DELETE FROM computer_processes WHERE timestamp < ?", (cutoff_date,))
c.execute("DELETE FROM sysmon_events WHERE timestamp < ?", (cutoff_date,))
c.execute("DELETE FROM network_connections WHERE timestamp < ?", (cutoff_date,))
conn.commit()
conn.close()
print(f"[Cleanup] Избришани стари записи пред {cutoff_date}")
except Exception as e:
print(f"[Cleanup] Грешка: {e}")
import time
@app.route("/api/chat", methods=["POST"])
def api_chat():
try:
t0 = time.time()
data = request.get_json(force=True) or {}
question = data.get("question", "").strip()
computer_name = data.get("computer_name") or None
if not question:
return jsonify({"error": "No question provided"}), 400
context = build_rag_context(question, computer_name=computer_name)
t1 = time.time()
answer = ask_llm_with_context(question, context)
t2 = time.time()
print(f"[CHAT] build_rag_context: {t1 - t0:.2f}s, LLM: {t2 - t1:.2f}s")
return jsonify({"answer": answer})
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
@app.route('/api/computer//sysmon')
def get_sysmon_processes(computer_name):
"""Добиј Sysmon процеси за компјутер"""
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Прво најди го computer_id
c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,))
result = c.fetchone()
if not result:
conn.close()
return jsonify({"error": "Computer not found"}), 404
computer_id = result[0]
# Земaj ги Sysmon процесите (последни 100)
c.execute('''SELECT
event_id,
event_type,
message,
timestamp,
details
FROM sysmon_events
WHERE computer_id = ?
ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
processes = []
for row in c.fetchall():
try:
details = json.loads(row[4]) if row[4] else {}
except:
details = {}
processes.append({
'process_id': row[0], # Event ID како PID
'process_name': row[1], # Event type како име
'timestamp': row[3],
'message': row[2],
'details': details
})
conn.close()
return jsonify({
'computer_name': computer_name,
'processes': processes,
'count': len(processes)
})
@app.route('/api/computer//network')
def get_network_connections(computer_name):
"""Добиј мрежни конекции за компјутер"""
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Прво најди го computer_id
c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,))
result = c.fetchone()
if not result:
conn.close()
return jsonify({"error": "Computer not found"}), 404
computer_id = result[0]
# Земaj ги мрежните конекции (последни 100)
c.execute('''SELECT
pid,
process_name,
local_address,
remote_address,
status,
timestamp
FROM network_connections
WHERE computer_id = ?
ORDER BY timestamp DESC LIMIT 100''', (computer_id,))
connections = []
for row in c.fetchall():
# Парсирај IP и порта
local = row[2] or "N/A"
remote = row[3] or "N/A"
connections.append({
'pid': row[0],
'process_name': row[1] or 'Unknown',
'local_address': local,
'remote_address': remote,
'local_port': local.split(':')[-1] if ':' in local else '',
'remote_port': remote.split(':')[-1] if ':' in remote else '',
'protocol': 'TCP' if ':443' in local or ':80' in local else 'Unknown',
'state': row[4] or 'ESTABLISHED',
'timestamp': row[5]
})
conn.close()
return jsonify({
'computer_name': computer_name,
'connections': connections,
'count': len(connections)
})
@app.route('/api/computer//detailed-sysmon')
def get_detailed_sysmon(computer_name):
"""Добиј детални Sysmon процеси со повеќе информации"""
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute('SELECT id FROM computers WHERE name = ?', (computer_name,))
result = c.fetchone()
if not result:
conn.close()
return jsonify({"error": "Computer not found"}), 404
computer_id = result[0]
c.execute('''SELECT event_id, event_type, message, timestamp, details
FROM sysmon_events
WHERE computer_id = ?
ORDER BY timestamp DESC LIMIT 50''', (computer_id,))
detailed_processes = []
for row in c.fetchall():
event_id = row[0]
event_type = row[1]
message = row[2]
timestamp = row[3]
details_str = row[4]
# Пробај да го парсираш details JSON
try:
details = json.loads(details_str) if details_str else {}
except:
details = {}
# Екстрактирај информации од details
process_info = {
'event_id': event_id,
'event_type': event_type,
'timestamp': timestamp,
'message': message,
'process_name': details.get('ProcessName', 'Unknown'),
'process_id': details.get('ProcessId', event_id),
'command_line': details.get('CommandLine', ''),
'parent_process': details.get('ParentProcessName', ''),
'parent_pid': details.get('ParentProcessId', ''),
'user': details.get('User', ''),
'details': details
}
# Додади дополнителни полиња за различни типови на настани
if event_id in [3, 22]: # Network connection or DNS
process_info['source_ip'] = details.get('SourceIp', '')
process_info['dest_ip'] = details.get('DestinationIp', '')
process_info['dest_port'] = details.get('DestinationPort', '')
detailed_processes.append(process_info)
conn.close()
return jsonify({
'computer_name': computer_name,
'processes': detailed_processes,
'count': len(detailed_processes)
})
@app.route("/api/admin/login", methods=["POST"])
def admin_login():
data = request.get_json(force=True) or {}
key = (data.get("admin_key") or "").strip()
if not ADMIN_KEY or key != ADMIN_KEY:
return jsonify({"error": "Unauthorized"}), 401
session_token = secrets.token_urlsafe(32)
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute("INSERT INTO admin_sessions(token, created_at) VALUES(?, datetime('now'))", (session_token,))
conn.commit()
conn.close()
return jsonify({"ok": True, "session": session_token})
@app.route("/api/admin/environments", methods=["GET"])
def admin_list_envs():
if not require_admin_session():
return jsonify({"error": "Unauthorized"}), 401
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute("SELECT name FROM environments ORDER BY name")
envs = [r[0] for r in c.fetchall()]
conn.close()
return jsonify({"environments": envs})
@app.route("/api/admin/environments", methods=["POST"])
def admin_create_env():
if not require_admin_session():
return jsonify({"error": "Unauthorized"}), 401
data = request.get_json(force=True) or {}
name = (data.get("name") or "").strip()
if not name:
return jsonify({"error": "Missing env name"}), 400
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
try:
c.execute("INSERT INTO environments(name, created_at) VALUES(?, datetime('now'))", (name,))
conn.commit()
except Exception as e:
conn.close()
return jsonify({"error": str(e)}), 400
conn.close()
return jsonify({"ok": True, "name": name})
@app.route("/api/admin/tokens", methods=["POST"])
def admin_generate_token():
if not require_admin_session():
return jsonify({"error": "Unauthorized"}), 401
data = request.get_json(force=True) or {}
env = (data.get("env") or "").strip()
if not env:
return jsonify({"error": "Missing env"}), 400
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute("SELECT 1 FROM environments WHERE name = ?", (env,))
if not c.fetchone():
conn.close()
return jsonify({"error": "Environment not found"}), 404
token = secrets.token_urlsafe(32)
c.execute("""
INSERT INTO env_tokens(env_name, token, created_at, expires_at)
VALUES(?, ?, datetime('now'), datetime('now', '+15 minutes'))
""", (env, token))
conn.commit()
conn.close()
return jsonify({"ok": True, "env": env, "token": token})
if __name__ == '__main__':
init_database()
init_admin_tables()
# Стартувај cleanup thread
cleanup_thread = threading.Thread(target=cleanup_old_data, daemon=True)
cleanup_thread.start()
local_ip = get_local_ip()
print("\n" + "=" * 70)
print("🚀 ENHANCED LAN LOG SERVER со Sysmon")
print("=" * 70)
print(f"🌐 Server IP: {local_ip}:5555")
print(f"📊 Dashboard: http://{local_ip}:5555/")
print(f"📊 Alternative: http://localhost:5555/")
print(f"🏥 Health check: http://{local_ip}:5555/ping")
print("\n🛡️ Sysmon monitoring е активирано")
print("✅ Кликни на секој компјутер за детални информации")
print("✅ Сите Sysmon настани се прикажани во табла")
print("✅ Кликни на секој настан за детали")
print("=" * 70 + "\n")
app.run(host='0.0.0.0', port=5555, debug=False, threaded=True)