"Update template files and add windows fixes"

This commit is contained in:
2025-05-29 12:54:24 +02:00
parent 5707d577cc
commit ad92d3d978
7 changed files with 443 additions and 98 deletions

View File

@@ -17,6 +17,16 @@ from typing import List, Dict, Tuple
import time import time
import subprocess import subprocess
import json import json
import signal
# Windows-spezifische Fixes früh importieren
if os.name == 'nt':
try:
from utils.windows_fixes import get_windows_thread_manager, apply_all_windows_fixes
apply_all_windows_fixes()
except ImportError:
# Fallback falls windows_fixes nicht verfügbar
get_windows_thread_manager = None
# Lokale Imports # Lokale Imports
from models import init_database, create_initial_admin, User, Printer, Job, Stats, SystemLog, get_db_session, GuestRequest, UserPermission, Notification from models import init_database, create_initial_admin, User, Printer, Job, Stats, SystemLog, get_db_session, GuestRequest, UserPermission, Notification
@@ -4177,10 +4187,46 @@ def trigger_queue_check():
# ===== STARTUP UND MAIN ===== # ===== STARTUP UND MAIN =====
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
import signal
import os
# Debug-Modus prüfen # Debug-Modus prüfen
debug_mode = len(sys.argv) > 1 and sys.argv[1] == "--debug" debug_mode = len(sys.argv) > 1 and sys.argv[1] == "--debug"
# 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.shutdown(wait=False)
app_logger.info("Job-Scheduler gestoppt")
except Exception as e:
app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}")
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: try:
# Datenbank initialisieren # Datenbank initialisieren
init_database() init_database()
@@ -4190,19 +4236,26 @@ if __name__ == "__main__":
register_template_helpers(app) register_template_helpers(app)
# Queue-Manager für automatische Drucker-Überwachung starten # Queue-Manager für automatische Drucker-Überwachung starten
try: # Nur im Hauptprozess starten (nicht bei Flask Auto-Reload)
queue_manager = start_queue_manager() if not debug_mode or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
app_logger.info("✅ Printer Queue Manager erfolgreich gestartet") try:
queue_manager = start_queue_manager()
# Shutdown-Handler registrieren app_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
def cleanup_queue_manager():
app_logger.info("🔄 Beende Queue Manager...")
stop_queue_manager()
atexit.register(cleanup_queue_manager) # Verbesserte Shutdown-Handler registrieren
def cleanup_queue_manager():
except Exception as e: try:
app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}") 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)
except Exception as e:
app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}")
else:
app_logger.info("🔄 Flask Auto-Reload erkannt - Queue Manager wird im Hauptprozess gestartet")
# Scheduler starten (falls aktiviert) # Scheduler starten (falls aktiviert)
if SCHEDULER_ENABLED: if SCHEDULER_ENABLED:
@@ -4215,12 +4268,25 @@ if __name__ == "__main__":
if debug_mode: if debug_mode:
# Debug-Modus: HTTP auf Port 5000 # Debug-Modus: HTTP auf Port 5000
app_logger.info("Starte Debug-Server auf 0.0.0.0:5000 (HTTP)") app_logger.info("Starte Debug-Server auf 0.0.0.0:5000 (HTTP)")
app.run(
host="0.0.0.0", # Windows-spezifische Flask-Konfiguration für bessere Thread-Behandlung
port=5000, if os.name == 'nt':
debug=True, app.run(
threaded=True host="0.0.0.0",
) port=5000,
debug=True,
threaded=True,
use_reloader=True,
reloader_interval=1,
passthrough_errors=False
)
else:
app.run(
host="0.0.0.0",
port=5000,
debug=True,
threaded=True
)
else: else:
# Produktions-Modus: HTTPS auf Port 443 # Produktions-Modus: HTTPS auf Port 443
ssl_context = get_ssl_context() ssl_context = get_ssl_context()
@@ -4243,6 +4309,14 @@ if __name__ == "__main__":
threaded=True threaded=True
) )
except KeyboardInterrupt:
app_logger.info("🔄 Tastatur-Unterbrechung empfangen - beende Anwendung...")
signal_handler(signal.SIGINT, None)
except Exception as e: except Exception as e:
app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}") app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}")
# Cleanup bei Fehler
try:
stop_queue_manager()
except:
pass
sys.exit(1) sys.exit(1)

View File

