🎉 Refactor & Update Backend Code, Add Utils 🖥️📊

This commit is contained in:
Till Tomczak 2025-05-31 23:54:57 +02:00
parent d4f899d280
commit 193164964e
14 changed files with 5346 additions and 241 deletions

View File

@ -5439,237 +5439,9 @@ def export_guest_requests():
}), 500 }), 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:80")
app.run(
host="0.0.0.0",
port=80,
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)
# ===== AUTO-OPTIMIERUNG-API-ENDPUNKTE ===== # ===== AUTO-OPTIMIERUNG-API-ENDPUNKTE =====
@app.route('/api/optimization/auto-optimize', methods=['POST']) @app.route('/api/optimization/auto-optimize', methods=['POST'])
@login_required @login_required
def auto_optimize_jobs(): def auto_optimize_jobs():
@ -5936,4 +5708,233 @@ def validate_optimization_settings(settings):
except Exception: except Exception:
return False return False
# ===== GASTANTRÄGE API-ROUTEN ===== # ===== GASTANTRÄGE API-ROUTEN =====
# ===== 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:80")
app.run(
host="0.0.0.0",
port=80,
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)

View File

@ -175,6 +175,441 @@ def control_printer_power(printer_id):
except Exception as e: except Exception as e:
printers_logger.error(f"❌ Allgemeiner Fehler bei Stromsteuerung: {str(e)}") printers_logger.error(f"❌ Allgemeiner Fehler bei Stromsteuerung: {str(e)}")
return jsonify({
"success": False,
"error": f"Allgemeiner Fehler: {str(e)}"
}), 500
@printers_blueprint.route("/test/socket/<int:printer_id>", methods=["GET"])
@login_required
@require_permission(Permission.ADMIN)
@measure_execution_time(logger=printers_logger, task_name="API-Steckdosen-Test-Status")
def test_socket_status(printer_id):
"""
Prüft den aktuellen Status einer Steckdose für Testzwecke (nur für Ausbilder/Administratoren).
Args:
printer_id: ID des Druckers dessen Steckdose getestet werden soll
Returns:
JSON mit detailliertem Status der Steckdose und Warnungen
"""
printers_logger.info(f"🔍 Steckdosen-Test-Status für Drucker {printer_id} von Admin {current_user.name}")
try:
# Drucker aus Datenbank holen
db_session = get_db_session()
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
db_session.close()
return jsonify({
"success": False,
"error": f"Drucker mit ID {printer_id} nicht gefunden"
}), 404
# Prüfen, ob Drucker eine Steckdose konfiguriert hat
if not printer.plug_ip or not printer.plug_username or not printer.plug_password:
db_session.close()
return jsonify({
"success": False,
"error": f"Drucker {printer.name} hat keine Steckdose konfiguriert",
"warning": "Steckdose kann nicht getestet werden - Konfiguration fehlt"
}), 400
# Prüfen, ob der Drucker gerade aktive Jobs hat
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(["running", "printing", "active"])
).all()
db_session.close()
# Steckdosen-Status prüfen
from PyP100 import PyP110
socket_status = None
socket_info = None
error_message = None
try:
# TP-Link Tapo P110 Verbindung herstellen
p110 = PyP110.P110(printer.plug_ip, printer.plug_username, printer.plug_password)
p110.handshake() # Authentifizierung
p110.login() # Login
# Geräteinformationen abrufen
device_info = p110.getDeviceInfo()
socket_status = "online" if device_info["result"]["device_on"] else "offline"
# Energieverbrauch abrufen (falls verfügbar)
try:
energy_info = p110.getEnergyUsage()
current_power = energy_info.get("result", {}).get("current_power", 0)
except:
current_power = None
socket_info = {
"device_on": device_info["result"]["device_on"],
"signal_level": device_info["result"].get("signal_level", 0),
"current_power": current_power,
"device_id": device_info["result"].get("device_id", "Unbekannt"),
"model": device_info["result"].get("model", "Unbekannt"),
"hw_ver": device_info["result"].get("hw_ver", "Unbekannt"),
"fw_ver": device_info["result"].get("fw_ver", "Unbekannt")
}
except Exception as e:
printers_logger.warning(f"⚠️ Fehler bei Steckdosen-Status-Abfrage für {printer.name}: {str(e)}")
socket_status = "error"
error_message = str(e)
# Warnungen und Empfehlungen zusammenstellen
warnings = []
recommendations = []
risk_level = "low"
if active_jobs:
warnings.append(f"ACHTUNG: Drucker hat {len(active_jobs)} aktive(n) Job(s)!")
risk_level = "high"
recommendations.append("Warten Sie bis alle Jobs abgeschlossen sind bevor Sie die Steckdose ausschalten")
if socket_status == "online" and socket_info and socket_info.get("device_on"):
if socket_info.get("current_power", 0) > 10: # Mehr als 10W Verbrauch
warnings.append(f"Drucker verbraucht aktuell {socket_info['current_power']}W - vermutlich aktiv")
risk_level = "medium" if risk_level == "low" else risk_level
recommendations.append("Prüfen Sie den Druckerstatus bevor Sie die Steckdose ausschalten")
else:
recommendations.append("Drucker scheint im Standby-Modus zu sein - Test sollte sicher möglich sein")
if socket_status == "error":
warnings.append("Steckdose nicht erreichbar - Netzwerk oder Konfigurationsproblem")
recommendations.append("Prüfen Sie die Netzwerkverbindung und Steckdosen-Konfiguration")
if not warnings and socket_status == "offline":
recommendations.append("Steckdose ist ausgeschaltet - Test kann sicher durchgeführt werden")
printers_logger.info(f"✅ Steckdosen-Test-Status erfolgreich abgerufen für {printer.name}")
return jsonify({
"success": True,
"printer": {
"id": printer.id,
"name": printer.name,
"model": printer.model,
"location": printer.location,
"status": printer.status
},
"socket": {
"status": socket_status,
"info": socket_info,
"error": error_message,
"ip_address": printer.plug_ip
},
"safety": {
"risk_level": risk_level,
"warnings": warnings,
"recommendations": recommendations,
"active_jobs_count": len(active_jobs),
"safe_to_test": len(warnings) == 0
},
"timestamp": datetime.now().isoformat()
})
except Exception as e:
printers_logger.error(f"❌ Allgemeiner Fehler bei Steckdosen-Test-Status: {str(e)}")
return jsonify({
"success": False,
"error": f"Allgemeiner Fehler: {str(e)}"
}), 500
@printers_blueprint.route("/test/socket/<int:printer_id>/control", methods=["POST"])
@login_required
@require_permission(Permission.ADMIN)
@measure_execution_time(logger=printers_logger, task_name="API-Steckdosen-Test-Steuerung")
def test_socket_control(printer_id):
"""
Steuert eine Steckdose für Testzwecke (nur für Ausbilder/Administratoren).
Diese Funktion zeigt Warnungen an, erlaubt aber trotzdem die Steuerung für Tests.
Args:
printer_id: ID des Druckers dessen Steckdose gesteuert werden soll
JSON-Parameter:
- action: "on" oder "off"
- force: boolean - überschreibt Sicherheitswarnungen (default: false)
- test_reason: string - Grund für den Test (optional)
Returns:
JSON mit Ergebnis der Steuerungsaktion und Warnungen
"""
printers_logger.info(f"🧪 Steckdosen-Test-Steuerung für Drucker {printer_id} von Admin {current_user.name}")
# Parameter validieren
data = request.get_json()
if not data or "action" not in data:
return jsonify({
"success": False,
"error": "Parameter 'action' fehlt"
}), 400
action = data["action"]
if action not in ["on", "off"]:
return jsonify({
"success": False,
"error": "Ungültige Aktion. Erlaubt sind 'on' oder 'off'."
}), 400
force = data.get("force", False)
test_reason = data.get("test_reason", "Routinetest")
try:
# Drucker aus Datenbank holen
db_session = get_db_session()
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
db_session.close()
return jsonify({
"success": False,
"error": f"Drucker mit ID {printer_id} nicht gefunden"
}), 404
# Prüfen, ob Drucker eine Steckdose konfiguriert hat
if not printer.plug_ip or not printer.plug_username or not printer.plug_password:
db_session.close()
return jsonify({
"success": False,
"error": f"Drucker {printer.name} hat keine Steckdose konfiguriert"
}), 400
# Aktive Jobs prüfen
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(["running", "printing", "active"])
).all()
# Sicherheitsprüfungen
warnings = []
should_block = False
if active_jobs and action == "off":
warnings.append(f"WARNUNG: {len(active_jobs)} aktive Job(s) würden abgebrochen!")
if not force:
should_block = True
if should_block:
db_session.close()
return jsonify({
"success": False,
"error": "Aktion blockiert aufgrund von Sicherheitsbedenken",
"warnings": warnings,
"hint": "Verwenden Sie 'force': true um die Aktion trotzdem auszuführen",
"requires_force": True
}), 409 # Conflict
# Steckdose steuern
from PyP100 import PyP110
try:
# TP-Link Tapo P110 Verbindung herstellen
p110 = PyP110.P110(printer.plug_ip, printer.plug_username, printer.plug_password)
p110.handshake() # Authentifizierung
p110.login() # Login
# Aktuellen Status vor der Änderung abrufen
device_info_before = p110.getDeviceInfo()
status_before = device_info_before["result"]["device_on"]
# Steckdose ein- oder ausschalten
if action == "on":
p110.turnOn()
success = True
message = "Steckdose für Test erfolgreich eingeschaltet"
new_printer_status = "starting"
else:
p110.turnOff()
success = True
message = "Steckdose für Test erfolgreich ausgeschaltet"
new_printer_status = "offline"
# Kurz warten und neuen Status prüfen
time.sleep(2)
device_info_after = p110.getDeviceInfo()
status_after = device_info_after["result"]["device_on"]
# Drucker-Status aktualisieren
printer.status = new_printer_status
printer.last_checked = datetime.now()
db_session.commit()
# Cache leeren, damit neue Status-Abfragen aktuell sind
printer_monitor.clear_all_caches()
# Test-Eintrag für Audit-Log
printers_logger.info(f"🧪 TEST DURCHGEFÜHRT: {action.upper()} für {printer.name} | "
f"Admin: {current_user.name} | Grund: {test_reason} | "
f"Force: {force} | Status: {status_before}{status_after}")
except Exception as e:
printers_logger.error(f"❌ Fehler bei Test-Steckdosensteuerung für {printer.name}: {str(e)}")
db_session.close()
return jsonify({
"success": False,
"error": f"Fehler bei Steckdosensteuerung: {str(e)}"
}), 500
db_session.close()
return jsonify({
"success": True,
"message": message,
"test_info": {
"admin": current_user.name,
"reason": test_reason,
"forced": force,
"status_before": status_before,
"status_after": status_after
},
"printer": {
"id": printer_id,
"name": printer.name,
"status": new_printer_status
},
"action": action,
"warnings": warnings,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
printers_logger.error(f"❌ Allgemeiner Fehler bei Test-Steckdosensteuerung: {str(e)}")
return jsonify({
"success": False,
"error": f"Allgemeiner Fehler: {str(e)}"
}), 500
@printers_blueprint.route("/test/all-sockets", methods=["GET"])
@login_required
@require_permission(Permission.ADMIN)
@measure_execution_time(logger=printers_logger, task_name="API-Alle-Steckdosen-Test-Status")
def test_all_sockets_status():
"""
Liefert den Test-Status aller konfigurierten Steckdosen (nur für Ausbilder/Administratoren).
Returns:
JSON mit Status aller Steckdosen und Gesamtübersicht
"""
printers_logger.info(f"🔍 Alle-Steckdosen-Test-Status von Admin {current_user.name}")
try:
# Alle Drucker mit Steckdosen-Konfiguration holen
db_session = get_db_session()
printers = db_session.query(Printer).filter(
Printer.plug_ip.isnot(None),
Printer.plug_username.isnot(None),
Printer.plug_password.isnot(None)
).all()
results = []
total_online = 0
total_offline = 0
total_error = 0
total_warnings = 0
from PyP100 import PyP110
for printer in printers:
# Aktive Jobs für diesen Drucker prüfen
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.status.in_(["running", "printing", "active"])
).count()
# Steckdosen-Status prüfen
socket_status = "unknown"
device_on = False
current_power = None
error_message = None
warnings = []
try:
p110 = PyP110.P110(printer.plug_ip, printer.plug_username, printer.plug_password)
p110.handshake()
p110.login()
device_info = p110.getDeviceInfo()
device_on = device_info["result"]["device_on"]
socket_status = "online" if device_on else "offline"
# Energieverbrauch abrufen
try:
energy_info = p110.getEnergyUsage()
current_power = energy_info.get("result", {}).get("current_power", 0)
except:
current_power = None
# Warnungen generieren
if active_jobs > 0:
warnings.append(f"{active_jobs} aktive Job(s)")
if device_on and current_power and current_power > 10:
warnings.append(f"Hoher Verbrauch: {current_power}W")
except Exception as e:
socket_status = "error"
error_message = str(e)
warnings.append(f"Verbindungsfehler: {str(e)[:50]}")
# Statistiken aktualisieren
if socket_status == "online":
total_online += 1
elif socket_status == "offline":
total_offline += 1
else:
total_error += 1
if warnings:
total_warnings += 1
results.append({
"printer": {
"id": printer.id,
"name": printer.name,
"model": printer.model,
"location": printer.location
},
"socket": {
"status": socket_status,
"device_on": device_on,
"current_power": current_power,
"ip_address": printer.plug_ip,
"error": error_message
},
"warnings": warnings,
"active_jobs": active_jobs,
"safe_to_test": len(warnings) == 0
})
db_session.close()
# Gesamtübersicht erstellen
summary = {
"total_sockets": len(results),
"online": total_online,
"offline": total_offline,
"error": total_error,
"with_warnings": total_warnings,
"safe_to_test": len(results) - total_warnings
}
printers_logger.info(f"✅ Alle-Steckdosen-Status erfolgreich abgerufen: {len(results)} Steckdosen")
return jsonify({
"success": True,
"sockets": results,
"summary": summary,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
printers_logger.error(f"❌ Fehler bei Alle-Steckdosen-Test-Status: {str(e)}")
return jsonify({ return jsonify({
"success": False, "success": False,
"error": f"Allgemeiner Fehler: {str(e)}" "error": f"Allgemeiner Fehler: {str(e)}"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -75189,3 +75189,434 @@ WHERE users.id = ?
2025-05-31 23:44:39 - myp.app - INFO - [WEB] Hostname: C040L0079726760 2025-05-31 23:44:39 - myp.app - INFO - [WEB] Hostname: C040L0079726760
2025-05-31 23:44:39 - myp.app - INFO - [TIME] Startzeit: 31.05.2025 23:44:39 2025-05-31 23:44:39 - myp.app - INFO - [TIME] Startzeit: 31.05.2025 23:44:39
2025-05-31 23:44:39 - myp.app - INFO - ================================================== 2025-05-31 23:44:39 - myp.app - INFO - ==================================================
2025-05-31 23:44:45 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:44:45] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:44:46 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:44:46] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:45:02 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
2025-05-31 23:45:02 - myp.app - INFO - 🔄 Beende Queue Manager...
2025-05-31 23:45:02 - myp.app - INFO - Job-Scheduler gestoppt
2025-05-31 23:45:02 - myp.app - INFO - 💾 Führe Datenbank-Cleanup durch...
2025-05-31 23:45:02 - myp.app - INFO - 📝 Führe WAL-Checkpoint durch...
2025-05-31 23:45:02 - myp.app - INFO - WAL-Checkpoint abgeschlossen: 0 Seiten übertragen, 0 Seiten zurückgesetzt
2025-05-31 23:45:02 - myp.app - INFO - 📁 Schalte Journal-Mode um...
2025-05-31 23:45:02 - myp.app - INFO - ✅ Datenbank-Cleanup abgeschlossen - WAL-Dateien sollten verschwunden sein
2025-05-31 23:45:02 - myp.app - INFO - ✅ Shutdown abgeschlossen
2025-05-31 23:45:22 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-05-31 23:45:22 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-05-31 23:45:22 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-05-31 23:45:22 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-05-31 23:45:22 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-05-31 23:45:22 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-05-31 23:45:22 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-05-31 23:45:22 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-05-31 23:45:22 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-05-31 23:45:22 - myp.security - INFO - 🔒 Security System initialisiert
2025-05-31 23:45:22 - myp.permissions - INFO - 🔐 Permission Template Helpers registriert
2025-05-31 23:45:22 - myp.app - INFO - ==================================================
2025-05-31 23:45:22 - myp.app - INFO - [START] MYP (Manage Your Printers) wird gestartet...
2025-05-31 23:45:22 - myp.app - INFO - [FOLDER] Log-Verzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\logs
2025-05-31 23:45:22 - myp.app - INFO - [CHART] Log-Level: INFO
2025-05-31 23:45:22 - myp.app - INFO - [PC] Betriebssystem: Windows 11
2025-05-31 23:45:22 - myp.app - INFO - [WEB] Hostname: C040L0079726760
2025-05-31 23:45:22 - myp.app - INFO - [TIME] Startzeit: 31.05.2025 23:45:22
2025-05-31 23:45:22 - myp.app - INFO - ==================================================
2025-05-31 23:45:22 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
2025-05-31 23:45:22 - myp.app - INFO - Datenbank mit Optimierungen initialisiert
2025-05-31 23:45:23 - myp.app - INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
2025-05-31 23:45:23 - myp.app - INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
2025-05-31 23:45:23 - myp.printer_monitor - INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
2025-05-31 23:45:23 - myp.printer_monitor - WARNING - ⚠️ Keine aktiven Drucker zur Initialisierung gefunden
2025-05-31 23:45:23 - myp.app - INFO - Keine Drucker zur Initialisierung gefunden
2025-05-31 23:45:23 - myp.app - INFO - 🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung
2025-05-31 23:45:23 - myp.app - INFO - Job-Scheduler gestartet
2025-05-31 23:45:23 - myp.app - INFO - Starte Debug-Server auf 0.0.0.0:5000 (HTTP)
2025-05-31 23:45:23 - myp.app - INFO - Windows-Debug-Modus: Auto-Reload deaktiviert
2025-05-31 23:45:23 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.178.111:5000
2025-05-31 23:45:23 - werkzeug - INFO - Press CTRL+C to quit
2025-05-31 23:45:24 - myp.printer_monitor - INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
2025-05-31 23:45:24 - myp.printer_monitor - INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
2025-05-31 23:45:24 - myp.printer_monitor - INFO - 🔍 Teste IP 1/6: 192.168.0.103
2025-05-31 23:45:24 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:24] "GET /dashboard HTTP/1.1" 200 -
2025-05-31 23:45:24 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:24] "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/css/components.css HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/css/professional-theme.css HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/optimization-features.js HTTP/1.1" 200 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/ui-components.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/css/tailwind.min.css HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/offline-app.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/css/optimization-animations.css HTTP/1.1" 200 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/debug-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/job-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/dark-mode-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/event-handlers.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/global-refresh-functions.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/csp-violation-handler.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/printer_monitor.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/notifications.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/session-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/js/auto-logout.js HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:45:25 - myp.printer_monitor - INFO - 🔄 Aktualisiere Live-Druckerstatus...
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:45:25 - myp.printer_monitor - INFO - Keine aktiven Drucker gefunden
2025-05-31 23:45:25 - myp.printer_monitor - INFO - 🔄 Aktualisiere Live-Druckerstatus...
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /api/user/settings HTTP/1.1" 200 -
2025-05-31 23:45:25 - myp.printer_monitor - INFO - Keine aktiven Drucker gefunden
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /api/printers/monitor/live-status?use_cache=true HTTP/1.1" 200 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/manifest.json HTTP/1.1" 304 -
2025-05-31 23:45:25 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:25] "GET /static/icons/icon-144x144.png HTTP/1.1" 304 -
2025-05-31 23:45:26 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:26] "POST /api/session/heartbeat HTTP/1.1" 200 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /printers HTTP/1.1" 200 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/css/professional-theme.css HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/css/components.css HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/css/tailwind.min.css HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/css/optimization-animations.css HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/ui-components.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/offline-app.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/optimization-features.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/debug-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/job-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/dark-mode-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/global-refresh-functions.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/event-handlers.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/csp-violation-handler.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/printer_monitor.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/session-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/auto-logout.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /static/js/notifications.js HTTP/1.1" 304 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /api/printers HTTP/1.1" 200 -
2025-05-31 23:45:28 - myp.printer_monitor - INFO - 🔄 Aktualisiere Live-Druckerstatus...
2025-05-31 23:45:28 - myp.printer_monitor - INFO - Keine aktiven Drucker gefunden
2025-05-31 23:45:28 - myp.printer_monitor - INFO - 🔄 Aktualisiere Live-Druckerstatus...
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:45:28 - myp.printer_monitor - INFO - Keine aktiven Drucker gefunden
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /api/user/settings HTTP/1.1" 200 -
2025-05-31 23:45:28 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:28] "GET /api/printers/monitor/live-status?use_cache=false HTTP/1.1" 200 -
2025-05-31 23:45:29 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:29] "GET /api/printers HTTP/1.1" 200 -
2025-05-31 23:45:29 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:29] "GET /static/manifest.json HTTP/1.1" 304 -
2025-05-31 23:45:29 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:29] "GET /static/icons/icon-144x144.png HTTP/1.1" 304 -
2025-05-31 23:45:30 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:30] "POST /api/session/heartbeat HTTP/1.1" 200 -
2025-05-31 23:45:30 - myp.printer_monitor - INFO - 🔍 Teste IP 2/6: 192.168.0.104
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /jobs HTTP/1.1" 200 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/css/tailwind.min.css HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/css/professional-theme.css HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/css/components.css HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/css/optimization-animations.css HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/ui-components.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/optimization-features.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/offline-app.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/csp-violation-handler.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/global-refresh-functions.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/event-handlers.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/dark-mode-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/debug-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/job-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/printer_monitor.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/notifications.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/session-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/js/auto-logout.js HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /api/user/settings HTTP/1.1" 200 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:45:32 - myp.app - WARNING - Schema-Problem beim User-Load für ID 1: tuple index out of range
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:45:32 - myp.app - INFO - User 1 erfolgreich über manuelle Abfrage geladen
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /api/jobs HTTP/1.1" 200 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/manifest.json HTTP/1.1" 304 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /api/printers HTTP/1.1" 200 -
2025-05-31 23:45:32 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:32] "GET /static/icons/icon-144x144.png HTTP/1.1" 304 -
2025-05-31 23:45:33 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:33] "POST /api/session/heartbeat HTTP/1.1" 200 -
2025-05-31 23:45:33 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:33] "GET /stats HTTP/1.1" 200 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/css/professional-theme.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/ui-components.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/css/optimization-animations.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/css/components.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/css/tailwind.min.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/offline-app.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/job-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/global-refresh-functions.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/optimization-features.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/dark-mode-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/event-handlers.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/debug-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/auto-logout.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/session-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/csp-violation-handler.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/notifications.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/printer_monitor.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /api/user/settings HTTP/1.1" 200 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/manifest.json HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/icons/icon-144x144.png HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /calendar HTTP/1.1" 200 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/css/tailwind.min.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/css/components.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/css/optimization-animations.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/css/professional-theme.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/fullcalendar/main.min.css HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/offline-app.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/ui-components.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/optimization-features.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/fullcalendar/core.min.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/fullcalendar/timegrid.min.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/fullcalendar/daygrid.min.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/fullcalendar/interaction.min.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/fullcalendar/list.min.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/debug-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/job-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:34] "GET /static/js/dark-mode-fix.js HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/js/global-refresh-functions.js HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/js/event-handlers.js HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/js/csp-violation-handler.js HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/js/printer_monitor.js HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/js/notifications.js HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/js/auto-logout.js HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/js/session-manager.js HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /api/calendar/events?start=2025-05-25T00:00:00%2B02:00&end=2025-06-01T00:00:00%2B02:00 HTTP/1.1" 404 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /api/user/settings HTTP/1.1" 200 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/manifest.json HTTP/1.1" 304 -
2025-05-31 23:45:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:35] "GET /static/icons/icon-144x144.png HTTP/1.1" 304 -
2025-05-31 23:45:36 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:36] "POST /api/session/heartbeat HTTP/1.1" 200 -
2025-05-31 23:45:36 - myp.printer_monitor - INFO - 🔍 Teste IP 3/6: 192.168.0.100
2025-05-31 23:45:38 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:38] "POST /api/optimization/auto-optimize HTTP/1.1" 404 -
2025-05-31 23:45:41 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:45:41] "POST /api/optimization/auto-optimize HTTP/1.1" 404 -
2025-05-31 23:45:42 - myp.printer_monitor - INFO - 🔍 Teste IP 4/6: 192.168.0.101
2025-05-31 23:45:48 - myp.printer_monitor - INFO - 🔍 Teste IP 5/6: 192.168.0.102
2025-05-31 23:45:54 - myp.printer_monitor - INFO - 🔍 Teste IP 6/6: 192.168.0.105
2025-05-31 23:46:00 - myp.printer_monitor - INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.2s
2025-05-31 23:46:04 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
2025-05-31 23:46:04 - myp.app - INFO - 🔄 Beende Queue Manager...
2025-05-31 23:46:05 - myp.app - INFO - Job-Scheduler gestoppt
2025-05-31 23:46:05 - myp.app - INFO - 💾 Führe Datenbank-Cleanup durch...
2025-05-31 23:46:05 - myp.app - INFO - 📝 Führe WAL-Checkpoint durch...
2025-05-31 23:46:05 - myp.app - INFO - WAL-Checkpoint abgeschlossen: 0 Seiten übertragen, 0 Seiten zurückgesetzt
2025-05-31 23:46:05 - myp.app - INFO - 📁 Schalte Journal-Mode um...
2025-05-31 23:46:05 - myp.app - INFO - ✅ Datenbank-Cleanup abgeschlossen - WAL-Dateien sollten verschwunden sein
2025-05-31 23:46:05 - myp.app - INFO - ✅ Shutdown abgeschlossen
2025-05-31 23:46:11 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-05-31 23:46:11 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-05-31 23:46:11 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-05-31 23:46:11 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-05-31 23:46:11 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-05-31 23:46:12 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-05-31 23:46:12 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-05-31 23:46:12 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-05-31 23:46:12 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-05-31 23:46:12 - myp.security - INFO - 🔒 Security System initialisiert
2025-05-31 23:46:12 - myp.permissions - INFO - 🔐 Permission Template Helpers registriert
2025-05-31 23:46:12 - myp.app - INFO - ==================================================
2025-05-31 23:46:12 - myp.app - INFO - [START] MYP (Manage Your Printers) wird gestartet...
2025-05-31 23:46:12 - myp.app - INFO - [FOLDER] Log-Verzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\logs
2025-05-31 23:46:12 - myp.app - INFO - [CHART] Log-Level: INFO
2025-05-31 23:46:12 - myp.app - INFO - [PC] Betriebssystem: Windows 11
2025-05-31 23:46:12 - myp.app - INFO - [WEB] Hostname: C040L0079726760
2025-05-31 23:46:12 - myp.app - INFO - [TIME] Startzeit: 31.05.2025 23:46:12
2025-05-31 23:46:12 - myp.app - INFO - ==================================================
2025-05-31 23:46:12 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
2025-05-31 23:46:12 - myp.app - INFO - Datenbank mit Optimierungen initialisiert
2025-05-31 23:46:12 - myp.app - INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
2025-05-31 23:46:12 - myp.app - INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
2025-05-31 23:46:12 - myp.printer_monitor - INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
2025-05-31 23:46:12 - myp.printer_monitor - WARNING - ⚠️ Keine aktiven Drucker zur Initialisierung gefunden
2025-05-31 23:46:12 - myp.app - INFO - Keine Drucker zur Initialisierung gefunden
2025-05-31 23:46:12 - myp.app - INFO - 🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung
2025-05-31 23:46:12 - myp.app - INFO - Job-Scheduler gestartet
2025-05-31 23:46:12 - myp.app - INFO - Starte Debug-Server auf 0.0.0.0:5000 (HTTP)
2025-05-31 23:46:12 - myp.app - INFO - Windows-Debug-Modus: Auto-Reload deaktiviert
2025-05-31 23:46:12 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.178.111:5000
2025-05-31 23:46:12 - werkzeug - INFO - Press CTRL+C to quit
2025-05-31 23:46:14 - myp.printer_monitor - INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
2025-05-31 23:46:14 - myp.printer_monitor - INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
2025-05-31 23:46:14 - myp.printer_monitor - INFO - 🔍 Teste IP 1/6: 192.168.0.103
2025-05-31 23:46:20 - myp.printer_monitor - INFO - 🔍 Teste IP 2/6: 192.168.0.104
2025-05-31 23:46:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:46:21] "POST /api/optimization/auto-optimize HTTP/1.1" 404 -
2025-05-31 23:46:26 - myp.printer_monitor - INFO - 🔍 Teste IP 3/6: 192.168.0.100
2025-05-31 23:46:32 - myp.printer_monitor - INFO - 🔍 Teste IP 4/6: 192.168.0.101
2025-05-31 23:46:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:46:35] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:46:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:46:35] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:46:38 - myp.printer_monitor - INFO - 🔍 Teste IP 5/6: 192.168.0.102
2025-05-31 23:46:44 - myp.printer_monitor - INFO - 🔍 Teste IP 6/6: 192.168.0.105
2025-05-31 23:46:49 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:46:49] "POST /api/optimization/auto-optimize HTTP/1.1" 404 -
2025-05-31 23:46:50 - myp.printer_monitor - INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.1s
2025-05-31 23:47:05 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:47:05] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:47:05 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:47:05] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:47:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:47:35] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:47:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:47:35] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:48:05 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:48:05] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:48:05 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:48:05] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:48:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:48:35] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:48:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:48:35] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:49:05 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:05] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:49:05 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:05] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /api/calendar/events?start=2025-05-25T00:00:00%2B02:00&end=2025-06-01T00:00:00%2B02:00 HTTP/1.1" 404 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /calendar HTTP/1.1" 200 -
2025-05-31 23:49:21 - myp.app - WARNING - Schema-Problem beim User-Load für ID 1: tuple index out of range
2025-05-31 23:49:21 - myp.app - INFO - User 1 erfolgreich über manuelle Abfrage geladen
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/fullcalendar/main.min.css HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/css/optimization-animations.css HTTP/1.1" 200 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/offline-app.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/css/tailwind.min.css HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/ui-components.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/optimization-features.js HTTP/1.1" 200 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/css/professional-theme.css HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/fullcalendar/daygrid.min.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/fullcalendar/core.min.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/css/components.css HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/fullcalendar/timegrid.min.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/fullcalendar/interaction.min.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - myp.app - WARNING - Schema-Problem beim User-Load für ID 1: (sqlite3.InterfaceError) bad parameter or other API misuse
[SQL: SELECT users.id AS users_id, users.email AS users_email, users.username AS users_username, users.password_hash AS users_password_hash, users.name AS users_name, users.role AS users_role, users.active AS users_active, users.created_at AS users_created_at, users.last_login AS users_last_login, users.updated_at AS users_updated_at, users.settings AS users_settings, users.last_activity AS users_last_activity, users.department AS users_department, users.position AS users_position, users.phone AS users_phone, users.bio AS users_bio
FROM users
WHERE users.id = ?
LIMIT ? OFFSET ?]
[parameters: (1, 1, 0)]
(Background on this error at: https://sqlalche.me/e/20/rvf5)
2025-05-31 23:49:21 - myp.app - INFO - User 1 erfolgreich über manuelle Abfrage geladen
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/job-manager.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/fullcalendar/list.min.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/global-refresh-functions.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/debug-fix.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/dark-mode-fix.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/event-handlers.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/csp-violation-handler.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/printer_monitor.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/notifications.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/session-manager.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/js/auto-logout.js HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /api/calendar/events?start=2025-05-25T00:00:00%2B02:00&end=2025-06-01T00:00:00%2B02:00 HTTP/1.1" 404 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /api/user/settings HTTP/1.1" 200 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/favicon.svg HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/manifest.json HTTP/1.1" 304 -
2025-05-31 23:49:21 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:21] "GET /static/icons/icon-144x144.png HTTP/1.1" 304 -
2025-05-31 23:49:22 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:22] "POST /api/session/heartbeat HTTP/1.1" 200 -
2025-05-31 23:49:23 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:49:23] "POST /api/optimization/auto-optimize HTTP/1.1" 404 -
2025-05-31 23:49:27 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
2025-05-31 23:49:27 - myp.app - INFO - 🔄 Beende Queue Manager...
2025-05-31 23:49:27 - myp.app - INFO - Job-Scheduler gestoppt
2025-05-31 23:49:27 - myp.app - INFO - 💾 Führe Datenbank-Cleanup durch...
2025-05-31 23:49:27 - myp.app - INFO - 📝 Führe WAL-Checkpoint durch...
2025-05-31 23:49:27 - myp.app - INFO - WAL-Checkpoint abgeschlossen: 0 Seiten übertragen, 0 Seiten zurückgesetzt
2025-05-31 23:49:27 - myp.app - INFO - 📁 Schalte Journal-Mode um...
2025-05-31 23:49:27 - myp.app - INFO - ✅ Datenbank-Cleanup abgeschlossen - WAL-Dateien sollten verschwunden sein
2025-05-31 23:49:27 - myp.app - INFO - ✅ Shutdown abgeschlossen
2025-05-31 23:49:29 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-05-31 23:49:29 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-05-31 23:49:29 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-05-31 23:49:29 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-05-31 23:49:29 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-05-31 23:49:29 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-05-31 23:49:29 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-05-31 23:49:29 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-05-31 23:49:30 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-05-31 23:49:30 - myp.security - INFO - 🔒 Security System initialisiert
2025-05-31 23:49:30 - myp.permissions - INFO - 🔐 Permission Template Helpers registriert
2025-05-31 23:49:30 - myp.app - INFO - ==================================================
2025-05-31 23:49:30 - myp.app - INFO - [START] MYP (Manage Your Printers) wird gestartet...
2025-05-31 23:49:30 - myp.app - INFO - [FOLDER] Log-Verzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\logs
2025-05-31 23:49:30 - myp.app - INFO - [CHART] Log-Level: INFO
2025-05-31 23:49:30 - myp.app - INFO - [PC] Betriebssystem: Windows 11
2025-05-31 23:49:30 - myp.app - INFO - [WEB] Hostname: C040L0079726760
2025-05-31 23:49:30 - myp.app - INFO - [TIME] Startzeit: 31.05.2025 23:49:30
2025-05-31 23:49:30 - myp.app - INFO - ==================================================
2025-05-31 23:50:29 - myp.windows_fixes - INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-05-31 23:50:29 - myp.windows_fixes - INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-05-31 23:50:29 - myp.windows_fixes - INFO - ✅ Globaler subprocess-Patch angewendet
2025-05-31 23:50:29 - myp.windows_fixes - INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-05-31 23:50:29 - myp.app - INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-05-31 23:50:29 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-05-31 23:50:29 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-05-31 23:50:29 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
2025-05-31 23:50:29 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
2025-05-31 23:50:29 - myp.security - INFO - 🔒 Security System initialisiert
2025-05-31 23:50:29 - myp.permissions - INFO - 🔐 Permission Template Helpers registriert
2025-05-31 23:50:29 - myp.app - INFO - ==================================================
2025-05-31 23:50:29 - myp.app - INFO - [START] MYP (Manage Your Printers) wird gestartet...
2025-05-31 23:50:29 - myp.app - INFO - [FOLDER] Log-Verzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\logs
2025-05-31 23:50:29 - myp.app - INFO - [CHART] Log-Level: INFO
2025-05-31 23:50:29 - myp.app - INFO - [PC] Betriebssystem: Windows 11
2025-05-31 23:50:29 - myp.app - INFO - [WEB] Hostname: C040L0079726760
2025-05-31 23:50:29 - myp.app - INFO - [TIME] Startzeit: 31.05.2025 23:50:29
2025-05-31 23:50:29 - myp.app - INFO - ==================================================
2025-05-31 23:50:30 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
2025-05-31 23:50:30 - myp.app - INFO - Datenbank mit Optimierungen initialisiert
2025-05-31 23:50:30 - myp.app - INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
2025-05-31 23:50:30 - myp.app - INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
2025-05-31 23:50:30 - myp.printer_monitor - INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
2025-05-31 23:50:30 - myp.printer_monitor - WARNING - ⚠️ Keine aktiven Drucker zur Initialisierung gefunden
2025-05-31 23:50:30 - myp.app - INFO - Keine Drucker zur Initialisierung gefunden
2025-05-31 23:50:30 - myp.app - INFO - 🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung
2025-05-31 23:50:30 - myp.app - INFO - Job-Scheduler gestartet
2025-05-31 23:50:30 - myp.app - INFO - Starte Debug-Server auf 0.0.0.0:5000 (HTTP)
2025-05-31 23:50:30 - myp.app - INFO - Windows-Debug-Modus: Auto-Reload deaktiviert
2025-05-31 23:50:30 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.178.111:5000
2025-05-31 23:50:30 - werkzeug - INFO - Press CTRL+C to quit
2025-05-31 23:50:31 - myp.printer_monitor - INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
2025-05-31 23:50:31 - myp.printer_monitor - INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
2025-05-31 23:50:31 - myp.printer_monitor - INFO - 🔍 Teste IP 1/6: 192.168.0.103
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /calendar HTTP/1.1" 200 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/css/professional-theme.css HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/offline-app.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/css/tailwind.min.css HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/css/components.css HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/optimization-features.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/fullcalendar/core.min.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/fullcalendar/daygrid.min.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/ui-components.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/css/optimization-animations.css HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/fullcalendar/main.min.css HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/fullcalendar/timegrid.min.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/fullcalendar/list.min.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/fullcalendar/interaction.min.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/debug-fix.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/job-manager.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/dark-mode-fix.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/global-refresh-functions.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/event-handlers.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/csp-violation-handler.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/printer_monitor.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/notifications.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/session-manager.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/js/auto-logout.js HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /api/calendar/events?start=2025-05-25T00:00:00%2B02:00&end=2025-06-01T00:00:00%2B02:00 HTTP/1.1" 404 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /api/user/settings HTTP/1.1" 200 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/manifest.json HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/favicon.svg HTTP/1.1" 304 -
2025-05-31 23:50:34 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:34] "GET /static/icons/icon-144x144.png HTTP/1.1" 304 -
2025-05-31 23:50:35 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:35] "POST /api/session/heartbeat HTTP/1.1" 200 -
2025-05-31 23:50:36 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:36] "POST /api/optimization/auto-optimize HTTP/1.1" 200 -
2025-05-31 23:50:36 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:36] "GET /api/jobs?page=1 HTTP/1.1" 200 -
2025-05-31 23:50:36 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:50:36] "GET /static/icons/apple-touch-icon.png HTTP/1.1" 200 -
2025-05-31 23:50:37 - myp.printer_monitor - INFO - 🔍 Teste IP 2/6: 192.168.0.104
2025-05-31 23:50:43 - myp.printer_monitor - INFO - 🔍 Teste IP 3/6: 192.168.0.100
2025-05-31 23:50:50 - myp.printer_monitor - INFO - 🔍 Teste IP 4/6: 192.168.0.101
2025-05-31 23:50:56 - myp.printer_monitor - INFO - 🔍 Teste IP 5/6: 192.168.0.102
2025-05-31 23:51:02 - myp.printer_monitor - INFO - 🔍 Teste IP 6/6: 192.168.0.105
2025-05-31 23:51:04 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:51:04] "GET /api/notifications HTTP/1.1" 200 -
2025-05-31 23:51:04 - werkzeug - INFO - 127.0.0.1 - - [31/May/2025 23:51:04] "GET /api/session/status HTTP/1.1" 200 -
2025-05-31 23:51:07 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
2025-05-31 23:51:07 - myp.app - INFO - 🔄 Beende Queue Manager...
2025-05-31 23:51:08 - myp.printer_monitor - INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.2s
2025-05-31 23:51:08 - myp.app - INFO - Job-Scheduler gestoppt
2025-05-31 23:51:08 - myp.app - INFO - 💾 Führe Datenbank-Cleanup durch...
2025-05-31 23:51:08 - myp.app - INFO - 📝 Führe WAL-Checkpoint durch...
2025-05-31 23:51:08 - myp.app - INFO - WAL-Checkpoint abgeschlossen: 0 Seiten übertragen, 0 Seiten zurückgesetzt
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 - ✅ Shutdown abgeschlossen

