import os import time import datetime import re import json import threading import config from core.file_manager import load_panel_settings, write_file stats_lock = threading.Lock() last_file_size = 0 def stats_collector(): global last_file_size while True: time.sleep(60) try: s = load_panel_settings() if s.get('query_log_enabled') != 'yes' or not os.path.exists(config.QUERY_LOG_PATH): continue current_size = os.path.getsize(config.QUERY_LOG_PATH) if current_size < last_file_size: last_file_size = 0 lines = [] with open(config.QUERY_LOG_PATH, 'r', errors='ignore') as f: f.seek(last_file_size) lines = f.readlines() last_file_size = f.tell() if not lines: continue now = datetime.datetime.now() d_key, t_key = now.strftime("%Y-%m-%d"), now.strftime("%H:%M") with stats_lock: st = {} if os.path.exists(config.STATS_FILE): try: with open(config.STATS_FILE, "r") as f: st = json.load(f) except: pass if d_key not in st: st[d_key] = { "queries": 0, "ips": {}, "tlds": {}, "timeline": {}, "blocked_queries": 0, "blocked_domains": {}, "blocked_clients": {}, "block_timeline": {} } day = st[d_key] q_count_min = 0 b_count_min = 0 for line in lines: line_lower = line.lower() # 1. Κανονικά Queries if 'query:' in line_lower: q_count_min += 1; day["queries"] = day.get("queries", 0) + 1 ip_match = re.search(r'([0-9a-fA-F\.\:]+)#\d+', line) if ip_match: ip = ip_match.group(1); day["ips"][ip] = day["ips"].get(ip, 0) + 1 q_match = re.search(r'query:\s+([^\s/]+)', line, re.I) if q_match: q_val = q_match.group(1).lower().strip('.') tld = q_val.split('.')[-1] if '.' in q_val else q_val day["tlds"][tld] = day.get("tlds", {}).get(tld, 0) + 1 # 2. Blocked / RPZ Queries (Πιάνει όλα τα formats του BIND) if 'rpz' in line_lower and ('rewrite' in line_lower or 'qname' in line_lower or 'passthru' in line_lower): b_count_min += 1 day["blocked_queries"] = day.get("blocked_queries", 0) + 1 # Ψάχνει IP και Domain αγνοώντας τυχόν "@0x..." m_rpz = re.search(r'client\s+(?:@[^\s]+\s+)?([0-9a-fA-F\.\:]+)#\d+\s+\((.*?)\)', line) if m_rpz: b_ip = m_rpz.group(1) b_dom = m_rpz.group(2).strip('.') day.setdefault("blocked_clients", {})[b_ip] = day.get("blocked_clients", {}).get(b_ip, 0) + 1 day.setdefault("blocked_domains", {})[b_dom] = day.get("blocked_domains", {}).get(b_dom, 0) + 1 day.setdefault("timeline", {})[t_key] = {"rps": round(q_count_min / 60.0, 3), "unique_ips": len(day["ips"])} day.setdefault("block_timeline", {})[t_key] = round(b_count_min / 60.0, 3) cutoff = (now - datetime.timedelta(days=int(s.get('stats_retention', 30)))).strftime("%Y-%m-%d") st = {k: v for k, v in st.items() if k >= cutoff} write_file(config.STATS_FILE, json.dumps(st)) except Exception as e: print(f"Stats Collector Error: {e}") def start_stats_daemon(): threading.Thread(target=stats_collector, daemon=True).start()