🎉 Improved backend structure & added utility modules 🎨📚

This commit is contained in:
Till Tomczak 2025-06-01 00:26:29 +02:00
parent 91548dfb0e
commit 9e1719df4d
27 changed files with 750 additions and 60 deletions

View File

@ -1724,14 +1724,10 @@ def api_admin_system_health():
"error": str(e) "error": str(e)
}), 500 }), 500
# ===== INTEGRATION IN BESTEHENDE ROUTEN ===== @app.route("/api/admin/system-health-dashboard", methods=['GET'])
# Erweitere bestehende Job-Routen um Dashboard-Events
@app.route("/api/admin/system-health", methods=['GET'])
@login_required @login_required
@admin_required @admin_required
def api_admin_system_health(): def api_admin_system_health_dashboard():
"""API-Endpunkt für System-Gesundheitscheck mit Dashboard-Integration.""" """API-Endpunkt für System-Gesundheitscheck mit Dashboard-Integration."""
try: try:
# Basis-System-Gesundheitscheck durchführen # Basis-System-Gesundheitscheck durchführen
@ -5064,6 +5060,192 @@ def setup_database_with_migrations():
app_logger.error(f"❌ Fehler bei Datenbank-Setup: {str(e)}") app_logger.error(f"❌ Fehler bei Datenbank-Setup: {str(e)}")
raise e raise e
# ===== PRIVACY UND TERMS ROUTEN =====
@app.route("/privacy")
def privacy_policy():
"""Datenschutzerklärung anzeigen"""
try:
return render_template("privacy_policy.html", title="Datenschutzerklärung")
except Exception as e:
app_logger.error(f"Fehler beim Laden der Datenschutzerklärung: {str(e)}")
flash("Fehler beim Laden der Datenschutzerklärung", "error")
return redirect(url_for("index"))
@app.route("/terms")
def terms_of_service():
"""Nutzungsbedingungen anzeigen"""
try:
return render_template("terms_of_service.html", title="Nutzungsbedingungen")
except Exception as e:
app_logger.error(f"Fehler beim Laden der Nutzungsbedingungen: {str(e)}")
flash("Fehler beim Laden der Nutzungsbedingungen", "error")
return redirect(url_for("index"))
@app.route("/legal")
def legal_notice():
"""Impressum anzeigen"""
try:
return render_template("legal_notice.html", title="Impressum")
except Exception as e:
app_logger.error(f"Fehler beim Laden des Impressums: {str(e)}")
flash("Fehler beim Laden des Impressums", "error")
return redirect(url_for("index"))
@app.route("/api/privacy/accept", methods=["POST"])
@login_required
def accept_privacy_policy():
"""API-Endpunkt für Akzeptierung der Datenschutzerklärung"""
db_session = get_db_session()
try:
data = request.get_json() or {}
version = data.get("version", "1.0")
# Benutzer aus der Datenbank laden
user = db_session.query(User).filter(User.id == int(current_user.id)).first()
if not user:
return jsonify({"error": "Benutzer nicht gefunden"}), 404
# Privacy-Akzeptierung in Benutzer-Einstellungen speichern
if hasattr(user, 'settings'):
import json
settings = json.loads(user.settings) if user.settings else {}
else:
settings = session.get('user_settings', {})
# Privacy-Akzeptierung hinzufügen
if 'privacy_acceptance' not in settings:
settings['privacy_acceptance'] = {}
settings['privacy_acceptance'] = {
'accepted': True,
'version': version,
'timestamp': datetime.now().isoformat(),
'ip_address': request.remote_addr
}
# Einstellungen speichern
if hasattr(user, 'settings'):
user.settings = json.dumps(settings)
user.updated_at = datetime.now()
db_session.commit()
else:
session['user_settings'] = settings
user_logger.info(f"Benutzer {current_user.username} hat Datenschutzerklärung v{version} akzeptiert")
return jsonify({
"success": True,
"message": "Datenschutzerklärung erfolgreich akzeptiert",
"version": version,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
db_session.rollback()
app_logger.error(f"Fehler bei Privacy-Akzeptierung: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
finally:
db_session.close()
@app.route("/api/terms/accept", methods=["POST"])
@login_required
def accept_terms_of_service():
"""API-Endpunkt für Akzeptierung der Nutzungsbedingungen"""
db_session = get_db_session()
try:
data = request.get_json() or {}
version = data.get("version", "1.0")
# Benutzer aus der Datenbank laden
user = db_session.query(User).filter(User.id == int(current_user.id)).first()
if not user:
return jsonify({"error": "Benutzer nicht gefunden"}), 404
# Terms-Akzeptierung in Benutzer-Einstellungen speichern
if hasattr(user, 'settings'):
import json
settings = json.loads(user.settings) if user.settings else {}
else:
settings = session.get('user_settings', {})
# Terms-Akzeptierung hinzufügen
if 'terms_acceptance' not in settings:
settings['terms_acceptance'] = {}
settings['terms_acceptance'] = {
'accepted': True,
'version': version,
'timestamp': datetime.now().isoformat(),
'ip_address': request.remote_addr
}
# Einstellungen speichern
if hasattr(user, 'settings'):
user.settings = json.dumps(settings)
user.updated_at = datetime.now()
db_session.commit()
else:
session['user_settings'] = settings
user_logger.info(f"Benutzer {current_user.username} hat Nutzungsbedingungen v{version} akzeptiert")
return jsonify({
"success": True,
"message": "Nutzungsbedingungen erfolgreich akzeptiert",
"version": version,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
db_session.rollback()
app_logger.error(f"Fehler bei Terms-Akzeptierung: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
finally:
db_session.close()
@app.route("/api/legal/status", methods=["GET"])
@login_required
def get_legal_status():
"""API-Endpunkt für Abfrage des rechtlichen Status (Privacy/Terms Akzeptierung)"""
try:
# Benutzer-Einstellungen laden
if hasattr(current_user, 'settings') and current_user.settings:
import json
settings = json.loads(current_user.settings)
else:
settings = session.get('user_settings', {})
privacy_acceptance = settings.get('privacy_acceptance', {})
terms_acceptance = settings.get('terms_acceptance', {})
return jsonify({
"success": True,
"legal_status": {
"privacy_policy": {
"accepted": privacy_acceptance.get('accepted', False),
"version": privacy_acceptance.get('version'),
"timestamp": privacy_acceptance.get('timestamp')
},
"terms_of_service": {
"accepted": terms_acceptance.get('accepted', False),
"version": terms_acceptance.get('version'),
"timestamp": terms_acceptance.get('timestamp')
},
"compliance_required": not (
privacy_acceptance.get('accepted', False) and
terms_acceptance.get('accepted', False)
)
}
})
except Exception as e:
app_logger.error(f"Fehler bei Legal-Status-Abfrage: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
# ===== STARTUP UND MAIN ===== # ===== STARTUP UND MAIN =====
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys

View File

@ -73,6 +73,7 @@ SESSION_LIFETIME = timedelta(hours=2) # Reduziert von 7 Tagen auf 2 Stunden fü
UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads") UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads")
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'gcode', '3mf', 'stl'} ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'gcode', '3mf', 'stl'}
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB Maximum-Dateigröße MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB Maximum-Dateigröße
MAX_FILE_SIZE = 16 * 1024 * 1024 # 16MB Maximum-Dateigröße für Drag & Drop System
# Umgebungskonfiguration # Umgebungskonfiguration
ENVIRONMENT = get_env_variable("MYP_ENVIRONMENT", "development") ENVIRONMENT = get_env_variable("MYP_ENVIRONMENT", "development")

Binary file not shown.

BIN
backend/database/myp.db-shm Normal file

Binary file not shown.

BIN
backend/database/myp.db-wal Normal file

Binary file not shown.

View File

@ -75620,3 +75620,140 @@ WHERE users.id = ?
2025-05-31 23:51:08 - myp.app - INFO - 📁 Schalte Journal-Mode um... 2025-05-31 23:51:08 - myp.app - INFO - 📁 Schalte Journal-Mode um...
2025-05-31 23:51:08 - myp.app - INFO - ✅ Datenbank-Cleanup abgeschlossen - WAL-Dateien sollten verschwunden sein 2025-05-31 23:51:08 - myp.app - INFO - ✅ Datenbank-Cleanup abgeschlossen - WAL-Dateien sollten verschwunden sein
2025-05-31 23:51:08 - myp.app - INFO - ✅ Shutdown abgeschlossen 2025-05-31 23:51:08 - myp.app - INFO - ✅ Shutdown abgeschlossen
2025-06-01 00:15:51 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:15:51 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:15:51 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:15:51 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:15:51 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:15:51 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:15:51 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:16:49 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:16:49 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:16:49 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:16:49 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:16:49 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:16:49 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:16:49 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:17:41 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:17:41 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:17:41 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:17:41 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:17:42 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:17:42 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:17:42 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:17:47 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:17:47 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:17:47 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:17:47 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:17:47 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:17:48 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:17:48 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:17:48 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:17:48 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:18:02 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:18:02 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:18:02 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:18:02 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:18:02 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:18:02 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:18:02 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:18:02 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:18:02 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:18:03 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:18:03 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:18:03 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:18:03 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:18:03 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:18:04 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:18:04 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:18:04 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:18:04 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:18:52 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:18:52 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:18:52 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:18:52 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:18:52 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:18:52 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:18:52 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:18:52 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:18:52 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:18:53 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
2025-06-01 00:19:31 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:19:31 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:19:31 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:19:31 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:19:31 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:19:31 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:19:31 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:19:31 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:19:31 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:19:32 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
2025-06-01 00:19:32 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
2025-06-01 00:19:52 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:19:52 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:19:52 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:19:52 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:19:52 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:19:52 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:19:52 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:19:52 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:19:52 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:19:53 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
2025-06-01 00:19:53 - myp.email_notification - INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
2025-06-01 00:19:53 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
2025-06-01 00:20:28 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:20:28 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:20:28 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:20:28 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:20:28 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:20:28 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:20:28 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:20:28 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:20:28 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:20:29 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
2025-06-01 00:20:29 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
2025-06-01 00:20:29 - myp.email_notification - INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
2025-06-01 00:20:29 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
2025-06-01 00:20:49 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:20:49 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:20:49 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:20:49 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:20:49 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:20:49 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:20:49 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:20:49 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:20:49 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:20:50 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
2025-06-01 00:20:50 - myp.email_notification - INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
2025-06-01 00:20:50 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
2025-06-01 00:20:50 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
2025-06-01 00:20:50 - myp.multi_location - INFO - Standard-Standort erstellt
2025-06-01 00:21:36 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-01 00:21:36 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-01 00:21:36 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-01 00:21:36 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-01 00:21:36 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-01 00:21:36 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-01 00:21:36 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-01 00:21:36 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-01 00:21:36 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-06-01 00:21:37 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
2025-06-01 00:21:37 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
2025-06-01 00:21:37 - myp.email_notification - INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
2025-06-01 00:21:37 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
2025-06-01 00:21:37 - myp.multi_location - INFO - Standard-Standort erstellt
2025-06-01 00:21:37 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
2025-06-01 00:21:37 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
2025-06-01 00:21:37 - myp.multi_location - INFO - Standard-Standort erstellt
2025-06-01 00:21:37 - myp.dashboard - INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
2025-06-01 00:21:37 - myp.dashboard - INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
2025-06-01 00:21:37 - myp.security - INFO - 🔒 Security System initialisiert
2025-06-01 00:21:37 - myp.permissions - INFO - 🔐 Permission Template Helpers registriert
2025-06-01 00:21:37 - myp.app - INFO - ==================================================
2025-06-01 00:21:37 - myp.app - INFO - [START] MYP (Manage Your Printers) wird gestartet...
2025-06-01 00:21:37 - myp.app - INFO - [FOLDER] Log-Verzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\logs
2025-06-01 00:21:37 - myp.app - INFO - [CHART] Log-Level: INFO
2025-06-01 00:21:37 - myp.app - INFO - [PC] Betriebssystem: Windows 11
2025-06-01 00:21:37 - myp.app - INFO - [WEB] Hostname: C040L0079726760
2025-06-01 00:21:37 - myp.app - INFO - [TIME] Startzeit: 01.06.2025 00:21:37
2025-06-01 00:21:37 - myp.app - INFO - ==================================================

View File

@ -2705,3 +2705,15 @@
2025-05-31 23:50:30 - myp.scheduler - INFO - Scheduler gestartet 2025-05-31 23:50:30 - myp.scheduler - INFO - Scheduler gestartet
2025-05-31 23:51:08 - myp.scheduler - INFO - Scheduler-Thread beendet 2025-05-31 23:51:08 - myp.scheduler - INFO - Scheduler-Thread beendet
2025-05-31 23:51:08 - myp.scheduler - INFO - Scheduler gestoppt 2025-05-31 23:51:08 - myp.scheduler - INFO - Scheduler gestoppt
2025-06-01 00:15:51 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:16:49 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:17:42 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:17:48 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:18:02 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:18:04 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:18:52 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:19:31 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:19:52 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:20:28 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:20:49 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-01 00:21:36 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True

View File

@ -3,55 +3,136 @@
# Automatisch generiert am: 2025-05-29 19:41:49 # Automatisch generiert am: 2025-05-29 19:41:49
# Installiere mit: pip install -r requirements.txt # Installiere mit: pip install -r requirements.txt
# ===== CORE FLASK FRAMEWORK ===== # ===== CORE FRAMEWORK =====
# Direkt in app.py verwendet Flask==3.0.0
Flask==3.1.1 Werkzeug==3.0.1
# ===== FLASK EXTENSIONS =====
Flask-Login==0.6.3 Flask-Login==0.6.3
Flask-WTF==1.2.1 Flask-WTF==1.2.1
Flask-SocketIO==5.3.6
WTForms==3.1.1
# ===== DATENBANK ===== # ===== DATABASE =====
# SQLAlchemy für Datenbankoperationen (models.py, app.py) SQLAlchemy==2.0.23
SQLAlchemy==2.0.36
# ===== SICHERHEIT UND AUTHENTIFIZIERUNG ===== # ===== SECURITY =====
# Werkzeug für Passwort-Hashing und Utilities (app.py) cryptography==41.0.8
bcrypt==4.2.1 bcrypt==4.1.2
cryptography==44.0.0
Werkzeug==3.1.3
# ===== SMART PLUG STEUERUNG ===== # ===== HTTP REQUESTS (für Online-Modus) =====
# PyP100 für TP-Link Tapo Smart Plugs (utils/job_scheduler.py) requests==2.31.0
PyP100 urllib3==2.1.0
# ===== RATE LIMITING UND CACHING ===== # ===== HARDWARE INTEGRATION =====
# Redis für Rate Limiting (utils/rate_limiter.py) - optional # TP-Link Tapo Smart Plugs
redis==5.2.1 PyP100==0.1.4
# ===== HTTP REQUESTS ===== # ===== REAL-TIME FEATURES =====
# Requests für HTTP-Anfragen (utils/queue_manager.py, utils/debug_drucker_erkennung.py) # WebSocket-Support mit Fallback-Optionen
requests==2.32.3 eventlet==0.33.3
python-socketio==5.10.0
# ===== TEMPLATE ENGINE ===== # ===== SCHEDULING & TASK MANAGEMENT =====
# Jinja2 und MarkupSafe (automatisch mit Flask installiert, aber explizit für utils/template_helpers.py) schedule==1.2.0
MarkupSafe==3.0.2 APScheduler==3.10.4
# ===== GIS & LOCATION SERVICES =====
geocoder==1.38.1
# ===== DATA PROCESSING & EXPORT =====
# Excel-Export (optional)
openpyxl==3.1.2
pandas==2.1.4
xlsxwriter==3.1.9
# CSV/JSON processing
chardet==5.2.0
# ===== EMAIL FUNCTIONALITY =====
# Email-Features für Benachrichtigungen
email-validator==2.1.0.post1
# ===== IMAGE PROCESSING =====
# Avatar und Bild-Upload
Pillow==10.1.0
# ===== DEVELOPMENT & DEBUGGING =====
# Nur für Development-Umgebung
# python-dotenv==1.0.0
# ===== DATE/TIME UTILITIES =====
python-dateutil==2.8.2
pytz==2023.3
# ===== FILE HANDLING =====
# Datei-Upload und -Verarbeitung
python-magic==0.4.27
python-magic-bin==0.4.14 # Windows binary
# ===== LOGGING & MONITORING =====
# Erweiterte Logging-Features
colorlog==6.8.0
# ===== NETWORK & CONNECTIVITY =====
# Netzwerk-Utilities für Drucker-Kommunikation
netifaces==0.11.0
ping3==4.0.4
# ===== BACKUP & COMPRESSION =====
# Backup-Funktionalität
zipfile36==0.1.3
# ===== CONFIGURATION =====
# Konfiguration und Settings
configparser==6.0.0
# ===== VALIDATION =====
# Formular-Validierung
cerberus==1.3.5
marshmallow==3.20.1
# ===== CACHING (optional) =====
# Cache-Funktionalität
cachetools==5.3.2
# ===== UTILITIES =====
# Allgemeine Utilities
python-slugify==8.0.1
click==8.1.7
# ===== COMPATIBILITY =====
# Windows-Kompatibilität
pywin32==306; sys_platform == "win32"
wmi==1.5.1; sys_platform == "win32"
# ===== OPTIONAL DEPENDENCIES =====
# Für erweiterte Features (automatisch installiert wenn verfügbar)
# PDF-Generation (für Reports)
reportlab==4.0.7
weasyprint==60.2
# Erweiterte Kryptographie
cryptography==41.0.8
# QR-Code Generation (für OTP-Codes)
qrcode==7.4.2
# ===== PRODUCTION DEPLOYMENT =====
# WSGI Server für Produktion
gunicorn==21.2.0
waitress==2.1.2
# ===== SYSTEM MONITORING ===== # ===== SYSTEM MONITORING =====
# psutil für System-Monitoring (utils/debug_utils.py, utils/debug_cli.py) # System-Überwachung
psutil==6.1.1 psutil==5.9.6
# ===== ZUSÄTZLICHE CORE ABHÄNGIGKEITEN ===== # ===== ERROR TRACKING =====
# Click für CLI-Kommandos (automatisch mit Flask) # Fehler-Tracking (optional)
# Keine Core-Requirements # sentry-sdk[flask]==1.38.0
# ===== WINDOWS-SPEZIFISCHE ABHÄNGIGKEITEN ===== # ===== API DOCUMENTATION =====
# Nur für Windows-Systeme erforderlich # API-Dokumentation (optional)
# Keine Windows-Requirements # flask-restx==1.3.0
# flasgger==0.9.7.1
# ===== OPTIONAL: ENTWICKLUNG UND TESTING =====
# Nur für Entwicklungsumgebung
pytest==8.3.4; extra == "dev"
pytest-cov==6.0.0; extra == "dev"
# ===== OPTIONAL: PRODUKTIONS-SERVER =====
# Gunicorn für Produktionsumgebung
gunicorn==23.0.0; extra == "prod"

File diff suppressed because one or more lines are too long

View File

@ -934,3 +934,35 @@ def get_advanced_table_css() -> str:
} }
} }
""" """
def create_table_config(table_id: str, columns: List[ColumnConfig], **kwargs) -> TableConfig:
"""
Erstellt eine neue Tabellen-Konfiguration.
Args:
table_id: Eindeutige ID für die Tabelle
columns: Liste der Spalten-Konfigurationen
**kwargs: Zusätzliche Konfigurationsoptionen
Returns:
TableConfig: Konfiguration für die erweiterte Tabelle
"""
return TableConfig(
table_id=table_id,
columns=columns,
default_sort=kwargs.get('default_sort', []),
default_filters=kwargs.get('default_filters', []),
pagination=kwargs.get('pagination', PaginationConfig()),
searchable=kwargs.get('searchable', True),
exportable=kwargs.get('exportable', True),
selectable=kwargs.get('selectable', False),
row_actions=kwargs.get('row_actions', [])
)
def get_advanced_tables_js() -> str:
"""Alias für die bestehende Funktion"""
return get_advanced_table_javascript()
def get_advanced_tables_css() -> str:
"""Alias für die bestehende Funktion"""
return get_advanced_table_css()