View File

@ -2514,3 +2514,10 @@
2025-05-31 23:35:03 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker 2025-05-31 23:35:03 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
2025-05-31 23:35:04 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check) 2025-05-31 23:35:04 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
2025-05-31 23:35:14 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check) 2025-05-31 23:35:14 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
2025-05-31 23:45:25 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-05-31 23:45:25 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
2025-05-31 23:45:28 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
2025-05-31 23:45:28 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-05-31 23:45:28 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
2025-05-31 23:45:29 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
2025-05-31 23:45:32 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)

View File

@ -2687,3 +2687,21 @@
2025-05-31 23:34:22 - myp.scheduler - INFO - Scheduler-Thread gestartet 2025-05-31 23:34:22 - myp.scheduler - INFO - Scheduler-Thread gestartet
2025-05-31 23:34:22 - myp.scheduler - INFO - Scheduler gestartet 2025-05-31 23:34:22 - myp.scheduler - INFO - Scheduler gestartet
2025-05-31 23:44:38 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True 2025-05-31 23:44:38 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-05-31 23:45:02 - myp.scheduler - INFO - Scheduler-Thread beendet
2025-05-31 23:45:02 - myp.scheduler - INFO - Scheduler gestoppt
2025-05-31 23:45:22 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-05-31 23:45:23 - myp.scheduler - INFO - Scheduler-Thread gestartet
2025-05-31 23:45:23 - myp.scheduler - INFO - Scheduler gestartet
2025-05-31 23:46:05 - myp.scheduler - INFO - Scheduler-Thread beendet
2025-05-31 23:46:05 - myp.scheduler - INFO - Scheduler gestoppt
2025-05-31 23:46:12 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-05-31 23:46:12 - myp.scheduler - INFO - Scheduler-Thread gestartet
2025-05-31 23:46:12 - myp.scheduler - INFO - Scheduler gestartet
2025-05-31 23:49:27 - myp.scheduler - INFO - Scheduler-Thread beendet
2025-05-31 23:49:27 - myp.scheduler - INFO - Scheduler gestoppt
2025-05-31 23:49:29 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-05-31 23:50:29 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-05-31 23:50:30 - myp.scheduler - INFO - Scheduler-Thread 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 gestoppt

