diff --git a/backend/APP_USAGE.md b/backend/APP_USAGE.md new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/backend/APP_USAGE.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/START_SERVER.py b/backend/START_SERVER.py new file mode 100644 index 000000000..383df91f5 --- /dev/null +++ b/backend/START_SERVER.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +MYP Druckerverwaltung - SINGLE ENTRY POINT +========================================== + +Diese Datei ist der EINZIGE Einstiegspunkt für das MYP-System. +Sie verwendet immer die korrekte und aktuellste App-Konfiguration. + +VERWENDUNG: +- Development: python START_SERVER.py +- Production: sudo python START_SERVER.py --production +- Mit SSL: python START_SERVER.py --ssl + +Dies ersetzt alle anderen App-Dateien und sorgt für Konsistenz. +""" + +import os +import sys +import platform +from datetime import datetime + +print("=" * 60) +print("🚀 MYP DRUCKERVERWALTUNG - UNIFIED STARTER") +print("=" * 60) +print(f"📅 Start-Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") +print(f"💻 Plattform: {platform.system()} {platform.release()}") +print(f"🐍 Python: {sys.version}") +print() + +# Bestimme Betriebsmodus +production_mode = '--production' in sys.argv or '--prod' in sys.argv +ssl_mode = '--ssl' in sys.argv or '--https' in sys.argv or production_mode + +print(f"🎯 Modus: {'🏭 PRODUCTION' if production_mode else '🔧 DEVELOPMENT'}") +print(f"🔐 SSL: {'✅ AKTIVIERT' if ssl_mode else '❌ DEAKTIVIERT'}") +print() + +# Warnung für Production-Modus +if production_mode: + print("⚠️ PRODUKTIONS-MODUS AKTIVIERT") + print(" - HTTPS-Only (Port 443)") + print(" - SSL-Zertifikate erforderlich") + print(" - Root-Berechtigung erforderlich (Linux)") + print() + +# Importiere und starte die Haupt-App +try: + print("📦 Lade MYP-System...") + + # Verwende immer app.py als einzige Quelle + from app import main as start_app + + print("✅ MYP-System erfolgreich geladen") + print("🚀 Server wird gestartet...") + print("=" * 60) + print() + + # Starte die App + start_app() + +except KeyboardInterrupt: + print() + print("🛑 Server durch Benutzer gestoppt (Strg+C)") + sys.exit(0) + +except ImportError as e: + print(f"❌ FEHLER: MYP-System konnte nicht geladen werden") + print(f" Details: {e}") + print() + print("💡 LÖSUNGSVORSCHLÄGE:") + print(" 1. Stelle sicher, dass alle Abhängigkeiten installiert sind:") + print(" pip install -r requirements.txt") + print(" 2. Prüfe, ob app.py existiert und funktional ist") + print(" 3. Führe das System im Backend-Verzeichnis aus") + sys.exit(1) + +except Exception as e: + print(f"❌ KRITISCHER FEHLER beim Start: {e}") + print() + print("💡 FEHLERBEHEBUNG:") + print(" 1. Prüfe die Log-Dateien für Details") + print(" 2. Stelle sicher, dass die Datenbank erreichbar ist") + print(" 3. Bei SSL-Problemen: Starte ohne --ssl") + import traceback + print(f" Debug-Info: {traceback.format_exc()}") + sys.exit(1) \ No newline at end of file diff --git a/backend/app.py b/backend/app.py index 39f4e75fd..7f7fb9378 100644 --- a/backend/app.py +++ b/backend/app.py @@ -510,7 +510,8 @@ def api_get_printers(): from models import get_db_session, Printer db_session = get_db_session() - printers = db_session.query(Printer).filter(Printer.active == True).all() + # Alle Drucker für API-Abfragen anzeigen (unabhängig von active-Status) + printers = db_session.query(Printer).all() printer_list = [] for printer in printers: @@ -545,7 +546,8 @@ def api_get_printer_status(): from utils.tapo_controller import tapo_controller db_session = get_db_session() - printers = db_session.query(Printer).filter(Printer.active == True).all() + # Alle Drucker für Status-Abfragen anzeigen (unabhängig von active-Status) + printers = db_session.query(Printer).all() status_list = [] for printer in printers: diff --git a/backend/app_unified.py b/backend/app_unified.py new file mode 100644 index 000000000..f7f0c2ed3 --- /dev/null +++ b/backend/app_unified.py @@ -0,0 +1,603 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +MYP Druckerverwaltung - UNIFIED VERSION +====================================== + +Einheitliche Flask App für Entwicklung UND Produktion. +Diese App ersetzt sowohl app.py als auch app_production.py. + +Verwendung: +- Development: python app_unified.py +- Production: sudo python app_unified.py --production +- SSL-Force: python app_unified.py --ssl + +Version: 6.0.0 Unified +""" + +import os +import sys +import ssl +import logging +import platform +import argparse +from datetime import datetime, timedelta + +# Füge App-Verzeichnis zum Python-Pfad hinzu +sys.path.insert(0, '/opt/myp') +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +# Import der Haupt-App-Logik +from app import app, app_logger, init_database, create_initial_admin, main as app_main +from app import start_queue_manager, stop_queue_manager, get_job_scheduler, cleanup_rate_limiter + +# Flask-Imports für Request-Handling +from flask import request, redirect + +# =========================== UMGEBUNGS-ERKENNUNG =========================== + +def detect_environment(): + """Erkennt automatisch die Laufzeitumgebung""" + + # Kommandozeilen-Argumente prüfen + if '--production' in sys.argv or '--prod' in sys.argv: + return 'production' + + if '--development' in sys.argv or '--dev' in sys.argv: + return 'development' + + # Umgebungsvariablen prüfen + env_mode = os.getenv('MYP_MODE', '').lower() + if env_mode in ['production', 'prod']: + return 'production' + elif env_mode in ['development', 'dev']: + return 'development' + + # Automatische Erkennung basierend auf System + if detect_raspberry_pi(): + return 'production' + + if platform.system() == 'Windows': + return 'development' + + # Standard: Development für unbekannte Systeme + return 'development' + +def detect_raspberry_pi(): + """Erkennt ob das System auf einem Raspberry Pi läuft""" + try: + with open('/proc/cpuinfo', 'r') as f: + cpuinfo = f.read() + if 'Raspberry Pi' in cpuinfo or 'BCM' in cpuinfo: + return True + except: + pass + + try: + machine = platform.machine().lower() + if 'arm' in machine or 'aarch64' in machine: + return True + except: + pass + + return os.getenv('FORCE_RASPBERRY_PI', '').lower() in ['true', '1', 'yes'] + +def should_use_ssl(): + """Bestimmt ob SSL verwendet werden soll""" + if '--ssl' in sys.argv or '--https' in sys.argv: + return True + + if '--no-ssl' in sys.argv or '--http' in sys.argv: + return False + + env_ssl = os.getenv('MYP_SSL', '').lower() + if env_ssl in ['true', '1', 'yes', 'force']: + return True + elif env_ssl in ['false', '0', 'no', 'disable']: + return False + + # Automatisch: SSL für Production, HTTP für Development + return detect_environment() == 'production' + +# =========================== KONFIGURATIONSKLASSEN =========================== + +class DevelopmentConfig: + """Konfiguration für Entwicklungsumgebung""" + + # Debug-Einstellungen + DEBUG = True + TESTING = False + + # HTTP-Konfiguration + FORCE_HTTPS = False + SSL_REQUIRED = False + HTTP_PORT = 5000 + + # Performance (weniger optimiert für bessere Debug-Möglichkeiten) + OPTIMIZED_MODE = False + USE_MINIFIED_ASSETS = False + DISABLE_ANIMATIONS = False + + # Session-Konfiguration (weniger restriktiv für Development) + SESSION_COOKIE_SECURE = False + SESSION_COOKIE_HTTPONLY = True + SESSION_COOKIE_SAMESITE = 'Lax' + + # Reload-Features für Development + TEMPLATES_AUTO_RELOAD = True + EXPLAIN_TEMPLATE_LOADING = False + +class ProductionConfig: + """Konfiguration für Produktionsumgebung""" + + # Produktions-Einstellungen + DEBUG = False + TESTING = False + + # HTTPS-Only Konfiguration + FORCE_HTTPS = True + SSL_REQUIRED = True + HTTPS_PORT = 443 + + # Performance-Optimierungen + OPTIMIZED_MODE = True + USE_MINIFIED_ASSETS = True + DISABLE_ANIMATIONS = True + + # Sicherheits-Einstellungen + SESSION_COOKIE_SECURE = True + SESSION_COOKIE_HTTPONLY = True + SESSION_COOKIE_SAMESITE = 'Strict' + WTF_CSRF_ENABLED = True + + # Template-Optimierungen + TEMPLATES_AUTO_RELOAD = False + EXPLAIN_TEMPLATE_LOADING = False + + # SSL-Konfiguration + SSL_CERT_PATH = None # Wird automatisch erkannt + SSL_KEY_PATH = None # Wird automatisch erkannt + +# =========================== SSL-SETUP =========================== + +def get_ssl_paths(): + """Ermittelt die SSL-Zertifikat-Pfade plattformspezifisch""" + + if platform.system() == 'Windows': + ssl_dir = os.path.join(os.path.dirname(__file__), 'ssl') + else: + # Probiere verschiedene Standard-Pfade + possible_dirs = [ + '/opt/myp/ssl', + '/etc/ssl/myp', + os.path.join(os.path.dirname(__file__), 'ssl'), + './ssl' + ] + + ssl_dir = None + for dir_path in possible_dirs: + if os.path.exists(dir_path): + ssl_dir = dir_path + break + + if not ssl_dir: + ssl_dir = possible_dirs[0] # Erstelle in /opt/myp/ssl + + cert_file = os.path.join(ssl_dir, 'cert.pem') + key_file = os.path.join(ssl_dir, 'key.pem') + + return ssl_dir, cert_file, key_file + +def setup_ssl_certificates(): + """Erstellt SSL-Zertifikate falls sie nicht existieren""" + + ssl_dir, cert_file, key_file = get_ssl_paths() + + app_logger.info(f"🔐 Prüfe SSL-Zertifikate in: {ssl_dir}") + + # Erstelle SSL-Verzeichnis + os.makedirs(ssl_dir, exist_ok=True) + + # Prüfe ob Zertifikate existieren + if os.path.exists(cert_file) and os.path.exists(key_file): + try: + # Teste Zertifikat-Gültigkeit + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.load_cert_chain(cert_file, key_file) + app_logger.info("✅ Bestehende SSL-Zertifikate sind gültig") + return cert_file, key_file + except Exception as e: + app_logger.warning(f"⚠️ Bestehende SSL-Zertifikate ungültig: {e}") + + # Erstelle neue Zertifikate + app_logger.info("🔧 Erstelle neue SSL-Zertifikate...") + + try: + # Versuche existierende SSL-Utilities zu verwenden + if os.path.exists('./ssl/ssl_fix.py'): + try: + import subprocess + result = subprocess.run([ + sys.executable, './ssl/ssl_fix.py' + ], capture_output=True, text=True, timeout=60) + + if result.returncode == 0: + app_logger.info("✅ SSL-Zertifikate mit ssl_fix.py erstellt") + return cert_file, key_file + except Exception as e: + app_logger.warning(f"⚠️ ssl_fix.py fehlgeschlagen: {e}") + + # Fallback: Einfache SSL-Erstellung + create_simple_ssl_certificates(ssl_dir, cert_file, key_file) + return cert_file, key_file + + except Exception as e: + app_logger.error(f"❌ SSL-Zertifikat-Erstellung fehlgeschlagen: {e}") + raise Exception(f"SSL-Setup fehlgeschlagen: {e}") + +def create_simple_ssl_certificates(ssl_dir, cert_file, key_file): + """Erstellt einfache selbstsignierte SSL-Zertifikate""" + + try: + # Versuche mit Python Cryptography Library + from cryptography import x509 + from cryptography.x509.oid import NameOID + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import rsa + import ipaddress + + app_logger.info("🐍 Erstelle SSL-Zertifikate mit Python Cryptography...") + + # Private Key generieren + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + + # Subject und Issuer + subject = issuer = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, "DE"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Baden-Wuerttemberg"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Stuttgart"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Mercedes-Benz AG"), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "MYP Druckerverwaltung"), + x509.NameAttribute(NameOID.COMMON_NAME, platform.node()), + ]) + + # Subject Alternative Names + san_list = [ + x509.DNSName("localhost"), + x509.DNSName("127.0.0.1"), + x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")), + x509.DNSName(platform.node()), + ] + + # Zertifikat erstellen + cert = x509.CertificateBuilder().subject_name( + subject + ).issuer_name( + issuer + ).public_key( + private_key.public_key() + ).serial_number( + x509.random_serial_number() + ).not_valid_before( + datetime.now() + ).not_valid_after( + datetime.now() + timedelta(days=365) + ).add_extension( + x509.SubjectAlternativeName(san_list), + critical=False, + ).sign(private_key, hashes.SHA256()) + + # Private Key schreiben + with open(key_file, 'wb') as f: + f.write(private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + )) + + # Zertifikat schreiben + with open(cert_file, 'wb') as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + + # Berechtigungen setzen (Unix) + try: + os.chmod(cert_file, 0o644) + os.chmod(key_file, 0o600) + except: + pass # Windows hat andere Berechtigungen + + app_logger.info("✅ SSL-Zertifikate mit Python Cryptography erstellt") + + except ImportError: + # Fallback: OpenSSL verwenden + app_logger.info("🔧 Erstelle SSL-Zertifikate mit OpenSSL...") + import subprocess + + # Private Key erstellen + subprocess.run([ + 'openssl', 'genrsa', '-out', key_file, '2048' + ], check=True, capture_output=True) + + # Selbstsigniertes Zertifikat erstellen + subprocess.run([ + 'openssl', 'req', '-new', '-x509', + '-key', key_file, + '-out', cert_file, + '-days', '365', + '-subj', f'/C=DE/ST=Baden-Wuerttemberg/L=Stuttgart/O=Mercedes-Benz AG/CN={platform.node()}' + ], check=True, capture_output=True) + + app_logger.info("✅ SSL-Zertifikate mit OpenSSL erstellt") + +def get_ssl_context(): + """Erstellt SSL-Kontext mit Zertifikaten""" + + if not should_use_ssl(): + return None + + try: + cert_file, key_file = setup_ssl_certificates() + + # SSL-Kontext erstellen + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.load_cert_chain(cert_file, key_file) + + # Sichere SSL-Einstellungen + context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS') + context.options |= ssl.OP_NO_SSLv2 + context.options |= ssl.OP_NO_SSLv3 + context.options |= ssl.OP_NO_TLSv1 + context.options |= ssl.OP_NO_TLSv1_1 + + app_logger.info("✅ SSL-Kontext erfolgreich konfiguriert") + return context + + except Exception as e: + app_logger.error(f"❌ SSL-Kontext-Erstellung fehlgeschlagen: {e}") + app_logger.warning("⚠️ Fallback zu HTTP ohne SSL") + return None + +# =========================== APP-KONFIGURATION =========================== + +def configure_app_for_environment(environment): + """Konfiguriert die App für die erkannte Umgebung""" + + if environment == 'production': + config_class = ProductionConfig + app_logger.info("🚀 Produktions-Modus aktiviert") + else: + config_class = DevelopmentConfig + app_logger.info("🔧 Entwicklungs-Modus aktiviert") + + # Konfiguration anwenden + for attr in dir(config_class): + if not attr.startswith('_'): + app.config[attr] = getattr(config_class, attr) + + # Jinja-Globals setzen + app.jinja_env.globals.update({ + 'environment': environment, + 'optimized_mode': config_class.OPTIMIZED_MODE, + 'use_minified_assets': config_class.USE_MINIFIED_ASSETS if hasattr(config_class, 'USE_MINIFIED_ASSETS') else False, + 'disable_animations': config_class.DISABLE_ANIMATIONS if hasattr(config_class, 'DISABLE_ANIMATIONS') else False, + }) + + return config_class + +# =========================== MIDDLEWARE =========================== + +@app.before_request +def force_https_if_required(): + """Erzwingt HTTPS wenn in der Konfiguration aktiviert""" + if (app.config.get('FORCE_HTTPS', False) and + not request.is_secure and + not request.headers.get('X-Forwarded-Proto') == 'https'): + + # Redirect zu HTTPS + url = request.url.replace('http://', 'https://', 1) + if ':5000' in url: + url = url.replace(':5000', ':443') + elif ':80' in url: + url = url.replace(':80', ':443') + + return redirect(url, code=301) + +@app.after_request +def add_environment_headers(response): + """Fügt umgebungsspezifische Headers hinzu""" + + if app.config.get('FORCE_HTTPS', False): + # Produktions-Sicherheits-Headers + response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' + response.headers['X-Content-Type-Options'] = 'nosniff' + response.headers['X-Frame-Options'] = 'SAMEORIGIN' + response.headers['X-XSS-Protection'] = '1; mode=block' + response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' + + # Cache-Headers für statische Dateien + if request.endpoint == 'static' or '/static/' in request.path: + if app.config.get('OPTIMIZED_MODE', False): + response.headers['Cache-Control'] = 'public, max-age=31536000' + else: + response.headers['Cache-Control'] = 'public, max-age=3600' + + return response + +# =========================== LOGGING-SETUP =========================== + +def setup_environment_logging(environment): + """Konfiguriert Logging für die Umgebung""" + + if environment == 'production': + # Produktions-Logging: Weniger verbose + logging.getLogger('werkzeug').setLevel(logging.WARNING) + logging.getLogger('urllib3').setLevel(logging.WARNING) + app_logger.setLevel(logging.INFO) + + # Entferne Debug-Handler + for handler in app_logger.handlers[:]: + if handler.level == logging.DEBUG: + app_logger.removeHandler(handler) + else: + # Development-Logging: Vollständig + app_logger.setLevel(logging.DEBUG) + + app_logger.info(f"✅ Logging für {environment} konfiguriert") + +# =========================== ARGUMENT-PARSER =========================== + +def parse_arguments(): + """Parst Kommandozeilen-Argumente für vereinheitlichte Steuerung""" + parser = argparse.ArgumentParser(description='MYP Druckerverwaltung - Unified Server') + + parser.add_argument('--production', '--prod', action='store_true', + help='Starte im Produktions-Modus') + parser.add_argument('--ssl', '--https', action='store_true', + help='Erzwinge SSL/HTTPS') + parser.add_argument('--port', type=int, default=None, + help='Port-Nummer') + + return parser.parse_args() + +def show_usage_info(): + """Zeigt Nutzungsinformationen an""" + environment = "Production" if '--production' in sys.argv else "Development" + ssl_enabled = '--ssl' in sys.argv or '--production' in sys.argv + + app_logger.info("🎯 MYP Unified App - Eine einzige funktionale App!") + app_logger.info(f"📋 Modus: {environment}") + app_logger.info(f"🔐 SSL: {'Aktiviert' if ssl_enabled else 'Deaktiviert'}") + app_logger.info(f"💻 Plattform: {platform.system()}") + app_logger.info("=" * 60) + +# =========================== HAUPTFUNKTION =========================== + +def main(): + """Hauptfunktion für den unified Server""" + + try: + # Argumente parsen + args = parse_arguments() + + # Umgebung ermitteln + environment = detect_environment() + + # Logging für Umgebung konfigurieren + setup_environment_logging(environment) + + app_logger.info("🚀 MYP Unified Server startet...") + app_logger.info(f"📅 Start-Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + app_logger.info(f"🖥️ Hostname: {platform.node()}") + app_logger.info(f"🐍 Python: {sys.version}") + app_logger.info(f"🌍 Umgebung: {environment}") + app_logger.info(f"💻 Plattform: {platform.system()} {platform.release()}") + + # App für Umgebung konfigurieren + config_class = configure_app_for_environment(environment) + + # Root-Berechtigung prüfen (nur für Production + Port 443) + if (environment == 'production' and + config_class.HTTPS_PORT == 443 and + hasattr(os, 'geteuid') and + os.geteuid() != 0): + app_logger.error("❌ Root-Berechtigung erforderlich für Port 443") + app_logger.error("💡 Führe aus mit: sudo python app_unified.py --production") + sys.exit(1) + elif platform.system() == 'Windows' and environment == 'production': + app_logger.info("🪟 Windows: Root-Check übersprungen") + + # SSL-Kontext erstellen falls erforderlich + ssl_context = get_ssl_context() + + # Datenbank initialisieren + init_database() + create_initial_admin() + + # Background-Services starten + start_queue_manager() + + scheduler = get_job_scheduler() + if scheduler: + scheduler.start() + app_logger.info("✅ Job-Scheduler gestartet") + + # Server-Konfiguration + if args.port: + port = args.port + elif ssl_context and environment == 'production': + port = 443 + elif environment == 'production': + port = 5443 # Alternative HTTPS-Port falls keine Root-Rechte + else: + port = 5000 # Development HTTP-Port + + # Debug-Modus + debug_mode = (environment == 'development' and not ssl_context) + + # Server-Informationen anzeigen + protocol = 'https' if ssl_context else 'http' + app_logger.info(f"🌐 Server läuft auf: {protocol}://{platform.node()}:{port}") + if platform.system() == 'Windows': + app_logger.info(f"🏠 Lokaler Zugriff: {protocol}://localhost:{port}") + + if ssl_context: + app_logger.info("🔐 SSL/HTTPS aktiviert") + else: + app_logger.info("🔓 HTTP-Modus (unverschlüsselt)") + + # Flask-Server starten + app.run( + host=platform.node(), + port=port, + ssl_context=ssl_context, + debug=debug_mode, + threaded=True, + use_reloader=False # Deaktiviert für Produktionsstabilität + ) + + except PermissionError: + app_logger.error("❌ Berechtigung verweigert") + if platform.system() != 'Windows': + app_logger.error("💡 Führe als Root aus: sudo python app_unified.py --production") + else: + app_logger.error("💡 Führe als Administrator aus") + sys.exit(1) + + except OSError as e: + if "Address already in use" in str(e): + app_logger.error("❌ Port bereits belegt") + app_logger.error("💡 Andere Services stoppen oder anderen Port verwenden") + else: + app_logger.error(f"❌ Netzwerk-Fehler: {e}") + sys.exit(1) + + except KeyboardInterrupt: + app_logger.info("🛑 Server durch Benutzer gestoppt") + sys.exit(0) + + except Exception as e: + app_logger.error(f"❌ Kritischer Fehler beim Server-Start: {e}") + import traceback + app_logger.error(f"Traceback: {traceback.format_exc()}") + sys.exit(1) + + finally: + # Cleanup + try: + stop_queue_manager() + if 'scheduler' in locals() and scheduler: + scheduler.shutdown() + cleanup_rate_limiter() + app_logger.info("✅ Cleanup abgeschlossen") + except: + pass + +if __name__ == "__main__": + args = parse_arguments() + show_usage_info() + + # Verwende die existierende App-Main-Funktion + app_main() \ No newline at end of file diff --git a/backend/blueprints/__pycache__/admin_unified.cpython-313.pyc b/backend/blueprints/__pycache__/admin_unified.cpython-313.pyc index 2d6ef8cca..25fdadf95 100644 Binary files a/backend/blueprints/__pycache__/admin_unified.cpython-313.pyc and b/backend/blueprints/__pycache__/admin_unified.cpython-313.pyc differ diff --git a/backend/blueprints/__pycache__/calendar.cpython-313.pyc b/backend/blueprints/__pycache__/calendar.cpython-313.pyc index 6349287bf..46764161d 100644 Binary files a/backend/blueprints/__pycache__/calendar.cpython-313.pyc and b/backend/blueprints/__pycache__/calendar.cpython-313.pyc differ diff --git a/backend/blueprints/__pycache__/guest.cpython-313.pyc b/backend/blueprints/__pycache__/guest.cpython-313.pyc index 87461b85f..55688f986 100644 Binary files a/backend/blueprints/__pycache__/guest.cpython-313.pyc and b/backend/blueprints/__pycache__/guest.cpython-313.pyc differ diff --git a/backend/blueprints/admin_unified.py b/backend/blueprints/admin_unified.py index 528804a3d..7944f90bb 100644 --- a/backend/blueprints/admin_unified.py +++ b/backend/blueprints/admin_unified.py @@ -135,7 +135,8 @@ def admin_plug_schedules(): # Alle Drucker für Filter-Dropdown with get_cached_session() as db_session: - printers = db_session.query(Printer).filter(Printer.active == True).all() + # Alle Drucker für Auswahlfelder anzeigen (unabhängig von active-Status) + printers = db_session.query(Printer).all() return render_template('admin_plug_schedules.html', stats=stats_24h, diff --git a/backend/blueprints/guest.py b/backend/blueprints/guest.py index 1e9404c67..ad5751dbb 100644 --- a/backend/blueprints/guest.py +++ b/backend/blueprints/guest.py @@ -56,7 +56,8 @@ def guest_request_form(): """Formular für Gastanfragen anzeigen und verarbeiten.""" with get_cached_session() as db_session: # Aktive Drucker für SelectField laden - printers = db_session.query(Printer).filter_by(active=True).all() + # Alle Drucker für Auswahlfelder anzeigen (unabhängig von active-Status) + printers = db_session.query(Printer).all() # Formular erstellen form = GuestRequestForm() @@ -717,7 +718,8 @@ def api_get_request_details(request_id): request_data = guest_request.to_dict() # Verfügbare Drucker für Zuweisung - available_printers = db_session.query(Printer).filter_by(active=True).all() + # Alle Drucker für Auswahlfelder anzeigen (unabhängig von active-Status) + available_printers = db_session.query(Printer).all() request_data["available_printers"] = [p.to_dict() for p in available_printers] # Job-Historie falls vorhanden diff --git a/backend/instance/printer_manager.db b/backend/instance/printer_manager.db index 670a00775..0c0dc26e4 100644 Binary files a/backend/instance/printer_manager.db and b/backend/instance/printer_manager.db differ diff --git a/backend/instance/printer_manager.db-shm b/backend/instance/printer_manager.db-shm index 546411886..cff03c4ec 100644 Binary files a/backend/instance/printer_manager.db-shm and b/backend/instance/printer_manager.db-shm differ diff --git a/backend/instance/printer_manager.db-wal b/backend/instance/printer_manager.db-wal index 263c0fd13..57bc7fb64 100644 Binary files a/backend/instance/printer_manager.db-wal and b/backend/instance/printer_manager.db-wal differ diff --git a/backend/logs/app/app.log b/backend/logs/app/app.log index 7eb50d6cc..67ab9e793 100644 --- a/backend/logs/app/app.log +++ b/backend/logs/app/app.log @@ -4110,3 +4110,8 @@ WHERE users.id = ? 2025-06-11 09:28:20 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs 2025-06-11 09:28:20 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs?page=1 2025-06-11 09:28:35 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs?page=1 +2025-06-11 09:28:50 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs?page=1 +2025-06-11 09:28:50 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs +2025-06-11 09:28:50 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs?page=1 +2025-06-11 09:29:03 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json +2025-06-11 09:29:05 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/static/icons/icon-192.png diff --git a/backend/logs/printers/printers.log b/backend/logs/printers/printers.log index c9e33fcbe..b03fe2148 100644 --- a/backend/logs/printers/printers.log +++ b/backend/logs/printers/printers.log @@ -324,3 +324,6 @@ 2025-06-11 09:25:49 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1) 2025-06-11 09:25:58 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 6 Drucker 2025-06-11 09:25:58 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 9043.66ms +2025-06-11 09:29:04 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1) +2025-06-11 09:29:04 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 6 Drucker +2025-06-11 09:29:04 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 2.40ms diff --git a/backend/logs/queue_manager/queue_manager.log b/backend/logs/queue_manager/queue_manager.log index d521035ea..2e4419600 100644 --- a/backend/logs/queue_manager/queue_manager.log +++ b/backend/logs/queue_manager/queue_manager.log @@ -323,3 +323,8 @@ 2025-06-11 09:25:49 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Queue-Überwachung gestartet (Intervall: 120 Sekunden) 2025-06-11 09:25:49 - [queue_manager] queue_manager - [INFO] INFO - ✅ Printer Queue Manager gestartet 2025-06-11 09:25:49 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestartet +2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Stoppe Queue-Manager... +2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - ⏳ Warte auf Monitor-Thread... +2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - 🛑 Shutdown-Signal empfangen - beende Monitor-Loop +2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - 🔚 Monitor-Loop beendet +2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestoppt diff --git a/backend/logs/user/user.log b/backend/logs/user/user.log index fcfae57b7..d4cad8673 100644 --- a/backend/logs/user/user.log +++ b/backend/logs/user/user.log @@ -111,3 +111,4 @@ 2025-06-11 09:25:49 - [user] user - [INFO] INFO - User admin retrieved settings via API 2025-06-11 09:26:14 - [user] user - [INFO] INFO - User admin retrieved settings via API 2025-06-11 09:26:20 - [user] user - [INFO] INFO - User admin retrieved settings via API +2025-06-11 09:29:04 - [user] user - [INFO] INFO - User admin retrieved settings via API diff --git a/backend/templates/jobs.html b/backend/templates/jobs.html index 907689b91..56adbc634 100644 --- a/backend/templates/jobs.html +++ b/backend/templates/jobs.html @@ -1156,6 +1156,12 @@ class JobManager { option.text += ' - Warteschlange'; } + // Inaktive Drucker kennzeichnen + if (printer.active === false) { + option.text += ' (Inaktiv)'; + option.style.color = '#9CA3AF'; + } + printerSelect.appendChild(option.cloneNode(true)); quickSelect.appendChild(option.cloneNode(true)); filterSelect.appendChild(filterOption);