@@ -108,7 +108,7 @@
<div class="flex-shrink-0"> <div class="flex-shrink-0">
<a href="{{ url_for('dashboard') }}" class="navbar-brand" aria-label="Zur Startseite"> <a href="{{ url_for('dashboard') }}" class="navbar-brand" aria-label="Zur Startseite">
<!-- Mercedes-Benz Logo --> <!-- Mercedes-Benz Logo -->
<div class="w-9 h-9 sm:w-10 sm:h-10 lg:w-12 lg:h-12 transition-all duration-300 group-hover:rotate-12"> <div class="w-7 h-7 sm:w-8 sm:h-8 lg:w-9 lg:h-9 transition-all duration-300 group-hover:rotate-12">
<svg class="w-full h-full text-slate-900 dark:text-white transition-colors duration-300" fill="currentColor" viewBox="0 0 80 80" aria-hidden="true"> <svg class="w-full h-full text-slate-900 dark:text-white transition-colors duration-300" fill="currentColor" viewBox="0 0 80 80" aria-hidden="true">
<path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5 <path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5
C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40 C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40
@@ -120,8 +120,8 @@
</div> </div>
<!-- Brand Text --> <!-- Brand Text -->
<div class="flex flex-col ml-2"> <div class="flex flex-col ml-2">
<span class="text-xl lg:text-2xl font-bold text-slate-900 dark:text-white transition-colors duration-300 tracking-tight">Mercedes-Benz</span> <span class="text-lg lg:text-xl font-bold text-slate-900 dark:text-white transition-colors duration-300 tracking-tight">Mercedes-Benz</span>
<span class="text-xs lg:text-sm text-slate-600 dark:text-slate-400 font-medium transition-colors duration-300">MYP 3D-Druck Platform</span> <span class="text-xs font-medium text-slate-600 dark:text-slate-400 transition-colors duration-300">MYP 3D-Druck Platform</span>
</div> </div>
</a> </a>
</div> </div>

View File