View File

@ -53,7 +53,7 @@
} }
.animate-pulse-scale { .animate-pulse-scale {
animation: pulse-scale 2s infinite ease-in-out; animation: pulse-scale 3s infinite ease-in-out;
} }
/* ===== FLOATING ANIMATIONS ===== */ /* ===== FLOATING ANIMATIONS ===== */
@ -82,12 +82,12 @@
} }
.animate-float { .animate-float {
animation: float 3s infinite ease-in-out; animation: float 4s infinite ease-in-out;
} }
.animate-float-delay { .animate-float-delay {
animation: float-delay 3s infinite ease-in-out; animation: float-delay 4s infinite ease-in-out;
animation-delay: 1s; animation-delay: 1.5s;
} }
/* ===== SLIDE-UP ANIMATIONS ===== */ /* ===== SLIDE-UP ANIMATIONS ===== */
@ -161,7 +161,7 @@
} }
.animate-glow { .animate-glow {
animation: glow 2s infinite ease-in-out; animation: glow 3s infinite ease-in-out;
} }
/* ===== KONFETTI ANIMATION ===== */ /* ===== KONFETTI ANIMATION ===== */
@ -190,7 +190,7 @@
opacity: 1; opacity: 1;
} }
100% { 100% {
transform: translateY(100vh) rotate(720deg); transform: translateY(120vh) rotate(720deg);
opacity: 0; opacity: 0;
} }
} }

