diff --git a/.gitattributes b/.gitattributes index 2f378656..b1e7a502 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,11 @@ -frontend/docker/images/*.tar.xz filter=lfs diff=lfs merge=lfs -text +# Standard: alles als Text behandeln und LF speichern +* text=auto eol=lf + +# Beispiel für Ausnahmen +*.png binary +*.jpg binary + + # Shell-Skripte: immer LF *.sh text eol=lf diff --git a/README.md b/README.md index fe479d38..57d5f5cb 100755 --- a/README.md +++ b/README.md @@ -50,3 +50,90 @@ MYP *(Manage your Printer)* ist eine Plattform zur Reservierung von 3D-Druckern, - `User` ist verknüpft mit `PrintJob`, `Account` und `Session` über Benutzer-ID. - `Printer` ist verknüpft mit `PrintJob` über die Drucker-ID. + +## Installation und Start + +### Windows + +1. Stellen Sie sicher, dass Docker Desktop installiert und gestartet ist. +2. Öffnen Sie PowerShell und navigieren Sie zum Projektverzeichnis. +3. Führen Sie das Start-Skript aus: + ``` + .\start.ps1 + ``` + +### Linux/Mac + +1. Stellen Sie sicher, dass Docker installiert und gestartet ist. +2. Öffnen Sie ein Terminal und navigieren Sie zum Projektverzeichnis. +3. Machen Sie die Skripte ausführbar: + ``` + chmod +x start.sh cleanup.sh + ``` +4. Führen Sie das Start-Skript aus: + ``` + ./start.sh + ``` + +## Bereinigung der Umgebung + +Um eine vollständige Bereinigung aller Container, Images und Volumes durchzuführen, können Sie folgende Skripte verwenden: + +### Windows + +``` +.\cleanup.ps1 +``` + +### Linux/Mac + +``` +./cleanup.sh +``` + +Die Bereinigung kann auch während des Starts der Anwendung durchgeführt werden, wenn alte Container erkannt werden. + +## Backend-Interface + +Das Backend-Interface ist unter folgenden URLs erreichbar: + +- Produktionsumgebung: http://localhost:5000 +- Debug-Server: http://localhost:5555 + +## Frontend-Interface + +Das Frontend-Interface ist unter folgenden URLs erreichbar: + +- Produktionsumgebung: http://localhost:80 oder https://localhost:443 +- Debug-Server: http://localhost:8081 + +## SSH-Server + +Die Start-Skripte sorgen automatisch dafür, dass ein SSH-Server installiert, aktiviert und gestartet wird: + +- Unter Linux: Verwendet systemd, um den OpenSSH-Server zu aktivieren und zu starten +- Unter Windows: Installiert und konfiguriert den Windows OpenSSH-Server + +Der SSH-Server wird so konfiguriert, dass er automatisch beim Systemstart aktiviert wird, um einen Fernzugriff auf das System zu ermöglichen. Auf Windows-Systemen wird auch eine entsprechende Firewall-Regel erstellt. + +### Manuelles Einrichten des SSH-Servers + +Falls die automatische Installation fehlschlägt: + +#### Linux (Debian/Ubuntu) +``` +sudo apt-get update +sudo apt-get install openssh-server +sudo systemctl enable ssh +sudo systemctl start ssh +``` + +#### Windows 10/11 +1. Öffnen Sie "Einstellungen" > "Apps" > "Optionale Features" +2. Klicken Sie auf "Feature hinzufügen" und wählen Sie "OpenSSH Server" +3. Nach der Installation öffnen Sie PowerShell als Administrator und führen Sie folgende Befehle aus: +``` +Set-Service -Name sshd -StartupType Automatic +Start-Service sshd +New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (TCP-In)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 +``` diff --git a/backend/app.py b/backend/app.py index 7d2e356a..4ea3fb5e 100755 --- a/backend/app.py +++ b/backend/app.py @@ -17,6 +17,9 @@ from datetime import timedelta from PyP100 import PyP100 from dotenv import load_dotenv +# Importiere Netzwerkkonfiguration +from network_config import NetworkConfig + # Lade Umgebungsvariablen load_dotenv() @@ -24,6 +27,9 @@ load_dotenv() app = Flask(__name__) CORS(app, supports_credentials=True) +# Initialisiere Netzwerkkonfiguration +network_config = NetworkConfig(app) + # Konfiguration app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev_secret_key') app.config['DATABASE'] = os.environ.get('DATABASE_PATH', 'instance/myp.db') diff --git a/backend/debug-server/app.py b/backend/debug-server/app.py index 78c73e03..1196b942 100644 --- a/backend/debug-server/app.py +++ b/backend/debug-server/app.py @@ -1,7 +1,45 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from flask import Flask, render_template, jsonify +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 @@ -10,17 +48,626 @@ 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(): - """Rendert die Hauptseite des Debug-Servers.""" - return render_template('index.html', - hostname=socket.gethostname(), - ip_address=get_ip_address(), - timestamp=datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")) + """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(): @@ -55,31 +702,36 @@ def docker(): @app.route('/ping/') def ping_host(host): - """Pingt einen Zielhost an und gibt das Ergebnis zurück.""" - if is_valid_hostname(host): - try: - result = subprocess.run(['ping', '-n', '4', host], - capture_output=True, text=True, 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 Ping-Befehl"}) - except Exception as e: - return jsonify({"success": False, "error": str(e)}) - else: - return jsonify({"success": False, "error": "Ungültiger Hostname oder IP-Adresse"}) + """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, timeout=30) + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + timeout=30) return jsonify({ "success": True, "output": result.stdout, @@ -98,8 +750,13 @@ 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, timeout=10) + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + timeout=10) return jsonify({ "success": result.returncode == 0, "output": result.stdout, @@ -135,173 +792,453 @@ def backend_status(): "message": str(e) }) -# Hilfsfunktionen - -def get_ip_address(): - """Ermittelt die primäre IP-Adresse des Servers.""" +@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: - # Für die lokale Netzwerk-IP - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.connect(("8.8.8.8", 80)) - return s.getsockname()[0] - except: - return "127.0.0.1" # Fallback auf localhost - finally: - s.close() - -def get_uptime(): - """Ermittelt die Systemlaufzeit.""" - return str(datetime.timedelta(seconds=int(psutil.boot_time()))) - -def get_memory_info(): - """Gibt Informationen über den Speicherverbrauch zurück.""" - mem = psutil.virtual_memory() - return { - "total": format_bytes(mem.total), - "used": format_bytes(mem.used), - "free": format_bytes(mem.available), - "percent": mem.percent - } - -def get_disk_info(): - """Gibt Informationen über den Festplattenspeicher zurück.""" - disk = psutil.disk_usage('/') - return { - "total": format_bytes(disk.total), - "used": format_bytes(disk.used), - "free": format_bytes(disk.free), - "percent": disk.percent - } - -def get_network_interfaces(): - """Gibt Informationen über die Netzwerkschnittstellen zurück.""" - interfaces = {} - for iface in netifaces.interfaces(): - addrs = netifaces.ifaddresses(iface) - if netifaces.AF_INET in addrs: - ipv4 = addrs[netifaces.AF_INET][0] - interfaces[iface] = { - "ip": ipv4.get('addr', ''), - "netmask": ipv4.get('netmask', ''), - "broadcast": ipv4.get('broadcast', '') + # 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'], } - else: - interfaces[iface] = {"ip": "Keine IPv4-Adresse", "netmask": "", "broadcast": ""} - - return interfaces - -def get_active_connections(): - """Gibt Informationen über aktive Netzwerkverbindungen zurück.""" - connections = [] - for conn in psutil.net_connections(kind='inet'): - if conn.status == 'ESTABLISHED': - connections.append({ - "local_address": f"{conn.laddr.ip}:{conn.laddr.port}", - "remote_address": f"{conn.raddr.ip}:{conn.raddr.port}", - "status": conn.status, - "pid": conn.pid - }) - - return connections[:20] # Begrenze auf 20 Verbindungen - -def get_dns_servers(): - """Ermittelt die DNS-Server-Konfiguration.""" - dns_servers = [] - try: - if os.name == 'nt': # Windows - output = subprocess.check_output( - ['powershell', '-Command', - "Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -ExpandProperty ServerAddresses"], - text=True) - for line in output.strip().split('\n'): - line = line.strip() - if line: - dns_servers.append(line) - else: # Linux/Unix - with open('/etc/resolv.conf', 'r') as f: - for line in f: - if line.startswith('nameserver'): - dns_servers.append(line.split()[1]) + } + + 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: - dns_servers.append(f"Fehler: {str(e)}") - - return dns_servers + logger.error(f"Fehler bei Container-Inspektion: {e}") + return jsonify({'success': False, 'error': str(e)}) -def get_default_gateway(): - """Ermittelt das Standard-Gateway.""" - gateways = netifaces.gateways() - if 'default' in gateways and netifaces.AF_INET in gateways['default']: - return gateways['default'][netifaces.AF_INET][0] - return "Kein Standard-Gateway gefunden" - -def get_docker_info(): - """Gibt Informationen über Docker und laufende Container zurück.""" - docker_info = {"installed": False, "version": "", "containers": []} +@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: - # Prüfe, ob Docker installiert ist - version_result = subprocess.run(['docker', '--version'], - capture_output=True, text=True, timeout=5) - if version_result.returncode == 0: - docker_info["installed"] = True - docker_info["version"] = version_result.stdout.strip() + # 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() - # Hole Informationen über laufende Container - container_result = subprocess.run( - ['docker', 'ps', '--format', '{{.ID}},{{.Image}},{{.Status}},{{.Ports}},{{.Names}}'], - capture_output=True, text=True, timeout=5) + # 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) - if container_result.returncode == 0: - for line in container_result.stdout.strip().split('\n'): - if line: - parts = line.split(',') - if len(parts) >= 5: - docker_info["containers"].append({ - "id": parts[0], - "image": parts[1], - "status": parts[2], - "ports": parts[3], - "name": parts[4] - }) - except (subprocess.SubprocessError, FileNotFoundError): - pass # Docker ist nicht installiert oder nicht zugänglich + 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' + } - return docker_info - -def is_valid_hostname(hostname): - """Überprüft, ob ein Hostname oder eine IP-Adresse gültig ist.""" - if len(hostname) > 255: - return False - if hostname == "localhost" or hostname == "127.0.0.1": - return True - - # Prüfe auf IP-Format + # Backend-Verbindung prüfen + config = get_config() try: - socket.inet_pton(socket.AF_INET, hostname) - return True - except socket.error: - pass + 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: - socket.inet_pton(socket.AF_INET6, hostname) - return True - except socket.error: - pass + 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' + } - # Prüfe auf Hostname-Format - allowed = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.") - return all(c in allowed for c in hostname) and not hostname.startswith(".") - -def format_bytes(size): - """Formatiert Bytes in eine lesbare Größe.""" - power = 2**10 # 1024 - n = 0 - power_labels = {0: 'B', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'} - while size > power and n < 4: - size /= power - n += 1 - return f"{size:.2f} {power_labels[n]}" + 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__": - print(f"MYP Backend Debug-Server wird gestartet auf Port {DEBUG_PORT}...") - app.run(host='0.0.0.0', port=DEBUG_PORT, debug=True) \ No newline at end of file + 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) \ No newline at end of file diff --git a/backend/debug-server/requirements.txt b/backend/debug-server/requirements.txt index 527e7789..0133aae1 100644 --- a/backend/debug-server/requirements.txt +++ b/backend/debug-server/requirements.txt @@ -1,3 +1,15 @@ -Flask==2.0.1 -psutil==5.9.0 -netifaces==0.11.0 \ No newline at end of file +flask==2.2.3 +flask-cors==3.0.10 +psutil==5.9.4 +matplotlib==3.7.1 +pandas==1.5.3 +requests==2.28.2 +netifaces==0.11.0 +Werkzeug==2.2.3 +docker==6.1.2 +pillow==9.4.0 +send_file==0.2.0 +python-dotenv>=1.0.0 +python-logging-loki>=0.3.1 +colorama>=0.4.6 +pygal>=3.0.0 \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 60e37985..1a04427a 100755 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -4,7 +4,9 @@ services: backend: build: . container_name: myp-backend - network_mode: host + networks: + backend_network: + ipv4_address: 192.168.0.5 environment: - SECRET_KEY=${SECRET_KEY:-7445630171969DFAC92C53CEC92E67A9CB2E00B3CB2F} - DATABASE_PATH=${DATABASE_PATH:-instance/myp.db} @@ -13,13 +15,25 @@ services: - "PRINTERS=${PRINTERS:-{\"Printer 1\": {\"ip\": \"192.168.0.100\"}, \"Printer 2\": {\"ip\": \"192.168.0.101\"}, \"Printer 3\": {\"ip\": \"192.168.0.102\"}, \"Printer 4\": {\"ip\": \"192.168.0.103\"}, \"Printer 5\": {\"ip\": \"192.168.0.104\"}, \"Printer 6\": {\"ip\": \"192.168.0.106\"}}}" - FLASK_APP=app.py - PYTHONUNBUFFERED=1 + - HOST=0.0.0.0 + - PORT=5000 + ports: + - "5000:5000" volumes: - ./logs:/app/logs - ./instance:/app/instance - restart: unless-stopped + restart: always healthcheck: test: ["CMD", "wget", "--spider", "http://localhost:5000/health"] interval: 30s timeout: 10s retries: 3 - start_period: 40s \ No newline at end of file + start_period: 40s + +networks: + backend_network: + driver: bridge + ipam: + config: + - subnet: 192.168.0.0/24 + gateway: 192.168.0.1 \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 79a3a167..13666c9a 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,4 +4,6 @@ pyjwt==2.8.0 python-dotenv==1.0.0 werkzeug==2.3.7 gunicorn==21.2.0 -PyP100==0.0.19 \ No newline at end of file +PyP100==0.0.19 +netifaces==0.11.0 +requests==2.31.0 \ No newline at end of file