@@ -251,7 +251,7 @@
<div class="flex justify-between items-center mb-8"> <div class="flex justify-between items-center mb-8">
<div> <div>
<h3 id="modalTitle" class="text-2xl font-bold text-slate-900 dark:text-white mb-2"> <h3 id="modalTitle" class="text-2xl font-bold text-slate-900 dark:text-white mb-2">
Neuen Produktionsauftrag erstellen Neuen Produktionsauftrag erstellen
</h3> </h3>
<p class="text-slate-600 dark:text-slate-400">Planen Sie Ihren nächsten 3D-Druckauftrag mit Präzision</p> <p class="text-slate-600 dark:text-slate-400">Planen Sie Ihren nächsten 3D-Druckauftrag mit Präzision</p>
</div> </div>
@@ -269,7 +269,7 @@
<!-- Titel --> <!-- Titel -->
<div class="md:col-span-2"> <div class="md:col-span-2">
<label for="eventTitle" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3"> <label for="eventTitle" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
📝 Auftrags-Bezeichnung Auftrags-Bezeichnung
</label> </label>
<input type="text" id="eventTitle" name="title" required <input type="text" id="eventTitle" name="title" required
class="w-full px-6 py-4 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300" class="w-full px-6 py-4 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300"
@@ -279,7 +279,7 @@
<!-- Beschreibung --> <!-- Beschreibung -->
<div class="md:col-span-2"> <div class="md:col-span-2">
<label for="eventDescription" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3"> <label for="eventDescription" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
📋 Detaillierte Beschreibung Detaillierte Beschreibung
</label> </label>
<textarea id="eventDescription" name="description" rows="3" <textarea id="eventDescription" name="description" rows="3"
class="w-full px-6 py-4 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300 resize-none" class="w-full px-6 py-4 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300 resize-none"
@@ -289,14 +289,14 @@
<!-- Drucker --> <!-- Drucker -->
<div> <div>
<label for="eventPrinter" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3"> <label for="eventPrinter" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
🏭 Produktionseinheit Produktionseinheit
</label> </label>
<div class="relative"> <div class="relative">
<select id="eventPrinter" name="printerId" required <select id="eventPrinter" name="printerId" required
class="appearance-none w-full px-6 py-4 pr-12 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300 cursor-pointer"> class="appearance-none w-full px-6 py-4 pr-12 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300 cursor-pointer">
<option value="">Drucker auswählen</option> <option value="">Drucker auswählen</option>
{% for printer in printers %} {% for printer in printers %}
<option value="{{ printer.id }}">{{ printer.name }} {% if printer.location %}📍 {{ printer.location }}{% endif %}</option> <option value="{{ printer.id }}">{{ printer.name }} {% if printer.location %}({{ printer.location }}){% endif %}</option>
{% endfor %} {% endfor %}
</select> </select>
<div class="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none"> <div class="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none">
@@ -310,14 +310,14 @@
<!-- Priorität --> <!-- Priorität -->
<div> <div>
<label for="eventPriority" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3"> <label for="eventPriority" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
Prioritätsstufe Prioritätsstufe
</label> </label>
<div class="relative"> <div class="relative">
<select id="eventPriority" name="priority" <select id="eventPriority" name="priority"
class="appearance-none w-full px-6 py-4 pr-12 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300 cursor-pointer"> class="appearance-none w-full px-6 py-4 pr-12 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300 cursor-pointer">
<option value="normal">🔵 Standard</option> <option value="normal">Standard</option>
<option value="high">🟡 Hoch</option> <option value="high">Hoch</option>
<option value="urgent">🔴 Dringend</option> <option value="urgent">Dringend</option>
</select> </select>
<div class="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none"> <div class="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none">
<svg class="h-5 w-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="h-5 w-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -330,7 +330,7 @@
<!-- Start-Zeit --> <!-- Start-Zeit -->
<div> <div>
<label for="eventStart" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3"> <label for="eventStart" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
🕐 Produktionsstart Produktionsstart
</label> </label>
<input type="datetime-local" id="eventStart" name="start" required <input type="datetime-local" id="eventStart" name="start" required
class="w-full px-6 py-4 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300"> class="w-full px-6 py-4 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300">
@@ -339,7 +339,7 @@
<!-- End-Zeit --> <!-- End-Zeit -->
<div> <div>
<label for="eventEnd" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3"> <label for="eventEnd" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
🏁 Produktionsende Produktionsende
</label> </label>
<input type="datetime-local" id="eventEnd" name="end" required <input type="datetime-local" id="eventEnd" name="end" required
class="w-full px-6 py-4 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300"> class="w-full px-6 py-4 border border-slate-200 dark:border-slate-600 rounded-2xl bg-white/90 dark:bg-slate-700/90 backdrop-blur-sm text-slate-800 dark:text-white focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 transition-all duration-300">
@@ -354,11 +354,11 @@
</button> </button>
<button type="submit" <button type="submit"
class="px-8 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-2xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300 shadow-lg hover:shadow-xl font-medium"> class="px-8 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-2xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300 shadow-lg hover:shadow-xl font-medium">
💾 Auftrag speichern Auftrag speichern
</button> </button>
<button type="button" id="deleteEventBtn" onclick="deleteEvent()" style="display: none;" <button type="button" id="deleteEventBtn" onclick="deleteEvent()" style="display: none;"
class="px-8 py-3 bg-gradient-to-r from-red-500 to-red-600 text-white rounded-2xl hover:from-red-600 hover:to-red-700 transition-all duration-300 shadow-lg hover:shadow-xl font-medium"> class="px-8 py-3 bg-gradient-to-r from-red-500 to-red-600 text-white rounded-2xl hover:from-red-600 hover:to-red-700 transition-all duration-300 shadow-lg hover:shadow-xl font-medium">
🗑️ Löschen Löschen
</button> </button>
</div> </div>
</form> </form>
@@ -446,7 +446,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (canEdit) { if (canEdit) {
// Edit existing event // Edit existing event
openCreateEventModal(); openCreateEventModal();
document.getElementById('modalTitle').textContent = 'Produktionsauftrag bearbeiten'; document.getElementById('modalTitle').textContent = 'Produktionsauftrag bearbeiten';
document.getElementById('eventId').value = info.event.id; document.getElementById('eventId').value = info.event.id;
document.getElementById('eventTitle').value = info.event.title; document.getElementById('eventTitle').value = info.event.title;
document.getElementById('eventDescription').value = info.event.extendedProps.description || ''; document.getElementById('eventDescription').value = info.event.extendedProps.description || '';

View File

@@ -237,14 +237,14 @@
{% if printers %} {% if printers %}
<div class="group"> <div class="group">
<label for="printer_id" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3"> <label for="printer_id" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
🏭 Bevorzugte Produktionseinheit Bevorzugte Produktionseinheit
</label> </label>
<div class="relative"> <div class="relative">
<select name="printer_id" id="printer_id" <select name="printer_id" id="printer_id"
class="appearance-none input-field w-full px-6 py-4 pr-12 border border-slate-200 dark:border-slate-600 rounded-2xl focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 dark:text-white font-medium cursor-pointer"> class="appearance-none input-field w-full px-6 py-4 pr-12 border border-slate-200 dark:border-slate-600 rounded-2xl focus:ring-4 focus:ring-blue-500/25 focus:border-blue-500 dark:text-white font-medium cursor-pointer">
<option value="">🤖 Automatische Zuweisung</option> <option value="">Automatische Zuweisung</option>
{% for printer in printers %} {% for printer in printers %}
<option value="{{ printer.id }}">{{ printer.name }} {% if printer.location %}📍 {{ printer.location }}{% endif %}</option> <option value="{{ printer.id }}">{{ printer.name }} {% if printer.location %}({{ printer.location }}){% endif %}</option>
{% endfor %} {% endfor %}
</select> </select>
<div class="absolute inset-y-0 right-0 flex items-center pr-6 pointer-events-none"> <div class="absolute inset-y-0 right-0 flex items-center pr-6 pointer-events-none">
@@ -254,7 +254,7 @@
</div> </div>
</div> </div>
<div class="mt-2 text-xs text-slate-500 dark:text-slate-400"> <div class="mt-2 text-xs text-slate-500 dark:text-slate-400">
💡 Lassen Sie das System den optimalen Drucker für Sie auswählen Tipp: Lassen Sie das System den optimalen Drucker für Sie auswählen
</div> </div>
</div> </div>
{% endif %} {% endif %}
@@ -272,7 +272,7 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h3 class="text-lg font-semibold text-slate-800 dark:text-slate-200 mb-4"> <h3 class="text-lg font-semibold text-slate-800 dark:text-slate-200 mb-4">
🏆 Mercedes-Benz Professional Service Mercedes-Benz Professional Service
</h3> </h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
@@ -308,7 +308,7 @@
<svg class="w-5 h-5 mr-3 group-hover:rotate-12 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 mr-3 group-hover:rotate-12 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/>
</svg> </svg>
🚀 Anfrage übermitteln Anfrage übermitteln
</span> </span>
</button> </button>
</div> </div>
@@ -365,7 +365,7 @@
</svg> </svg>
</div> </div>
<h3 class="text-2xl font-bold text-slate-900 dark:text-white mb-4"> <h3 class="text-2xl font-bold text-slate-900 dark:text-white mb-4">
🎉 Anfrage erfolgreich übermittelt! Anfrage erfolgreich übermittelt!
</h3> </h3>
<p class="text-slate-600 dark:text-slate-400 mb-6"> <p class="text-slate-600 dark:text-slate-400 mb-6">
Ihre Professional-Gastanfrage wurde eingereicht und wird mit höchster Priorität bearbeitet. Sie können den Bearbeitungsstatus jederzeit unter folgendem Link verfolgen: Ihre Professional-Gastanfrage wurde eingereicht und wird mit höchster Priorität bearbeitet. Sie können den Bearbeitungsstatus jederzeit unter folgendem Link verfolgen:
@@ -376,11 +376,11 @@
<div class="flex items-center justify-center space-x-4"> <div class="flex items-center justify-center space-x-4">
<button onclick="closeSuccessModal()" <button onclick="closeSuccessModal()"
class="px-8 py-3 bg-gradient-to-r from-green-500 to-emerald-600 text-white rounded-2xl hover:from-green-600 hover:to-emerald-700 transition-all duration-300 shadow-lg font-medium"> class="px-8 py-3 bg-gradient-to-r from-green-500 to-emerald-600 text-white rounded-2xl hover:from-green-600 hover:to-emerald-700 transition-all duration-300 shadow-lg font-medium">
Verstanden Verstanden
</button> </button>
<button onclick="copyStatusUrl()" <button onclick="copyStatusUrl()"
class="px-8 py-3 border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 rounded-2xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-all duration-300 font-medium"> class="px-8 py-3 border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 rounded-2xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-all duration-300 font-medium">
📋 Link kopieren Link kopieren
</button> </button>
</div> </div>
</div> </div>

View File

@@ -14,12 +14,25 @@
background: rgba(15, 23, 42, 0.95); background: rgba(15, 23, 42, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
} }
.premium-gradient {
/* Light Mode Professional Gradient */
.professional-gradient {
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 25%, #cbd5e1 50%, #94a3b8 75%, #64748b 100%);
}
/* Dark Mode Professional Gradient */
.dark .professional-gradient {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 25%, #334155 50%, #475569 75%, #64748b 100%); background: linear-gradient(135deg, #0f172a 0%, #1e293b 25%, #334155 50%, #475569 75%, #64748b 100%);
} }
.premium-shadow {
.professional-shadow {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.05); box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.05);
} }
.dark .professional-shadow {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.status-badge { .status-badge {
@apply px-3 py-1 text-xs font-semibold rounded-full; @apply px-3 py-1 text-xs font-semibold rounded-full;
} }
@@ -60,14 +73,14 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="min-h-screen"> <div class="min-h-screen bg-gradient-to-br from-slate-100 via-blue-50 to-indigo-100 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<!-- Hero Header --> <!-- Hero Header -->
<div class="premium-gradient relative overflow-hidden"> <div class="professional-gradient relative overflow-hidden">
<div class="absolute inset-0 bg-black/20"></div> <div class="absolute inset-0 bg-black/20 dark:bg-black/40"></div>
<div class="relative max-w-7xl mx-auto px-6 lg:px-8 py-16"> <div class="relative max-w-7xl mx-auto px-6 lg:px-8 py-16">
<div class="text-center"> <div class="text-center">
<!-- Mercedes-Benz Logo --> <!-- Mercedes-Benz Logo -->
<div class="inline-flex items-center justify-center w-20 h-20 mercedes-glass rounded-full mb-6 premium-shadow"> <div class="inline-flex items-center justify-center w-20 h-20 mercedes-glass rounded-full mb-6 professional-shadow">
<svg class="w-10 h-10 text-white" viewBox="0 0 80 80" fill="currentColor"> <svg class="w-10 h-10 text-white" viewBox="0 0 80 80" fill="currentColor">
<path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5 <path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5
C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40 C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40
@@ -78,12 +91,12 @@
</svg> </svg>
</div> </div>
<h1 class="text-4xl md:text-5xl font-bold mb-4 text-white tracking-tight"> <h1 class="text-4xl md:text-5xl font-bold mb-4 text-white dark:text-slate-100 tracking-tight">
<span class="bg-gradient-to-r from-white to-blue-200 bg-clip-text text-transparent"> <span class="bg-gradient-to-r from-white to-blue-200 dark:from-slate-100 dark:to-slate-300 bg-clip-text text-transparent">
📋 Druckanträge Übersicht Druckanträge Übersicht
</span> </span>
</h1> </h1>
<p class="text-lg md:text-xl text-blue-100 max-w-3xl mx-auto leading-relaxed"> <p class="text-lg md:text-xl text-blue-100 dark:text-slate-300 max-w-3xl mx-auto leading-relaxed">
Transparente Übersicht aller eingereichten Druckanträge mit Datenschutz-konformer Darstellung Transparente Übersicht aller eingereichten Druckanträge mit Datenschutz-konformer Darstellung
</p> </p>
</div> </div>
@@ -94,7 +107,7 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-8 relative z-10 pb-16"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-8 relative z-10 pb-16">
<!-- Info Banner --> <!-- Info Banner -->
<div class="mercedes-glass rounded-2xl p-6 mb-8 premium-shadow"> <div class="mercedes-glass rounded-2xl p-6 mb-8 professional-shadow">
<div class="flex items-start space-x-4"> <div class="flex items-start space-x-4">
<div class="flex-shrink-0"> <div class="flex-shrink-0">
<div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center"> <div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center">
@@ -105,15 +118,15 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h3 class="text-lg font-semibold text-slate-800 dark:text-slate-200 mb-2"> <h3 class="text-lg font-semibold text-slate-800 dark:text-slate-200 mb-2">
🔒 Datenschutz & Transparenz Datenschutz & Transparenz
</h3> </h3>
<p class="text-slate-600 dark:text-slate-400 text-sm leading-relaxed"> <p class="text-slate-600 dark:text-slate-400 text-sm leading-relaxed">
Diese Übersicht zeigt alle eingereichten Druckanträge in anonymisierter Form. Persönliche Daten sind durch "***" zensiert, um die Privatsphäre zu schützen und gleichzeitig Transparenz über den Bearbeitungsstand zu gewährleisten. Diese Übersicht zeigt alle eingereichten Druckanträge in anonymisierter Form. Persönliche Daten sind durch "***" zensiert, um die Privatsphäre zu schützen und gleichzeitig Transparenz über den Bearbeitungsstand zu gewährleisten.
</p> </p>
<div class="mt-3 flex flex-wrap gap-2"> <div class="mt-3 flex flex-wrap gap-2">
<span class="status-badge bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-200">Datenkonform</span> <span class="status-badge bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-200">Datenkonform</span>
<span class="status-badge bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200">🛡️ Anonymisiert</span> <span class="status-badge bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200">Anonymisiert</span>
<span class="status-badge bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-200">👁️ Transparent</span> <span class="status-badge bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-200">Transparent</span>
</div> </div>
</div> </div>
</div> </div>
@@ -126,27 +139,27 @@
{% set approved_requests = requests|selectattr("status", "equalto", "approved")|list|length %} {% set approved_requests = requests|selectattr("status", "equalto", "approved")|list|length %}
{% set denied_requests = requests|selectattr("status", "equalto", "denied")|list|length %} {% set denied_requests = requests|selectattr("status", "equalto", "denied")|list|length %}
<div class="mercedes-glass rounded-xl p-6 text-center premium-shadow"> <div class="mercedes-glass rounded-xl p-6 text-center professional-shadow">
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-2">{{ total_requests }}</div> <div class="text-3xl font-bold text-slate-900 dark:text-white mb-2">{{ total_requests }}</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Gesamt</div> <div class="text-sm text-slate-600 dark:text-slate-400">Gesamt</div>
</div> </div>
<div class="mercedes-glass rounded-xl p-6 text-center premium-shadow"> <div class="mercedes-glass rounded-xl p-6 text-center professional-shadow">
<div class="text-3xl font-bold text-yellow-600 mb-2">{{ pending_requests }}</div> <div class="text-3xl font-bold text-yellow-600 mb-2">{{ pending_requests }}</div>
<div class="text-sm text-slate-600 dark:text-slate-400">🔄 Prüfung</div> <div class="text-sm text-slate-600 dark:text-slate-400">Prüfung</div>
</div> </div>
<div class="mercedes-glass rounded-xl p-6 text-center premium-shadow"> <div class="mercedes-glass rounded-xl p-6 text-center professional-shadow">
<div class="text-3xl font-bold text-green-600 mb-2">{{ approved_requests }}</div> <div class="text-3xl font-bold text-green-600 mb-2">{{ approved_requests }}</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Genehmigt</div> <div class="text-sm text-slate-600 dark:text-slate-400">Genehmigt</div>
</div> </div>
<div class="mercedes-glass rounded-xl p-6 text-center premium-shadow"> <div class="mercedes-glass rounded-xl p-6 text-center professional-shadow">
<div class="text-3xl font-bold text-red-600 mb-2">{{ denied_requests }}</div> <div class="text-3xl font-bold text-red-600 mb-2">{{ denied_requests }}</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Abgelehnt</div> <div class="text-sm text-slate-600 dark:text-slate-400">Abgelehnt</div>
</div> </div>
</div> </div>
<!-- Requests List --> <!-- Requests List -->
{% if error %} {% if error %}
<div class="mercedes-glass rounded-2xl p-8 text-center premium-shadow"> <div class="mercedes-glass rounded-2xl p-8 text-center professional-shadow">
<div class="text-red-500 mb-4"> <div class="text-red-500 mb-4">
<svg class="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
@@ -156,7 +169,7 @@
<p class="text-slate-600 dark:text-slate-400">{{ error }}</p> <p class="text-slate-600 dark:text-slate-400">{{ error }}</p>
</div> </div>
{% elif requests|length == 0 %} {% elif requests|length == 0 %}
<div class="mercedes-glass rounded-2xl p-8 text-center premium-shadow"> <div class="mercedes-glass rounded-2xl p-8 text-center professional-shadow">
<div class="text-slate-400 mb-4"> <div class="text-slate-400 mb-4">
<svg class="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
@@ -177,7 +190,7 @@
{% else %} {% else %}
<div class="space-y-4"> <div class="space-y-4">
{% for request in requests %} {% for request in requests %}
<div class="request-card {{ request.status }} mercedes-glass rounded-2xl p-6 premium-shadow"> <div class="request-card {{ request.status }} mercedes-glass rounded-2xl p-6 professional-shadow">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0"> <div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
<!-- Left Section: Request Info --> <!-- Left Section: Request Info -->
@@ -188,16 +201,16 @@
</div> </div>
<div class="status-badge status-{{ request.status }}"> <div class="status-badge status-{{ request.status }}">
{% if request.status == 'pending' %} {% if request.status == 'pending' %}
🔄 Wird geprüft Wird geprüft
{% elif request.status == 'approved' %} {% elif request.status == 'approved' %}
Genehmigt Genehmigt
{% elif request.status == 'denied' %} {% elif request.status == 'denied' %}
Abgelehnt Abgelehnt
{% endif %} {% endif %}
</div> </div>
{% if request.job_status %} {% if request.job_status %}
<div class="status-badge bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-200"> <div class="status-badge bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-200">
🖨️ {{ request.job_status|title }} Druckstatus: {{ request.job_status|title }}
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@@ -9,6 +9,8 @@ import logging
import subprocess import subprocess
import os import os
import requests import requests
import signal
import atexit
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List, Dict, Optional, Tuple from typing import List, Dict, Optional, Tuple
from contextlib import contextmanager from contextlib import contextmanager
@@ -65,44 +67,85 @@ def check_printer_status(ip_address: str, timeout: int = 5) -> Tuple[str, bool]:
class PrinterQueueManager: class PrinterQueueManager:
""" """
Verwaltet die Warteschlangen für offline Drucker und überwacht deren Status. Verwaltet die Warteschlangen für offline Drucker und überwacht deren Status.
Verbesserte Version mit ordnungsgemäßem Thread-Management für Windows.
""" """
def __init__(self): def __init__(self):
self.is_running = False self.is_running = False
self.monitor_thread = None self.monitor_thread = None
self.shutdown_event = threading.Event() # Sauberes Shutdown-Signal
self.check_interval = 120 # 2 Minuten zwischen Status-Checks self.check_interval = 120 # 2 Minuten zwischen Status-Checks
self.last_status_cache = {} # Cache für letzten bekannten Status self.last_status_cache = {} # Cache für letzten bekannten Status
self.notification_cooldown = {} # Verhindert Spam-Benachrichtigungen self.notification_cooldown = {} # Verhindert Spam-Benachrichtigungen
self._lock = threading.Lock() # Thread-Sicherheit
# Windows-spezifische Signal-Handler registrieren
if os.name == 'nt':
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
def _signal_handler(self, signum, frame):
"""Signal-Handler für ordnungsgemäßes Shutdown."""
queue_logger.warning(f"🛑 Signal {signum} empfangen - stoppe Queue Manager...")
self.stop()
def start(self): def start(self):
"""Startet den Queue-Manager.""" """Startet den Queue-Manager mit verbessertem Thread-Management."""
if not self.is_running: with self._lock:
self.is_running = True if not self.is_running:
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True) self.is_running = True
self.monitor_thread.start() self.shutdown_event.clear()
queue_logger.info("✅ Printer Queue Manager erfolgreich gestartet") self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=False)
self.monitor_thread.start()
queue_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
def stop(self): def stop(self):
"""Stoppt den Queue-Manager.""" """Stoppt den Queue-Manager ordnungsgemäß."""
self.is_running = False with self._lock:
if self.monitor_thread and self.monitor_thread.is_alive(): if self.is_running:
self.monitor_thread.join(timeout=5) queue_logger.info("🔄 Beende Queue Manager...")
queue_logger.info("❌ Printer Queue Manager gestoppt") self.is_running = False
self.shutdown_event.set()
if self.monitor_thread and self.monitor_thread.is_alive():
queue_logger.debug("⏳ Warte auf Thread-Beendigung...")
self.monitor_thread.join(timeout=10)
if self.monitor_thread.is_alive():
queue_logger.warning("⚠️ Thread konnte nicht ordnungsgemäß beendet werden")
else:
queue_logger.info("✅ Monitor-Thread erfolgreich beendet")
self.monitor_thread = None
queue_logger.info("❌ Printer Queue Manager gestoppt")
def _monitor_loop(self): def _monitor_loop(self):
"""Hauptschleife für die Überwachung der Drucker.""" """Hauptschleife für die Überwachung der Drucker mit verbessertem Shutdown-Handling."""
queue_logger.info(f"🔄 Queue-Überwachung gestartet (Intervall: {self.check_interval} Sekunden)") queue_logger.info(f"🔄 Queue-Überwachung gestartet (Intervall: {self.check_interval} Sekunden)")
while self.is_running: while self.is_running and not self.shutdown_event.is_set():
try: try:
self._check_waiting_jobs() self._check_waiting_jobs()
time.sleep(self.check_interval)
# Verwende Event.wait() statt time.sleep() für unterbrechbares Warten
if self.shutdown_event.wait(timeout=self.check_interval):
# Shutdown-Signal erhalten
queue_logger.info("🛑 Shutdown-Signal empfangen - beende Monitor-Loop")
break
except Exception as e: except Exception as e:
queue_logger.error(f"❌ Fehler in Monitor-Schleife: {str(e)}") queue_logger.error(f"❌ Fehler in Monitor-Schleife: {str(e)}")
time.sleep(30) # Kürzere Wartezeit bei Fehlern # Kürzere Wartezeit bei Fehlern, aber auch unterbrechbar
if self.shutdown_event.wait(timeout=30):
break
queue_logger.info("🔚 Monitor-Loop beendet")
def _check_waiting_jobs(self): def _check_waiting_jobs(self):
"""Überprüft alle wartenden Jobs und aktiviert sie bei verfügbaren Druckern.""" """Überprüft alle wartenden Jobs und aktiviert sie bei verfügbaren Druckern."""
if self.shutdown_event.is_set():
return
db_session = get_db_session() db_session = get_db_session()
try: try:
@@ -119,6 +162,10 @@ class PrinterQueueManager:
activated_jobs = [] activated_jobs = []
for job in waiting_jobs: for job in waiting_jobs:
# Shutdown-Check zwischen Jobs
if self.shutdown_event.is_set():
break
# Drucker-Status prüfen # Drucker-Status prüfen
printer = db_session.query(Printer).get(job.printer_id) printer = db_session.query(Printer).get(job.printer_id)
if not printer: if not printer:
@@ -181,9 +228,10 @@ class PrinterQueueManager:
db_session.commit() db_session.commit()
queue_logger.info(f"{len(activated_jobs)} Jobs erfolgreich aktiviert") queue_logger.info(f"{len(activated_jobs)} Jobs erfolgreich aktiviert")
# Benachrichtigungen versenden # Benachrichtigungen versenden (nur wenn nicht im Shutdown)
for item in activated_jobs: if not self.shutdown_event.is_set():
self._send_job_activation_notification(item["job"], item["printer"]) for item in activated_jobs:
self._send_job_activation_notification(item["job"], item["printer"])
else: else:
# Auch offline-Status speichern # Auch offline-Status speichern
db_session.commit() db_session.commit()
@@ -196,6 +244,9 @@ class PrinterQueueManager:
def _send_job_activation_notification(self, job: Job, printer: Printer): def _send_job_activation_notification(self, job: Job, printer: Printer):
"""Sendet eine Benachrichtigung, wenn ein Job aktiviert wird.""" """Sendet eine Benachrichtigung, wenn ein Job aktiviert wird."""
if self.shutdown_event.is_set():
return
try: try:
# Cooldown prüfen (keine Spam-Benachrichtigungen) # Cooldown prüfen (keine Spam-Benachrichtigungen)
cooldown_key = f"job_{job.id}_activated" cooldown_key = f"job_{job.id}_activated"
@@ -284,16 +335,25 @@ class PrinterQueueManager:
} }
finally: finally:
db_session.close() db_session.close()
def is_healthy(self) -> bool:
"""Prüft, ob der Queue Manager ordnungsgemäß läuft."""
return (self.is_running and
self.monitor_thread is not None and
self.monitor_thread.is_alive() and
not self.shutdown_event.is_set())
# Globale Instanz des Queue-Managers # Globale Instanz des Queue-Managers
_queue_manager_instance = None _queue_manager_instance = None
_queue_manager_lock = threading.Lock()
def get_queue_manager() -> PrinterQueueManager: def get_queue_manager() -> PrinterQueueManager:
"""Gibt die globale Instanz des Queue-Managers zurück.""" """Gibt die globale Instanz des Queue-Managers zurück."""
global _queue_manager_instance global _queue_manager_instance
if _queue_manager_instance is None: with _queue_manager_lock:
_queue_manager_instance = PrinterQueueManager() if _queue_manager_instance is None:
return _queue_manager_instance _queue_manager_instance = PrinterQueueManager()
return _queue_manager_instance
def start_queue_manager(): def start_queue_manager():
"""Startet den globalen Queue-Manager.""" """Startet den globalen Queue-Manager."""
@@ -304,6 +364,10 @@ def start_queue_manager():
def stop_queue_manager(): def stop_queue_manager():
"""Stoppt den globalen Queue-Manager.""" """Stoppt den globalen Queue-Manager."""
global _queue_manager_instance global _queue_manager_instance
if _queue_manager_instance: with _queue_manager_lock:
_queue_manager_instance.stop() if _queue_manager_instance:
_queue_manager_instance = None _queue_manager_instance.stop()
_queue_manager_instance = None
# Automatisches Cleanup bei Prozess-Ende registrieren
atexit.register(stop_queue_manager)