View File

@ -366,14 +366,14 @@ class OptimizationManager {
// Sound-Effekt (optional) // Sound-Effekt (optional)
this.playSuccessSound(); this.playSuccessSound();
// Auto-Close nach 10 Sekunden // Auto-Close nach 20 Sekunden (verlängert für bessere Animation-Wirkung)
setTimeout(() => { setTimeout(() => {
if (modal && modal.parentNode) { if (modal && modal.parentNode) {
modal.style.opacity = '0'; modal.style.opacity = '0';
modal.style.transform = 'scale(0.95)'; modal.style.transform = 'scale(0.95)';
setTimeout(() => modal.remove(), 300); setTimeout(() => modal.remove(), 300);
} }
}, 10000); }, 20000);
} }
/** /**
@ -383,10 +383,10 @@ class OptimizationManager {
const colors = ['#FFD700', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7']; const colors = ['#FFD700', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'];
let confetti = ''; let confetti = '';
for (let i = 0; i < 30; i++) { for (let i = 0; i < 50; i++) {
const color = colors[Math.floor(Math.random() * colors.length)]; const color = colors[Math.floor(Math.random() * colors.length)];
const delay = Math.random() * 3; const delay = Math.random() * 5;
const duration = 3 + Math.random() * 2; const duration = 4 + Math.random() * 3;
const left = Math.random() * 100; const left = Math.random() * 100;
confetti += ` confetti += `

View File

@ -0,0 +1,936 @@
"""
Erweitertes Tabellen-System für das MYP-System
=============================================
Dieses Modul stellt erweiterte Tabellen-Funktionalität bereit:
- Sortierung nach allen Spalten
- Erweiterte Filter-Optionen
- Pagination mit anpassbaren Seitengrößen
- Spalten-Auswahl und -anpassung
- Export-Funktionen
- Responsive Design
"""
import re
import json
import math
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Tuple, Union, Callable
from dataclasses import dataclass, asdict
from enum import Enum
from flask import request, jsonify
from sqlalchemy import func, text, or_, and_
from sqlalchemy.orm import Query
from utils.logging_config import get_logger
from models import Job, User, Printer, GuestRequest, get_db_session
logger = get_logger("advanced_tables")
class SortDirection(Enum):
ASC = "asc"
DESC = "desc"
class FilterOperator(Enum):
EQUALS = "eq"
NOT_EQUALS = "ne"
CONTAINS = "contains"
NOT_CONTAINS = "not_contains"
STARTS_WITH = "starts_with"
ENDS_WITH = "ends_with"
GREATER_THAN = "gt"
LESS_THAN = "lt"
GREATER_EQUAL = "gte"
LESS_EQUAL = "lte"
BETWEEN = "between"
IN = "in"
NOT_IN = "not_in"
IS_NULL = "is_null"
IS_NOT_NULL = "is_not_null"
@dataclass
class SortConfig:
"""Sortierung-Konfiguration"""
column: str
direction: SortDirection = SortDirection.ASC
@dataclass
class FilterConfig:
"""Filter-Konfiguration"""
column: str
operator: FilterOperator
value: Any = None
values: List[Any] = None
@dataclass
class PaginationConfig:
"""Pagination-Konfiguration"""
page: int = 1
page_size: int = 25
max_page_size: int = 100
@dataclass
class ColumnConfig:
"""Spalten-Konfiguration"""
key: str
label: str
sortable: bool = True
filterable: bool = True
searchable: bool = True
visible: bool = True
width: Optional[str] = None
align: str = "left" # left, center, right
format_type: str = "text" # text, number, date, datetime, boolean, currency
format_options: Dict[str, Any] = None
@dataclass
class TableConfig:
"""Gesamt-Tabellen-Konfiguration"""
table_id: str
columns: List[ColumnConfig]
default_sort: List[SortConfig] = None
default_filters: List[FilterConfig] = None
pagination: PaginationConfig = None
searchable: bool = True
exportable: bool = True
selectable: bool = False
row_actions: List[Dict[str, Any]] = None
class AdvancedTableQuery:
"""Builder für erweiterte Tabellen-Abfragen"""
def __init__(self, base_query: Query, model_class):
self.base_query = base_query
self.model_class = model_class
self.filters = []
self.sorts = []
self.search_term = None
self.search_columns = []
def add_filter(self, filter_config: FilterConfig):
"""Fügt einen Filter hinzu"""
self.filters.append(filter_config)
return self
def add_sort(self, sort_config: SortConfig):
"""Fügt eine Sortierung hinzu"""
self.sorts.append(sort_config)
return self
def set_search(self, term: str, columns: List[str]):
"""Setzt globale Suche"""
self.search_term = term
self.search_columns = columns
return self
def build_query(self) -> Query:
"""Erstellt die finale Query"""
query = self.base_query
# Filter anwenden
for filter_config in self.filters:
query = self._apply_filter(query, filter_config)
# Globale Suche anwenden
if self.search_term and self.search_columns:
query = self._apply_search(query)
# Sortierung anwenden
for sort_config in self.sorts:
query = self._apply_sort(query, sort_config)
return query
def _apply_filter(self, query: Query, filter_config: FilterConfig) -> Query:
"""Wendet einen Filter auf die Query an"""
column = getattr(self.model_class, filter_config.column, None)
if not column:
logger.warning(f"Spalte {filter_config.column} nicht gefunden in {self.model_class}")
return query
op = filter_config.operator
value = filter_config.value
values = filter_config.values
if op == FilterOperator.EQUALS:
return query.filter(column == value)
elif op == FilterOperator.NOT_EQUALS:
return query.filter(column != value)
elif op == FilterOperator.CONTAINS:
return query.filter(column.ilike(f"%{value}%"))
elif op == FilterOperator.NOT_CONTAINS:
return query.filter(~column.ilike(f"%{value}%"))
elif op == FilterOperator.STARTS_WITH:
return query.filter(column.ilike(f"{value}%"))
elif op == FilterOperator.ENDS_WITH:
return query.filter(column.ilike(f"%{value}"))
elif op == FilterOperator.GREATER_THAN:
return query.filter(column > value)
elif op == FilterOperator.LESS_THAN:
return query.filter(column < value)
elif op == FilterOperator.GREATER_EQUAL:
return query.filter(column >= value)
elif op == FilterOperator.LESS_EQUAL:
return query.filter(column <= value)
elif op == FilterOperator.BETWEEN and values and len(values) >= 2:
return query.filter(column.between(values[0], values[1]))
elif op == FilterOperator.IN and values:
return query.filter(column.in_(values))
elif op == FilterOperator.NOT_IN and values:
return query.filter(~column.in_(values))
elif op == FilterOperator.IS_NULL:
return query.filter(column.is_(None))
elif op == FilterOperator.IS_NOT_NULL:
return query.filter(column.isnot(None))
return query
def _apply_search(self, query: Query) -> Query:
"""Wendet globale Suche an"""
if not self.search_term or not self.search_columns:
return query
search_conditions = []
for column_name in self.search_columns:
column = getattr(self.model_class, column_name, None)
if column:
# Konvertiere zu String für Suche in numerischen Spalten
search_conditions.append(
func.cast(column, sqlalchemy.String).ilike(f"%{self.search_term}%")
)
if search_conditions:
return query.filter(or_(*search_conditions))
return query
def _apply_sort(self, query: Query, sort_config: SortConfig) -> Query:
"""Wendet Sortierung an"""
column = getattr(self.model_class, sort_config.column, None)
if not column:
logger.warning(f"Spalte {sort_config.column} für Sortierung nicht gefunden")
return query
if sort_config.direction == SortDirection.DESC:
return query.order_by(column.desc())
else:
return query.order_by(column.asc())
class TableDataProcessor:
"""Verarbeitet Tabellendaten für die Ausgabe"""
def __init__(self, config: TableConfig):
self.config = config
def process_data(self, data: List[Any]) -> List[Dict[str, Any]]:
"""Verarbeitet rohe Daten für Tabellen-Ausgabe"""
processed_rows = []
for item in data:
row = {}
for column in self.config.columns:
if not column.visible:
continue
# Wert extrahieren
value = self._extract_value(item, column.key)
# Formatieren
formatted_value = self._format_value(value, column)
row[column.key] = {
'raw': value,
'formatted': formatted_value,
'sortable': column.sortable,
'filterable': column.filterable
}
# Row Actions hinzufügen
if self.config.row_actions:
row['_actions'] = self._get_row_actions(item)
# Row Metadata
row['_id'] = getattr(item, 'id', None)
row['_type'] = item.__class__.__name__.lower()
processed_rows.append(row)
return processed_rows
def _extract_value(self, item: Any, key: str) -> Any:
"""Extrahiert Wert aus einem Objekt"""
try:
# Unterstützung für verschachtelte Attribute (z.B. "user.name")
if '.' in key:
obj = item
for part in key.split('.'):
obj = getattr(obj, part, None)
if obj is None:
break
return obj
else:
return getattr(item, key, None)
except AttributeError:
return None
def _format_value(self, value: Any, column: ColumnConfig) -> str:
"""Formatiert einen Wert basierend auf dem Spaltentyp"""
if value is None:
return ""
format_type = column.format_type
options = column.format_options or {}
if format_type == "date" and isinstance(value, datetime):
date_format = options.get('format', '%d.%m.%Y')
return value.strftime(date_format)
elif format_type == "datetime" and isinstance(value, datetime):
datetime_format = options.get('format', '%d.%m.%Y %H:%M')
return value.strftime(datetime_format)
elif format_type == "number" and isinstance(value, (int, float)):
decimals = options.get('decimals', 0)
return f"{value:.{decimals}f}"
elif format_type == "currency" and isinstance(value, (int, float)):
currency = options.get('currency', '')
decimals = options.get('decimals', 2)
return f"{value:.{decimals}f} {currency}"
elif format_type == "boolean":
true_text = options.get('true_text', 'Ja')
false_text = options.get('false_text', 'Nein')
return true_text if value else false_text
elif format_type == "truncate":
max_length = options.get('max_length', 50)
text = str(value)
if len(text) > max_length:
return text[:max_length-3] + "..."
return text
return str(value)
def _get_row_actions(self, item: Any) -> List[Dict[str, Any]]:
"""Generiert verfügbare Aktionen für eine Zeile"""
actions = []
for action_config in self.config.row_actions:
# Prüfe Bedingungen für Aktion
if self._check_action_condition(item, action_config):
actions.append({
'type': action_config['type'],
'label': action_config['label'],
'icon': action_config.get('icon'),
'url': self._build_action_url(item, action_config),
'method': action_config.get('method', 'GET'),
'confirm': action_config.get('confirm'),
'class': action_config.get('class', '')
})
return actions
def _check_action_condition(self, item: Any, action_config: Dict[str, Any]) -> bool:
"""Prüft ob eine Aktion für ein Item verfügbar ist"""
condition = action_config.get('condition')
if not condition:
return True
try:
# Einfache Bedingungsprüfung
if isinstance(condition, dict):
for key, expected_value in condition.items():
actual_value = self._extract_value(item, key)
if actual_value != expected_value:
return False
return True
except Exception:
return False
def _build_action_url(self, item: Any, action_config: Dict[str, Any]) -> str:
"""Erstellt URL für eine Aktion"""
url_template = action_config.get('url', '')
# Ersetze Platzhalter in URL
try:
return url_template.format(id=getattr(item, 'id', ''))
except Exception:
return url_template
def parse_table_request(request_data: Dict[str, Any]) -> Tuple[List[SortConfig], List[FilterConfig], PaginationConfig, str]:
"""Parst Tabellen-Request-Parameter"""
# Sortierung parsen
sorts = []
sort_data = request_data.get('sort', [])
if isinstance(sort_data, dict):
sort_data = [sort_data]
for sort_item in sort_data:
if isinstance(sort_item, dict):
column = sort_item.get('column')
direction = SortDirection(sort_item.get('direction', 'asc'))
if column:
sorts.append(SortConfig(column=column, direction=direction))
# Filter parsen
filters = []
filter_data = request_data.get('filters', [])
if isinstance(filter_data, dict):
filter_data = [filter_data]
for filter_item in filter_data:
if isinstance(filter_item, dict):
column = filter_item.get('column')
operator = FilterOperator(filter_item.get('operator', 'eq'))
value = filter_item.get('value')
values = filter_item.get('values')
if column:
filters.append(FilterConfig(
column=column,
operator=operator,
value=value,
values=values
))
# Pagination parsen
page = int(request_data.get('page', 1))
page_size = min(int(request_data.get('page_size', 25)), 100)
pagination = PaginationConfig(page=page, page_size=page_size)
# Suche parsen
search = request_data.get('search', '')
return sorts, filters, pagination, search
def get_advanced_table_javascript() -> str:
"""JavaScript für erweiterte Tabellen"""
return """
class AdvancedTable {
constructor(tableId, config = {}) {
this.tableId = tableId;
this.config = {
apiUrl: '/api/table-data',
pageSize: 25,
searchDelay: 500,
sortable: true,
filterable: true,
searchable: true,
...config
};
this.currentSort = [];
this.currentFilters = [];
this.currentPage = 1;
this.currentSearch = '';
this.totalPages = 1;
this.totalItems = 0;
this.searchTimeout = null;
this.init();
}
init() {
this.setupTable();
this.setupEventListeners();
this.loadData();
}
setupTable() {
const table = document.getElementById(this.tableId);
if (!table) return;
table.classList.add('advanced-table');
// Add table wrapper
const wrapper = document.createElement('div');
wrapper.className = 'table-wrapper';
table.parentNode.insertBefore(wrapper, table);
wrapper.appendChild(table);
// Add controls
this.createControls(wrapper);
}
createControls(wrapper) {
const controls = document.createElement('div');
controls.className = 'table-controls';
controls.innerHTML = `
<div class="table-controls-left">
<div class="search-box">
<input type="text" id="${this.tableId}-search" placeholder="Suchen..." class="search-input">
<span class="search-icon">🔍</span>
</div>
<div class="page-size-selector">
<label>Einträge pro Seite:</label>
<select id="${this.tableId}-page-size">
<option value="10">10</option>
<option value="25" selected>25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
<div class="table-controls-right">
<button class="btn-filter" id="${this.tableId}-filter-btn">Filter</button>
<button class="btn-export" id="${this.tableId}-export-btn">Export</button>
<button class="btn-refresh" id="${this.tableId}-refresh-btn"></button>
</div>
`;
wrapper.insertBefore(controls, wrapper.firstChild);
// Add pagination
const pagination = document.createElement('div');
pagination.className = 'table-pagination';
pagination.id = `${this.tableId}-pagination`;
wrapper.appendChild(pagination);
}
setupEventListeners() {
// Search
const searchInput = document.getElementById(`${this.tableId}-search`);
searchInput?.addEventListener('input', (e) => {
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => {
this.currentSearch = e.target.value;
this.currentPage = 1;
this.loadData();
}, this.config.searchDelay);
});
// Page size
const pageSizeSelect = document.getElementById(`${this.tableId}-page-size`);
pageSizeSelect?.addEventListener('change', (e) => {
this.config.pageSize = parseInt(e.target.value);
this.currentPage = 1;
this.loadData();
});
// Refresh
const refreshBtn = document.getElementById(`${this.tableId}-refresh-btn`);
refreshBtn?.addEventListener('click', () => {
this.loadData();
});
// Export
const exportBtn = document.getElementById(`${this.tableId}-export-btn`);
exportBtn?.addEventListener('click', () => {
this.exportData();
});
// Table header clicks (sorting)
const table = document.getElementById(this.tableId);
table?.addEventListener('click', (e) => {
const th = e.target.closest('th[data-sortable="true"]');
if (th) {
const column = th.dataset.column;
this.toggleSort(column);
}
});
}
toggleSort(column) {
const existingSort = this.currentSort.find(s => s.column === column);
if (existingSort) {
if (existingSort.direction === 'asc') {
existingSort.direction = 'desc';
} else {
// Remove sort
this.currentSort = this.currentSort.filter(s => s.column !== column);
}
} else {
this.currentSort.push({ column, direction: 'asc' });
}
this.updateSortHeaders();
this.loadData();
}
updateSortHeaders() {
const table = document.getElementById(this.tableId);
const headers = table?.querySelectorAll('th[data-column]');
headers?.forEach(th => {
const column = th.dataset.column;
const sort = this.currentSort.find(s => s.column === column);
th.classList.remove('sort-asc', 'sort-desc');
if (sort) {
th.classList.add(`sort-${sort.direction}`);
}
});
}
async loadData() {
try {
const params = {
page: this.currentPage,
page_size: this.config.pageSize,
search: this.currentSearch,
sort: this.currentSort,
filters: this.currentFilters
};
const response = await fetch(this.config.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params)
});
const data = await response.json();
if (data.success) {
this.renderTable(data.data);
this.updatePagination(data.pagination);
} else {
console.error('Table data loading failed:', data.error);
}
} catch (error) {
console.error('Table data loading error:', error);
}
}
renderTable(data) {
const table = document.getElementById(this.tableId);
const tbody = table?.querySelector('tbody');
if (!tbody) return;
tbody.innerHTML = '';
data.forEach(row => {
const tr = document.createElement('tr');
tr.dataset.id = row._id;
// Render cells
Object.keys(row).forEach(key => {
if (key.startsWith('_')) return; // Skip metadata
const td = document.createElement('td');
const cellData = row[key];
if (typeof cellData === 'object' && cellData.formatted !== undefined) {
td.innerHTML = cellData.formatted;
td.dataset.raw = cellData.raw;
} else {
td.textContent = cellData;
}
tr.appendChild(td);
});
// Add actions column if exists
if (row._actions && row._actions.length > 0) {
const actionsTd = document.createElement('td');
actionsTd.className = 'actions-cell';
actionsTd.innerHTML = this.renderActions(row._actions);
tr.appendChild(actionsTd);
}
tbody.appendChild(tr);
});
}
renderActions(actions) {
return actions.map(action => {
const confirmAttr = action.confirm ? `onclick="return confirm('${action.confirm}')"` : '';
const icon = action.icon ? `<span class="action-icon">${action.icon}</span>` : '';
return `<a href="${action.url}" class="action-btn ${action.class}" ${confirmAttr}>
${icon}${action.label}
</a>`;
}).join(' ');
}
updatePagination(pagination) {
this.currentPage = pagination.page;
this.totalPages = pagination.total_pages;
this.totalItems = pagination.total_items;
const paginationEl = document.getElementById(`${this.tableId}-pagination`);
if (!paginationEl) return;
paginationEl.innerHTML = `
<div class="pagination-info">
Zeige ${pagination.start_item}-${pagination.end_item} von ${pagination.total_items} Einträgen
</div>
<div class="pagination-controls">
${this.renderPaginationButtons()}
</div>
`;
// Event listeners für Pagination
paginationEl.querySelectorAll('.page-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
const page = parseInt(btn.dataset.page);
if (page !== this.currentPage) {
this.currentPage = page;
this.loadData();
}
});
});
}
renderPaginationButtons() {
const buttons = [];
const maxButtons = 7;
// Previous button
buttons.push(`
<button class="page-btn ${this.currentPage === 1 ? 'disabled' : ''}"
data-page="${this.currentPage - 1}" ${this.currentPage === 1 ? 'disabled' : ''}>
</button>
`);
// Page number buttons
let startPage = Math.max(1, this.currentPage - Math.floor(maxButtons / 2));
let endPage = Math.min(this.totalPages, startPage + maxButtons - 1);
if (endPage - startPage + 1 < maxButtons) {
startPage = Math.max(1, endPage - maxButtons + 1);
}
for (let i = startPage; i <= endPage; i++) {
buttons.push(`
<button class="page-btn ${i === this.currentPage ? 'active' : ''}"
data-page="${i}">
${i}
</button>
`);
}
// Next button
buttons.push(`
<button class="page-btn ${this.currentPage === this.totalPages ? 'disabled' : ''}"
data-page="${this.currentPage + 1}" ${this.currentPage === this.totalPages ? 'disabled' : ''}>
</button>
`);
return buttons.join('');
}
exportData() {
const params = new URLSearchParams({
search: this.currentSearch,
sort: JSON.stringify(this.currentSort),
filters: JSON.stringify(this.currentFilters),
format: 'csv'
});
window.open(`${this.config.apiUrl}/export?${params}`, '_blank');
}
}
// Auto-initialize tables with data-advanced-table attribute
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('[data-advanced-table]').forEach(table => {
const config = JSON.parse(table.dataset.advancedTable || '{}');
new AdvancedTable(table.id, config);
});
});
"""
def get_advanced_table_css() -> str:
"""CSS für erweiterte Tabellen"""
return """
.table-wrapper {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.table-controls {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
}
.table-controls-left {
display: flex;
align-items: center;
gap: 1rem;
}
.search-box {
position: relative;
}
.search-input {
padding: 0.5rem 0.75rem;
padding-right: 2rem;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 0.875rem;
}
.search-icon {
position: absolute;
right: 0.5rem;
top: 50%;
transform: translateY(-50%);
color: #6b7280;
}
.page-size-selector {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.table-controls-right {
display: flex;
gap: 0.5rem;
}
.advanced-table {
width: 100%;
border-collapse: collapse;
}
.advanced-table th {
background: #f8f9fa;
padding: 0.75rem;
text-align: left;
font-weight: 600;
border-bottom: 2px solid #e9ecef;
position: relative;
}
.advanced-table th[data-sortable="true"] {
cursor: pointer;
user-select: none;
}
.advanced-table th[data-sortable="true"]:hover {
background: #e9ecef;
}
.advanced-table th.sort-asc::after {
content: "";
color: #3b82f6;
}
.advanced-table th.sort-desc::after {
content: "";
color: #3b82f6;
}
.advanced-table td {
padding: 0.75rem;
border-bottom: 1px solid #e9ecef;
}
.advanced-table tbody tr:hover {
background: #f8f9fa;
}
.actions-cell {
white-space: nowrap;
}
.action-btn {
display: inline-block;
padding: 0.25rem 0.5rem;
margin: 0 0.125rem;
font-size: 0.75rem;
text-decoration: none;
border-radius: 4px;
background: #e5e7eb;
color: #374151;
}
.action-btn:hover {
background: #d1d5db;
}
.action-btn.btn-primary {
background: #3b82f6;
color: white;
}
.action-btn.btn-danger {
background: #ef4444;
color: white;
}
.table-pagination {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}
.pagination-controls {
display: flex;
gap: 0.25rem;
}
.page-btn {
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
background: white;
cursor: pointer;
border-radius: 4px;
}
.page-btn:hover:not(.disabled) {
background: #f3f4f6;
}
.page-btn.active {
background: #3b82f6;
color: white;
border-color: #3b82f6;
}
.page-btn.disabled {
opacity: 0.5;
cursor: not-allowed;
}
@media (max-width: 768px) {
.table-controls {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.table-controls-left,
.table-controls-right {
justify-content: center;
}
.advanced-table {
font-size: 0.875rem;
}
.advanced-table th,
.advanced-table td {
padding: 0.5rem;
}
.table-pagination {
flex-direction: column;
gap: 1rem;
}
}
"""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,909 @@
"""
Multi-Format-Report-Generator für das MYP-System
===============================================
Dieses Modul stellt umfassende Report-Generierung in verschiedenen Formaten bereit:
- PDF-Reports mit professionellem Layout
- Excel-Reports mit Diagrammen und Formatierungen
- CSV-Export für Datenanalyse
- JSON-Export für API-Integration
"""
import os
import io
import json
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Union, BinaryIO
from dataclasses import dataclass, asdict
from abc import ABC, abstractmethod
# PDF-Generation
try:
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, cm
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image, PageBreak
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.lineplots import LinePlot
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.piecharts import Pie
from reportlab.lib.validators import Auto
PDF_AVAILABLE = True
except ImportError:
PDF_AVAILABLE = False
# Excel-Generation
try:
import xlsxwriter
from xlsxwriter.workbook import Workbook
from xlsxwriter.worksheet import Worksheet
EXCEL_AVAILABLE = True
except ImportError:
EXCEL_AVAILABLE = False
import csv
from flask import make_response, jsonify
from utils.logging_config import get_logger
from models import Job, User, Printer, Stats, GuestRequest, get_db_session
logger = get_logger("reports")
@dataclass
class ReportConfig:
"""Konfiguration für Report-Generierung"""
title: str
subtitle: str = ""
author: str = "MYP System"
date_range: tuple = None
include_charts: bool = True
include_summary: bool = True
template: str = "standard"
logo_path: str = None
footer_text: str = "Generiert vom MYP-System"
@dataclass
class ChartData:
"""Daten für Diagramme"""
chart_type: str # 'line', 'bar', 'pie'
title: str
data: List[Dict[str, Any]]
labels: List[str] = None
colors: List[str] = None
class BaseReportGenerator(ABC):
"""Abstrakte Basis-Klasse für Report-Generatoren"""
def __init__(self, config: ReportConfig):
self.config = config
self.data = {}
self.charts = []
@abstractmethod
def generate(self, output_stream: BinaryIO) -> bool:
"""Generiert den Report in den angegebenen Stream"""
pass
def add_data_section(self, name: str, data: List[Dict[str, Any]], headers: List[str] = None):
"""Fügt eine Datensektion hinzu"""
self.data[name] = {
'data': data,
'headers': headers or (list(data[0].keys()) if data else [])
}
def add_chart(self, chart: ChartData):
"""Fügt ein Diagramm hinzu"""
self.charts.append(chart)
class PDFReportGenerator(BaseReportGenerator):
"""PDF-Report-Generator mit professionellem Layout"""
def __init__(self, config: ReportConfig):
super().__init__(config)
if not PDF_AVAILABLE:
raise ImportError("ReportLab ist nicht installiert. Verwenden Sie: pip install reportlab")
self.doc = None
self.story = []
self.styles = getSampleStyleSheet()
self._setup_custom_styles()
def _setup_custom_styles(self):
"""Richtet benutzerdefinierte Styles ein"""
# Titel-Style
self.styles.add(ParagraphStyle(
name='CustomTitle',
parent=self.styles['Heading1'],
fontSize=24,
spaceAfter=30,
alignment=1, # Zentriert
textColor=colors.HexColor('#1f2937')
))
# Untertitel-Style
self.styles.add(ParagraphStyle(
name='CustomSubtitle',
parent=self.styles['Heading2'],
fontSize=16,
spaceAfter=20,
alignment=1,
textColor=colors.HexColor('#6b7280')
))
# Sektions-Header
self.styles.add(ParagraphStyle(
name='SectionHeader',
parent=self.styles['Heading2'],
fontSize=14,
spaceBefore=20,
spaceAfter=10,
textColor=colors.HexColor('#374151'),
borderWidth=1,
borderColor=colors.HexColor('#d1d5db'),
borderPadding=5
))
def generate(self, output_stream: BinaryIO) -> bool:
"""Generiert PDF-Report"""
try:
self.doc = SimpleDocTemplate(
output_stream,
pagesize=A4,
rightMargin=2*cm,
leftMargin=2*cm,
topMargin=2*cm,
bottomMargin=2*cm
)
self._build_header()
self._build_summary()
self._build_data_sections()
self._build_charts()
self._build_footer()
self.doc.build(self.story)
return True
except Exception as e:
logger.error(f"Fehler bei PDF-Generierung: {str(e)}")
return False
def _build_header(self):
"""Erstellt den Report-Header"""
# Logo (falls vorhanden)
if self.config.logo_path and os.path.exists(self.config.logo_path):
try:
logo = Image(self.config.logo_path, width=2*inch, height=1*inch)
self.story.append(logo)
self.story.append(Spacer(1, 0.2*inch))
except Exception as e:
logger.warning(f"Logo konnte nicht geladen werden: {str(e)}")
# Titel
title = Paragraph(self.config.title, self.styles['CustomTitle'])
self.story.append(title)
# Untertitel
if self.config.subtitle:
subtitle = Paragraph(self.config.subtitle, self.styles['CustomSubtitle'])
self.story.append(subtitle)
# Generierungsdatum
date_text = f"Generiert am: {datetime.now().strftime('%d.%m.%Y %H:%M')}"
date_para = Paragraph(date_text, self.styles['Normal'])
self.story.append(date_para)
# Autor
author_text = f"Erstellt von: {self.config.author}"
author_para = Paragraph(author_text, self.styles['Normal'])
self.story.append(author_para)
self.story.append(Spacer(1, 0.3*inch))
def _build_summary(self):
"""Erstellt die Zusammenfassung"""
if not self.config.include_summary:
return
header = Paragraph("Zusammenfassung", self.styles['SectionHeader'])
self.story.append(header)
# Sammle Statistiken aus den Daten
total_records = sum(len(section['data']) for section in self.data.values())
summary_data = [
['Gesamtanzahl Datensätze', str(total_records)],
['Berichtszeitraum', self._format_date_range()],
['Anzahl Sektionen', str(len(self.data))],
['Anzahl Diagramme', str(len(self.charts))]
]
summary_table = Table(summary_data, colWidths=[4*inch, 2*inch])
summary_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#f3f4f6')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.white),
('GRID', (0, 0), (-1, -1), 1, colors.HexColor('#d1d5db'))
]))
self.story.append(summary_table)
self.story.append(Spacer(1, 0.2*inch))
def _build_data_sections(self):
"""Erstellt die Datensektionen"""
for section_name, section_data in self.data.items():
# Sektions-Header
header = Paragraph(section_name, self.styles['SectionHeader'])
self.story.append(header)
# Daten-Tabelle
table_data = [section_data['headers']]
table_data.extend([
[str(row.get(header, '')) for header in section_data['headers']]
for row in section_data['data']
])
# Spaltenbreiten berechnen
col_count = len(section_data['headers'])
col_width = (self.doc.width - 2*inch) / col_count
col_widths = [col_width] * col_count
table = Table(table_data, colWidths=col_widths, repeatRows=1)
table.setStyle(TableStyle([
# Header-Styling
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#3b82f6')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 10),
# Daten-Styling
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 9),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f9fafb')]),
# Rahmen
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#d1d5db')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 6),
('RIGHTPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 8),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
self.story.append(table)
self.story.append(Spacer(1, 0.2*inch))
# Seitenumbruch bei vielen Daten
if len(section_data['data']) > 20:
self.story.append(PageBreak())
def _build_charts(self):
"""Erstellt die Diagramme"""
if not self.config.include_charts or not self.charts:
return
header = Paragraph("Diagramme", self.styles['SectionHeader'])
self.story.append(header)
for chart in self.charts:
chart_title = Paragraph(chart.title, self.styles['Heading3'])
self.story.append(chart_title)
# Diagramm basierend auf Typ erstellen
drawing = self._create_chart_drawing(chart)
if drawing:
self.story.append(drawing)
self.story.append(Spacer(1, 0.2*inch))
def _create_chart_drawing(self, chart: ChartData) -> Optional[Drawing]:
"""Erstellt ein Diagramm-Drawing"""
try:
drawing = Drawing(400, 300)
if chart.chart_type == 'bar':
bar_chart = VerticalBarChart()
bar_chart.x = 50
bar_chart.y = 50
bar_chart.height = 200
bar_chart.width = 300
# Daten vorbereiten
values = [[item.get('value', 0) for item in chart.data]]
categories = [item.get('label', f'Item {i}') for i, item in enumerate(chart.data)]
bar_chart.data = values
bar_chart.categoryAxis.categoryNames = categories
bar_chart.valueAxis.valueMin = 0
# Farben setzen
if chart.colors:
bar_chart.bars[0].fillColor = colors.HexColor(chart.colors[0] if chart.colors else '#3b82f6')
drawing.add(bar_chart)
elif chart.chart_type == 'pie':
pie_chart = Pie()
pie_chart.x = 150
pie_chart.y = 100
pie_chart.width = 100
pie_chart.height = 100
# Daten vorbereiten
pie_chart.data = [item.get('value', 0) for item in chart.data]
pie_chart.labels = [item.get('label', f'Item {i}') for i, item in enumerate(chart.data)]
# Farben setzen
if chart.colors:
pie_chart.slices.fillColor = colors.HexColor(chart.colors[0] if chart.colors else '#3b82f6')
drawing.add(pie_chart)
return drawing
except Exception as e:
logger.error(f"Fehler bei Diagramm-Erstellung: {str(e)}")
return None
def _build_footer(self):
"""Erstellt den Report-Footer"""
footer_text = self.config.footer_text
footer = Paragraph(footer_text, self.styles['Normal'])
self.story.append(Spacer(1, 0.3*inch))
self.story.append(footer)
def _format_date_range(self) -> str:
"""Formatiert den Datumsbereich"""
if not self.config.date_range:
return "Alle verfügbaren Daten"
start_date, end_date = self.config.date_range
return f"{start_date.strftime('%d.%m.%Y')} - {end_date.strftime('%d.%m.%Y')}"
class ExcelReportGenerator(BaseReportGenerator):
"""Excel-Report-Generator mit Diagrammen und Formatierungen"""
def __init__(self, config: ReportConfig):
super().__init__(config)
if not EXCEL_AVAILABLE:
raise ImportError("XlsxWriter ist nicht installiert. Verwenden Sie: pip install xlsxwriter")
self.workbook = None
self.formats = {}
def generate(self, output_stream: BinaryIO) -> bool:
"""Generiert Excel-Report"""
try:
self.workbook = xlsxwriter.Workbook(output_stream, {'in_memory': True})
self._setup_formats()
# Zusammenfassungs-Arbeitsblatt
if self.config.include_summary:
self._create_summary_worksheet()
# Daten-Arbeitsblätter
for section_name, section_data in self.data.items():
self._create_data_worksheet(section_name, section_data)
# Diagramm-Arbeitsblätter
if self.config.include_charts and self.charts:
self._create_charts_worksheet()
self.workbook.close()
return True
except Exception as e:
logger.error(f"Fehler bei Excel-Generierung: {str(e)}")
return False
def _setup_formats(self):
"""Richtet Excel-Formate ein"""
self.formats = {
'title': self.workbook.add_format({
'font_size': 18,
'bold': True,
'align': 'center',
'bg_color': '#1f2937',
'font_color': 'white',
'border': 1
}),
'header': self.workbook.add_format({
'font_size': 12,
'bold': True,
'bg_color': '#3b82f6',
'font_color': 'white',
'align': 'center',
'border': 1
}),
'data': self.workbook.add_format({
'align': 'center',
'border': 1
}),
'data_alt': self.workbook.add_format({
'align': 'center',
'bg_color': '#f9fafb',
'border': 1
}),
'number': self.workbook.add_format({
'num_format': '#,##0',
'align': 'right',
'border': 1
}),
'currency': self.workbook.add_format({
'num_format': '#,##0.00 €',
'align': 'right',
'border': 1
}),
'percentage': self.workbook.add_format({
'num_format': '0.00%',
'align': 'right',
'border': 1
}),
'date': self.workbook.add_format({
'num_format': 'dd.mm.yyyy',
'align': 'center',
'border': 1
})
}
def _create_summary_worksheet(self):
"""Erstellt das Zusammenfassungs-Arbeitsblatt"""
worksheet = self.workbook.add_worksheet('Zusammenfassung')
# Titel
worksheet.merge_range('A1:E1', self.config.title, self.formats['title'])
# Untertitel
if self.config.subtitle:
worksheet.merge_range('A2:E2', self.config.subtitle, self.formats['header'])
# Metadaten
row = 4
metadata = [
['Generiert am:', datetime.now().strftime('%d.%m.%Y %H:%M')],
['Erstellt von:', self.config.author],
['Berichtszeitraum:', self._format_date_range()],
['Anzahl Sektionen:', str(len(self.data))],
['Anzahl Diagramme:', str(len(self.charts))]
]
for label, value in metadata:
worksheet.write(row, 0, label, self.formats['header'])
worksheet.write(row, 1, value, self.formats['data'])
row += 1
# Statistiken pro Sektion
row += 2
worksheet.write(row, 0, 'Sektions-Übersicht:', self.formats['header'])
row += 1
for section_name, section_data in self.data.items():
worksheet.write(row, 0, section_name, self.formats['data'])
worksheet.write(row, 1, len(section_data['data']), self.formats['number'])
row += 1
# Spaltenbreiten anpassen
worksheet.set_column('A:A', 25)
worksheet.set_column('B:B', 20)
def _create_data_worksheet(self, section_name: str, section_data: Dict[str, Any]):
"""Erstellt ein Daten-Arbeitsblatt"""
# Ungültige Zeichen für Arbeitsblatt-Namen ersetzen
safe_name = ''.join(c for c in section_name if c.isalnum() or c in ' -_')[:31]
worksheet = self.workbook.add_worksheet(safe_name)
# Header schreiben
headers = section_data['headers']
for col, header in enumerate(headers):
worksheet.write(0, col, header, self.formats['header'])
# Daten schreiben
for row_idx, row_data in enumerate(section_data['data'], start=1):
for col_idx, header in enumerate(headers):
value = row_data.get(header, '')
# Format basierend auf Datentyp wählen
cell_format = self._get_cell_format(value, row_idx)
worksheet.write(row_idx, col_idx, value, cell_format)
# Autofilter hinzufügen
if section_data['data']:
worksheet.autofilter(0, 0, len(section_data['data']), len(headers) - 1)
# Spaltenbreiten anpassen
for col_idx, header in enumerate(headers):
max_length = max(
len(str(header)),
max(len(str(row.get(header, ''))) for row in section_data['data']) if section_data['data'] else 0
)
worksheet.set_column(col_idx, col_idx, min(max_length + 2, 50))
def _create_charts_worksheet(self):
"""Erstellt das Diagramm-Arbeitsblatt"""
worksheet = self.workbook.add_worksheet('Diagramme')
row = 0
for chart_idx, chart_data in enumerate(self.charts):
# Diagramm-Titel
worksheet.write(row, 0, chart_data.title, self.formats['header'])
row += 2
# Daten für Diagramm vorbereiten
data_worksheet_name = f'Chart_Data_{chart_idx}'
data_worksheet = self.workbook.add_worksheet(data_worksheet_name)
# Daten ins Data-Arbeitsblatt schreiben
labels = [item.get('label', f'Item {i}') for i, item in enumerate(chart_data.data)]
values = [item.get('value', 0) for item in chart_data.data]
data_worksheet.write_column('A1', ['Label'] + labels)
data_worksheet.write_column('B1', ['Value'] + values)
# Excel-Diagramm erstellen
if chart_data.chart_type == 'bar':
chart = self.workbook.add_chart({'type': 'column'})
elif chart_data.chart_type == 'line':
chart = self.workbook.add_chart({'type': 'line'})
elif chart_data.chart_type == 'pie':
chart = self.workbook.add_chart({'type': 'pie'})
else:
chart = self.workbook.add_chart({'type': 'column'})
# Datenreihe hinzufügen
chart.add_series({
'name': chart_data.title,
'categories': [data_worksheet_name, 1, 0, len(labels), 0],
'values': [data_worksheet_name, 1, 1, len(values), 1],
})
chart.set_title({'name': chart_data.title})
chart.set_x_axis({'name': 'Kategorien'})
chart.set_y_axis({'name': 'Werte'})
# Diagramm ins Arbeitsblatt einfügen
worksheet.insert_chart(row, 0, chart)
row += 15 # Platz für nächstes Diagramm
def _get_cell_format(self, value: Any, row_idx: int):
"""Bestimmt das Zellformat basierend auf dem Wert"""
# Alternierende Zeilenfarben
base_format = self.formats['data'] if row_idx % 2 == 1 else self.formats['data_alt']
# Spezielle Formate für Zahlen, Daten, etc.
if isinstance(value, (int, float)):
return self.formats['number']
elif isinstance(value, datetime):
return self.formats['date']
elif isinstance(value, str) and value.endswith('%'):
return self.formats['percentage']
elif isinstance(value, str) and '' in value:
return self.formats['currency']
return base_format
def _format_date_range(self) -> str:
"""Formatiert den Datumsbereich"""
if not self.config.date_range:
return "Alle verfügbaren Daten"
start_date, end_date = self.config.date_range
return f"{start_date.strftime('%d.%m.%Y')} - {end_date.strftime('%d.%m.%Y')}"
class CSVReportGenerator(BaseReportGenerator):
"""CSV-Report-Generator für Datenanalyse"""
def generate(self, output_stream: BinaryIO) -> bool:
"""Generiert CSV-Report"""
try:
# Text-Stream für CSV-Writer
text_stream = io.TextIOWrapper(output_stream, encoding='utf-8-sig', newline='')
writer = csv.writer(text_stream, delimiter=';', quoting=csv.QUOTE_MINIMAL)
# Header mit Metadaten
writer.writerow([f'# {self.config.title}'])
writer.writerow([f'# Generiert am: {datetime.now().strftime("%d.%m.%Y %H:%M")}'])
writer.writerow([f'# Erstellt von: {self.config.author}'])
writer.writerow(['']) # Leerzeile
# Daten-Sektionen
for section_name, section_data in self.data.items():
writer.writerow([f'# Sektion: {section_name}'])
# Headers
writer.writerow(section_data['headers'])
# Daten
for row in section_data['data']:
csv_row = [str(row.get(header, '')) for header in section_data['headers']]
writer.writerow(csv_row)
writer.writerow(['']) # Leerzeile zwischen Sektionen
text_stream.flush()
return True
except Exception as e:
logger.error(f"Fehler bei CSV-Generierung: {str(e)}")
return False
class JSONReportGenerator(BaseReportGenerator):
"""JSON-Report-Generator für API-Integration"""
def generate(self, output_stream: BinaryIO) -> bool:
"""Generiert JSON-Report"""
try:
report_data = {
'metadata': {
'title': self.config.title,
'subtitle': self.config.subtitle,
'author': self.config.author,
'generated_at': datetime.now().isoformat(),
'date_range': {
'start': self.config.date_range[0].isoformat() if self.config.date_range else None,
'end': self.config.date_range[1].isoformat() if self.config.date_range else None
} if self.config.date_range else None
},
'data': self.data,
'charts': [asdict(chart) for chart in self.charts] if self.charts else []
}
json_str = json.dumps(report_data, ensure_ascii=False, indent=2, default=str)
output_stream.write(json_str.encode('utf-8'))
return True
except Exception as e:
logger.error(f"Fehler bei JSON-Generierung: {str(e)}")
return False
class ReportFactory:
"""Factory für Report-Generatoren"""
GENERATORS = {
'pdf': PDFReportGenerator,
'excel': ExcelReportGenerator,
'xlsx': ExcelReportGenerator,
'csv': CSVReportGenerator,
'json': JSONReportGenerator
}
@classmethod
def create_generator(cls, format_type: str, config: ReportConfig) -> BaseReportGenerator:
"""Erstellt einen Report-Generator für das angegebene Format"""
format_type = format_type.lower()
if format_type not in cls.GENERATORS:
raise ValueError(f"Unbekanntes Report-Format: {format_type}")
generator_class = cls.GENERATORS[format_type]
return generator_class(config)
@classmethod
def get_available_formats(cls) -> List[str]:
"""Gibt verfügbare Report-Formate zurück"""
available = []
for format_type, generator_class in cls.GENERATORS.items():
try:
# Test ob Generator funktioniert
if format_type in ['pdf'] and not PDF_AVAILABLE:
continue
elif format_type in ['excel', 'xlsx'] and not EXCEL_AVAILABLE:
continue
available.append(format_type)
except ImportError:
continue
return available
# Vordefinierte Report-Templates
class JobReportBuilder:
"""Builder für Job-Reports"""
@staticmethod
def build_jobs_report(
start_date: datetime = None,
end_date: datetime = None,
user_id: int = None,
printer_id: int = None,
include_completed: bool = True,
include_cancelled: bool = False
) -> Dict[str, Any]:
"""Erstellt Job-Report-Daten"""
with get_db_session() as db_session:
query = db_session.query(Job)
# Filter anwenden
if start_date:
query = query.filter(Job.created_at >= start_date)
if end_date:
query = query.filter(Job.created_at <= end_date)
if user_id:
query = query.filter(Job.user_id == user_id)
if printer_id:
query = query.filter(Job.printer_id == printer_id)
status_filters = []
if include_completed:
status_filters.append('finished')
if include_cancelled:
status_filters.append('cancelled')
if not include_cancelled and not include_completed:
status_filters = ['scheduled', 'running', 'paused']
if status_filters:
query = query.filter(Job.status.in_(status_filters))
jobs = query.all()
# Daten vorbereiten
job_data = []
for job in jobs:
job_data.append({
'ID': job.id,
'Name': job.name,
'Benutzer': job.user.name if job.user else 'Unbekannt',
'Drucker': job.printer.name if job.printer else 'Unbekannt',
'Status': job.status,
'Erstellt': job.created_at.strftime('%d.%m.%Y %H:%M') if job.created_at else '',
'Gestartet': job.start_at.strftime('%d.%m.%Y %H:%M') if job.start_at else '',
'Beendet': job.end_at.strftime('%d.%m.%Y %H:%M') if job.end_at else '',
'Dauer (Min)': job.duration_minutes or 0,
'Material (g)': job.material_used or 0,
'Beschreibung': job.description or ''
})
return {
'data': job_data,
'headers': ['ID', 'Name', 'Benutzer', 'Drucker', 'Status', 'Erstellt', 'Gestartet', 'Beendet', 'Dauer (Min)', 'Material (g)', 'Beschreibung']
}
class UserReportBuilder:
"""Builder für Benutzer-Reports"""
@staticmethod
def build_users_report(include_inactive: bool = False) -> Dict[str, Any]:
"""Erstellt Benutzer-Report-Daten"""
with get_db_session() as db_session:
query = db_session.query(User)
if not include_inactive:
query = query.filter(User.active == True)
users = query.all()
# Daten vorbereiten
user_data = []
for user in users:
user_data.append({
'ID': user.id,
'Name': user.name,
'E-Mail': user.email,
'Benutzername': user.username,
'Rolle': user.role,
'Aktiv': 'Ja' if user.active else 'Nein',
'Abteilung': user.department or '',
'Position': user.position or '',
'Erstellt': user.created_at.strftime('%d.%m.%Y') if user.created_at else '',
'Letzter Login': user.last_login.strftime('%d.%m.%Y %H:%M') if user.last_login else 'Nie'
})
return {
'data': user_data,
'headers': ['ID', 'Name', 'E-Mail', 'Benutzername', 'Rolle', 'Aktiv', 'Abteilung', 'Position', 'Erstellt', 'Letzter Login']
}
class PrinterReportBuilder:
"""Builder für Drucker-Reports"""
@staticmethod
def build_printers_report(include_inactive: bool = False) -> Dict[str, Any]:
"""Erstellt Drucker-Report-Daten"""
with get_db_session() as db_session:
query = db_session.query(Printer)
if not include_inactive:
query = query.filter(Printer.active == True)
printers = query.all()
# Daten vorbereiten
printer_data = []
for printer in printers:
printer_data.append({
'ID': printer.id,
'Name': printer.name,
'Modell': printer.model or '',
'Standort': printer.location or '',
'IP-Adresse': printer.ip_address or '',
'MAC-Adresse': printer.mac_address,
'Plug-IP': printer.plug_ip,
'Status': printer.status,
'Aktiv': 'Ja' if printer.active else 'Nein',
'Erstellt': printer.created_at.strftime('%d.%m.%Y') if printer.created_at else '',
'Letzte Prüfung': printer.last_checked.strftime('%d.%m.%Y %H:%M') if printer.last_checked else 'Nie'
})
return {
'data': printer_data,
'headers': ['ID', 'Name', 'Modell', 'Standort', 'IP-Adresse', 'MAC-Adresse', 'Plug-IP', 'Status', 'Aktiv', 'Erstellt', 'Letzte Prüfung']
}
def generate_comprehensive_report(
format_type: str,
start_date: datetime = None,
end_date: datetime = None,
include_jobs: bool = True,
include_users: bool = True,
include_printers: bool = True,
user_id: int = None
) -> bytes:
"""Generiert einen umfassenden System-Report"""
# Konfiguration
config = ReportConfig(
title="MYP System Report",
subtitle="Umfassende Systemübersicht",
author="MYP System",
date_range=(start_date, end_date) if start_date and end_date else None,
include_charts=True,
include_summary=True
)
# Generator erstellen
generator = ReportFactory.create_generator(format_type, config)
# Daten hinzufügen
if include_jobs:
job_data = JobReportBuilder.build_jobs_report(
start_date=start_date,
end_date=end_date,
user_id=user_id
)
generator.add_data_section("Jobs", job_data['data'], job_data['headers'])
# Job-Status-Diagramm
status_counts = {}
for job in job_data['data']:
status = job['Status']
status_counts[status] = status_counts.get(status, 0) + 1
chart_data = ChartData(
chart_type='pie',
title='Job-Status-Verteilung',
data=[{'label': status, 'value': count} for status, count in status_counts.items()]
)
generator.add_chart(chart_data)
if include_users:
user_data = UserReportBuilder.build_users_report()
generator.add_data_section("Benutzer", user_data['data'], user_data['headers'])
if include_printers:
printer_data = PrinterReportBuilder.build_printers_report()
generator.add_data_section("Drucker", printer_data['data'], printer_data['headers'])
# Report generieren
output = io.BytesIO()
success = generator.generate(output)
if success:
output.seek(0)
return output.getvalue()
else:
raise Exception("Report-Generierung fehlgeschlagen")
# Zusätzliche Abhängigkeiten zu requirements.txt hinzufügen
ADDITIONAL_REQUIREMENTS = [
"reportlab>=4.0.0",
"xlsxwriter>=3.0.0"
]