View File

@ -686,3 +686,105 @@ def get_maintenance_javascript() -> str:
window.maintenanceManager = new MaintenanceManager(); window.maintenanceManager = new MaintenanceManager();
}); });
""" """
def create_maintenance_task(printer_id: int, title: str, description: str = "",
maintenance_type: MaintenanceType = MaintenanceType.PREVENTIVE,
priority: MaintenancePriority = MaintenancePriority.NORMAL) -> int:
"""
Erstellt eine neue Wartungsaufgabe.
Args:
printer_id: ID des Druckers
title: Titel der Wartungsaufgabe
description: Beschreibung der Aufgabe
maintenance_type: Art der Wartung
priority: Priorität der Aufgabe
Returns:
int: ID der erstellten Aufgabe
"""
task = MaintenanceTask(
printer_id=printer_id,
title=title,
description=description,
maintenance_type=maintenance_type,
priority=priority,
checklist=maintenance_manager.create_maintenance_checklist(maintenance_type)
)
return maintenance_manager.create_task(task)
def schedule_maintenance(printer_id: int, maintenance_type: MaintenanceType,
interval_days: int, description: str = "") -> MaintenanceSchedule:
"""
Plant regelmäßige Wartungen (Alias für maintenance_manager.schedule_maintenance).
Args:
printer_id: ID des Druckers
maintenance_type: Art der Wartung
interval_days: Intervall in Tagen
description: Beschreibung
Returns:
MaintenanceSchedule: Erstellter Wartungsplan
"""
return maintenance_manager.schedule_maintenance(
printer_id=printer_id,
maintenance_type=maintenance_type,
interval_days=interval_days,
description=description
)
def get_maintenance_overview() -> Dict[str, Any]:
"""
Holt eine Übersicht aller Wartungsaktivitäten.
Returns:
Dict: Wartungsübersicht mit Statistiken und anstehenden Aufgaben
"""
upcoming = maintenance_manager.get_upcoming_maintenance()
overdue = maintenance_manager.get_overdue_tasks()
metrics = maintenance_manager.get_maintenance_metrics()
# Aktive Tasks
active_tasks = [task for task in maintenance_manager.tasks.values()
if task.status == MaintenanceStatus.IN_PROGRESS]
# Completed tasks in last 30 days
thirty_days_ago = datetime.now() - timedelta(days=30)
recent_completed = [task for task in maintenance_manager.maintenance_history
if task.completed_at and task.completed_at >= thirty_days_ago]
return {
'summary': {
'total_tasks': len(maintenance_manager.tasks),
'active_tasks': len(active_tasks),
'upcoming_tasks': len(upcoming),
'overdue_tasks': len(overdue),
'completed_this_month': len(recent_completed)
},
'upcoming_tasks': [asdict(task) for task in upcoming[:10]],
'overdue_tasks': [asdict(task) for task in overdue],
'active_tasks': [asdict(task) for task in active_tasks],
'recent_completed': [asdict(task) for task in recent_completed[:5]],
'metrics': asdict(metrics),
'schedules': {
printer_id: [asdict(schedule) for schedule in schedules]
for printer_id, schedules in maintenance_manager.schedules.items()
}
}
def update_maintenance_status(task_id: int, new_status: MaintenanceStatus,
notes: str = "") -> bool:
"""
Aktualisiert den Status einer Wartungsaufgabe (Alias für maintenance_manager.update_task_status).
Args:
task_id: ID der Wartungsaufgabe
new_status: Neuer Status
notes: Optionale Notizen
Returns:
bool: True wenn erfolgreich aktualisiert
"""
return maintenance_manager.update_task_status(task_id, new_status, notes)

