#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import locale # Setze Standardkodierung auf UTF-8 if sys.platform.startswith('win'): # Setze für Windows die richtige Codepage try: # Versuche zuerst, die Codepage direkt zu setzen import os os.system('chcp 65001 > nul') except: # Fallback-Methode, falls der direkte Aufruf fehlschlägt try: import subprocess subprocess.run(["cmd.exe", "/c", "chcp", "65001"], capture_output=True, check=False) except: print("Konnte Codepage nicht auf UTF-8 setzen. Eventuell werden Unicode-Zeichen nicht korrekt angezeigt.") # Setze das Locale auf die passende Einstellung try: locale.setlocale(locale.LC_ALL, 'C.UTF-8') except: try: locale.setlocale(locale.LC_ALL, '') # Systemstandard except: print("Konnte Locale nicht setzen.") else: # Für Unix-Systeme try: locale.setlocale(locale.LC_ALL, 'C.UTF-8') except: try: locale.setlocale(locale.LC_ALL, '') # Systemstandard except: print("Konnte Locale nicht setzen.") from flask import Flask, render_template, jsonify, request, redirect, url_for import socket import platform import os import subprocess import json import datetime import psutil import netifaces import requests import re import time import logging import matplotlib matplotlib.use('Agg') # Verwende Agg-Backend für Server ohne Display import matplotlib.pyplot as plt import io import base64 from flask_cors import CORS import pandas as pd import threading import docker import tempfile from werkzeug.utils import secure_filename import shutil app = Flask(__name__) CORS(app) DEBUG_PORT = 5555 # Port für den Debug-Server # Logging konfigurieren logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(os.path.join('..', '..', 'logs', 'debug-server.log')), logging.StreamHandler() ] ) logger = logging.getLogger('debug-server') # Konfigurationsdatei CONFIG_FILE = '../instance/network_config.json' DEFAULT_CONFIG = { 'backend_hostname': '192.168.0.105', 'backend_port': 5000, 'frontend_hostname': '192.168.0.106', 'frontend_port': 3000 } # Cache für Docker-Container-IDs und Namen CONTAINER_CACHE = {} # Port-Scanner-Thread-Flag PORT_SCAN_RUNNING = False # Docker-Client initialisieren try: docker_client = docker.from_env() DOCKER_AVAILABLE = True except: DOCKER_AVAILABLE = False logger.warning("Docker ist nicht verfügbar. Docker-bezogene Funktionen werden deaktiviert.") def get_config(): """Lädt die Netzwerkkonfiguration.""" if os.path.exists(CONFIG_FILE): try: with open(CONFIG_FILE, 'r') as f: return json.load(f) except Exception as e: logger.error(f"Fehler beim Laden der Konfiguration: {e}") return DEFAULT_CONFIG.copy() def save_config(config): """Speichert die Netzwerkkonfiguration.""" os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True) try: with open(CONFIG_FILE, 'w') as f: json.dump(config, f, indent=4) return True except Exception as e: logger.error(f"Fehler beim Speichern der Konfiguration: {e}") return False @app.route('/') def index(): """Zeigt die Debug-Oberfläche an.""" return redirect(url_for('dashboard')) @app.route('/dashboard') def dashboard(): """Zeigt das neue Debug-Dashboard an.""" interfaces = get_network_interfaces() config = get_config() # Prüfe Verbindungen backend_status = "Nicht überprüft" frontend_status = "Nicht überprüft" try: backend_url = f"http://{config['backend_hostname']}:{config['backend_port']}/api/test" response = requests.get(backend_url, timeout=3) if response.status_code == 200: backend_status = "Verbunden" else: backend_status = f"Fehler: HTTP {response.status_code}" except requests.exceptions.RequestException as e: backend_status = f"Nicht erreichbar" if ping_host(config['backend_hostname']): backend_status += " (Host antwortet auf Ping)" try: frontend_url = f"http://{config['frontend_hostname']}:{config['frontend_port']}" response = requests.get(frontend_url, timeout=3) if response.status_code == 200: frontend_status = "Verbunden" else: frontend_status = f"Fehler: HTTP {response.status_code}" except requests.exceptions.RequestException as e: frontend_status = f"Nicht erreichbar" if ping_host(config['frontend_hostname']): frontend_status += " (Host antwortet auf Ping)" return render_template('dashboard.html', interfaces=interfaces, config=config, backend_status=backend_status, frontend_status=frontend_status, last_check=datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")) @app.route('/debug') def debug(): """Zeigt die alte Debug-Oberfläche an.""" interfaces = get_network_interfaces() config = get_config() # Prüfe Verbindungen backend_status = "Nicht überprüft" frontend_status = "Nicht überprüft" try: backend_url = f"http://{config['backend_hostname']}:{config['backend_port']}/api/test" response = requests.get(backend_url, timeout=3) if response.status_code == 200: backend_status = "Verbunden" else: backend_status = f"Fehler: HTTP {response.status_code}" except requests.exceptions.RequestException as e: backend_status = f"Nicht erreichbar" if ping_host(config['backend_hostname']): backend_status += " (Host antwortet auf Ping)" try: frontend_url = f"http://{config['frontend_hostname']}:{config['frontend_port']}" response = requests.get(frontend_url, timeout=3) if response.status_code == 200: frontend_status = "Verbunden" else: frontend_status = f"Fehler: HTTP {response.status_code}" except requests.exceptions.RequestException as e: frontend_status = f"Nicht erreichbar" if ping_host(config['frontend_hostname']): frontend_status += " (Host antwortet auf Ping)" return render_template('debug.html', interfaces=interfaces, config=config, backend_status=backend_status, frontend_status=frontend_status, last_check=datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")) # API-Endpunkte für das Dashboard @app.route('/api/system/metrics') def system_metrics(): """Liefert aktuelle Systemmetriken.""" try: # CPU-Auslastung cpu_percent = psutil.cpu_percent(interval=0.5) # Speicherauslastung memory = psutil.virtual_memory() memory_info = { 'total': memory.total, 'available': memory.available, 'used': memory.used, 'percent': memory.percent } # Festplattennutzung disk_usage = [] for partition in psutil.disk_partitions(): if os.name == 'nt' and ('cdrom' in partition.opts or partition.fstype == ''): # Unter Windows: CD-ROMs und spezielle Laufwerke überspringen continue usage = psutil.disk_usage(partition.mountpoint) disk_usage.append({ 'device': partition.device, 'mountpoint': partition.mountpoint, 'fstype': partition.fstype, 'total': usage.total, 'used': usage.used, 'free': usage.free, 'percent': usage.percent }) # Netzwerk-I/O net_io = psutil.net_io_counters() network_io = { 'bytes_sent': net_io.bytes_sent, 'bytes_recv': net_io.bytes_recv, 'packets_sent': net_io.packets_sent, 'packets_recv': net_io.packets_recv, 'errin': net_io.errin, 'errout': net_io.errout, 'dropin': net_io.dropin, 'dropout': net_io.dropout } return jsonify({ 'success': True, 'timestamp': datetime.datetime.now().isoformat(), 'cpu_percent': cpu_percent, 'memory': memory_info, 'disk_usage': disk_usage, 'network_io': network_io }) except Exception as e: logger.error(f"Fehler beim Abrufen der Systemmetriken: {str(e)}") return jsonify({ 'success': False, 'message': f"Fehler beim Abrufen der Systemmetriken: {str(e)}" }), 500 @app.route('/api/docker/status') def docker_status(): """Liefert Statusinformationen zu Docker-Containern.""" try: containers = [] docker_running = False try: # Prüfe, ob Docker läuft result = subprocess.run(['docker', 'info'], capture_output=True, text=True, check=False) docker_running = result.returncode == 0 if docker_running: # Liste alle Container auf result = subprocess.run(['docker', 'ps', '-a', '--format', '{{.ID}}|{{.Names}}|{{.Status}}|{{.Image}}'], capture_output=True, text=True, check=True) lines = result.stdout.strip().split('\n') for line in lines: if not line: continue parts = line.split('|') if len(parts) < 4: continue container_id, name, status, image = parts is_running = status.startswith('Up') # CPU- und Speichernutzung nur für laufende Container abrufen cpu_percent = None memory_usage = None network_io = None if is_running: try: # CPU-Nutzung cpu_result = subprocess.run(['docker', 'stats', container_id, '--no-stream', '--format', '{{.CPUPerc}}'], capture_output=True, text=True, check=True) cpu_text = cpu_result.stdout.strip() cpu_match = re.search(r'([\d.]+)%', cpu_text) if cpu_match: cpu_percent = float(cpu_match.group(1)) # Speichernutzung mem_result = subprocess.run(['docker', 'stats', container_id, '--no-stream', '--format', '{{.MemUsage}}'], capture_output=True, text=True, check=True) mem_text = mem_result.stdout.strip() mem_match = re.search(r'([\d.]+)([KMGiB]+)', mem_text) if mem_match: value = float(mem_match.group(1)) unit = mem_match.group(2) # Konvertiere in Bytes if unit.startswith('K'): memory_usage = value * 1024 elif unit.startswith('M'): memory_usage = value * 1024 * 1024 elif unit.startswith('G'): memory_usage = value * 1024 * 1024 * 1024 else: memory_usage = value # Netzwerknutzung net_result = subprocess.run(['docker', 'stats', container_id, '--no-stream', '--format', '{{.NetIO}}'], capture_output=True, text=True, check=True) net_text = net_result.stdout.strip() net_parts = net_text.split(' / ') if len(net_parts) == 2: rx_text, tx_text = net_parts # Empfangen (RX) rx_match = re.search(r'([\d.]+)([KMGiB]+)', rx_text) if rx_match: rx_value = float(rx_match.group(1)) rx_unit = rx_match.group(2) # Konvertiere in Bytes rx_bytes = rx_value if rx_unit.startswith('K'): rx_bytes = rx_value * 1024 elif rx_unit.startswith('M'): rx_bytes = rx_value * 1024 * 1024 elif rx_unit.startswith('G'): rx_bytes = rx_value * 1024 * 1024 * 1024 # Gesendet (TX) tx_match = re.search(r'([\d.]+)([KMGiB]+)', tx_text) if tx_match: tx_value = float(tx_match.group(1)) tx_unit = tx_match.group(2) # Konvertiere in Bytes tx_bytes = tx_value if tx_unit.startswith('K'): tx_bytes = tx_value * 1024 elif tx_unit.startswith('M'): tx_bytes = tx_value * 1024 * 1024 elif tx_unit.startswith('G'): tx_bytes = tx_value * 1024 * 1024 * 1024 network_io = { 'rx_bytes': rx_bytes, 'tx_bytes': tx_bytes } except Exception as e: logger.error(f"Fehler beim Abrufen der Container-Metriken für {name}: {str(e)}") # In Cache speichern für spätere Verwendung CONTAINER_CACHE[container_id] = name containers.append({ 'id': container_id, 'name': name, 'status': status, 'image': image, 'running': is_running, 'cpu_percent': cpu_percent, 'memory_usage': memory_usage, 'network_io': network_io }) except Exception as docker_error: logger.error(f"Docker-Fehler: {str(docker_error)}") return jsonify({ 'success': True, 'docker_running': docker_running, 'containers': containers }) except Exception as e: logger.error(f"Fehler beim Abrufen der Docker-Container-Informationen: {str(e)}") return jsonify({ 'success': False, 'message': f"Fehler beim Abrufen der Docker-Container-Informationen: {str(e)}" }), 500 @app.route('/api/docker/info') def docker_info(): """Gibt detaillierte Docker-Informationen zurück.""" try: info = { 'version': 'Unbekannt', 'api_version': 'Unbekannt', 'os': 'Unbekannt', 'status': 'Nicht verfügbar' } try: result = subprocess.run(['docker', 'info', '--format', '{{json .}}'], capture_output=True, text=True, check=False) if result.returncode == 0: json_data = json.loads(result.stdout) info['version'] = json_data.get('ServerVersion', 'Unbekannt') info['api_version'] = json_data.get('ApiVersion', 'Unbekannt') info['os'] = json_data.get('OperatingSystem', 'Unbekannt') info['status'] = 'Läuft' else: info['status'] = 'Gestoppt oder nicht installiert' except Exception as docker_error: logger.error(f"Docker-Info-Fehler: {str(docker_error)}") info['status'] = 'Fehler: ' + str(docker_error) return jsonify({ 'success': True, 'info': info }) except Exception as e: logger.error(f"Fehler beim Abrufen der Docker-Informationen: {str(e)}") return jsonify({ 'success': False, 'message': f"Fehler beim Abrufen der Docker-Informationen: {str(e)}" }), 500 @app.route('/api/docker/restart/', methods=['POST']) def restart_container(container_id): """Startet einen Docker-Container neu.""" try: result = subprocess.run(['docker', 'restart', container_id], capture_output=True, text=True, check=False) if result.returncode == 0: return jsonify({ 'success': True, 'message': f"Container wurde erfolgreich neugestartet." }) else: error_message = result.stderr.strip() logger.error(f"Fehler beim Neustarten des Containers: {error_message}") return jsonify({ 'success': False, 'message': f"Fehler beim Neustarten des Containers: {error_message}" }), 500 except Exception as e: logger.error(f"Fehler beim Neustarten des Containers: {str(e)}") return jsonify({ 'success': False, 'message': f"Fehler beim Neustarten des Containers: {str(e)}" }), 500 @app.route('/api/docker/logs/') def container_logs(container_id): """Gibt die Logs eines Docker-Containers zurück.""" try: # Benutze den Container-Namen aus dem Cache, wenn verfügbar container_name = CONTAINER_CACHE.get(container_id, container_id) # Limitiere die Anzahl der zurückgegebenen Zeilen tail_option = request.args.get('tail', '100') result = subprocess.run(['docker', 'logs', '--tail', tail_option, container_id], capture_output=True, text=True, check=False) if result.returncode == 0: return jsonify({ 'success': True, 'container_id': container_id, 'container_name': container_name, 'logs': result.stdout }) else: error_message = result.stderr.strip() logger.error(f"Fehler beim Abrufen der Container-Logs: {error_message}") return jsonify({ 'success': False, 'message': f"Fehler beim Abrufen der Container-Logs: {error_message}" }), 500 except Exception as e: logger.error(f"Fehler beim Abrufen der Container-Logs: {str(e)}") return jsonify({ 'success': False, 'message': f"Fehler beim Abrufen der Container-Logs: {str(e)}" }), 500 @app.route('/api/logs/analyze') def analyze_logs(): """Analysiert Logdateien und gibt Fehler zurück.""" try: log_type = request.args.get('type', 'backend') filter_text = request.args.get('filter', '') log_file = None # Bestimme den Pfad zur Logdatei if log_type == 'backend': log_file = os.path.join('..', '..', 'logs', 'backend.log') elif log_type == 'frontend': log_file = os.path.join('..', '..', 'logs', 'frontend.log') elif log_type == 'docker': # Docker-Logs dynamisch abrufen result = subprocess.run(['docker', 'logs', 'myp-backend'], capture_output=True, text=True, check=False) if result.returncode == 0: docker_logs = result.stdout entries = parse_docker_logs(docker_logs, filter_text) return jsonify({ 'success': True, 'entries': entries }) else: return jsonify({ 'success': False, 'message': f"Konnte Docker-Logs nicht abrufen: {result.stderr}" }) else: return jsonify({ 'success': False, 'message': f"Unbekannter Log-Typ: {log_type}" }) # Logdatei lesen und analysieren if log_file and os.path.exists(log_file): entries = [] with open(log_file, 'r', encoding='utf-8', errors='ignore') as f: log_content = f.readlines() # Logs parsen und nach Fehlern filtern error_pattern = re.compile(r'(ERROR|CRITICAL|FEHLER|Exception|Error|Fehler)', re.IGNORECASE) for line in log_content: if error_pattern.search(line) and (not filter_text or filter_text.lower() in line.lower()): # Versuche, Zeitstempel, Meldungstyp und Meldung zu extrahieren timestamp_match = re.search(r'(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2})', line) timestamp = timestamp_match.group(1) if timestamp_match else 'Unbekannt' # Entferne Zeitstempel und andere Metadaten message = re.sub(r'^\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}.*?\s-\s', '', line).strip() entries.append({ 'timestamp': timestamp, 'message': message, 'raw': line.strip() }) return jsonify({ 'success': True, 'entries': entries }) else: return jsonify({ 'success': False, 'message': f"Logdatei nicht gefunden: {log_file}" }) except Exception as e: logger.error(f"Fehler bei der Log-Analyse: {str(e)}") return jsonify({ 'success': False, 'message': f"Fehler bei der Log-Analyse: {str(e)}" }), 500 @app.route('/save-config', methods=['POST']) def save_config_route(): """Speichert die Netzwerkkonfiguration.""" try: config = get_config() # Aktualisiere Konfiguration config['backend_hostname'] = request.form.get('backend_hostname', DEFAULT_CONFIG['backend_hostname']) config['backend_port'] = int(request.form.get('backend_port', DEFAULT_CONFIG['backend_port'])) config['frontend_hostname'] = request.form.get('frontend_hostname', DEFAULT_CONFIG['frontend_hostname']) config['frontend_port'] = int(request.form.get('frontend_port', DEFAULT_CONFIG['frontend_port'])) # Speichere Konfiguration if save_config(config): return jsonify({"success": True, "message": "Konfiguration erfolgreich gespeichert."}) else: return jsonify({"success": False, "message": "Fehler beim Speichern der Konfiguration."}) except Exception as e: return jsonify({"success": False, "message": f"Fehler: {str(e)}"}) @app.route('/sync-frontend', methods=['POST']) def sync_frontend(): """Synchronisiert die Frontend-Konfiguration mit der gespeicherten Backend-URL.""" try: config = get_config() backend_url = f"http://{config['backend_hostname']}:{config['backend_port']}" # Erstelle notwendige Verzeichnisse frontend_dir = "../../frontend" env_file = os.path.join(frontend_dir, ".env.local") if not os.path.exists(frontend_dir): return jsonify({"success": False, "message": "Frontend-Verzeichnis nicht gefunden."}) # Bestimme den Hostnamen für OAuth hostname = socket.gethostname() if "corpintra" in hostname: frontend_hostname = "m040tbaraspi001.de040.corpintra.net" oauth_url = "http://m040tbaraspi001.de040.corpintra.net/auth/login/callback" else: frontend_hostname = hostname oauth_url = f"http://{hostname}:3000/auth/login/callback" # Erstelle .env.local-Datei env_content = f"""# Backend API Konfiguration NEXT_PUBLIC_API_URL={backend_url} # Frontend-URL für OAuth Callback NEXT_PUBLIC_FRONTEND_URL=http://{frontend_hostname} # Explizite OAuth Callback URL für GitHub NEXT_PUBLIC_OAUTH_CALLBACK_URL={oauth_url} # OAuth Konfiguration OAUTH_CLIENT_ID=client_id OAUTH_CLIENT_SECRET=client_secret """ try: with open(env_file, 'w') as f: f.write(env_content) os.chmod(env_file, 0o600) return jsonify({"success": True, "message": "Frontend erfolgreich mit Backend-URL konfiguriert."}) except Exception as e: return jsonify({"success": False, "message": f"Fehler beim Schreiben der Frontend-Konfiguration: {str(e)}"}) except Exception as e: return jsonify({"success": False, "message": f"Fehler: {str(e)}"}) @app.route('/test-connection', methods=['POST']) def test_connection_route(): """Testet die Verbindung zum Backend und Frontend.""" try: backend_hostname = request.form.get('backend_hostname') backend_port = int(request.form.get('backend_port')) frontend_hostname = request.form.get('frontend_hostname') frontend_port = int(request.form.get('frontend_port')) results = { 'backend': { 'ping': ping_host(backend_hostname), 'connection': check_connection(backend_hostname, backend_port) }, 'frontend': { 'ping': ping_host(frontend_hostname), 'connection': check_connection(frontend_hostname, frontend_port) } } return jsonify({"success": True, "results": results}) except Exception as e: return jsonify({"success": False, "message": f"Fehler: {str(e)}"}) @app.route('/systeminfo') def systeminfo(): """Gibt Systeminformationen zurück.""" info = { "hostname": socket.gethostname(), "platform": platform.platform(), "architecture": platform.machine(), "processor": platform.processor(), "python_version": platform.python_version(), "uptime": get_uptime(), "memory": get_memory_info(), "disk": get_disk_info(), } return jsonify(info) @app.route('/network') def network(): """Gibt Netzwerkinformationen zurück.""" info = { "interfaces": get_network_interfaces(), "connections": get_active_connections(), "dns_servers": get_dns_servers(), "gateway": get_default_gateway(), } return jsonify(info) @app.route('/docker') def docker(): """Gibt Docker-Informationen zurück.""" return jsonify(get_docker_info()) @app.route('/ping/') def ping_host(host): """Pingt einen Host an.""" param = '-n' if platform.system().lower() == 'windows' else '-c' command = ['ping', param, '1', host] try: # Verwende errors='replace' um Dekodierungsfehler zu behandeln result = subprocess.run(command, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=5) return result.returncode == 0 except subprocess.TimeoutExpired: return False except Exception as e: print(f"Fehler beim Ping von {host}: {e}") return False @app.route('/traceroute/') def traceroute_host(host): """Führt einen Traceroute zum Zielhost durch und gibt das Ergebnis zurück.""" if is_valid_hostname(host): try: # Verwende errors='replace' um Dekodierungsfehler zu behandeln result = subprocess.run(['tracert', host], capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=30) return jsonify({ "success": True, "output": result.stdout, "error": result.stderr, "return_code": result.returncode }) except subprocess.TimeoutExpired: return jsonify({"success": False, "error": "Zeitüberschreitung beim Traceroute-Befehl"}) except Exception as e: return jsonify({"success": False, "error": str(e)}) else: return jsonify({"success": False, "error": "Ungültiger Hostname oder IP-Adresse"}) @app.route('/nslookup/') def nslookup_host(host): """Führt eine DNS-Abfrage für den angegebenen Host durch.""" if is_valid_hostname(host): try: # Verwende errors='replace' um Dekodierungsfehler zu behandeln result = subprocess.run(['nslookup', host], capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=10) return jsonify({ "success": result.returncode == 0, "output": result.stdout, "error": result.stderr, "return_code": result.returncode }) except subprocess.TimeoutExpired: return jsonify({"success": False, "error": "Zeitüberschreitung beim NSLookup-Befehl"}) except Exception as e: return jsonify({"success": False, "error": str(e)}) else: return jsonify({"success": False, "error": "Ungültiger Hostname oder IP-Adresse"}) @app.route('/backend-status') def backend_status(): """Überprüft den Status des Haupt-Backend-Servers.""" try: # Benutze den Localhost und den Port des Haupt-Backends (Standard: 5000) backend_port = 5000 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(2) result = s.connect_ex(('localhost', backend_port)) s.close() return jsonify({ "status": "online" if result == 0 else "offline", "port": backend_port, "error_code": result }) except Exception as e: return jsonify({ "status": "error", "message": str(e) }) @app.route('/api/docker/inspect/') def inspect_container(container_id): """Gibt detaillierte Informationen zu einem Docker-Container zurück.""" if not DOCKER_AVAILABLE: return jsonify({'success': False, 'error': 'Docker ist nicht verfügbar'}) try: # Container-Details abrufen container = docker_client.containers.get(container_id) inspect_data = container.attrs # Vereinfachte Übersicht zusammenstellen simplified_data = { 'id': inspect_data['Id'], 'name': inspect_data['Name'].lstrip('/'), 'image': inspect_data['Config']['Image'], 'created': inspect_data['Created'], 'state': inspect_data['State'], 'ports': inspect_data['NetworkSettings']['Ports'], 'volumes': inspect_data['Mounts'], 'networks': inspect_data['NetworkSettings']['Networks'], 'environment': inspect_data['Config']['Env'], 'labels': inspect_data['Config']['Labels'], 'cmd': inspect_data['Config']['Cmd'], 'entrypoint': inspect_data['Config']['Entrypoint'], 'host_config': { 'restart_policy': inspect_data['HostConfig']['RestartPolicy'], 'binds': inspect_data['HostConfig']['Binds'], 'port_bindings': inspect_data['HostConfig']['PortBindings'], } } return jsonify({'success': True, 'data': simplified_data}) except docker.errors.NotFound: return jsonify({'success': False, 'error': f'Container mit ID {container_id} nicht gefunden'}) except Exception as e: logger.error(f"Fehler bei Container-Inspektion: {e}") return jsonify({'success': False, 'error': str(e)}) @app.route('/api/docker/logs/download/') def download_container_logs(container_id): """Lädt die Logs eines Docker-Containers als Datei herunter.""" if not DOCKER_AVAILABLE: return jsonify({'success': False, 'error': 'Docker ist nicht verfügbar'}) try: # Container-Details abrufen container = docker_client.containers.get(container_id) logs = container.logs(timestamps=True).decode('utf-8', errors='replace') # Temporäre Datei erstellen temp_dir = tempfile.mkdtemp() container_name = container.name.replace('/', '_') file_name = f"{container_name}_logs_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" file_path = os.path.join(temp_dir, secure_filename(file_name)) # Logs in Datei schreiben with open(file_path, 'w', encoding='utf-8') as f: f.write(logs) # Datei zum Download senden response = send_file(file_path, as_attachment=True, download_name=file_name) # Aufräumen nach Response @response.call_on_close def cleanup(): shutil.rmtree(temp_dir) return response except docker.errors.NotFound: return jsonify({'success': False, 'error': f'Container mit ID {container_id} nicht gefunden'}) except Exception as e: logger.error(f"Fehler beim Herunterladen der Container-Logs: {e}") return jsonify({'success': False, 'error': str(e)}) @app.route('/api/network/scan-ports', methods=['POST']) def scan_ports(): """Führt einen Port-Scan auf dem angegebenen Host durch.""" global PORT_SCAN_RUNNING if PORT_SCAN_RUNNING: return jsonify({'success': False, 'error': 'Ein Port-Scan läuft bereits'}) data = request.json target_host = data.get('host') port_range = data.get('port_range', '1-1024') if not target_host: return jsonify({'success': False, 'error': 'Kein Ziel-Host angegeben'}) # Überprüfe, ob der Hostname/IP gültig ist if not is_valid_hostname(target_host): return jsonify({'success': False, 'error': 'Ungültiger Hostname oder IP-Adresse'}) # Parsen des Port-Bereichs try: if '-' in port_range: start_port, end_port = map(int, port_range.split('-')) else: start_port = end_port = int(port_range) if start_port < 1 or end_port > 65535 or start_port > end_port: raise ValueError("Ungültiger Port-Bereich") except Exception: return jsonify({'success': False, 'error': 'Ungültiger Port-Bereich (Format: 1-1024)'}) # Port-Scan im Hintergrund starten thread = threading.Thread(target=port_scan_thread, args=(target_host, start_port, end_port)) thread.daemon = True thread.start() PORT_SCAN_RUNNING = True return jsonify({'success': True, 'message': f'Port-Scan auf {target_host} für Ports {start_port}-{end_port} gestartet'}) @app.route('/api/network/scan-status') def scan_status(): """Gibt den Status des laufenden Port-Scans zurück.""" global PORT_SCAN_RUNNING global PORT_SCAN_RESULTS if PORT_SCAN_RUNNING: return jsonify({'success': True, 'status': 'running', 'message': 'Port-Scan läuft'}) else: # Wenn kein Scan läuft, gib die Ergebnisse des letzten Scans zurück (falls vorhanden) if 'PORT_SCAN_RESULTS' in globals(): return jsonify({'success': True, 'status': 'completed', 'results': PORT_SCAN_RESULTS}) else: return jsonify({'success': True, 'status': 'idle', 'message': 'Kein Port-Scan aktiv'}) def port_scan_thread(host, start_port, end_port): """Führt einen Port-Scan im Hintergrund durch.""" global PORT_SCAN_RUNNING global PORT_SCAN_RESULTS try: results = [] service_dict = get_common_services() # Timeout für Socket-Verbindungen (in Sekunden) timeout = 0.5 # Port-Scan durchführen for port in range(start_port, end_port + 1): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) result = sock.connect_ex((host, port)) if result == 0: service = service_dict.get(port, "Unbekannter Dienst") results.append({ 'port': port, 'status': 'open', 'service': service }) sock.close() # Kurze Pause, um die CPU-Last zu reduzieren time.sleep(0.01) # Ergebnisse speichern PORT_SCAN_RESULTS = { 'host': host, 'start_port': start_port, 'end_port': end_port, 'timestamp': datetime.datetime.now().isoformat(), 'open_ports': results } except Exception as e: logger.error(f"Fehler bei Port-Scan: {e}") PORT_SCAN_RESULTS = { 'host': host, 'error': str(e), 'timestamp': datetime.datetime.now().isoformat() } finally: PORT_SCAN_RUNNING = False def get_common_services(): """Gibt ein Wörterbuch mit gängigen Port-Dienst-Zuordnungen zurück.""" return { 21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS", 80: "HTTP", 110: "POP3", 143: "IMAP", 443: "HTTPS", 465: "SMTP (SSL)", 587: "SMTP (TLS)", 993: "IMAP (SSL)", 995: "POP3 (SSL)", 1433: "MS SQL Server", 3306: "MySQL", 3389: "RDP", 5000: "Flask/Python Web", 5432: "PostgreSQL", 6379: "Redis", 8080: "HTTP-Alternativ", 8443: "HTTPS-Alternativ", 9000: "PHP-FPM", 9090: "Prometheus", 9200: "Elasticsearch", 27017: "MongoDB" } @app.route('/api/network/interfaces') def get_interfaces(): """Gibt detaillierte Informationen zu allen Netzwerkschnittstellen zurück.""" interfaces = [] for iface_name in netifaces.interfaces(): iface_info = {} iface_info['name'] = iface_name # IP-Adressen abrufen addresses = netifaces.ifaddresses(iface_name) # IPv4-Adressen if netifaces.AF_INET in addresses: iface_info['ipv4'] = addresses[netifaces.AF_INET] else: iface_info['ipv4'] = [] # IPv6-Adressen if netifaces.AF_INET6 in addresses: iface_info['ipv6'] = addresses[netifaces.AF_INET6] else: iface_info['ipv6'] = [] # MAC-Adresse if netifaces.AF_LINK in addresses: iface_info['mac'] = addresses[netifaces.AF_LINK][0].get('addr', 'Nicht verfügbar') else: iface_info['mac'] = 'Nicht verfügbar' # Statistiken try: stats = psutil.net_io_counters(pernic=True).get(iface_name) if stats: iface_info['stats'] = { 'bytes_sent': stats.bytes_sent, 'bytes_recv': stats.bytes_recv, 'packets_sent': stats.packets_sent, 'packets_recv': stats.packets_recv, 'errin': stats.errin, 'errout': stats.errout, 'dropin': stats.dropin, 'dropout': stats.dropout } except: iface_info['stats'] = 'Nicht verfügbar' interfaces.append(iface_info) return jsonify({'success': True, 'interfaces': interfaces}) @app.route('/api/network/active-connections') def active_connections(): """Gibt alle aktiven Netzwerkverbindungen zurück.""" try: connections = [] for conn in psutil.net_connections(kind='inet'): if conn.status == 'ESTABLISHED': connection_info = { 'local_address': f"{conn.laddr.ip}:{conn.laddr.port}", 'remote_address': f"{conn.raddr.ip}:{conn.raddr.port}" if conn.raddr else 'Keine', 'status': conn.status, 'pid': conn.pid, 'process': None } # Prozessinformationen hinzufügen, falls verfügbar if conn.pid: try: process = psutil.Process(conn.pid) connection_info['process'] = { 'name': process.name(), 'create_time': datetime.datetime.fromtimestamp(process.create_time()).strftime('%Y-%m-%d %H:%M:%S'), 'username': process.username() } except (psutil.NoSuchProcess, psutil.AccessDenied): pass connections.append(connection_info) return jsonify({'success': True, 'connections': connections}) except Exception as e: logger.error(f"Fehler beim Abrufen der aktiven Verbindungen: {e}") return jsonify({'success': False, 'error': str(e)}) @app.route('/api/network/route-table') def route_table(): """Gibt die Routing-Tabelle zurück.""" try: if sys.platform.startswith('win'): output = subprocess.check_output(['route', 'print'], universal_newlines=True) else: output = subprocess.check_output(['netstat', '-rn'], universal_newlines=True) return jsonify({'success': True, 'route_table': output}) except subprocess.SubprocessError as e: logger.error(f"Fehler beim Abrufen der Routing-Tabelle: {e}") return jsonify({'success': False, 'error': str(e)}) @app.route('/api/logs/tail/') def tail_logs(log_type): """Gibt die letzten Zeilen eines Logfiles zurück.""" log_files = { 'backend': os.path.join('..', '..', 'logs', 'backend.log'), 'frontend': os.path.join('..', '..', 'logs', 'frontend.log'), 'debug': os.path.join('..', '..', 'logs', 'debug-server.log') } if log_type not in log_files: return jsonify({'success': False, 'error': f'Unbekannter Log-Typ: {log_type}'}) log_file = log_files[log_type] lines = request.args.get('lines', 100, type=int) if not os.path.exists(log_file): return jsonify({'success': False, 'error': f'Log-Datei existiert nicht: {log_file}'}) try: if sys.platform.startswith('win'): # Windows-spezifischer Befehl output = subprocess.check_output( ['powershell', '-Command', f'Get-Content -Path "{log_file}" -Tail {lines}'], universal_newlines=True ) else: # Unix-Befehl output = subprocess.check_output( ['tail', '-n', str(lines), log_file], universal_newlines=True ) log_entries = output.splitlines() return jsonify({'success': True, 'entries': log_entries, 'count': len(log_entries)}) except subprocess.SubprocessError as e: logger.error(f"Fehler beim Lesen der Log-Datei: {e}") return jsonify({'success': False, 'error': str(e)}) @app.route('/healthcheck') def healthcheck(): """Gibt eine einfache Gesundheitsprüfung zurück.""" checks = {} # Systemressourcen prüfen cpu_percent = psutil.cpu_percent(interval=0.1) memory = psutil.virtual_memory() disk = psutil.disk_usage('/') checks['system'] = { 'cpu': { 'percent': cpu_percent, 'status': 'healthy' if cpu_percent < 90 else 'warning' if cpu_percent < 95 else 'critical' }, 'memory': { 'percent': memory.percent, 'status': 'healthy' if memory.percent < 90 else 'warning' if memory.percent < 95 else 'critical' }, 'disk': { 'percent': disk.percent, 'status': 'healthy' if disk.percent < 90 else 'warning' if disk.percent < 95 else 'critical' } } # Docker-Status prüfen if DOCKER_AVAILABLE: try: docker_info = docker_client.info() docker_status = 'running' docker_containers_running = docker_info.get('ContainersRunning', 0) checks['docker'] = { 'status': docker_status, 'containers_running': docker_containers_running, 'overall': 'healthy' } except Exception: checks['docker'] = { 'status': 'error', 'overall': 'critical' } else: checks['docker'] = { 'status': 'not_available', 'overall': 'warning' } # Backend-Verbindung prüfen config = get_config() try: backend_url = f"http://{config['backend_hostname']}:{config['backend_port']}/api/test" response = requests.get(backend_url, timeout=2) backend_status = { 'status': 'connected' if response.status_code == 200 else 'error', 'http_code': response.status_code, 'overall': 'healthy' if response.status_code == 200 else 'critical' } except requests.exceptions.RequestException: backend_status = { 'status': 'unreachable', 'overall': 'critical' } checks['backend'] = backend_status # Frontend-Verbindung prüfen try: frontend_url = f"http://{config['frontend_hostname']}:{config['frontend_port']}" response = requests.get(frontend_url, timeout=2) frontend_status = { 'status': 'connected' if response.status_code == 200 else 'error', 'http_code': response.status_code, 'overall': 'healthy' if response.status_code == 200 else 'critical' } except requests.exceptions.RequestException: frontend_status = { 'status': 'unreachable', 'overall': 'critical' } checks['frontend'] = frontend_status # Gesamtstatus bestimmen overall_status = 'healthy' for component, check in checks.items(): if check.get('overall') == 'critical': overall_status = 'critical' break elif check.get('overall') == 'warning' and overall_status != 'critical': overall_status = 'warning' return jsonify({ 'status': overall_status, 'timestamp': datetime.datetime.now().isoformat(), 'checks': checks }) if __name__ == "__main__": try: os.makedirs("../../logs", exist_ok=True) print(f"Debug-Server startet auf Port {DEBUG_PORT}") app.run(host="0.0.0.0", port=DEBUG_PORT, debug=True) except Exception as e: print(f"Fehler beim Starten des Debug-Servers: {e}") sys.exit(1)