From f2bd44a7189632994197edae8ac5daa2130e0f09 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Fri, 30 May 2025 22:13:46 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9A=20Improved=20printer=20management?= =?UTF-8?q?=20system=20&=20session=20handling=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/app.py | 462 ++++++++++++----------- backend/app/database/myp.db | Bin 110592 -> 110592 bytes backend/app/static/js/printer_monitor.js | 24 +- backend/app/static/js/session-manager.js | 7 +- backend/app/templates/printers.html | 2 +- 5 files changed, 255 insertions(+), 240 deletions(-) diff --git a/backend/app/app.py b/backend/app/app.py index 086a922c..f21c74c2 100644 --- a/backend/app/app.py +++ b/backend/app/app.py @@ -3496,7 +3496,7 @@ def toggle_printer_power(printer_id): # Standard-Zustand ermitteln (Toggle-Verhalten) db_session = get_db_session() - printer = db_session.query(Printer).get(printer_id) + printer = db_session.get(Printer, printer_id) # Modernized from query().get() if not printer: db_session.close() @@ -4808,234 +4808,6 @@ def extend_session(): return jsonify({"error": "Session-Verlängerung fehlgeschlagen"}), 500 -# ===== STARTUP UND MAIN ===== -if __name__ == "__main__": - import sys - import signal - import os - - # Debug-Modus prüfen - debug_mode = len(sys.argv) > 1 and sys.argv[1] == "--debug" - - # Windows-spezifische Umgebungsvariablen setzen für bessere Flask-Kompatibilität - if os.name == 'nt' and debug_mode: - # Entferne problematische Werkzeug-Variablen - os.environ.pop('WERKZEUG_SERVER_FD', None) - os.environ.pop('WERKZEUG_RUN_MAIN', None) - - # Setze saubere Umgebung - os.environ['FLASK_ENV'] = 'development' - os.environ['PYTHONIOENCODING'] = 'utf-8' - os.environ['PYTHONUTF8'] = '1' - - # Windows-spezifisches Signal-Handling für ordnungsgemäßes Shutdown - def signal_handler(sig, frame): - """Signal-Handler für ordnungsgemäßes Shutdown.""" - app_logger.warning(f"🛑 Signal {sig} empfangen - fahre System herunter...") - try: - # Queue Manager stoppen - app_logger.info("🔄 Beende Queue Manager...") - stop_queue_manager() - - # Scheduler stoppen falls aktiviert - if SCHEDULER_ENABLED and scheduler: - try: - scheduler.stop() - app_logger.info("Job-Scheduler gestoppt") - except Exception as e: - app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}") - - # ===== DATENBANKVERBINDUNGEN ORDNUNGSGEMÄSS SCHLIESSEN ===== - app_logger.info("💾 Führe Datenbank-Cleanup durch...") - try: - from models import get_db_session, create_optimized_engine - from sqlalchemy import text - - # WAL-Checkpoint ausführen um .shm und .wal Dateien zu bereinigen - engine = create_optimized_engine() - - with engine.connect() as conn: - # Vollständiger WAL-Checkpoint (TRUNCATE-Modus) - app_logger.info("📝 Führe WAL-Checkpoint durch...") - result = conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)")).fetchone() - - if result: - app_logger.info(f"WAL-Checkpoint abgeschlossen: {result[1]} Seiten übertragen, {result[2]} Seiten zurückgesetzt") - - # Alle pending Transaktionen committen - conn.commit() - - # Journal-Mode zu DELETE wechseln (entfernt .wal/.shm Dateien) - app_logger.info("📁 Schalte Journal-Mode um...") - conn.execute(text("PRAGMA journal_mode=DELETE")) - - # Optimize und Vacuum für sauberen Zustand - conn.execute(text("PRAGMA optimize")) - conn.execute(text("VACUUM")) - - conn.commit() - - # Engine-Connection-Pool schließen - engine.dispose() - - app_logger.info("✅ Datenbank-Cleanup abgeschlossen - WAL-Dateien sollten verschwunden sein") - - except Exception as db_error: - app_logger.error(f"❌ Fehler beim Datenbank-Cleanup: {str(db_error)}") - - app_logger.info("✅ Shutdown abgeschlossen") - sys.exit(0) - except Exception as e: - app_logger.error(f"❌ Fehler beim Shutdown: {str(e)}") - sys.exit(1) - - # Signal-Handler registrieren (Windows-kompatibel) - if os.name == 'nt': # Windows - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - # Zusätzlich für Flask-Development-Server - signal.signal(signal.SIGBREAK, signal_handler) - else: # Unix/Linux - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - signal.signal(signal.SIGHUP, signal_handler) - - try: - # Datenbank initialisieren - init_database() - create_initial_admin() - - # Template-Hilfsfunktionen registrieren - register_template_helpers(app) - - # Drucker-Monitor Steckdosen-Initialisierung beim Start - try: - app_logger.info("🖨️ Starte automatische Steckdosen-Initialisierung...") - initialization_results = printer_monitor.initialize_all_outlets_on_startup() - - if initialization_results: - success_count = sum(1 for success in initialization_results.values() if success) - total_count = len(initialization_results) - app_logger.info(f"✅ Steckdosen-Initialisierung: {success_count}/{total_count} Drucker erfolgreich") - - if success_count < total_count: - app_logger.warning(f"⚠️ {total_count - success_count} Drucker konnten nicht initialisiert werden") - else: - app_logger.info("ℹ️ Keine Drucker zur Initialisierung gefunden") - - except Exception as e: - app_logger.error(f"❌ Fehler bei automatischer Steckdosen-Initialisierung: {str(e)}") - - # Queue-Manager für automatische Drucker-Überwachung starten - # Nur im Produktionsmodus starten (nicht im Debug-Modus) - if not debug_mode: - try: - queue_manager = start_queue_manager() - app_logger.info("✅ Printer Queue Manager erfolgreich gestartet") - - # Verbesserte Shutdown-Handler registrieren - def cleanup_queue_manager(): - try: - app_logger.info("🔄 Beende Queue Manager...") - stop_queue_manager() - except Exception as e: - app_logger.error(f"❌ Fehler beim Queue Manager Cleanup: {str(e)}") - - atexit.register(cleanup_queue_manager) - - # ===== DATENBANK-CLEANUP BEIM PROGRAMMENDE ===== - def cleanup_database(): - """Führt Datenbank-Cleanup beim normalen Programmende aus.""" - try: - app_logger.info("💾 Führe finales Datenbank-Cleanup durch...") - from models import create_optimized_engine - from sqlalchemy import text - - engine = create_optimized_engine() - - with engine.connect() as conn: - # WAL-Checkpoint für sauberes Beenden - result = conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)")).fetchone() - if result and result[1] > 0: - app_logger.info(f"Final WAL-Checkpoint: {result[1]} Seiten übertragen") - - # Journal-Mode umschalten um .wal/.shm Dateien zu entfernen - conn.execute(text("PRAGMA journal_mode=DELETE")) - conn.commit() - - # Connection-Pool ordnungsgemäß schließen - engine.dispose() - app_logger.info("✅ Finales Datenbank-Cleanup abgeschlossen") - - except Exception as e: - app_logger.error(f"❌ Fehler beim finalen Datenbank-Cleanup: {str(e)}") - - atexit.register(cleanup_database) - - except Exception as e: - app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}") - else: - app_logger.info("🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung") - - # Scheduler starten (falls aktiviert) - if SCHEDULER_ENABLED: - try: - scheduler.start() - app_logger.info("Job-Scheduler gestartet") - except Exception as e: - app_logger.error(f"Fehler beim Starten des Schedulers: {str(e)}") - - if debug_mode: - # Debug-Modus: HTTP auf Port 5000 - app_logger.info("Starte Debug-Server auf 0.0.0.0:5000 (HTTP)") - - # Windows-spezifische Flask-Konfiguration - run_kwargs = { - "host": "0.0.0.0", - "port": 5000, - "debug": True, - "threaded": True - } - - if os.name == 'nt': - # Windows: Deaktiviere Auto-Reload um WERKZEUG_SERVER_FD Fehler zu vermeiden - run_kwargs["use_reloader"] = False - run_kwargs["passthrough_errors"] = False - app_logger.info("Windows-Debug-Modus: Auto-Reload deaktiviert") - - app.run(**run_kwargs) - else: - # Produktions-Modus: HTTPS auf Port 443 - ssl_context = get_ssl_context() - - if ssl_context: - app_logger.info("Starte HTTPS-Server auf 0.0.0.0:443") - app.run( - host="0.0.0.0", - port=443, - debug=False, - ssl_context=ssl_context, - threaded=True - ) - else: - app_logger.info("Starte HTTP-Server auf 0.0.0.0:8080") - app.run( - host="0.0.0.0", - port=8080, - debug=False, - threaded=True - ) - except KeyboardInterrupt: - app_logger.info("🔄 Tastatur-Unterbrechung empfangen - beende Anwendung...") - signal_handler(signal.SIGINT, None) - except Exception as e: - app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}") - # Cleanup bei Fehler - try: - stop_queue_manager() - except: - pass - sys.exit(1) # ===== GASTANTRÄGE API-ROUTEN ===== @@ -5618,4 +5390,234 @@ def export_guest_requests(): return jsonify({ 'success': False, 'message': f'Fehler beim Export: {str(e)}' - }), 500 \ No newline at end of file + }), 500 + + +# ===== STARTUP UND MAIN ===== +if __name__ == "__main__": + import sys + import signal + import os + + # Debug-Modus prüfen + debug_mode = len(sys.argv) > 1 and sys.argv[1] == "--debug" + + # Windows-spezifische Umgebungsvariablen setzen für bessere Flask-Kompatibilität + if os.name == 'nt' and debug_mode: + # Entferne problematische Werkzeug-Variablen + os.environ.pop('WERKZEUG_SERVER_FD', None) + os.environ.pop('WERKZEUG_RUN_MAIN', None) + + # Setze saubere Umgebung + os.environ['FLASK_ENV'] = 'development' + os.environ['PYTHONIOENCODING'] = 'utf-8' + os.environ['PYTHONUTF8'] = '1' + + # Windows-spezifisches Signal-Handling für ordnungsgemäßes Shutdown + def signal_handler(sig, frame): + """Signal-Handler für ordnungsgemäßes Shutdown.""" + app_logger.warning(f"🛑 Signal {sig} empfangen - fahre System herunter...") + try: + # Queue Manager stoppen + app_logger.info("🔄 Beende Queue Manager...") + stop_queue_manager() + + # Scheduler stoppen falls aktiviert + if SCHEDULER_ENABLED and scheduler: + try: + scheduler.stop() + app_logger.info("Job-Scheduler gestoppt") + except Exception as e: + app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}") + + # ===== DATENBANKVERBINDUNGEN ORDNUNGSGEMÄSS SCHLIESSEN ===== + app_logger.info("💾 Führe Datenbank-Cleanup durch...") + try: + from models import get_db_session, create_optimized_engine + from sqlalchemy import text + + # WAL-Checkpoint ausführen um .shm und .wal Dateien zu bereinigen + engine = create_optimized_engine() + + with engine.connect() as conn: + # Vollständiger WAL-Checkpoint (TRUNCATE-Modus) + app_logger.info("📝 Führe WAL-Checkpoint durch...") + result = conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)")).fetchone() + + if result: + app_logger.info(f"WAL-Checkpoint abgeschlossen: {result[1]} Seiten übertragen, {result[2]} Seiten zurückgesetzt") + + # Alle pending Transaktionen committen + conn.commit() + + # Journal-Mode zu DELETE wechseln (entfernt .wal/.shm Dateien) + app_logger.info("📁 Schalte Journal-Mode um...") + conn.execute(text("PRAGMA journal_mode=DELETE")) + + # Optimize und Vacuum für sauberen Zustand + conn.execute(text("PRAGMA optimize")) + conn.execute(text("VACUUM")) + + conn.commit() + + # Engine-Connection-Pool schließen + engine.dispose() + + app_logger.info("✅ Datenbank-Cleanup abgeschlossen - WAL-Dateien sollten verschwunden sein") + + except Exception as db_error: + app_logger.error(f"❌ Fehler beim Datenbank-Cleanup: {str(db_error)}") + + app_logger.info("✅ Shutdown abgeschlossen") + sys.exit(0) + except Exception as e: + app_logger.error(f"❌ Fehler beim Shutdown: {str(e)}") + sys.exit(1) + + # Signal-Handler registrieren (Windows-kompatibel) + if os.name == 'nt': # Windows + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + # Zusätzlich für Flask-Development-Server + signal.signal(signal.SIGBREAK, signal_handler) + else: # Unix/Linux + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGHUP, signal_handler) + + try: + # Datenbank initialisieren + init_database() + create_initial_admin() + + # Template-Hilfsfunktionen registrieren + register_template_helpers(app) + + # Drucker-Monitor Steckdosen-Initialisierung beim Start + try: + app_logger.info("🖨️ Starte automatische Steckdosen-Initialisierung...") + initialization_results = printer_monitor.initialize_all_outlets_on_startup() + + if initialization_results: + success_count = sum(1 for success in initialization_results.values() if success) + total_count = len(initialization_results) + app_logger.info(f"✅ Steckdosen-Initialisierung: {success_count}/{total_count} Drucker erfolgreich") + + if success_count < total_count: + app_logger.warning(f"⚠️ {total_count - success_count} Drucker konnten nicht initialisiert werden") + else: + app_logger.info("ℹ️ Keine Drucker zur Initialisierung gefunden") + + except Exception as e: + app_logger.error(f"❌ Fehler bei automatischer Steckdosen-Initialisierung: {str(e)}") + + # Queue-Manager für automatische Drucker-Überwachung starten + # Nur im Produktionsmodus starten (nicht im Debug-Modus) + if not debug_mode: + try: + queue_manager = start_queue_manager() + app_logger.info("✅ Printer Queue Manager erfolgreich gestartet") + + # Verbesserte Shutdown-Handler registrieren + def cleanup_queue_manager(): + try: + app_logger.info("🔄 Beende Queue Manager...") + stop_queue_manager() + except Exception as e: + app_logger.error(f"❌ Fehler beim Queue Manager Cleanup: {str(e)}") + + atexit.register(cleanup_queue_manager) + + # ===== DATENBANK-CLEANUP BEIM PROGRAMMENDE ===== + def cleanup_database(): + """Führt Datenbank-Cleanup beim normalen Programmende aus.""" + try: + app_logger.info("💾 Führe finales Datenbank-Cleanup durch...") + from models import create_optimized_engine + from sqlalchemy import text + + engine = create_optimized_engine() + + with engine.connect() as conn: + # WAL-Checkpoint für sauberes Beenden + result = conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)")).fetchone() + if result and result[1] > 0: + app_logger.info(f"Final WAL-Checkpoint: {result[1]} Seiten übertragen") + + # Journal-Mode umschalten um .wal/.shm Dateien zu entfernen + conn.execute(text("PRAGMA journal_mode=DELETE")) + conn.commit() + + # Connection-Pool ordnungsgemäß schließen + engine.dispose() + app_logger.info("✅ Finales Datenbank-Cleanup abgeschlossen") + + except Exception as e: + app_logger.error(f"❌ Fehler beim finalen Datenbank-Cleanup: {str(e)}") + + atexit.register(cleanup_database) + + except Exception as e: + app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}") + else: + app_logger.info("🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung") + + # Scheduler starten (falls aktiviert) + if SCHEDULER_ENABLED: + try: + scheduler.start() + app_logger.info("Job-Scheduler gestartet") + except Exception as e: + app_logger.error(f"Fehler beim Starten des Schedulers: {str(e)}") + + if debug_mode: + # Debug-Modus: HTTP auf Port 5000 + app_logger.info("Starte Debug-Server auf 0.0.0.0:5000 (HTTP)") + + # Windows-spezifische Flask-Konfiguration + run_kwargs = { + "host": "0.0.0.0", + "port": 5000, + "debug": True, + "threaded": True + } + + if os.name == 'nt': + # Windows: Deaktiviere Auto-Reload um WERKZEUG_SERVER_FD Fehler zu vermeiden + run_kwargs["use_reloader"] = False + run_kwargs["passthrough_errors"] = False + app_logger.info("Windows-Debug-Modus: Auto-Reload deaktiviert") + + app.run(**run_kwargs) + else: + # Produktions-Modus: HTTPS auf Port 443 + ssl_context = get_ssl_context() + + if ssl_context: + app_logger.info("Starte HTTPS-Server auf 0.0.0.0:443") + app.run( + host="0.0.0.0", + port=443, + debug=False, + ssl_context=ssl_context, + threaded=True + ) + else: + app_logger.info("Starte HTTP-Server auf 0.0.0.0:8080") + app.run( + host="0.0.0.0", + port=8080, + debug=False, + threaded=True + ) + except KeyboardInterrupt: + app_logger.info("🔄 Tastatur-Unterbrechung empfangen - beende Anwendung...") + signal_handler(signal.SIGINT, None) + except Exception as e: + app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}") + # Cleanup bei Fehler + try: + stop_queue_manager() + except: + pass + sys.exit(1) \ No newline at end of file diff --git a/backend/app/database/myp.db b/backend/app/database/myp.db index 7f7c5c33b21aeefd8cbe3250df3b54c2867fbb8d..617e3bb8019da4257bd5b76cbb4043e986b63bea 100644 GIT binary patch delta 315 zcmZp8z}E19ZGyC*ECU0BG!Vmp^h6zFM%j%CAN5QPQ&Izp9dq5>BFvLaT)j$zlTBUn z^-=;2LOs%aO^g!DeXD|<^3u!A(tIqFvdS`Dv)$YZC%5Sb@fleeSXdbv>lvFEnOT@_ zex|RWz{keGAIrCkPn?1?mhhUe@-f(pTB~cTi;F8yE{v6$d_5+B zyELyTH8D9OF)1f?vVN?Z4A?{?OFa{FBSSNzMiUlPO<8CfBqslkMsK7iq zAzo~=LEN>4e5)Dwzwkfjzr}x+e-Hoa&4LEg`4t6NlsOrZEM`R)*nWne(PRMtaf({c delta 303 zcmZp8z}E19ZGyC*Bm)D3G!Vmp)I=R)M#+r{AN5SbqfFhSGJO0@{fu%dTnk-&13gnJ z{F7a?LvzXu^Yl&Avn=xhbCL~xyfafgE%XZu^;{E;Cb#Ja@fli~8dw<`>X}%Ym>C;v zex|RWz{kVDAIrCkPn_a2mhc*~@-f(pTB~cTi;K%n&Wn|rd@&|~ zqo61=uOu@seX>rhnhel5BP&B=JqsgaQxlU$Ll#sWS;(p-CjXA*6NKn6)w47*GdD17 zRA8Q*5HGgbAnw{izKsn0U-+N%-{L>Zzn_2OW { - this.printers.set(printer.id, { - ...printer, - statusInfo: this.statusCategories[printer.status] || this.statusCategories['offline'] + // Null-Check für data.printers hinzufügen + if (data && data.printers && typeof data.printers === 'object') { + Object.values(data.printers).forEach(printer => { + this.printers.set(printer.id, { + ...printer, + statusInfo: this.statusCategories[printer.status] || this.statusCategories['offline'] + }); }); - }); + } else { + console.warn('⚠️ Keine gültigen Drucker-Daten erhalten:', data); + // Benachrichtige Callbacks über Fehler + this.notifyCallbacks({ + type: 'error', + message: 'Ungültige Drucker-Daten erhalten', + data: data + }); + return; + } - this.lastUpdate = new Date(data.timestamp); + this.lastUpdate = new Date(data.timestamp || Date.now()); // Änderungen erkennen und benachrichtigen const changes = this.detectChanges(previousPrinters, this.printers); diff --git a/backend/app/static/js/session-manager.js b/backend/app/static/js/session-manager.js index 188c1d7c..103d17af 100644 --- a/backend/app/static/js/session-manager.js +++ b/backend/app/static/js/session-manager.js @@ -109,9 +109,9 @@ class SessionManager { 'X-Requested-With': 'XMLHttpRequest' }; - // CSRF-Token hinzufügen wenn verfügbar + // CSRF-Token hinzufügen wenn verfügbar - Flask-WTF erwartet X-CSRFToken oder den Token im Body if (csrfToken) { - headers['X-CSRF-Token'] = csrfToken; + headers['X-CSRFToken'] = csrfToken; } const response = await fetch('/api/session/heartbeat', { @@ -119,7 +119,8 @@ class SessionManager { headers: headers, body: JSON.stringify({ timestamp: new Date().toISOString(), - page: window.location.pathname + page: window.location.pathname, + csrf_token: csrfToken // Zusätzlich im Body senden }) }); diff --git a/backend/app/templates/printers.html b/backend/app/templates/printers.html index 40109443..a0a7a798 100644 --- a/backend/app/templates/printers.html +++ b/backend/app/templates/printers.html @@ -1249,7 +1249,7 @@ class PrinterManager { async init() { await this.loadPrinters(); - this.setupFilters(); + this.populateFilterDropdowns(); // setupFilters() existiert nicht - verwende populateFilterDropdowns() this.initializePerformanceMonitoring(); }