View File

@ -520,23 +520,138 @@ class MultiLocationManager:
} }
# Globale Instanz # Globale Instanz
multi_location_manager = MultiLocationManager() location_manager = MultiLocationManager()
# Alias für Import-Kompatibilität
LocationManager = MultiLocationManager
def create_location(name: str, code: str, location_type: LocationType = LocationType.BRANCH,
address: str = "", city: str = "", country: str = "",
parent_id: Optional[int] = None) -> int:
"""
Erstellt einen neuen Standort (globale Funktion).
Args:
name: Name des Standorts
code: Kurzer Code für den Standort
location_type: Art des Standorts
address: Adresse
city: Stadt
country: Land
parent_id: Parent-Standort ID
Returns:
int: ID des erstellten Standorts
"""
location = Location(
name=name,
code=code,
location_type=location_type,
address=address,
city=city,
country=country,
parent_id=parent_id
)
return location_manager.create_location(location)
def assign_user_to_location(user_id: int, location_id: int,
access_level: AccessLevel = AccessLevel.READ_WRITE,
granted_by: int = 1, is_primary: bool = False) -> bool:
"""
Weist einen Benutzer einem Standort zu.
Args:
user_id: ID des Benutzers
location_id: ID des Standorts
access_level: Zugriffslevel
granted_by: ID des gewährenden Benutzers
is_primary: Ob dies der primäre Standort ist
Returns:
bool: True wenn erfolgreich
"""
return location_manager.grant_location_access(
user_id=user_id,
location_id=location_id,
access_level=access_level,
granted_by=granted_by,
is_primary=is_primary
)
def get_user_locations(user_id: int) -> List[Location]:
"""
Holt alle Standorte eines Benutzers (globale Funktion).
Args:
user_id: ID des Benutzers
Returns:
List[Location]: Liste der zugänglichen Standorte
"""
return location_manager.get_user_locations(user_id)
def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""
Berechnet die Entfernung zwischen zwei Koordinaten (Haversine-Formel).
Args:
lat1, lon1: Koordinaten des ersten Punkts
lat2, lon2: Koordinaten des zweiten Punkts
Returns:
float: Entfernung in Kilometern
"""
from math import radians, sin, cos, sqrt, atan2
R = 6371 # Erdradius in km
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
dlat = lat2 - lat1
dlon = lon2 - lon1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
c = 2 * atan2(sqrt(a), sqrt(1-a))
return R * c
def find_nearest_location(latitude: float, longitude: float,
radius_km: float = 50) -> Optional[Location]:
"""
Findet den nächstgelegenen Standort.
Args:
latitude: Breitengrad
longitude: Längengrad
radius_km: Suchradius in Kilometern
Returns:
Optional[Location]: Nächstgelegener Standort oder None
"""
nearest_locations = location_manager.find_nearest_locations(
latitude=latitude,
longitude=longitude,
radius_km=radius_km,
limit=1
)
return nearest_locations[0][0] if nearest_locations else None
def get_location_dashboard_data(user_id: int) -> Dict[str, Any]: def get_location_dashboard_data(user_id: int) -> Dict[str, Any]:
"""Holt Dashboard-Daten für Standorte eines Benutzers""" """Holt Dashboard-Daten für Standorte eines Benutzers"""
user_locations = multi_location_manager.get_user_locations(user_id) user_locations = location_manager.get_user_locations(user_id)
primary_location = multi_location_manager.get_user_primary_location(user_id) primary_location = location_manager.get_user_primary_location(user_id)
dashboard_data = { dashboard_data = {
'user_locations': [asdict(loc) for loc in user_locations], 'user_locations': [asdict(loc) for loc in user_locations],
'primary_location': asdict(primary_location) if primary_location else None, 'primary_location': asdict(primary_location) if primary_location else None,
'location_count': len(user_locations), 'location_count': len(user_locations),
'hierarchy': multi_location_manager.get_location_hierarchy() 'hierarchy': location_manager.get_location_hierarchy()
} }
# Füge Statistiken für jeden Standort hinzu # Füge Statistiken für jeden Standort hinzu
for location in user_locations: for location in user_locations:
location_stats = multi_location_manager.get_location_statistics(location.id) location_stats = location_manager.get_location_statistics(location.id)
dashboard_data[f'stats_{location.id}'] = location_stats dashboard_data[f'stats_{location.id}'] = location_stats
return dashboard_data return dashboard_data
@ -553,7 +668,7 @@ def create_location_from_address(name: str, address: str, city: str,
country=country country=country
) )
return multi_location_manager.create_location(location) return location_manager.create_location(location)
# JavaScript für Multi-Location Frontend # JavaScript für Multi-Location Frontend
def get_multi_location_javascript() -> str: def get_multi_location_javascript() -> str:

View File

@ -43,6 +43,7 @@ class Permission(Enum):
EXTEND_JOB = "extend_job" EXTEND_JOB = "extend_job"
CANCEL_JOB = "cancel_job" CANCEL_JOB = "cancel_job"
VIEW_JOB_HISTORY = "view_job_history" VIEW_JOB_HISTORY = "view_job_history"
APPROVE_JOBS = "approve_jobs" # Berechtigung zum Genehmigen und Verwalten von Jobs
# Benutzer-Berechtigungen # Benutzer-Berechtigungen
VIEW_USERS = "view_users" VIEW_USERS = "view_users"
@ -59,6 +60,7 @@ class Permission(Enum):
EXPORT_DATA = "export_data" EXPORT_DATA = "export_data"
BACKUP_DATABASE = "backup_database" BACKUP_DATABASE = "backup_database"
MANAGE_SETTINGS = "manage_settings" MANAGE_SETTINGS = "manage_settings"
ADMIN = "admin" # Allgemeine Admin-Berechtigung für administrative Funktionen
# Gast-Berechtigungen # Gast-Berechtigungen
VIEW_GUEST_REQUESTS = "view_guest_requests" VIEW_GUEST_REQUESTS = "view_guest_requests"
@ -149,6 +151,7 @@ ROLE_PERMISSIONS[Role.SUPERVISOR] = ROLE_PERMISSIONS[Role.TECHNICIAN] | {
Permission.MANAGE_GUEST_REQUESTS, Permission.MANAGE_GUEST_REQUESTS,
Permission.MANAGE_SHIFTS, Permission.MANAGE_SHIFTS,
Permission.VIEW_USER_DETAILS, Permission.VIEW_USER_DETAILS,
Permission.APPROVE_JOBS, # Jobs genehmigen und verwalten
} }
# Admin erweitert Supervisor-Permissions # Admin erweitert Supervisor-Permissions
@ -161,6 +164,7 @@ ROLE_PERMISSIONS[Role.ADMIN] = ROLE_PERMISSIONS[Role.SUPERVISOR] | {
Permission.EXPORT_DATA, Permission.EXPORT_DATA,
Permission.VIEW_LOGS, Permission.VIEW_LOGS,
Permission.MANAGE_SETTINGS, Permission.MANAGE_SETTINGS,
Permission.ADMIN, # Allgemeine Admin-Berechtigung hinzufügen
} }
# Super Admin hat alle Berechtigungen # Super Admin hat alle Berechtigungen

View File

@ -25,12 +25,28 @@ from concurrent.futures import ThreadPoolExecutor
# WebSocket-Support # WebSocket-Support
try: try:
from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect
# Eventlet separat importieren für bessere Fehlerbehandlung
try:
from eventlet import wsgi, listen from eventlet import wsgi, listen
import eventlet import eventlet
EVENTLET_AVAILABLE = True
except (ImportError, AttributeError) as e:
# Eventlet nicht verfügbar oder nicht kompatibel (z.B. Python 3.13)
print(f"⚠️ Eventlet nicht verfügbar: {e}")
print("🔄 Fallback auf standard threading mode für WebSocket")
EVENTLET_AVAILABLE = False
eventlet = None
wsgi = None
listen = None
WEBSOCKET_AVAILABLE = True WEBSOCKET_AVAILABLE = True
except ImportError: except ImportError as e:
print(f"⚠️ Flask-SocketIO nicht verfügbar: {e}")
print("📡 Dashboard läuft ohne Real-time Updates")
WEBSOCKET_AVAILABLE = False WEBSOCKET_AVAILABLE = False
EVENTLET_AVAILABLE = False
SocketIO = None SocketIO = None
eventlet = None
from flask import request, session, current_app from flask import request, session, current_app
from flask_login import current_user from flask_login import current_user
@ -122,16 +138,24 @@ class DashboardManager:
logger.warning("WebSocket-Funktionalität nicht verfügbar. Flask-SocketIO installieren.") logger.warning("WebSocket-Funktionalität nicht verfügbar. Flask-SocketIO installieren.")
return None return None
# Bestimme den async_mode basierend auf verfügbaren Bibliotheken
if EVENTLET_AVAILABLE:
async_mode = 'eventlet'
logger.info("Dashboard WebSocket-Server wird mit eventlet initialisiert")
else:
async_mode = 'threading'
logger.info("Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)")
self.socketio = SocketIO( self.socketio = SocketIO(
app, app,
cors_allowed_origins=cors_allowed_origins, cors_allowed_origins=cors_allowed_origins,
logger=False, logger=False,
engineio_logger=False, engineio_logger=False,
async_mode='eventlet' async_mode=async_mode
) )
self._setup_socket_handlers() self._setup_socket_handlers()
logger.info("Dashboard WebSocket-Server initialisiert") logger.info(f"Dashboard WebSocket-Server initialisiert (async_mode: {async_mode})")
return self.socketio return self.socketio
def _setup_socket_handlers(self): def _setup_socket_handlers(self):