View File

@@ -0,0 +1,194 @@
"""
Windows-spezifische Fixes für Thread- und Socket-Probleme
Behebt bekannte Issues mit Flask Auto-Reload auf Windows.
"""
import os
import sys
import signal
import threading
import time
import atexit
from typing import List, Callable
from utils.logging_config import get_logger
# Logger für Windows-Fixes
windows_logger = get_logger("windows_fixes")
class WindowsThreadManager:
"""
Verwaltet Threads und deren ordnungsgemäße Beendigung auf Windows.
Behebt Socket-Fehler beim Flask Auto-Reload.
"""
def __init__(self):
self.managed_threads: List[threading.Thread] = []
self.cleanup_functions: List[Callable] = []
self.shutdown_event = threading.Event()
self._lock = threading.Lock()
self._is_shutting_down = False
# Signal-Handler nur auf Windows registrieren
if os.name == 'nt':
self._register_signal_handlers()
def _register_signal_handlers(self):
"""Registriert Windows-spezifische Signal-Handler."""
try:
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
# Windows-spezifisches SIGBREAK
if hasattr(signal, 'SIGBREAK'):
signal.signal(signal.SIGBREAK, self._signal_handler)
windows_logger.debug("✅ Windows Signal-Handler registriert")
except Exception as e:
windows_logger.warning(f"⚠️ Signal-Handler konnten nicht registriert werden: {str(e)}")
def _signal_handler(self, sig, frame):
"""Signal-Handler für ordnungsgemäßes Shutdown."""
if not self._is_shutting_down:
windows_logger.warning(f"🛑 Windows Signal {sig} empfangen - initiiere Shutdown")
self.shutdown_all()
def register_thread(self, thread: threading.Thread):
"""Registriert einen Thread für ordnungsgemäße Beendigung."""
with self._lock:
if thread not in self.managed_threads:
self.managed_threads.append(thread)
windows_logger.debug(f"📝 Thread {thread.name} registriert")
def register_cleanup_function(self, func: Callable):
"""Registriert eine Cleanup-Funktion."""
with self._lock:
if func not in self.cleanup_functions:
self.cleanup_functions.append(func)
windows_logger.debug(f"📝 Cleanup-Funktion registriert")
def shutdown_all(self):
"""Beendet alle verwalteten Threads und führt Cleanup durch."""
if self._is_shutting_down:
return
with self._lock:
self._is_shutting_down = True
windows_logger.info("🔄 Starte Windows Thread-Shutdown...")
# Shutdown-Event setzen
self.shutdown_event.set()
# Cleanup-Funktionen ausführen
for func in self.cleanup_functions:
try:
windows_logger.debug(f"🧹 Führe Cleanup-Funktion aus: {func.__name__}")
func()
except Exception as e:
windows_logger.error(f"❌ Fehler bei Cleanup-Funktion {func.__name__}: {str(e)}")
# Threads beenden
active_threads = [t for t in self.managed_threads if t.is_alive()]
if active_threads:
windows_logger.info(f"⏳ Warte auf {len(active_threads)} aktive Threads...")
for thread in active_threads:
try:
windows_logger.debug(f"🔄 Beende Thread: {thread.name}")
thread.join(timeout=5)
if thread.is_alive():
windows_logger.warning(f"⚠️ Thread {thread.name} konnte nicht ordnungsgemäß beendet werden")
else:
windows_logger.debug(f"✅ Thread {thread.name} erfolgreich beendet")
except Exception as e:
windows_logger.error(f"❌ Fehler beim Beenden von Thread {thread.name}: {str(e)}")
windows_logger.info("✅ Windows Thread-Shutdown abgeschlossen")
# Globale Instanz
_windows_thread_manager = None
def get_windows_thread_manager() -> WindowsThreadManager:
"""Gibt die globale Instanz des Windows Thread-Managers zurück."""
global _windows_thread_manager
if _windows_thread_manager is None:
_windows_thread_manager = WindowsThreadManager()
return _windows_thread_manager
def fix_windows_socket_issues():
"""
Anwendung von Windows-spezifischen Socket-Fixes.
Verhindert Socket-Fehler beim Flask Auto-Reload.
"""
if os.name != 'nt':
return
try:
# Socket-Wiederverwendung aktivieren
import socket
socket.socket._bind_orig = socket.socket.bind
def patched_bind(self, address):
"""Gepatchte bind-Methode mit SO_REUSEADDR."""
try:
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except:
pass
return self._bind_orig(address)
socket.socket.bind = patched_bind
windows_logger.debug("✅ Windows Socket-Patches angewendet")
except Exception as e:
windows_logger.warning(f"⚠️ Socket-Patches konnten nicht angewendet werden: {str(e)}")
def setup_windows_environment():
"""
Richtet die Windows-Umgebung für bessere Flask-Kompatibilität ein.
"""
if os.name != 'nt':
return
try:
# Umgebungsvariablen für bessere Windows-Kompatibilität
os.environ['PYTHONIOENCODING'] = 'utf-8'
os.environ['PYTHONUTF8'] = '1'
# Thread-Pool-Größe optimieren
if 'WERKZEUG_SERVER_FD' not in os.environ:
# Nur im Hauptprozess
os.environ['WERKZEUG_RUN_MAIN'] = 'true'
windows_logger.debug("✅ Windows-Umgebung optimiert")
except Exception as e:
windows_logger.warning(f"⚠️ Windows-Umgebung konnte nicht optimiert werden: {str(e)}")
def is_flask_reloader_process() -> bool:
"""
Prüft, ob der aktuelle Prozess der Flask-Reloader-Prozess ist.
"""
return os.environ.get('WERKZEUG_RUN_MAIN') != 'true'
def apply_all_windows_fixes():
"""
Wendet alle Windows-spezifischen Fixes an.
"""
if os.name != 'nt':
windows_logger.debug("⏭️ Keine Windows-Fixes nötig (nicht Windows)")
return
windows_logger.info("🔧 Wende Windows-spezifische Fixes an...")
setup_windows_environment()
fix_windows_socket_issues()
# Thread-Manager initialisieren
thread_manager = get_windows_thread_manager()
# Atexit-Handler registrieren
atexit.register(thread_manager.shutdown_all)
windows_logger.info("✅ Alle Windows-Fixes erfolgreich angewendet")
# Automatisch Windows-Fixes beim Import anwenden
if os.name == 'nt':
apply_all_windows_fixes()